summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 07:24:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 07:24:22 +0000
commit45d6379135504814ab723b57f0eb8be23393a51d (patch)
treed4f2ec4acca824a8446387a758b0ce4238a4dffa /lib
parentInitial commit. (diff)
downloadbind9-45d6379135504814ab723b57f0eb8be23393a51d.tar.xz
bind9-45d6379135504814ab723b57f0eb8be23393a51d.zip
Adding upstream version 1:9.16.44.upstream/1%9.16.44upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--lib/Kyuafile20
-rw-r--r--lib/Makefile.in23
-rw-r--r--lib/bind9/Makefile.in79
-rw-r--r--lib/bind9/check.c5512
-rw-r--r--lib/bind9/getaddresses.c164
l---------lib/bind9/include/.clang-format1
-rw-r--r--lib/bind9/include/Makefile.in19
-rw-r--r--lib/bind9/include/bind9/Makefile.in41
-rw-r--r--lib/bind9/include/bind9/check.h66
-rw-r--r--lib/bind9/include/bind9/getaddresses.h54
-rw-r--r--lib/bind9/include/bind9/version.h18
-rw-r--r--lib/bind9/version.c18
-rw-r--r--lib/bind9/win32/DLLMain.c49
-rw-r--r--lib/bind9/win32/libbind9.def8
-rw-r--r--lib/bind9/win32/libbind9.vcxproj.filters.in45
-rw-r--r--lib/bind9/win32/libbind9.vcxproj.in134
-rw-r--r--lib/bind9/win32/libbind9.vcxproj.user3
-rw-r--r--lib/bind9/win32/version.c18
-rw-r--r--lib/dns/Kyuafile15
-rw-r--r--lib/dns/Makefile.in215
-rw-r--r--lib/dns/acl.c665
-rw-r--r--lib/dns/adb.c4885
-rw-r--r--lib/dns/badcache.c525
-rw-r--r--lib/dns/byaddr.c282
-rw-r--r--lib/dns/cache.c1510
-rw-r--r--lib/dns/callbacks.c107
-rw-r--r--lib/dns/catz.c2105
-rw-r--r--lib/dns/client.c1353
-rw-r--r--lib/dns/clientinfo.c38
-rw-r--r--lib/dns/compress.c584
-rw-r--r--lib/dns/db.c1139
-rw-r--r--lib/dns/dbiterator.c135
-rw-r--r--lib/dns/dbtable.c249
-rw-r--r--lib/dns/diff.c688
-rw-r--r--lib/dns/dispatch.c3591
-rw-r--r--lib/dns/dlz.c541
-rw-r--r--lib/dns/dns64.c325
-rw-r--r--lib/dns/dnsrps.c1005
-rw-r--r--lib/dns/dnssec.c2522
-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.c2797
-rw-r--r--lib/dns/dst_internal.h286
-rw-r--r--lib/dns/dst_openssl.h73
-rw-r--r--lib/dns/dst_parse.c804
-rw-r--r--lib/dns/dst_parse.h134
-rw-r--r--lib/dns/dst_pkcs11.h43
-rw-r--r--lib/dns/dst_result.c110
-rw-r--r--lib/dns/dyndb.c465
-rw-r--r--lib/dns/ecdb.c797
-rw-r--r--lib/dns/ecs.c113
-rw-r--r--lib/dns/fixedname.c39
-rw-r--r--lib/dns/forward.c227
-rw-r--r--lib/dns/gen-unix.h100
-rw-r--r--lib/dns/gen-win32.h295
-rw-r--r--lib/dns/gen.c1028
-rw-r--r--lib/dns/geoip2.c389
-rw-r--r--lib/dns/gssapi_link.c358
-rw-r--r--lib/dns/gssapictx.c957
-rw-r--r--lib/dns/hmac_link.c521
l---------lib/dns/include/.clang-format1
-rw-r--r--lib/dns/include/Makefile.in19
-rw-r--r--lib/dns/include/dns/Makefile.in63
-rw-r--r--lib/dns/include/dns/acl.h253
-rw-r--r--lib/dns/include/dns/adb.h835
-rw-r--r--lib/dns/include/dns/badcache.h152
-rw-r--r--lib/dns/include/dns/bit.h28
-rw-r--r--lib/dns/include/dns/byaddr.h152
-rw-r--r--lib/dns/include/dns/cache.h360
-rw-r--r--lib/dns/include/dns/callbacks.h103
-rw-r--r--lib/dns/include/dns/catz.h473
-rw-r--r--lib/dns/include/dns/cert.h63
-rw-r--r--lib/dns/include/dns/client.h476
-rw-r--r--lib/dns/include/dns/clientinfo.h95
-rw-r--r--lib/dns/include/dns/compress.h302
-rw-r--r--lib/dns/include/dns/db.h1800
-rw-r--r--lib/dns/include/dns/dbiterator.h293
-rw-r--r--lib/dns/include/dns/dbtable.h159
-rw-r--r--lib/dns/include/dns/diff.h282
-rw-r--r--lib/dns/include/dns/dispatch.h586
-rw-r--r--lib/dns/include/dns/dlz.h336
-rw-r--r--lib/dns/include/dns/dlz_dlopen.h159
-rw-r--r--lib/dns/include/dns/dns64.h174
-rw-r--r--lib/dns/include/dns/dnsrps.h115
-rw-r--r--lib/dns/include/dns/dnssec.h401
-rw-r--r--lib/dns/include/dns/dnstap.h396
-rw-r--r--lib/dns/include/dns/ds.h68
-rw-r--r--lib/dns/include/dns/dsdigest.h72
-rw-r--r--lib/dns/include/dns/dyndb.h162
-rw-r--r--lib/dns/include/dns/ecdb.h49
-rw-r--r--lib/dns/include/dns/ecs.h84
-rw-r--r--lib/dns/include/dns/edns.h29
-rw-r--r--lib/dns/include/dns/events.h90
-rw-r--r--lib/dns/include/dns/fixedname.h85
-rw-r--r--lib/dns/include/dns/forward.h124
-rw-r--r--lib/dns/include/dns/geoip.h114
-rw-r--r--lib/dns/include/dns/ipkeylist.h90
-rw-r--r--lib/dns/include/dns/iptable.h70
-rw-r--r--lib/dns/include/dns/journal.h341
-rw-r--r--lib/dns/include/dns/kasp.h717
-rw-r--r--lib/dns/include/dns/keydata.h51
-rw-r--r--lib/dns/include/dns/keyflags.h48
-rw-r--r--lib/dns/include/dns/keymgr.h134
-rw-r--r--lib/dns/include/dns/keytable.h350
-rw-r--r--lib/dns/include/dns/keyvalues.h107
-rw-r--r--lib/dns/include/dns/lib.h45
-rw-r--r--lib/dns/include/dns/librpz.h956
-rw-r--r--lib/dns/include/dns/lmdb.h25
-rw-r--r--lib/dns/include/dns/log.h114
-rw-r--r--lib/dns/include/dns/lookup.h131
-rw-r--r--lib/dns/include/dns/master.h264
-rw-r--r--lib/dns/include/dns/masterdump.h364
-rw-r--r--lib/dns/include/dns/message.h1492
-rw-r--r--lib/dns/include/dns/name.h1410
-rw-r--r--lib/dns/include/dns/ncache.h187
-rw-r--r--lib/dns/include/dns/nsec.h114
-rw-r--r--lib/dns/include/dns/nsec3.h272
-rw-r--r--lib/dns/include/dns/nta.h218
-rw-r--r--lib/dns/include/dns/opcode.h46
-rw-r--r--lib/dns/include/dns/order.h93
-rw-r--r--lib/dns/include/dns/peer.h279
-rw-r--r--lib/dns/include/dns/portlist.h102
-rw-r--r--lib/dns/include/dns/private.h69
-rw-r--r--lib/dns/include/dns/rbt.h1060
-rw-r--r--lib/dns/include/dns/rcode.h110
-rw-r--r--lib/dns/include/dns/rdata.h812
-rw-r--r--lib/dns/include/dns/rdataclass.h94
-rw-r--r--lib/dns/include/dns/rdatalist.h123
-rw-r--r--lib/dns/include/dns/rdataset.h615
-rw-r--r--lib/dns/include/dns/rdatasetiter.h164
-rw-r--r--lib/dns/include/dns/rdataslab.h173
-rw-r--r--lib/dns/include/dns/rdatatype.h97
-rw-r--r--lib/dns/include/dns/request.h365
-rw-r--r--lib/dns/include/dns/resolver.h747
-rw-r--r--lib/dns/include/dns/result.h212
-rw-r--r--lib/dns/include/dns/rootns.h39
-rw-r--r--lib/dns/include/dns/rpz.h435
-rw-r--r--lib/dns/include/dns/rriterator.h182
-rw-r--r--lib/dns/include/dns/rrl.h272
-rw-r--r--lib/dns/include/dns/sdb.h214
-rw-r--r--lib/dns/include/dns/sdlz.h359
-rw-r--r--lib/dns/include/dns/secalg.h72
-rw-r--r--lib/dns/include/dns/secproto.h65
-rw-r--r--lib/dns/include/dns/soa.h96
-rw-r--r--lib/dns/include/dns/ssu.h243
-rw-r--r--lib/dns/include/dns/stats.h826
-rw-r--r--lib/dns/include/dns/tcpmsg.h143
-rw-r--r--lib/dns/include/dns/time.h73
-rw-r--r--lib/dns/include/dns/timer.h48
-rw-r--r--lib/dns/include/dns/tkey.h246
-rw-r--r--lib/dns/include/dns/tsec.h132
-rw-r--r--lib/dns/include/dns/tsig.h297
-rw-r--r--lib/dns/include/dns/ttl.h80
-rw-r--r--lib/dns/include/dns/types.h438
-rw-r--r--lib/dns/include/dns/update.h75
-rw-r--r--lib/dns/include/dns/validator.h243
-rw-r--r--lib/dns/include/dns/version.h25
-rw-r--r--lib/dns/include/dns/view.h1359
-rw-r--r--lib/dns/include/dns/xfrin.h98
-rw-r--r--lib/dns/include/dns/zone.h2726
-rw-r--r--lib/dns/include/dns/zonekey.h38
-rw-r--r--lib/dns/include/dns/zoneverify.h50
-rw-r--r--lib/dns/include/dns/zt.h224
-rw-r--r--lib/dns/include/dst/Makefile.in36
-rw-r--r--lib/dns/include/dst/dst.h1227
-rw-r--r--lib/dns/include/dst/gssapi.h196
-rw-r--r--lib/dns/include/dst/result.h67
-rw-r--r--lib/dns/ipkeylist.c209
-rw-r--r--lib/dns/iptable.c174
-rw-r--r--lib/dns/journal.c2856
-rw-r--r--lib/dns/kasp.c527
-rw-r--r--lib/dns/key.c192
-rw-r--r--lib/dns/keydata.c76
-rw-r--r--lib/dns/keymgr.c2628
-rw-r--r--lib/dns/keytable.c943
-rw-r--r--lib/dns/lib.c119
-rw-r--r--lib/dns/log.c84
-rw-r--r--lib/dns/lookup.c450
-rw-r--r--lib/dns/mapapi16
-rw-r--r--lib/dns/master.c3264
-rw-r--r--lib/dns/masterdump.c2133
-rw-r--r--lib/dns/message.c4748
-rw-r--r--lib/dns/name.c2692
-rw-r--r--lib/dns/ncache.c777
-rw-r--r--lib/dns/nsec.c466
-rw-r--r--lib/dns/nsec3.c2195
-rw-r--r--lib/dns/nta.c722
-rw-r--r--lib/dns/openssl_link.c214
-rw-r--r--lib/dns/openssldh_link.c815
-rw-r--r--lib/dns/opensslecdsa_link.c905
-rw-r--r--lib/dns/openssleddsa_link.c708
-rw-r--r--lib/dns/opensslrsa_link.c1405
-rw-r--r--lib/dns/order.c150
-rw-r--r--lib/dns/peer.c919
-rw-r--r--lib/dns/pkcs11.c40
-rw-r--r--lib/dns/pkcs11ecdsa_link.c1153
-rw-r--r--lib/dns/pkcs11eddsa_link.c1124
-rw-r--r--lib/dns/pkcs11rsa_link.c2115
-rw-r--r--lib/dns/portlist.c252
-rw-r--r--lib/dns/private.c417
-rw-r--r--lib/dns/rbt.c3808
-rw-r--r--lib/dns/rbtdb.c10667
-rw-r--r--lib/dns/rbtdb.h52
-rw-r--r--lib/dns/rcode.c588
-rw-r--r--lib/dns/rdata.c2365
-rw-r--r--lib/dns/rdata/any_255/tsig_250.c621
-rw-r--r--lib/dns/rdata/any_255/tsig_250.h32
-rw-r--r--lib/dns/rdata/ch_3/a_1.c325
-rw-r--r--lib/dns/rdata/ch_3/a_1.h31
-rw-r--r--lib/dns/rdata/generic/afsdb_18.c316
-rw-r--r--lib/dns/rdata/generic/afsdb_18.h27
-rw-r--r--lib/dns/rdata/generic/amtrelay_260.c471
-rw-r--r--lib/dns/rdata/generic/amtrelay_260.h30
-rw-r--r--lib/dns/rdata/generic/avc_258.c144
-rw-r--r--lib/dns/rdata/generic/avc_258.h32
-rw-r--r--lib/dns/rdata/generic/caa_257.c627
-rw-r--r--lib/dns/rdata/generic/caa_257.h27
-rw-r--r--lib/dns/rdata/generic/cdnskey_60.c163
-rw-r--r--lib/dns/rdata/generic/cdnskey_60.h20
-rw-r--r--lib/dns/rdata/generic/cds_59.c166
-rw-r--r--lib/dns/rdata/generic/cds_59.h20
-rw-r--r--lib/dns/rdata/generic/cert_37.c284
-rw-r--r--lib/dns/rdata/generic/cert_37.h28
-rw-r--r--lib/dns/rdata/generic/cname_5.c230
-rw-r--r--lib/dns/rdata/generic/cname_5.h23
-rw-r--r--lib/dns/rdata/generic/csync_62.c273
-rw-r--r--lib/dns/rdata/generic/csync_62.h30
-rw-r--r--lib/dns/rdata/generic/dlv_32769.c162
-rw-r--r--lib/dns/rdata/generic/dlv_32769.h20
-rw-r--r--lib/dns/rdata/generic/dname_39.c230
-rw-r--r--lib/dns/rdata/generic/dname_39.h26
-rw-r--r--lib/dns/rdata/generic/dnskey_48.c164
-rw-r--r--lib/dns/rdata/generic/dnskey_48.h23
-rw-r--r--lib/dns/rdata/generic/doa_259.c361
-rw-r--r--lib/dns/rdata/generic/doa_259.h29
-rw-r--r--lib/dns/rdata/generic/ds_43.c385
-rw-r--r--lib/dns/rdata/generic/ds_43.h29
-rw-r--r--lib/dns/rdata/generic/eui48_108.c211
-rw-r--r--lib/dns/rdata/generic/eui48_108.h23
-rw-r--r--lib/dns/rdata/generic/eui64_109.c214
-rw-r--r--lib/dns/rdata/generic/eui64_109.h23
-rw-r--r--lib/dns/rdata/generic/gpos_27.c256
-rw-r--r--lib/dns/rdata/generic/gpos_27.h31
-rw-r--r--lib/dns/rdata/generic/hinfo_13.c219
-rw-r--r--lib/dns/rdata/generic/hinfo_13.h26
-rw-r--r--lib/dns/rdata/generic/hip_55.c525
-rw-r--r--lib/dns/rdata/generic/hip_55.h42
-rw-r--r--lib/dns/rdata/generic/ipseckey_45.c526
-rw-r--r--lib/dns/rdata/generic/ipseckey_45.h30
-rw-r--r--lib/dns/rdata/generic/isdn_20.c247
-rw-r--r--lib/dns/rdata/generic/isdn_20.h29
-rw-r--r--lib/dns/rdata/generic/key_25.c468
-rw-r--r--lib/dns/rdata/generic/key_25.h30
-rw-r--r--lib/dns/rdata/generic/keydata_65533.c462
-rw-r--r--lib/dns/rdata/generic/keydata_65533.h30
-rw-r--r--lib/dns/rdata/generic/l32_105.c230
-rw-r--r--lib/dns/rdata/generic/l32_105.h24
-rw-r--r--lib/dns/rdata/generic/l64_106.c224
-rw-r--r--lib/dns/rdata/generic/l64_106.h24
-rw-r--r--lib/dns/rdata/generic/loc_29.c838
-rw-r--r--lib/dns/rdata/generic/loc_29.h37
-rw-r--r--lib/dns/rdata/generic/lp_107.c276
-rw-r--r--lib/dns/rdata/generic/lp_107.h25
-rw-r--r--lib/dns/rdata/generic/mb_7.c232
-rw-r--r--lib/dns/rdata/generic/mb_7.h24
-rw-r--r--lib/dns/rdata/generic/md_3.c234
-rw-r--r--lib/dns/rdata/generic/md_3.h24
-rw-r--r--lib/dns/rdata/generic/mf_4.c233
-rw-r--r--lib/dns/rdata/generic/mf_4.h24
-rw-r--r--lib/dns/rdata/generic/mg_8.c228
-rw-r--r--lib/dns/rdata/generic/mg_8.h24
-rw-r--r--lib/dns/rdata/generic/minfo_14.c332
-rw-r--r--lib/dns/rdata/generic/minfo_14.h25
-rw-r--r--lib/dns/rdata/generic/mr_9.c229
-rw-r--r--lib/dns/rdata/generic/mr_9.h24
-rw-r--r--lib/dns/rdata/generic/mx_15.c356
-rw-r--r--lib/dns/rdata/generic/mx_15.h25
-rw-r--r--lib/dns/rdata/generic/naptr_35.c740
-rw-r--r--lib/dns/rdata/generic/naptr_35.h34
-rw-r--r--lib/dns/rdata/generic/nid_104.c224
-rw-r--r--lib/dns/rdata/generic/nid_104.h24
-rw-r--r--lib/dns/rdata/generic/ninfo_56.c169
-rw-r--r--lib/dns/rdata/generic/ninfo_56.h36
-rw-r--r--lib/dns/rdata/generic/ns_2.c254
-rw-r--r--lib/dns/rdata/generic/ns_2.h24
-rw-r--r--lib/dns/rdata/generic/nsec3_50.c424
-rw-r--r--lib/dns/rdata/generic/nsec3_50.h112
-rw-r--r--lib/dns/rdata/generic/nsec3param_51.c321
-rw-r--r--lib/dns/rdata/generic/nsec3param_51.h32
-rw-r--r--lib/dns/rdata/generic/nsec_47.c290
-rw-r--r--lib/dns/rdata/generic/nsec_47.h28
-rw-r--r--lib/dns/rdata/generic/null_10.c186
-rw-r--r--lib/dns/rdata/generic/null_10.h25
-rw-r--r--lib/dns/rdata/generic/nxt_30.c350
-rw-r--r--lib/dns/rdata/generic/nxt_30.h28
-rw-r--r--lib/dns/rdata/generic/openpgpkey_61.c249
-rw-r--r--lib/dns/rdata/generic/openpgpkey_61.h24
-rw-r--r--lib/dns/rdata/generic/opt_41.c472
-rw-r--r--lib/dns/rdata/generic/opt_41.h49
-rw-r--r--lib/dns/rdata/generic/proforma.c164
-rw-r--r--lib/dns/rdata/generic/proforma.h25
-rw-r--r--lib/dns/rdata/generic/ptr_12.c278
-rw-r--r--lib/dns/rdata/generic/ptr_12.h24
-rw-r--r--lib/dns/rdata/generic/rkey_57.c160
-rw-r--r--lib/dns/rdata/generic/rkey_57.h19
-rw-r--r--lib/dns/rdata/generic/rp_17.c320
-rw-r--r--lib/dns/rdata/generic/rp_17.h27
-rw-r--r--lib/dns/rdata/generic/rrsig_46.c639
-rw-r--r--lib/dns/rdata/generic/rrsig_46.h34
-rw-r--r--lib/dns/rdata/generic/rt_21.c322
-rw-r--r--lib/dns/rdata/generic/rt_21.h27
-rw-r--r--lib/dns/rdata/generic/sig_24.c590
-rw-r--r--lib/dns/rdata/generic/sig_24.h35
-rw-r--r--lib/dns/rdata/generic/sink_40.c291
-rw-r--r--lib/dns/rdata/generic/sink_40.h27
-rw-r--r--lib/dns/rdata/generic/smimea_53.c152
-rw-r--r--lib/dns/rdata/generic/smimea_53.h19
-rw-r--r--lib/dns/rdata/generic/soa_6.c452
-rw-r--r--lib/dns/rdata/generic/soa_6.h30
-rw-r--r--lib/dns/rdata/generic/spf_99.c145
-rw-r--r--lib/dns/rdata/generic/spf_99.h35
-rw-r--r--lib/dns/rdata/generic/sshfp_44.c296
-rw-r--r--lib/dns/rdata/generic/sshfp_44.h29
-rw-r--r--lib/dns/rdata/generic/ta_32768.c162
-rw-r--r--lib/dns/rdata/generic/ta_32768.h22
-rw-r--r--lib/dns/rdata/generic/talink_58.c267
-rw-r--r--lib/dns/rdata/generic/talink_58.h28
-rw-r--r--lib/dns/rdata/generic/tkey_249.c580
-rw-r--r--lib/dns/rdata/generic/tkey_249.h34
-rw-r--r--lib/dns/rdata/generic/tlsa_52.c339
-rw-r--r--lib/dns/rdata/generic/tlsa_52.h30
-rw-r--r--lib/dns/rdata/generic/txt_16.c359
-rw-r--r--lib/dns/rdata/generic/txt_16.h46
-rw-r--r--lib/dns/rdata/generic/uri_256.c318
-rw-r--r--lib/dns/rdata/generic/uri_256.h26
-rw-r--r--lib/dns/rdata/generic/x25_19.c232
-rw-r--r--lib/dns/rdata/generic/x25_19.h27
-rw-r--r--lib/dns/rdata/generic/zonemd_63.c350
-rw-r--r--lib/dns/rdata/generic/zonemd_63.h34
-rw-r--r--lib/dns/rdata/hs_4/a_1.c235
-rw-r--r--lib/dns/rdata/hs_4/a_1.h23
-rw-r--r--lib/dns/rdata/in_1/a6_38.c486
-rw-r--r--lib/dns/rdata/in_1/a6_38.h28
-rw-r--r--lib/dns/rdata/in_1/a_1.c278
-rw-r--r--lib/dns/rdata/in_1/a_1.h23
-rw-r--r--lib/dns/rdata/in_1/aaaa_28.c265
-rw-r--r--lib/dns/rdata/in_1/aaaa_28.h25
-rw-r--r--lib/dns/rdata/in_1/apl_42.c481
-rw-r--r--lib/dns/rdata/in_1/apl_42.h53
-rw-r--r--lib/dns/rdata/in_1/atma_34.c317
-rw-r--r--lib/dns/rdata/in_1/atma_34.h28
-rw-r--r--lib/dns/rdata/in_1/dhcid_49.c235
-rw-r--r--lib/dns/rdata/in_1/dhcid_49.h25
-rw-r--r--lib/dns/rdata/in_1/eid_31.c224
-rw-r--r--lib/dns/rdata/in_1/eid_31.h28
-rw-r--r--lib/dns/rdata/in_1/https_65.c186
-rw-r--r--lib/dns/rdata/in_1/https_65.h35
-rw-r--r--lib/dns/rdata/in_1/kx_36.c289
-rw-r--r--lib/dns/rdata/in_1/kx_36.h27
-rw-r--r--lib/dns/rdata/in_1/nimloc_32.c224
-rw-r--r--lib/dns/rdata/in_1/nimloc_32.h28
-rw-r--r--lib/dns/rdata/in_1/nsap-ptr_23.c243
-rw-r--r--lib/dns/rdata/in_1/nsap-ptr_23.h26
-rw-r--r--lib/dns/rdata/in_1/nsap_22.c259
-rw-r--r--lib/dns/rdata/in_1/nsap_22.h27
-rw-r--r--lib/dns/rdata/in_1/px_26.c379
-rw-r--r--lib/dns/rdata/in_1/px_26.h28
-rw-r--r--lib/dns/rdata/in_1/srv_33.c410
-rw-r--r--lib/dns/rdata/in_1/srv_33.h29
-rw-r--r--lib/dns/rdata/in_1/svcb_64.c1268
-rw-r--r--lib/dns/rdata/in_1/svcb_64.h40
-rw-r--r--lib/dns/rdata/in_1/wks_11.c440
-rw-r--r--lib/dns/rdata/in_1/wks_11.h26
-rw-r--r--lib/dns/rdata/rdatastructpre.h36
-rw-r--r--lib/dns/rdata/rdatastructsuf.h16
-rw-r--r--lib/dns/rdatalist.c448
-rw-r--r--lib/dns/rdatalist_p.h65
-rw-r--r--lib/dns/rdataset.c750
-rw-r--r--lib/dns/rdatasetiter.c71
-rw-r--r--lib/dns/rdataslab.c1005
-rw-r--r--lib/dns/request.c1545
-rw-r--r--lib/dns/resolver.c12095
-rw-r--r--lib/dns/result.c454
-rw-r--r--lib/dns/rootns.c566
-rw-r--r--lib/dns/rpz.c2891
-rw-r--r--lib/dns/rriterator.c220
-rw-r--r--lib/dns/rrl.c1367
-rw-r--r--lib/dns/sdb.c1613
-rw-r--r--lib/dns/sdlz.c2108
-rw-r--r--lib/dns/soa.c137
-rw-r--r--lib/dns/ssu.c667
-rw-r--r--lib/dns/ssu_external.c260
-rw-r--r--lib/dns/stats.c653
-rw-r--r--lib/dns/tcpmsg.c236
-rw-r--r--lib/dns/tests/Kdh.+002+18602.key1
-rw-r--r--lib/dns/tests/Krsa.+008+29238.key5
-rw-r--r--lib/dns/tests/Kyuafile45
-rw-r--r--lib/dns/tests/Makefile.in287
-rw-r--r--lib/dns/tests/acl_test.c158
-rw-r--r--lib/dns/tests/comparekeys/Kexample-d.+008+53461.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample-d.+008+53461.private13
-rw-r--r--lib/dns/tests/comparekeys/Kexample-e.+008+53973.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample-e.+008+53973.private13
-rw-r--r--lib/dns/tests/comparekeys/Kexample-n.+008+37464.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample-n.+008+37464.private13
-rw-r--r--lib/dns/tests/comparekeys/Kexample-p.+008+53461.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample-p.+008+53461.private13
-rw-r--r--lib/dns/tests/comparekeys/Kexample-private.+002+65316.key1
-rw-r--r--lib/dns/tests/comparekeys/Kexample-private.+002+65316.private9
-rw-r--r--lib/dns/tests/comparekeys/Kexample-q.+008+53461.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample-q.+008+53461.private13
-rw-r--r--lib/dns/tests/comparekeys/Kexample.+002+65316.key1
-rw-r--r--lib/dns/tests/comparekeys/Kexample.+002+65316.private9
-rw-r--r--lib/dns/tests/comparekeys/Kexample.+008+53461.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample.+008+53461.private13
-rw-r--r--lib/dns/tests/comparekeys/Kexample.+013+19786.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample.+013+19786.private6
-rw-r--r--lib/dns/tests/comparekeys/Kexample.+015+63663.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample.+015+63663.private6
-rw-r--r--lib/dns/tests/comparekeys/Kexample2.+002+19823.key1
-rw-r--r--lib/dns/tests/comparekeys/Kexample2.+002+19823.private9
-rw-r--r--lib/dns/tests/comparekeys/Kexample2.+008+37993.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample2.+008+37993.private13
-rw-r--r--lib/dns/tests/comparekeys/Kexample2.+013+16384.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample2.+013+16384.private6
-rw-r--r--lib/dns/tests/comparekeys/Kexample2.+015+37529.key5
-rw-r--r--lib/dns/tests/comparekeys/Kexample2.+015+37529.private6
-rw-r--r--lib/dns/tests/comparekeys/Kexample3.+002+17187.key1
-rw-r--r--lib/dns/tests/comparekeys/Kexample3.+002+17187.private9
-rw-r--r--lib/dns/tests/db_test.c428
-rw-r--r--lib/dns/tests/dbdiff_test.c185
-rw-r--r--lib/dns/tests/dbiterator_test.c394
-rw-r--r--lib/dns/tests/dbversion_test.c499
-rw-r--r--lib/dns/tests/dh_test.c112
-rw-r--r--lib/dns/tests/dispatch_test.c360
-rw-r--r--lib/dns/tests/dnstap_test.c402
-rw-r--r--lib/dns/tests/dnstest.c643
-rw-r--r--lib/dns/tests/dnstest.h132
-rw-r--r--lib/dns/tests/dst_test.c504
-rw-r--r--lib/dns/tests/geoip_test.c433
-rw-r--r--lib/dns/tests/keytable_test.c720
-rw-r--r--lib/dns/tests/master_test.c633
-rw-r--r--lib/dns/tests/mkraw.pl26
-rw-r--r--lib/dns/tests/name_test.c796
-rw-r--r--lib/dns/tests/nsec3_test.c196
-rw-r--r--lib/dns/tests/nsec3param_test.c304
-rw-r--r--lib/dns/tests/peer_test.c175
-rw-r--r--lib/dns/tests/private_test.c236
-rw-r--r--lib/dns/tests/rbt_serialize_test.c489
-rw-r--r--lib/dns/tests/rbt_test.c1390
-rw-r--r--lib/dns/tests/rbtdb_test.c423
-rw-r--r--lib/dns/tests/rdata_test.c3229
-rw-r--r--lib/dns/tests/rdataset_test.c146
-rw-r--r--lib/dns/tests/rdatasetstats_test.c312
-rw-r--r--lib/dns/tests/resolver_test.c228
-rw-r--r--lib/dns/tests/result_test.c133
-rw-r--r--lib/dns/tests/rsa_test.c242
-rw-r--r--lib/dns/tests/sigs_test.c462
-rw-r--r--lib/dns/tests/testdata/db/data.db22
-rw-r--r--lib/dns/tests/testdata/dbiterator/zone1.data30
-rw-r--r--lib/dns/tests/testdata/dbiterator/zone2.data319
-rw-r--r--lib/dns/tests/testdata/diff/zone1.data13
-rw-r--r--lib/dns/tests/testdata/diff/zone2.data14
-rw-r--r--lib/dns/tests/testdata/diff/zone3.data12
-rw-r--r--lib/dns/tests/testdata/dnstap/dnstap.savedbin0 -> 30398 bytes
-rw-r--r--lib/dns/tests/testdata/dnstap/dnstap.text96
-rw-r--r--lib/dns/tests/testdata/dnstap/query.auth4
-rw-r--r--lib/dns/tests/testdata/dnstap/query.recursive4
-rw-r--r--lib/dns/tests/testdata/dnstap/response.auth19
-rw-r--r--lib/dns/tests/testdata/dnstap/response.recursive19
-rw-r--r--lib/dns/tests/testdata/dst/Ktest.+008+11349.key5
-rw-r--r--lib/dns/tests/testdata/dst/Ktest.+008+11349.private13
-rw-r--r--lib/dns/tests/testdata/dst/Ktest.+013+49130.key5
-rw-r--r--lib/dns/tests/testdata/dst/Ktest.+013+49130.private6
-rw-r--r--lib/dns/tests/testdata/dst/test1.data3077
-rw-r--r--lib/dns/tests/testdata/dst/test1.ecdsa256sig1
-rw-r--r--lib/dns/tests/testdata/dst/test1.rsasha256sig1
-rw-r--r--lib/dns/tests/testdata/dst/test2.data3077
-rw-r--r--lib/dns/tests/testdata/dstrandom/random.databin0 -> 4096 bytes
-rw-r--r--lib/dns/tests/testdata/master/master1.data11
-rw-r--r--lib/dns/tests/testdata/master/master10.data7
-rw-r--r--lib/dns/tests/testdata/master/master11.data6
-rw-r--r--lib/dns/tests/testdata/master/master12.data.in1
-rw-r--r--lib/dns/tests/testdata/master/master13.data.in1
-rw-r--r--lib/dns/tests/testdata/master/master14.data.in1
-rw-r--r--lib/dns/tests/testdata/master/master15.data1609
-rw-r--r--lib/dns/tests/testdata/master/master16.data1609
-rw-r--r--lib/dns/tests/testdata/master/master17.data14
-rw-r--r--lib/dns/tests/testdata/master/master18.data10
-rw-r--r--lib/dns/tests/testdata/master/master2.data11
-rw-r--r--lib/dns/tests/testdata/master/master3.data11
-rw-r--r--lib/dns/tests/testdata/master/master4.data11
-rw-r--r--lib/dns/tests/testdata/master/master5.data11
-rw-r--r--lib/dns/tests/testdata/master/master6.data33
-rw-r--r--lib/dns/tests/testdata/master/master7.data17
-rw-r--r--lib/dns/tests/testdata/master/master8.data4
-rw-r--r--lib/dns/tests/testdata/master/master9.data4
-rw-r--r--lib/dns/tests/testdata/nsec3/1024.db16
-rw-r--r--lib/dns/tests/testdata/nsec3/2048.db16
-rw-r--r--lib/dns/tests/testdata/nsec3/4096.db16
-rw-r--r--lib/dns/tests/testdata/nsec3/min-1024.db20
-rw-r--r--lib/dns/tests/testdata/nsec3/min-2048.db18
-rw-r--r--lib/dns/tests/testdata/nsec3param/nsec3.db.signed73
-rw-r--r--lib/dns/tests/testdata/zt/zone1.db22
-rw-r--r--lib/dns/tests/testkeys/Kexample.+008+20386.key5
-rw-r--r--lib/dns/tests/testkeys/Kexample.+008+20386.private13
-rw-r--r--lib/dns/tests/testkeys/Kexample.+008+37464.key5
-rw-r--r--lib/dns/tests/testkeys/Kexample.+008+37464.private13
-rw-r--r--lib/dns/tests/time_test.c218
-rw-r--r--lib/dns/tests/tsig_test.c605
-rw-r--r--lib/dns/tests/update_test.c381
-rw-r--r--lib/dns/tests/zonemgr_test.c265
-rw-r--r--lib/dns/tests/zt_test.c376
-rw-r--r--lib/dns/time.c216
-rw-r--r--lib/dns/timer.c54
-rw-r--r--lib/dns/tkey.c1604
-rw-r--r--lib/dns/tsec.c151
-rw-r--r--lib/dns/tsig.c1902
-rw-r--r--lib/dns/tsig_p.h43
-rw-r--r--lib/dns/ttl.c225
-rw-r--r--lib/dns/update.c2280
-rw-r--r--lib/dns/validator.c3394
-rw-r--r--lib/dns/version.c20
-rw-r--r--lib/dns/view.c2642
-rw-r--r--lib/dns/win32/DLLMain.c49
-rw-r--r--lib/dns/win32/gen.vcxproj.filters.in27
-rw-r--r--lib/dns/win32/gen.vcxproj.in134
-rw-r--r--lib/dns/win32/gen.vcxproj.user3
-rw-r--r--lib/dns/win32/libdns.def.in1548
-rw-r--r--lib/dns/win32/libdns.vcxproj.filters.in668
-rw-r--r--lib/dns/win32/libdns.vcxproj.in345
-rw-r--r--lib/dns/win32/libdns.vcxproj.user3
-rw-r--r--lib/dns/win32/version.c20
-rw-r--r--lib/dns/xfrin.c1704
-rw-r--r--lib/dns/zone.c23609
-rw-r--r--lib/dns/zone_p.h53
-rw-r--r--lib/dns/zonekey.c54
-rw-r--r--lib/dns/zoneverify.c2038
-rw-r--r--lib/dns/zt.c617
-rw-r--r--lib/irs/Kyuafile15
-rw-r--r--lib/irs/Makefile.in90
-rw-r--r--lib/irs/context.c334
-rw-r--r--lib/irs/dnsconf.c291
-rw-r--r--lib/irs/gai_strerror.c96
-rw-r--r--lib/irs/getaddrinfo.c1365
-rw-r--r--lib/irs/getnameinfo.c438
l---------lib/irs/include/.clang-format1
-rw-r--r--lib/irs/include/Makefile.in19
-rw-r--r--lib/irs/include/irs/Makefile.in46
-rw-r--r--lib/irs/include/irs/context.h155
-rw-r--r--lib/irs/include/irs/dnsconf.h92
-rw-r--r--lib/irs/include/irs/netdb.h.in193
-rw-r--r--lib/irs/include/irs/platform.h.in32
-rw-r--r--lib/irs/include/irs/resconf.h118
-rw-r--r--lib/irs/include/irs/types.h26
-rw-r--r--lib/irs/include/irs/version.h18
-rw-r--r--lib/irs/resconf.c689
-rw-r--r--lib/irs/tests/Kyuafile15
-rw-r--r--lib/irs/tests/Makefile.in51
-rw-r--r--lib/irs/tests/resconf_test.c142
-rw-r--r--lib/irs/tests/testdata/domain.conf12
-rw-r--r--lib/irs/tests/testdata/nameserver-v4.conf12
-rw-r--r--lib/irs/tests/testdata/nameserver-v6-scoped.conf12
-rw-r--r--lib/irs/tests/testdata/nameserver-v6.conf12
-rw-r--r--lib/irs/tests/testdata/options-bad-ndots.conf13
-rw-r--r--lib/irs/tests/testdata/options-debug.conf12
-rw-r--r--lib/irs/tests/testdata/options-empty.conf13
-rw-r--r--lib/irs/tests/testdata/options-ndots.conf12
-rw-r--r--lib/irs/tests/testdata/options-timeout.conf12
-rw-r--r--lib/irs/tests/testdata/options-unknown.conf12
-rw-r--r--lib/irs/tests/testdata/options.conf12
-rw-r--r--lib/irs/tests/testdata/port.conf12
-rw-r--r--lib/irs/tests/testdata/resolv.conf19
-rw-r--r--lib/irs/tests/testdata/search.conf12
-rw-r--r--lib/irs/tests/testdata/sortlist-v4.conf12
-rw-r--r--lib/irs/tests/testdata/timeout.conf12
-rw-r--r--lib/irs/tests/testdata/unknown.conf12
-rw-r--r--lib/irs/version.c18
-rw-r--r--lib/irs/win32/DLLMain.c49
-rw-r--r--lib/irs/win32/Makefile.in19
l---------lib/irs/win32/include/.clang-format1
-rw-r--r--lib/irs/win32/include/Makefile.in19
-rw-r--r--lib/irs/win32/include/irs/Makefile.in28
-rw-r--r--lib/irs/win32/include/irs/netdb.h207
-rw-r--r--lib/irs/win32/include/irs/platform.h34
-rw-r--r--lib/irs/win32/libirs.def27
-rw-r--r--lib/irs/win32/libirs.vcxproj.filters.in69
-rw-r--r--lib/irs/win32/libirs.vcxproj.in142
-rw-r--r--lib/irs/win32/libirs.vcxproj.user3
-rw-r--r--lib/irs/win32/resconf.c130
-rw-r--r--lib/irs/win32/version.c18
-rw-r--r--lib/isc/Kyuafile15
-rw-r--r--lib/isc/Makefile.in144
-rw-r--r--lib/isc/aes.c72
-rw-r--r--lib/isc/app.c544
-rw-r--r--lib/isc/assertions.c129
-rw-r--r--lib/isc/astack.c85
-rw-r--r--lib/isc/backtrace-emptytbl.c29
-rw-r--r--lib/isc/backtrace.c304
-rw-r--r--lib/isc/base32.c443
-rw-r--r--lib/isc/base64.c270
-rw-r--r--lib/isc/bind9.c27
-rw-r--r--lib/isc/buffer.c542
-rw-r--r--lib/isc/bufferlist.c56
-rw-r--r--lib/isc/commandline.c265
-rw-r--r--lib/isc/counter.c112
-rw-r--r--lib/isc/crc64.c140
-rw-r--r--lib/isc/entropy.c28
-rw-r--r--lib/isc/entropy_private.h37
-rw-r--r--lib/isc/error.c94
-rw-r--r--lib/isc/event.c95
-rw-r--r--lib/isc/fsaccess.c103
-rw-r--r--lib/isc/hash.c153
-rw-r--r--lib/isc/heap.c280
-rw-r--r--lib/isc/hex.c212
-rw-r--r--lib/isc/hmac.c146
-rw-r--r--lib/isc/ht.c342
-rw-r--r--lib/isc/httpd.c1347
l---------lib/isc/include/.clang-format1
-rw-r--r--lib/isc/include/Makefile.in19
-rw-r--r--lib/isc/include/isc/Makefile.in63
-rw-r--r--lib/isc/include/isc/aes.h44
-rw-r--r--lib/isc/include/isc/app.h283
-rw-r--r--lib/isc/include/isc/assertions.h79
-rw-r--r--lib/isc/include/isc/astack.h49
-rw-r--r--lib/isc/include/isc/atomic.h73
-rw-r--r--lib/isc/include/isc/backtrace.h127
-rw-r--r--lib/isc/include/isc/barrier.h39
-rw-r--r--lib/isc/include/isc/base32.h146
-rw-r--r--lib/isc/include/isc/base64.h101
-rw-r--r--lib/isc/include/isc/bind9.h29
-rw-r--r--lib/isc/include/isc/buffer.h1103
-rw-r--r--lib/isc/include/isc/bufferlist.h80
-rw-r--r--lib/isc/include/isc/cmocka.h55
-rw-r--r--lib/isc/include/isc/commandline.h59
-rw-r--r--lib/isc/include/isc/counter.h87
-rw-r--r--lib/isc/include/isc/crc64.h58
-rw-r--r--lib/isc/include/isc/deprecated.h23
-rw-r--r--lib/isc/include/isc/endian.h190
-rw-r--r--lib/isc/include/isc/errno.h30
-rw-r--r--lib/isc/include/isc/error.h57
-rw-r--r--lib/isc/include/isc/event.h120
-rw-r--r--lib/isc/include/isc/eventclass.h48
-rw-r--r--lib/isc/include/isc/file.h400
-rw-r--r--lib/isc/include/isc/formatcheck.h36
-rw-r--r--lib/isc/include/isc/fsaccess.h174
-rw-r--r--lib/isc/include/isc/fuzz.h26
-rw-r--r--lib/isc/include/isc/hash.h66
-rw-r--r--lib/isc/include/isc/heap.h164
-rw-r--r--lib/isc/include/isc/hex.h101
-rw-r--r--lib/isc/include/isc/hmac.h147
-rw-r--r--lib/isc/include/isc/ht.h183
-rw-r--r--lib/isc/include/isc/httpd.h85
-rw-r--r--lib/isc/include/isc/interfaceiter.h130
-rw-r--r--lib/isc/include/isc/iterated_hash.h38
-rw-r--r--lib/isc/include/isc/lang.h27
-rw-r--r--lib/isc/include/isc/lex.h447
-rw-r--r--lib/isc/include/isc/lfsr.h124
-rw-r--r--lib/isc/include/isc/lib.h47
-rw-r--r--lib/isc/include/isc/likely.h33
-rw-r--r--lib/isc/include/isc/list.h229
-rw-r--r--lib/isc/include/isc/log.h848
-rw-r--r--lib/isc/include/isc/magic.h37
-rw-r--r--lib/isc/include/isc/managers.h29
-rw-r--r--lib/isc/include/isc/md.h205
-rw-r--r--lib/isc/include/isc/mem.h633
-rw-r--r--lib/isc/include/isc/meminfo.h33
-rw-r--r--lib/isc/include/isc/mutexblock.h57
-rw-r--r--lib/isc/include/isc/netaddr.h199
-rw-r--r--lib/isc/include/isc/netmgr.h540
-rw-r--r--lib/isc/include/isc/netscope.h39
-rw-r--r--lib/isc/include/isc/nonce.h33
-rw-r--r--lib/isc/include/isc/os.h32
-rw-r--r--lib/isc/include/isc/parseint.h60
-rw-r--r--lib/isc/include/isc/platform.h.in100
-rw-r--r--lib/isc/include/isc/pool.h141
-rw-r--r--lib/isc/include/isc/portset.h138
-rw-r--r--lib/isc/include/isc/print.h33
-rw-r--r--lib/isc/include/isc/quota.h156
-rw-r--r--lib/isc/include/isc/radix.h224
-rw-r--r--lib/isc/include/isc/random.h65
-rw-r--r--lib/isc/include/isc/ratelimiter.h148
-rw-r--r--lib/isc/include/isc/refcount.h156
-rw-r--r--lib/isc/include/isc/regex.h36
-rw-r--r--lib/isc/include/isc/region.h98
-rw-r--r--lib/isc/include/isc/resource.h90
-rw-r--r--lib/isc/include/isc/result.h122
-rw-r--r--lib/isc/include/isc/resultclass.h43
-rw-r--r--lib/isc/include/isc/rwlock.h107
-rw-r--r--lib/isc/include/isc/safe.h47
-rw-r--r--lib/isc/include/isc/serial.h72
-rw-r--r--lib/isc/include/isc/siphash.h37
-rw-r--r--lib/isc/include/isc/sockaddr.h254
-rw-r--r--lib/isc/include/isc/socket.h914
-rw-r--r--lib/isc/include/isc/stats.h256
-rw-r--r--lib/isc/include/isc/stdio.h74
-rw-r--r--lib/isc/include/isc/strerr.h23
-rw-r--r--lib/isc/include/isc/string.h43
-rw-r--r--lib/isc/include/isc/symtab.h138
-rw-r--r--lib/isc/include/isc/task.h721
-rw-r--r--lib/isc/include/isc/taskpool.h138
-rw-r--r--lib/isc/include/isc/timer.h323
-rw-r--r--lib/isc/include/isc/tm.h42
-rw-r--r--lib/isc/include/isc/types.h131
-rw-r--r--lib/isc/include/isc/url.h82
-rw-r--r--lib/isc/include/isc/utf8.h43
-rw-r--r--lib/isc/include/isc/util.h445
-rw-r--r--lib/isc/include/isc/version.h18
-rw-r--r--lib/isc/include/pk11/Makefile.in40
-rw-r--r--lib/isc/include/pk11/constants.h37
-rw-r--r--lib/isc/include/pk11/internal.h49
-rw-r--r--lib/isc/include/pk11/pk11.h290
-rw-r--r--lib/isc/include/pk11/result.h48
-rw-r--r--lib/isc/include/pk11/site.h16
-rw-r--r--lib/isc/include/pkcs11/Makefile.in40
-rw-r--r--lib/isc/include/pkcs11/pkcs11.h1655
-rw-r--r--lib/isc/iterated_hash.c64
-rw-r--r--lib/isc/lex.c1133
-rw-r--r--lib/isc/lfsr.c153
-rw-r--r--lib/isc/lib.c85
-rw-r--r--lib/isc/lib_p.h20
-rw-r--r--lib/isc/log.c1896
-rw-r--r--lib/isc/managers.c95
-rw-r--r--lib/isc/md.c175
-rw-r--r--lib/isc/mem.c2450
-rw-r--r--lib/isc/mem_p.h36
-rw-r--r--lib/isc/mutexblock.c35
-rw-r--r--lib/isc/netaddr.c479
-rw-r--r--lib/isc/netmgr/Makefile.in41
-rw-r--r--lib/isc/netmgr/netmgr-int.h1615
-rw-r--r--lib/isc/netmgr/netmgr.c3396
-rw-r--r--lib/isc/netmgr/tcp.c1456
-rw-r--r--lib/isc/netmgr/tcpdns.c1505
-rw-r--r--lib/isc/netmgr/udp.c1211
-rw-r--r--lib/isc/netmgr/uv-compat.c152
-rw-r--r--lib/isc/netmgr/uv-compat.h126
-rw-r--r--lib/isc/netmgr/uverr2result.c104
-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.c231
-rw-r--r--lib/isc/openssl_shim.h135
-rw-r--r--lib/isc/parseint.c79
-rw-r--r--lib/isc/pk11.c1112
-rw-r--r--lib/isc/pk11_result.c73
-rw-r--r--lib/isc/pool.c163
-rw-r--r--lib/isc/portset.c135
-rw-r--r--lib/isc/pthreads/Makefile.in32
-rw-r--r--lib/isc/pthreads/condition.c72
l---------lib/isc/pthreads/include/.clang-format1
-rw-r--r--lib/isc/pthreads/include/Makefile.in19
-rw-r--r--lib/isc/pthreads/include/isc/Makefile.in36
-rw-r--r--lib/isc/pthreads/include/isc/condition.h64
-rw-r--r--lib/isc/pthreads/include/isc/mutex.h129
-rw-r--r--lib/isc/pthreads/include/isc/once.h33
-rw-r--r--lib/isc/pthreads/include/isc/thread.h74
-rw-r--r--lib/isc/pthreads/mutex.c302
-rw-r--r--lib/isc/pthreads/thread.c129
-rw-r--r--lib/isc/quota.c198
-rw-r--r--lib/isc/radix.c706
-rw-r--r--lib/isc/random.c161
-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/result.c306
-rw-r--r--lib/isc/rwlock.c646
-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.c513
-rw-r--r--lib/isc/stats.c203
-rw-r--r--lib/isc/string.c149
-rw-r--r--lib/isc/symtab.c294
-rw-r--r--lib/isc/task.c1486
-rw-r--r--lib/isc/task_p.h106
-rw-r--r--lib/isc/taskpool.c157
-rw-r--r--lib/isc/tests/Kyuafile43
-rw-r--r--lib/isc/tests/Makefile.in227
-rw-r--r--lib/isc/tests/aes_test.c249
-rw-r--r--lib/isc/tests/buffer_test.c351
-rw-r--r--lib/isc/tests/counter_test.c105
-rw-r--r--lib/isc/tests/crc64_test.c108
-rw-r--r--lib/isc/tests/errno_test.c123
-rw-r--r--lib/isc/tests/file_test.c154
-rw-r--r--lib/isc/tests/hash_test.c114
-rw-r--r--lib/isc/tests/heap_test.c115
-rw-r--r--lib/isc/tests/hmac_test.c949
-rw-r--r--lib/isc/tests/ht_test.c358
-rw-r--r--lib/isc/tests/isctest.c173
-rw-r--r--lib/isc/tests/isctest.h63
-rw-r--r--lib/isc/tests/lex_test.c432
-rw-r--r--lib/isc/tests/md_test.c587
-rw-r--r--lib/isc/tests/mem_test.c477
-rw-r--r--lib/isc/tests/netaddr_test.c159
-rw-r--r--lib/isc/tests/netmgr_test.c2218
-rw-r--r--lib/isc/tests/parse_test.c96
-rw-r--r--lib/isc/tests/pool_test.c194
-rw-r--r--lib/isc/tests/quota_test.c358
-rw-r--r--lib/isc/tests/radix_test.c124
-rw-r--r--lib/isc/tests/random_test.c894
-rw-r--r--lib/isc/tests/regex_test.c2374
-rw-r--r--lib/isc/tests/result_test.c132
-rw-r--r--lib/isc/tests/safe_test.c107
-rw-r--r--lib/isc/tests/siphash_test.c183
-rw-r--r--lib/isc/tests/sockaddr_test.c188
-rw-r--r--lib/isc/tests/socket_test.c833
-rw-r--r--lib/isc/tests/stats_test.c141
-rw-r--r--lib/isc/tests/symtab_test.c171
-rw-r--r--lib/isc/tests/task_test.c1595
-rw-r--r--lib/isc/tests/taskpool_test.c204
-rw-r--r--lib/isc/tests/testdata/file/keep0
-rw-r--r--lib/isc/tests/time_test.c430
-rw-r--r--lib/isc/tests/timer_test.c634
-rw-r--r--lib/isc/tests/uv_wrap.h323
-rw-r--r--lib/isc/timer.c753
-rw-r--r--lib/isc/timer_p.h25
-rw-r--r--lib/isc/tls.c161
-rw-r--r--lib/isc/tls_p.h20
-rw-r--r--lib/isc/tm.c469
-rw-r--r--lib/isc/trampoline.c218
-rw-r--r--lib/isc/trampoline_p.h90
-rw-r--r--lib/isc/unix/Makefile.in48
-rw-r--r--lib/isc/unix/dir.c268
-rw-r--r--lib/isc/unix/errno.c24
-rw-r--r--lib/isc/unix/errno2result.c131
-rw-r--r--lib/isc/unix/errno2result.h37
-rw-r--r--lib/isc/unix/file.c840
-rw-r--r--lib/isc/unix/fsaccess.c87
-rw-r--r--lib/isc/unix/ifiter_getifaddrs.c240
l---------lib/isc/unix/include/.clang-format1
-rw-r--r--lib/isc/unix/include/Makefile.in19
-rw-r--r--lib/isc/unix/include/isc/Makefile.in37
-rw-r--r--lib/isc/unix/include/isc/align.h20
-rw-r--r--lib/isc/unix/include/isc/dir.h75
-rw-r--r--lib/isc/unix/include/isc/net.h310
-rw-r--r--lib/isc/unix/include/isc/netdb.h51
-rw-r--r--lib/isc/unix/include/isc/offset.h28
-rw-r--r--lib/isc/unix/include/isc/stat.h47
-rw-r--r--lib/isc/unix/include/isc/stdatomic.h224
-rw-r--r--lib/isc/unix/include/isc/stdtime.h64
-rw-r--r--lib/isc/unix/include/isc/syslog.h41
-rw-r--r--lib/isc/unix/include/isc/time.h452
-rw-r--r--lib/isc/unix/interfaceiter.c301
-rw-r--r--lib/isc/unix/meminfo.c50
-rw-r--r--lib/isc/unix/net.c860
-rw-r--r--lib/isc/unix/os.c68
-rw-r--r--lib/isc/unix/pk11_api.c708
-rw-r--r--lib/isc/unix/resource.c219
-rw-r--r--lib/isc/unix/socket.c5521
-rw-r--r--lib/isc/unix/socket_p.h27
-rw-r--r--lib/isc/unix/stdio.c152
-rw-r--r--lib/isc/unix/stdtime.c68
-rw-r--r--lib/isc/unix/syslog.c73
-rw-r--r--lib/isc/unix/time.c553
-rw-r--r--lib/isc/url.c671
-rw-r--r--lib/isc/utf8.c89
-rw-r--r--lib/isc/version.c18
-rw-r--r--lib/isc/win32/.dir-locals.el35
-rw-r--r--lib/isc/win32/DLLMain.c52
-rw-r--r--lib/isc/win32/Makefile.in36
-rw-r--r--lib/isc/win32/condition.c255
-rw-r--r--lib/isc/win32/dir.c314
-rw-r--r--lib/isc/win32/errno.c21
-rw-r--r--lib/isc/win32/errno2result.c118
-rw-r--r--lib/isc/win32/errno2result.h35
-rw-r--r--lib/isc/win32/file.c1003
-rw-r--r--lib/isc/win32/fsaccess.c404
l---------lib/isc/win32/include/.clang-format1
-rw-r--r--lib/isc/win32/include/Makefile.in19
-rw-r--r--lib/isc/win32/include/isc/Makefile.in32
-rw-r--r--lib/isc/win32/include/isc/align.h15
-rw-r--r--lib/isc/win32/include/isc/bind_registry.h42
-rw-r--r--lib/isc/win32/include/isc/bindevt.h83
-rw-r--r--lib/isc/win32/include/isc/condition.h60
-rw-r--r--lib/isc/win32/include/isc/dir.h73
-rw-r--r--lib/isc/win32/include/isc/ipv6.h130
-rw-r--r--lib/isc/win32/include/isc/mutex.h47
-rw-r--r--lib/isc/win32/include/isc/net.h402
-rw-r--r--lib/isc/win32/include/isc/netdb.h48
-rw-r--r--lib/isc/win32/include/isc/ntgroups.h28
-rw-r--r--lib/isc/win32/include/isc/ntpaths.h66
-rw-r--r--lib/isc/win32/include/isc/offset.h26
-rw-r--r--lib/isc/win32/include/isc/once.h40
-rw-r--r--lib/isc/win32/include/isc/platform.h.in132
-rw-r--r--lib/isc/win32/include/isc/stat.h58
-rw-r--r--lib/isc/win32/include/isc/stdatomic.h595
-rw-r--r--lib/isc/win32/include/isc/stdtime.h62
-rw-r--r--lib/isc/win32/include/isc/syslog.h39
-rw-r--r--lib/isc/win32/include/isc/thread.h103
-rw-r--r--lib/isc/win32/include/isc/time.h468
-rw-r--r--lib/isc/win32/include/isc/win32os.h41
-rw-r--r--lib/isc/win32/interfaceiter.c550
-rw-r--r--lib/isc/win32/ipv6.c18
-rw-r--r--lib/isc/win32/libgen.h22
-rw-r--r--lib/isc/win32/libisc.def.exclude42
-rw-r--r--lib/isc/win32/libisc.def.in805
-rw-r--r--lib/isc/win32/libisc.vcxproj.filters.in671
-rw-r--r--lib/isc/win32/libisc.vcxproj.in483
-rw-r--r--lib/isc/win32/libisc.vcxproj.user3
-rw-r--r--lib/isc/win32/meminfo.c26
-rw-r--r--lib/isc/win32/net.c301
-rw-r--r--lib/isc/win32/netdb.h182
-rw-r--r--lib/isc/win32/ntgroups.c215
-rw-r--r--lib/isc/win32/ntpaths.c151
-rw-r--r--lib/isc/win32/once.c43
-rw-r--r--lib/isc/win32/os.c41
-rw-r--r--lib/isc/win32/pk11_api.c706
-rw-r--r--lib/isc/win32/resource.c66
-rw-r--r--lib/isc/win32/socket.c3965
-rw-r--r--lib/isc/win32/stdio.c158
-rw-r--r--lib/isc/win32/stdtime.c43
-rw-r--r--lib/isc/win32/syslog.c171
-rw-r--r--lib/isc/win32/syslog.h70
-rw-r--r--lib/isc/win32/thread.c82
-rw-r--r--lib/isc/win32/time.c651
-rw-r--r--lib/isc/win32/unistd.h55
-rw-r--r--lib/isc/win32/version.c18
-rw-r--r--lib/isc/win32/win32os.c113
-rw-r--r--lib/isc/xoshiro128starstar.c63
-rw-r--r--lib/isccc/Kyuafile15
-rw-r--r--lib/isccc/Makefile.in81
-rw-r--r--lib/isccc/alist.c320
-rw-r--r--lib/isccc/base64.c75
-rw-r--r--lib/isccc/cc.c1059
-rw-r--r--lib/isccc/ccmsg.c231
l---------lib/isccc/include/.clang-format1
-rw-r--r--lib/isccc/include/Makefile.in19
-rw-r--r--lib/isccc/include/isccc/Makefile.in41
-rw-r--r--lib/isccc/include/isccc/alist.h89
-rw-r--r--lib/isccc/include/isccc/base64.h82
-rw-r--r--lib/isccc/include/isccc/cc.h131
-rw-r--r--lib/isccc/include/isccc/ccmsg.h146
-rw-r--r--lib/isccc/include/isccc/events.h46
-rw-r--r--lib/isccc/include/isccc/result.h56
-rw-r--r--lib/isccc/include/isccc/sexpr.h122
-rw-r--r--lib/isccc/include/isccc/symtab.h136
-rw-r--r--lib/isccc/include/isccc/symtype.h40
-rw-r--r--lib/isccc/include/isccc/types.h55
-rw-r--r--lib/isccc/include/isccc/util.h229
-rw-r--r--lib/isccc/include/isccc/version.h18
-rw-r--r--lib/isccc/result.c76
-rw-r--r--lib/isccc/sexpr.c317
-rw-r--r--lib/isccc/symtab.c293
-rw-r--r--lib/isccc/tests/Kyuafile15
-rw-r--r--lib/isccc/tests/Makefile.in51
-rw-r--r--lib/isccc/tests/result_test.c84
-rw-r--r--lib/isccc/version.c18
-rw-r--r--lib/isccc/win32/DLLMain.c49
-rw-r--r--lib/isccc/win32/libisccc.def65
-rw-r--r--lib/isccc/win32/libisccc.vcxproj.filters.in87
-rw-r--r--lib/isccc/win32/libisccc.vcxproj.in148
-rw-r--r--lib/isccc/win32/libisccc.vcxproj.user3
-rw-r--r--lib/isccc/win32/version.c18
-rw-r--r--lib/isccfg/Kyuafile15
-rw-r--r--lib/isccfg/Makefile.in79
-rw-r--r--lib/isccfg/aclconf.c934
-rw-r--r--lib/isccfg/dnsconf.c58
l---------lib/isccfg/include/.clang-format1
-rw-r--r--lib/isccfg/include/Makefile.in19
-rw-r--r--lib/isccfg/include/isccfg/Makefile.in42
-rw-r--r--lib/isccfg/include/isccfg/aclconf.h94
-rw-r--r--lib/isccfg/include/isccfg/cfg.h626
-rw-r--r--lib/isccfg/include/isccfg/dnsconf.h30
-rw-r--r--lib/isccfg/include/isccfg/grammar.h620
-rw-r--r--lib/isccfg/include/isccfg/kaspconf.h60
-rw-r--r--lib/isccfg/include/isccfg/log.h49
-rw-r--r--lib/isccfg/include/isccfg/namedconf.h57
-rw-r--r--lib/isccfg/include/isccfg/version.h18
-rw-r--r--lib/isccfg/kaspconf.c423
-rw-r--r--lib/isccfg/log.c41
-rw-r--r--lib/isccfg/namedconf.c3891
-rw-r--r--lib/isccfg/parser.c4145
-rw-r--r--lib/isccfg/tests/Kyuafile16
-rw-r--r--lib/isccfg/tests/Makefile.in57
-rw-r--r--lib/isccfg/tests/duration_test.c278
-rw-r--r--lib/isccfg/tests/parser_test.c290
-rw-r--r--lib/isccfg/version.c18
-rw-r--r--lib/isccfg/win32/DLLMain.c51
-rw-r--r--lib/isccfg/win32/libisccfg.def175
-rw-r--r--lib/isccfg/win32/libisccfg.vcxproj.filters.in72
-rw-r--r--lib/isccfg/win32/libisccfg.vcxproj.in143
-rw-r--r--lib/isccfg/win32/libisccfg.vcxproj.user3
-rw-r--r--lib/isccfg/win32/version.c18
-rw-r--r--lib/ns/Kyuafile15
-rw-r--r--lib/ns/Makefile.in89
-rw-r--r--lib/ns/client.c3053
-rw-r--r--lib/ns/hooks.c544
l---------lib/ns/include/.clang-format1
-rw-r--r--lib/ns/include/Makefile.in19
-rw-r--r--lib/ns/include/ns/Makefile.in37
-rw-r--r--lib/ns/include/ns/client.h585
-rw-r--r--lib/ns/include/ns/hooks.h420
-rw-r--r--lib/ns/include/ns/interfacemgr.h202
-rw-r--r--lib/ns/include/ns/lib.h37
-rw-r--r--lib/ns/include/ns/listenlist.h101
-rw-r--r--lib/ns/include/ns/log.h74
-rw-r--r--lib/ns/include/ns/notify.h47
-rw-r--r--lib/ns/include/ns/query.h235
-rw-r--r--lib/ns/include/ns/server.h189
-rw-r--r--lib/ns/include/ns/sortlist.h83
-rw-r--r--lib/ns/include/ns/stats.h141
-rw-r--r--lib/ns/include/ns/types.h35
-rw-r--r--lib/ns/include/ns/update.h45
-rw-r--r--lib/ns/include/ns/version.h18
-rw-r--r--lib/ns/include/ns/xfrout.h33
-rw-r--r--lib/ns/interfacemgr.c1263
-rw-r--r--lib/ns/lib.c86
-rw-r--r--lib/ns/listenlist.c130
-rw-r--r--lib/ns/log.c64
-rw-r--r--lib/ns/notify.c179
-rw-r--r--lib/ns/query.c12013
-rw-r--r--lib/ns/server.c233
-rw-r--r--lib/ns/sortlist.c167
-rw-r--r--lib/ns/stats.c128
-rw-r--r--lib/ns/tests/Kyuafile18
-rw-r--r--lib/ns/tests/Makefile.in85
-rw-r--r--lib/ns/tests/listenlist_test.c140
-rw-r--r--lib/ns/tests/notify_test.c172
-rw-r--r--lib/ns/tests/nstest.c1018
-rw-r--r--lib/ns/tests/nstest.h165
-rw-r--r--lib/ns/tests/plugin_test.c209
-rw-r--r--lib/ns/tests/query_test.c632
-rw-r--r--lib/ns/tests/testdata/notify/notify1.msg3
-rw-r--r--lib/ns/tests/testdata/notify/zone1.db26
-rw-r--r--lib/ns/tests/testdata/query/foo.db20
-rw-r--r--lib/ns/update.c3696
-rw-r--r--lib/ns/version.c18
-rw-r--r--lib/ns/win32/DLLMain.c49
-rw-r--r--lib/ns/win32/libns.def107
-rw-r--r--lib/ns/win32/libns.vcxproj.filters114
-rw-r--r--lib/ns/win32/libns.vcxproj.in156
-rw-r--r--lib/ns/win32/libns.vcxproj.user3
-rw-r--r--lib/ns/win32/version.c22
-rw-r--r--lib/ns/xfrout.c1813
-rw-r--r--lib/win32/bindevt/bindevt.c23
-rw-r--r--lib/win32/bindevt/bindevt.mc41
-rw-r--r--lib/win32/bindevt/bindevt.vcxproj.filters.in12
-rw-r--r--lib/win32/bindevt/bindevt.vcxproj.in137
-rw-r--r--lib/win32/bindevt/bindevt.vcxproj.user3
1040 files changed, 395769 insertions, 0 deletions
diff --git a/lib/Kyuafile b/lib/Kyuafile
new file mode 100644
index 0000000..b5fd894
--- /dev/null
+++ b/lib/Kyuafile
@@ -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.
+
+syntax(2)
+test_suite('bind9')
+
+include('dns/Kyuafile')
+include('irs/Kyuafile')
+include('isc/Kyuafile')
+include('isccc/Kyuafile')
+include('isccfg/Kyuafile')
+include('ns/Kyuafile')
diff --git a/lib/Makefile.in b/lib/Makefile.in
new file mode 100644
index 0000000..4cabc8c
--- /dev/null
+++ b/lib/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+# Note: the order of SUBDIRS is important.
+# Attempt to disable parallel processing.
+.NOTPARALLEL:
+.NO_PARALLEL:
+SUBDIRS = isc isccc dns ns isccfg bind9 irs
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/bind9/Makefile.in b/lib/bind9/Makefile.in
new file mode 100644
index 0000000..e304dea
--- /dev/null
+++ b/lib/bind9/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. ${BIND9_INCLUDES} ${DNS_INCLUDES} ${ISC_INCLUDES} \
+ ${ISCCFG_INCLUDES} ${NS_INCLUDES} \
+ ${FSTRM_CFLAGS} ${OPENSSL_CFLAGS}
+
+CDEFINES =
+CWARNINGS =
+
+ISCLIBS = ../../lib/isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@
+ISCCFGLIBS = ../../lib/isccfg/libisccfg.@A@
+DNSLIBS = ../../lib/dns/libdns.@A@ @NO_LIBTOOL_DNSLIBS@
+NSLIBS = ../../lib/ns/libns.@A@
+
+ISCDEPLIBS = ../../lib/isc/libisc.@A@
+ISCCFGDEPLIBS = ../../lib/isccfg/libisccfg.@A@
+DNSDEPLIBS = ../../lib/dns/libdns.@A@
+
+LIBS = @LIBS@
+
+SUBDIRS = include
+
+# Alphabetically
+OBJS = check.@O@ getaddresses.@O@ version.@O@
+
+# Alphabetically
+SRCS = check.c getaddresses.c version.c
+
+TARGETS = timestamp
+
+@BIND9_MAKE_RULES@
+
+version.@O@: version.c
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} \
+ -DVERSION=\"${VERSION}\" \
+ -c ${srcdir}/version.c
+
+libbind9.@SA@: ${OBJS}
+ ${AR} ${ARFLAGS} $@ ${OBJS}
+ ${RANLIB} $@
+
+libbind9.la: ${OBJS} ${ISCCFGDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} \
+ ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libbind9.la -rpath ${libdir} \
+ -release "${VERSION}" \
+ ${OBJS} ${NSLIBS} ${DNSLIBS} ${ISCCFGLIBS} ${ISCLIBS} \
+ @DNS_CRYPTO_LIBS@ ${LIBS}
+
+timestamp: libbind9.@A@
+ touch timestamp
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}
+
+install:: timestamp installdirs
+ ${LIBTOOL_MODE_INSTALL} ${INSTALL_LIBRARY} libbind9.@A@ ${DESTDIR}${libdir}
+
+uninstall::
+ ${LIBTOOL_MODE_UNINSTALL} rm -f ${DESTDIR}${libdir}/libbind9.@A@
+
+clean distclean::
+ rm -f libbind9.@A@ timestamp
diff --git a/lib/bind9/check.c b/lib/bind9/check.c
new file mode 100644
index 0000000..3a78a17
--- /dev/null
+++ b/lib/bind9/check.c
@@ -0,0 +1,5512 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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>
+
+#ifdef HAVE_DNSTAP
+#include <fstrm.h>
+#endif
+
+#include <isc/aes.h>
+#include <isc/base64.h>
+#include <isc/buffer.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/platform.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 <pk11/site.h>
+
+#include <dns/acl.h>
+#include <dns/dnstap.h>
+#include <dns/fixedname.h>
+#include <dns/kasp.h>
+#include <dns/keyvalues.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 <dst/result.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);
+ }
+ 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_dscp(const cfg_obj_t *options, isc_log_t *logctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ const cfg_obj_t *obj = NULL;
+
+ /*
+ * Check that DSCP setting is within range
+ */
+ obj = NULL;
+ (void)cfg_map_get(options, "dscp", &obj);
+ if (obj != NULL) {
+ uint32_t dscp = cfg_obj_asuint32(obj);
+ if (dscp >= 64) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'dscp' out of range (0-63)");
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ return (result);
+}
+
+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 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, 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";
+ 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;
+ }
+ 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;
+ }
+ }
+
+ tresult = check_dscp(options, logctx);
+ if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ 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, "geoip-use-ecs", &obj);
+ if (obj != NULL && cfg_obj_asboolean(obj)) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'geoip-use-ecs yes': "
+ "ECS can no longer be used in geoip ACLs");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ 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");
+ }
+ }
+
+ 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);
+}
+
+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;
+
+ addr = cfg_tuple_get(cfg_listelt_value(element),
+ "remoteselement");
+ key = cfg_tuple_get(cfg_listelt_value(element), "key");
+
+ 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;
+ }
+ }
+ }
+ 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;
+ }
+ }
+ 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_subdomainkrb5:
+ 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;
+
+ typeobj = cfg_listelt_value(element2);
+ DE_CONST(cfg_obj_asstring(typeobj), r.base);
+ 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", 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,
+ 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;
+ dns_masterformat_t masterformat;
+ 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;
+ } 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 slave 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 slave 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 slave 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;
+ }
+ }
+ }
+
+ /*
+ * Check that max-zone-ttl isn't used with masterfile-format map
+ */
+ masterformat = dns_masterformat_text;
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "masterfile-format", &obj);
+ if (obj != NULL) {
+ const char *masterformatstr = cfg_obj_asstring(obj);
+ if (strcasecmp(masterformatstr, "text") == 0) {
+ masterformat = dns_masterformat_text;
+ } else if (strcasecmp(masterformatstr, "raw") == 0) {
+ masterformat = dns_masterformat_raw;
+ } else if (strcasecmp(masterformatstr, "map") == 0) {
+ masterformat = dns_masterformat_map;
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "masterfile-format: format 'map' is "
+ "deprecated");
+ } else {
+ UNREACHABLE();
+ }
+ }
+
+ if (masterformat == dns_masterformat_map) {
+ 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(zconfig, logctx, ISC_LOG_ERROR,
+ "zone '%s': 'max-zone-ttl' is not "
+ "compatible with 'masterfile-format map'",
+ 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, 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 isc_result_t
+check_servers(const cfg_obj_t *config, const cfg_obj_t *voptions,
+ isc_symtab_t *symtab, 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)) {
+ 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;
+ }
+ }
+ }
+ 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 master or slave zone",
+ rpz_catz, zonename, forview, viewname);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+ return (result);
+}
+
+#ifdef HAVE_DLOPEN
+/*%
+ * 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);
+}
+#endif /* ifdef HAVE_DLOPEN */
+
+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,
+ 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;
+#ifndef HAVE_DLOPEN
+ const cfg_obj_t *dyndb = NULL;
+#endif /* ifndef HAVE_DLOPEN */
+ 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,
+ actx, logctx, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+#ifndef HAVE_DLOPEN
+ if (voptions != NULL) {
+ (void)cfg_map_get(voptions, "dyndb", &dyndb);
+ } else {
+ (void)cfg_map_get(config, "dyndb", &dyndb);
+ }
+
+ if (dyndb != NULL) {
+ cfg_obj_log(dyndb, logctx, ISC_LOG_ERROR,
+ "dynamic loading of databases is not supported");
+ if (tresult != ISC_R_SUCCESS) {
+ result = ISC_R_NOTIMPLEMENTED;
+ }
+ }
+#endif /* ifndef HAVE_DLOPEN */
+
+ /*
+ * 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;
+ }
+ }
+
+ 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, 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, logctx, mctx, optlevel_view);
+ } else {
+ tresult = check_options(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);
+ }
+ }
+
+#ifdef HAVE_DLOPEN
+ {
+ 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;
+ }
+ }
+#endif /* HAVE_DLOPEN */
+
+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,
+ 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_obj_t *obj = 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, 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;
+ }
+
+ (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, 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,
+ inview, logctx, mctx);
+ }
+ if (tresult != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ if (views != NULL && options != NULL) {
+ obj = NULL;
+ tresult = cfg_map_get(options, "cache-file", &obj);
+ if (tresult == ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'cache-file' cannot be a global "
+ "option if views are present");
+ 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/.clang-format b/lib/bind9/include/.clang-format
new file mode 120000
index 0000000..0e62f72
--- /dev/null
+++ b/lib/bind9/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/bind9/include/Makefile.in b/lib/bind9/include/Makefile.in
new file mode 100644
index 0000000..51a415b
--- /dev/null
+++ b/lib/bind9/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = bind9
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/bind9/include/bind9/Makefile.in b/lib/bind9/include/bind9/Makefile.in
new file mode 100644
index 0000000..3a8c799
--- /dev/null
+++ b/lib/bind9/include/bind9/Makefile.in
@@ -0,0 +1,41 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+#
+# Only list headers that are to be installed and are not
+# machine generated. The latter are handled specially in the
+# install target below.
+#
+HEADERS = check.h getaddresses.h version.h
+
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/bind9
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/bind9 || exit 1; \
+ done
+
+uninstall::
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/bind9/$$i || exit 1; \
+ done
diff --git a/lib/bind9/include/bind9/check.h b/lib/bind9/include/bind9/check.h
new file mode 100644
index 0000000..3919223
--- /dev/null
+++ b/lib/bind9/include/bind9/check.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.
+ */
+
+#ifndef BIND9_CHECK_H
+#define BIND9_CHECK_H 1
+
+/*! \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,
+ 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.
+ *
+ * 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
+
+#endif /* BIND9_CHECK_H */
diff --git a/lib/bind9/include/bind9/getaddresses.h b/lib/bind9/include/bind9/getaddresses.h
new file mode 100644
index 0000000..2ae677f
--- /dev/null
+++ b/lib/bind9/include/bind9/getaddresses.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.
+ */
+
+#ifndef BIND9_GETADDRESSES_H
+#define BIND9_GETADDRESSES_H 1
+
+/*! \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
+
+#endif /* BIND9_GETADDRESSES_H */
diff --git a/lib/bind9/include/bind9/version.h b/lib/bind9/include/bind9/version.h
new file mode 100644
index 0000000..066f7b9
--- /dev/null
+++ b/lib/bind9/include/bind9/version.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file bind9/version.h */
+
+#include <isc/platform.h>
+
+LIBBIND9_EXTERNAL_DATA extern const char bind9_version[];
diff --git a/lib/bind9/version.c b/lib/bind9/version.c
new file mode 100644
index 0000000..e69c0d9
--- /dev/null
+++ b/lib/bind9/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <bind9/version.h>
+
+const char bind9_version[] = VERSION;
diff --git a/lib/bind9/win32/DLLMain.c b/lib/bind9/win32/DLLMain.c
new file mode 100644
index 0000000..62c6e53
--- /dev/null
+++ b/lib/bind9/win32/DLLMain.c
@@ -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.
+ */
+
+#include <signal.h>
+#include <windows.h>
+
+/*
+ * Called when we enter the DLL
+ */
+__declspec(dllexport) BOOL WINAPI
+ DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
+ switch (fdwReason) {
+ /*
+ * The DLL is loading due to process
+ * initialization or a call to LoadLibrary.
+ */
+ case DLL_PROCESS_ATTACH:
+ break;
+
+ /* The attached process creates a new thread. */
+ case DLL_THREAD_ATTACH:
+ break;
+
+ /* The thread of the attached process terminates. */
+ case DLL_THREAD_DETACH:
+ break;
+
+ /*
+ * The DLL is unloading from a process due to
+ * process termination or a call to FreeLibrary.
+ */
+ case DLL_PROCESS_DETACH:
+ break;
+
+ default:
+ break;
+ }
+ return (TRUE);
+}
diff --git a/lib/bind9/win32/libbind9.def b/lib/bind9/win32/libbind9.def
new file mode 100644
index 0000000..b9a14ad
--- /dev/null
+++ b/lib/bind9/win32/libbind9.def
@@ -0,0 +1,8 @@
+LIBRARY libbind9
+
+; Exported Functions
+EXPORTS
+bind9_check_namedconf
+bind9_check_key
+bind9_getaddresses
+
diff --git a/lib/bind9/win32/libbind9.vcxproj.filters.in b/lib/bind9/win32/libbind9.vcxproj.filters.in
new file mode 100644
index 0000000..4e28330
--- /dev/null
+++ b/lib/bind9/win32/libbind9.vcxproj.filters.in
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="libbind9.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="DLLMain.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="version.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\check.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\getaddresses.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\bind9\check.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\bind9\getaddresses.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\bind9\version.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/lib/bind9/win32/libbind9.vcxproj.in b/lib/bind9/win32/libbind9.vcxproj.in
new file mode 100644
index 0000000..5ee7c6d
--- /dev/null
+++ b/lib/bind9/win32/libbind9.vcxproj.in
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="@TOOLS_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|@PLATFORM@">
+ <Configuration>Debug</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|@PLATFORM@">
+ <Configuration>Release</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{E741C10B-B075-4206-9596-46765B665E03}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>libbind9</RootNamespace>
+ @WINDOWS_TARGET_PLATFORM_VERSION@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBBIND9_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;..\..\isccfg\include;..\..\dns\include;..\..\ns\include;@LIBXML2_INC@@OPENSSL_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BrowseInformation>true</BrowseInformation>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>..\..\isc\win32\$(Configuration);..\..\dns\win32\$(Configuration);..\..\isccfg\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libisc.lib;libdns.lib;libisccfg.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>.\libbind9.def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>@INTRINSIC@</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBBIND9_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;..\..\isccfg\include;..\..\dns\include;..\..\ns\include;@LIBXML2_INC@@OPENSSL_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <WholeProgramOptimization>false</WholeProgramOptimization>
+ <StringPooling>true</StringPooling>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>..\..\isc\win32\$(Configuration);..\..\dns\win32\$(Configuration);..\..\isccfg\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libisc.lib;libdns.lib;libisccfg.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>.\libbind9.def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <None Include="libbind9.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\check.c" />
+ <ClCompile Include="..\getaddresses.c" />
+ <ClCompile Include="DLLMain.c" />
+ <ClCompile Include="version.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\bind9\check.h" />
+ <ClInclude Include="..\include\bind9\getaddresses.h" />
+ <ClInclude Include="..\include\bind9\version.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/lib/bind9/win32/libbind9.vcxproj.user b/lib/bind9/win32/libbind9.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/lib/bind9/win32/libbind9.vcxproj.user
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+</Project> \ No newline at end of file
diff --git a/lib/bind9/win32/version.c b/lib/bind9/win32/version.c
new file mode 100644
index 0000000..ba9f07b
--- /dev/null
+++ b/lib/bind9/win32/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <versions.h>
+
+#include <bind9/version.h>
+
+LIBBIND9_EXTERNAL_DATA const char bind9_version[] = VERSION;
diff --git a/lib/dns/Kyuafile b/lib/dns/Kyuafile
new file mode 100644
index 0000000..c796010
--- /dev/null
+++ b/lib/dns/Kyuafile
@@ -0,0 +1,15 @@
+-- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+--
+-- SPDX-License-Identifier: MPL-2.0
+--
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, you can obtain one at https://mozilla.org/MPL/2.0/.
+--
+-- See the COPYRIGHT file distributed with this work for additional
+-- information regarding copyright ownership.
+
+syntax(2)
+test_suite('bind9')
+
+include('tests/Kyuafile')
diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in
new file mode 100644
index 0000000..5061c47
--- /dev/null
+++ b/lib/dns/Makefile.in
@@ -0,0 +1,215 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+# Attempt to disable parallel processing.
+.NOTPARALLEL:
+.NO_PARALLEL:
+
+VERSION=@BIND9_VERSION@
+@BIND9_MAJOR@
+
+@LIBDNS_MAPAPI@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. -I${top_srcdir}/lib/dns -Iinclude ${DNS_INCLUDES} \
+ ${ISC_INCLUDES} \
+ ${FSTRM_CFLAGS} \
+ ${OPENSSL_CFLAGS} @DST_GSSAPI_INC@ \
+ ${PROTOBUF_C_CFLAGS} \
+ ${JSON_C_CFLAGS} \
+ ${LIBXML2_CFLAGS} \
+ ${LMDB_CFLAGS} \
+ ${MAXMINDDB_CFLAGS}
+
+CDEFINES = @USE_GSSAPI@
+
+CWARNINGS =
+
+ISCLIBS = ../../lib/isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@
+
+ISCDEPLIBS = ../../lib/isc/libisc.@A@
+
+LIBS = ${FSTRM_LIBS} ${MAXMINDDB_LIBS} ${LMDB_LIBS} ${PROTOBUF_C_LIBS} @LIBS@
+
+# Alphabetically
+
+DSTOBJS = @DST_EXTRA_OBJS@ \
+ dst_api.@O@ dst_parse.@O@ dst_result.@O@ \
+ gssapi_link.@O@ gssapictx.@O@ hmac_link.@O@ \
+ openssl_link.@O@ openssldh_link.@O@ \
+ opensslecdsa_link.@O@ openssleddsa_link.@O@ opensslrsa_link.@O@ \
+ pkcs11rsa_link.@O@ \
+ pkcs11ecdsa_link.@O@ pkcs11eddsa_link.@O@ pkcs11.@O@ \
+ key.@O@
+
+GEOIP2LINKOBJS = geoip2.@O@
+
+DNSTAPOBJS = dnstap.@O@ dnstap.pb-c.@O@
+
+# Alphabetically
+DNSOBJS = acl.@O@ adb.@O@ badcache.@O@ byaddr.@O@ \
+ cache.@O@ callbacks.@O@ catz.@O@ clientinfo.@O@ compress.@O@ \
+ db.@O@ dbiterator.@O@ dbtable.@O@ diff.@O@ dispatch.@O@ \
+ dlz.@O@ dns64.@O@ dnsrps.@O@ dnssec.@O@ ds.@O@ dyndb.@O@ \
+ ecs.@O@ fixedname.@O@ forward.@O@ \
+ ipkeylist.@O@ iptable.@O@ journal.@O@ kasp.@O@ keydata.@O@ \
+ keymgr.@O@ keytable.@O@ lib.@O@ log.@O@ lookup.@O@ \
+ master.@O@ masterdump.@O@ message.@O@ \
+ name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ nta.@O@ \
+ order.@O@ peer.@O@ portlist.@O@ private.@O@ \
+ rbt.@O@ rbtdb.@O@ rcode.@O@ rdata.@O@ \
+ rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \
+ request.@O@ resolver.@O@ result.@O@ rootns.@O@ \
+ rpz.@O@ rrl.@O@ rriterator.@O@ sdb.@O@ \
+ sdlz.@O@ soa.@O@ ssu.@O@ ssu_external.@O@ \
+ stats.@O@ tcpmsg.@O@ time.@O@ timer.@O@ tkey.@O@ \
+ tsec.@O@ tsig.@O@ ttl.@O@ update.@O@ validator.@O@ \
+ version.@O@ view.@O@ xfrin.@O@ zone.@O@ zonekey.@O@ \
+ zoneverify.@O@ zt.@O@
+PORTDNSOBJS = client.@O@ ecdb.@O@
+
+OBJS= @DNSTAPOBJS@ ${DNSOBJS} ${OTHEROBJS} ${DSTOBJS} \
+ ${PORTDNSOBJS} @GEOIP2LINKOBJS@
+
+DSTSRCS = @DST_EXTRA_SRCS@ \
+ dst_api.c dst_parse.c \
+ dst_result.c gssapi_link.c gssapictx.c hmac_link.c \
+ openssl_link.c openssldh_link.c \
+ opensslecdsa_link.c openssleddsa_link.c opensslrsa_link.c \
+ pkcs11rsa_link.c \
+ pkcs11ecdsa_link.c pkcs11eddsa_link.c pkcs11.c \
+ key.c
+
+GEOIPL2INKSRCS = geoip2.c
+
+DNSTAPSRCS = dnstap.c dnstap.pb-c.c
+
+DNSSRCS = acl.c adb.c badcache.c byaddr.c \
+ cache.c callbacks.c clientinfo.c compress.c \
+ db.c dbiterator.c dbtable.c diff.c dispatch.c \
+ dlz.c dns64.c dnsrps.c dnssec.c ds.c dyndb.c \
+ ecs.c fixedname.c forward.c ipkeylist.c iptable.c \
+ journal.c kasp.c keydata.c keymgr.c keytable.c \
+ lib.c log.c lookup.c master.c masterdump.c message.c \
+ name.c ncache.c nsec.c nsec3.c nta.c \
+ order.c peer.c portlist.c \
+ rbt.c rbtdb.c rcode.c rdata.c rdatalist.c \
+ rdataset.c rdatasetiter.c rdataslab.c request.c \
+ resolver.c result.c rootns.c rpz.c rrl.c rriterator.c \
+ sdb.c sdlz.c soa.c ssu.c ssu_external.c \
+ stats.c tcpmsg.c time.c timer.c tkey.c \
+ tsec.c tsig.c ttl.c update.c validator.c \
+ version.c view.c xfrin.c zone.c zoneverify.c \
+ zonekey.c zt.c ${OTHERSRCS}
+PORTDNSSRCS = client.c ecdb.c
+
+SRCS = ${DSTSRCS} ${DNSSRCS} \
+ ${PORTDNSSRCS} @DNSTAPSRCS@ @GEOIP2LINKSRCS@
+
+SUBDIRS = include
+TARGETS = timestamp
+TESTDIRS = @UNITTESTS@
+
+DEPENDEXTRA = ./gen -F include/dns/rdatastruct.h \
+ -s ${srcdir} -d >> Makefile ;
+
+@BIND9_MAKE_RULES@
+
+PROTOC_C = @PROTOC_C@
+
+version.@O@: version.c
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} \
+ -DVERSION=\"${VERSION}\" \
+ -DMAJOR=\"${MAJOR}\" \
+ -DMAPAPI=\"${MAPAPI}\" \
+ -c ${srcdir}/version.c
+
+libdns.@SA@: ${OBJS}
+ ${AR} ${ARFLAGS} $@ ${OBJS}
+ ${RANLIB} $@
+
+libdns.la: ${OBJS}
+ ${LIBTOOL_MODE_LINK} \
+ ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libdns.la -rpath ${libdir} \
+ -release "${VERSION}" \
+ ${OBJS} ${ISCLIBS} @DNS_CRYPTO_LIBS@ ${LIBS}
+
+include: gen
+ ${MAKE} include/dns/enumtype.h
+ ${MAKE} include/dns/enumclass.h
+ ${MAKE} include/dns/rdatastruct.h
+ ${MAKE} code.h
+
+include/dns/enumtype.h: gen
+ ./gen -s ${srcdir} -t > $@ || { rm -f $@ ; exit 1; }
+
+include/dns/enumclass.h: gen
+ ./gen -s ${srcdir} -c > $@ || { rm -f $@ ; exit 1; }
+
+include/dns/rdatastruct.h: gen \
+ ${srcdir}/rdata/rdatastructpre.h \
+ ${srcdir}/rdata/rdatastructsuf.h
+ ./gen -s ${srcdir} -i \
+ -P ${srcdir}/rdata/rdatastructpre.h \
+ -S ${srcdir}/rdata/rdatastructsuf.h > $@ || \
+ { rm -f $@ ; exit 1; }
+
+code.h: gen
+ ./gen -s ${srcdir} > code.h || { rm -f $@ ; exit 1; }
+
+gen: gen.c
+ ${BUILD_CC} ${BUILD_CFLAGS} -I${top_srcdir}/lib/isc/include \
+ ${LFS_CFLAGS} ${LFS_LDFLAGS} \
+ ${BUILD_CPPFLAGS} ${BUILD_LDFLAGS} -o $@ ${srcdir}/gen.c \
+ ${BUILD_LIBS} ${LFS_LIBS}
+
+timestamp: include libdns.@A@
+ touch timestamp
+
+testdirs: libdns.@A@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}
+
+install:: timestamp installdirs
+ ${LIBTOOL_MODE_INSTALL} ${INSTALL_LIBRARY} libdns.@A@ ${DESTDIR}${libdir}
+
+uninstall::
+ ${LIBTOOL_MODE_UNINSTALL} rm -f ${DESTDIR}${libdir}/libdns.@A@
+
+clean distclean::
+ rm -f libdns.@A@ timestamp
+ rm -f gen code.h include/dns/enumtype.h include/dns/enumclass.h
+ rm -f include/dns/rdatastruct.h
+ rm -f dnstap.pb-c.c dnstap.pb-c.h
+
+newrr::
+ rm -f code.h include/dns/enumtype.h include/dns/enumclass.h
+ rm -f include/dns/rdatastruct.h
+
+rdata.@O@: include
+
+depend: include @DNSTAPSRCS@
+subdirs: include
+${OBJS}: include
+
+# dnstap
+dnstap.@O@: dnstap.c dnstap.pb-c.c
+
+dnstap.pb-c.c dnstap.pb-c.h: dnstap.proto
+ $(PROTOC_C) --c_out=. --proto_path ${srcdir} dnstap.proto
+
+dnstap.pb-c.@O@: dnstap.pb-c.c
diff --git a/lib/dns/acl.c b/lib/dns/acl.c
new file mode 100644
index 0000000..9359e53
--- /dev/null
+++ b/lib/dns/acl.c
@@ -0,0 +1,665 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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>
+
+/*
+ * Create a new ACL, including an IP table and an array with room
+ * for 'n' ACL elements. The elements are uninitialized and the
+ * length is 0.
+ */
+isc_result_t
+dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target) {
+ isc_result_t result;
+ dns_acl_t *acl;
+
+ /*
+ * Work around silly limitation of isc_mem_get().
+ */
+ if (n == 0) {
+ n = 1;
+ }
+
+ acl = isc_mem_get(mctx, sizeof(*acl));
+
+ acl->mctx = NULL;
+ isc_mem_attach(mctx, &acl->mctx);
+
+ acl->name = NULL;
+
+ isc_refcount_init(&acl->refcount, 1);
+
+ result = dns_iptable_create(mctx, &acl->iptable);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(mctx, acl, sizeof(*acl));
+ return (result);
+ }
+
+ acl->elements = NULL;
+ acl->alloc = 0;
+ acl->length = 0;
+ acl->has_negatives = false;
+
+ ISC_LINK_INIT(acl, nextincache);
+ /*
+ * Must set magic early because we use dns_acl_detach() to clean up.
+ */
+ acl->magic = DNS_ACL_MAGIC;
+
+ acl->elements = isc_mem_get(mctx, n * sizeof(dns_aclelement_t));
+ acl->alloc = n;
+ memset(acl->elements, 0, n * sizeof(dns_aclelement_t));
+ *target = acl;
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Create a new ACL and initialize it with the value "any" or "none",
+ * depending on the value of the "neg" parameter.
+ * "any" is a positive iptable entry with bit length 0.
+ * "none" is the same as "!any".
+ */
+static isc_result_t
+dns_acl_anyornone(isc_mem_t *mctx, bool neg, dns_acl_t **target) {
+ isc_result_t result;
+ dns_acl_t *acl = NULL;
+
+ result = dns_acl_create(mctx, 0, &acl);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_iptable_addprefix(acl->iptable, NULL, 0, !neg);
+ if (result != ISC_R_SUCCESS) {
+ dns_acl_detach(&acl);
+ return (result);
+ }
+
+ *target = acl;
+ return (result);
+}
+
+/*
+ * Create a new ACL that matches everything.
+ */
+isc_result_t
+dns_acl_any(isc_mem_t *mctx, dns_acl_t **target) {
+ return (dns_acl_anyornone(mctx, false, target));
+}
+
+/*
+ * Create a new ACL that matches nothing.
+ */
+isc_result_t
+dns_acl_none(isc_mem_t *mctx, dns_acl_t **target) {
+ return (dns_acl_anyornone(mctx, true, target));
+}
+
+/*
+ * If pos is true, test whether acl is set to "{ any; }"
+ * If pos is false, test whether acl is set to "{ none; }"
+ */
+static bool
+dns_acl_isanyornone(dns_acl_t *acl, bool pos) {
+ /* Should never happen but let's be safe */
+ if (acl == NULL || acl->iptable == NULL ||
+ acl->iptable->radix == NULL || acl->iptable->radix->head == NULL ||
+ acl->iptable->radix->head->prefix == NULL)
+ {
+ return (false);
+ }
+
+ if (acl->length != 0 || dns_acl_node_count(acl) != 1) {
+ return (false);
+ }
+
+ if (acl->iptable->radix->head->prefix->bitlen == 0 &&
+ acl->iptable->radix->head->data[0] != NULL &&
+ acl->iptable->radix->head->data[0] ==
+ acl->iptable->radix->head->data[1] &&
+ *(bool *)(acl->iptable->radix->head->data[0]) == pos)
+ {
+ return (true);
+ }
+
+ return (false); /* All others */
+}
+
+/*
+ * Test whether acl is set to "{ any; }"
+ */
+bool
+dns_acl_isany(dns_acl_t *acl) {
+ return (dns_acl_isanyornone(acl, true));
+}
+
+/*
+ * Test whether acl is set to "{ none; }"
+ */
+bool
+dns_acl_isnone(dns_acl_t *acl) {
+ return (dns_acl_isanyornone(acl, false));
+}
+
+/*
+ * Determine whether a given address or signer matches a given ACL.
+ * For a match with a positive ACL element or iptable radix entry,
+ * return with a positive value in match; for a match with a negated ACL
+ * element or radix entry, return with a negative value in match.
+ */
+
+isc_result_t
+dns_acl_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
+ const dns_acl_t *acl, const dns_aclenv_t *env, int *match,
+ const dns_aclelement_t **matchelt) {
+ uint16_t bitlen;
+ isc_prefix_t pfx;
+ isc_radix_node_t *node = NULL;
+ const isc_netaddr_t *addr = reqaddr;
+ isc_netaddr_t v4addr;
+ isc_result_t result;
+ int match_num = -1;
+ unsigned int i;
+
+ REQUIRE(reqaddr != NULL);
+ REQUIRE(matchelt == NULL || *matchelt == NULL);
+
+ if (env != NULL && env->match_mapped && addr->family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&addr->type.in6))
+ {
+ isc_netaddr_fromv4mapped(&v4addr, addr);
+ addr = &v4addr;
+ }
+
+ /* Always match with host addresses. */
+ bitlen = (addr->family == AF_INET6) ? 128 : 32;
+ NETADDR_TO_PREFIX_T(addr, pfx, bitlen);
+
+ /* Assume no match. */
+ *match = 0;
+
+ /* Search radix. */
+ result = isc_radix_search(acl->iptable->radix, &node, &pfx);
+
+ /* Found a match. */
+ if (result == ISC_R_SUCCESS && node != NULL) {
+ int fam = ISC_RADIX_FAMILY(&pfx);
+ match_num = node->node_num[fam];
+ if (*(bool *)node->data[fam]) {
+ *match = match_num;
+ } else {
+ *match = -match_num;
+ }
+ }
+
+ isc_refcount_destroy(&pfx.refcount);
+
+ /* Now search non-radix elements for a match with a lower node_num. */
+ for (i = 0; i < acl->length; i++) {
+ dns_aclelement_t *e = &acl->elements[i];
+
+ /* Already found a better match? */
+ if (match_num != -1 && match_num < e->node_num) {
+ break;
+ }
+
+ if (dns_aclelement_match(reqaddr, reqsigner, e, env, matchelt))
+ {
+ if (match_num == -1 || e->node_num < match_num) {
+ if (e->negative) {
+ *match = -e->node_num;
+ } else {
+ *match = e->node_num;
+ }
+ }
+ break;
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Merge the contents of one ACL into another. Call dns_iptable_merge()
+ * for the IP tables, then concatenate the element arrays.
+ *
+ * If pos is set to false, then the nested ACL is to be negated. This
+ * means reverse the sense of each *positive* element or IP table node,
+ * but leave negatives alone, so as to prevent a double-negative causing
+ * an unexpected positive match in the parent ACL.
+ */
+isc_result_t
+dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, bool pos) {
+ isc_result_t result;
+ unsigned int newalloc, nelem, i;
+ int max_node = 0, nodes;
+
+ /* Resize the element array if needed. */
+ if (dest->length + source->length > dest->alloc) {
+ void *newmem;
+
+ newalloc = dest->alloc + source->alloc;
+ if (newalloc < 4) {
+ newalloc = 4;
+ }
+
+ newmem = isc_mem_get(dest->mctx,
+ newalloc * sizeof(dns_aclelement_t));
+
+ /* Zero. */
+ memset(newmem, 0, newalloc * sizeof(dns_aclelement_t));
+
+ /* Copy in the original elements */
+ memmove(newmem, dest->elements,
+ dest->length * sizeof(dns_aclelement_t));
+
+ /* Release the memory for the old elements array */
+ isc_mem_put(dest->mctx, dest->elements,
+ dest->alloc * sizeof(dns_aclelement_t));
+ dest->elements = newmem;
+ dest->alloc = newalloc;
+ }
+
+ /*
+ * Now copy in the new elements, increasing their node_num
+ * values so as to keep the new ACL consistent. If we're
+ * negating, then negate positive elements, but keep negative
+ * elements the same for security reasons.
+ */
+ nelem = dest->length;
+ dest->length += source->length;
+ for (i = 0; i < source->length; i++) {
+ if (source->elements[i].node_num > max_node) {
+ max_node = source->elements[i].node_num;
+ }
+
+ /* Copy type. */
+ dest->elements[nelem + i].type = source->elements[i].type;
+
+ /* Adjust node numbering. */
+ dest->elements[nelem + i].node_num =
+ source->elements[i].node_num + dns_acl_node_count(dest);
+
+ /* Duplicate nested acl. */
+ if (source->elements[i].type == dns_aclelementtype_nestedacl &&
+ source->elements[i].nestedacl != NULL)
+ {
+ dns_acl_attach(source->elements[i].nestedacl,
+ &dest->elements[nelem + i].nestedacl);
+ }
+
+ /* Duplicate key name. */
+ if (source->elements[i].type == dns_aclelementtype_keyname) {
+ dns_name_init(&dest->elements[nelem + i].keyname, NULL);
+ dns_name_dup(&source->elements[i].keyname, dest->mctx,
+ &dest->elements[nelem + i].keyname);
+ }
+
+#if defined(HAVE_GEOIP2)
+ /* Duplicate GeoIP data */
+ if (source->elements[i].type == dns_aclelementtype_geoip) {
+ dest->elements[nelem + i].geoip_elem =
+ source->elements[i].geoip_elem;
+ }
+#endif /* if defined(HAVE_GEOIP2) */
+
+ /* reverse sense of positives if this is a negative acl */
+ if (!pos && !source->elements[i].negative) {
+ dest->elements[nelem + i].negative = true;
+ } else {
+ dest->elements[nelem + i].negative =
+ source->elements[i].negative;
+ }
+ }
+
+ /*
+ * Merge the iptables. Make sure the destination ACL's
+ * node_count value is set correctly afterward.
+ */
+ nodes = max_node + dns_acl_node_count(dest);
+ result = dns_iptable_merge(dest->iptable, source->iptable, pos);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (nodes > dns_acl_node_count(dest)) {
+ dns_acl_node_count(dest) = nodes;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Like dns_acl_match, but matches against the single ACL element 'e'
+ * rather than a complete ACL, and returns true iff it matched.
+ *
+ * To determine whether the match was positive or negative, the
+ * caller should examine e->negative. Since the element 'e' may be
+ * a reference to a named ACL or a nested ACL, a matching element
+ * returned through 'matchelt' is not necessarily 'e' itself.
+ */
+
+bool
+dns_aclelement_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
+ const dns_aclelement_t *e, const dns_aclenv_t *env,
+ const dns_aclelement_t **matchelt) {
+ dns_acl_t *inner = NULL;
+ int indirectmatch;
+ isc_result_t result;
+
+ switch (e->type) {
+ case dns_aclelementtype_keyname:
+ if (reqsigner != NULL && dns_name_equal(reqsigner, &e->keyname))
+ {
+ if (matchelt != NULL) {
+ *matchelt = e;
+ }
+ return (true);
+ } else {
+ return (false);
+ }
+
+ case dns_aclelementtype_nestedacl:
+ inner = e->nestedacl;
+ break;
+
+ case dns_aclelementtype_localhost:
+ if (env == NULL || env->localhost == NULL) {
+ return (false);
+ }
+ inner = env->localhost;
+ break;
+
+ case dns_aclelementtype_localnets:
+ if (env == NULL || env->localnets == NULL) {
+ return (false);
+ }
+ inner = env->localnets;
+ break;
+
+#if defined(HAVE_GEOIP2)
+ case dns_aclelementtype_geoip:
+ if (env == NULL || env->geoip == NULL) {
+ return (false);
+ }
+ return (dns_geoip_match(reqaddr, env->geoip, &e->geoip_elem));
+#endif /* if defined(HAVE_GEOIP2) */
+ default:
+ UNREACHABLE();
+ }
+
+ result = dns_acl_match(reqaddr, reqsigner, inner, env, &indirectmatch,
+ matchelt);
+ INSIST(result == ISC_R_SUCCESS);
+
+ /*
+ * Treat negative matches in indirect ACLs as "no match".
+ * That way, a negated indirect ACL will never become a
+ * surprise positive match through double negation.
+ * XXXDCL this should be documented.
+ */
+ if (indirectmatch > 0) {
+ if (matchelt != NULL) {
+ *matchelt = e;
+ }
+ return (true);
+ }
+
+ /*
+ * A negative indirect match may have set *matchelt, but we don't
+ * want it set when we return.
+ */
+ if (matchelt != NULL) {
+ *matchelt = NULL;
+ }
+
+ return (false);
+}
+
+void
+dns_acl_attach(dns_acl_t *source, dns_acl_t **target) {
+ REQUIRE(DNS_ACL_VALID(source));
+
+ isc_refcount_increment(&source->refcount);
+ *target = source;
+}
+
+static void
+destroy(dns_acl_t *dacl) {
+ unsigned int i;
+
+ INSIST(!ISC_LINK_LINKED(dacl, nextincache));
+
+ for (i = 0; i < dacl->length; i++) {
+ dns_aclelement_t *de = &dacl->elements[i];
+ if (de->type == dns_aclelementtype_keyname) {
+ dns_name_free(&de->keyname, dacl->mctx);
+ } else if (de->type == dns_aclelementtype_nestedacl) {
+ dns_acl_detach(&de->nestedacl);
+ }
+ }
+ if (dacl->elements != NULL) {
+ isc_mem_put(dacl->mctx, dacl->elements,
+ dacl->alloc * sizeof(dns_aclelement_t));
+ }
+ if (dacl->name != NULL) {
+ isc_mem_free(dacl->mctx, dacl->name);
+ }
+ if (dacl->iptable != NULL) {
+ dns_iptable_detach(&dacl->iptable);
+ }
+ isc_refcount_destroy(&dacl->refcount);
+ dacl->magic = 0;
+ isc_mem_putanddetach(&dacl->mctx, dacl, sizeof(*dacl));
+}
+
+void
+dns_acl_detach(dns_acl_t **aclp) {
+ REQUIRE(aclp != NULL && DNS_ACL_VALID(*aclp));
+ dns_acl_t *acl = *aclp;
+ *aclp = NULL;
+
+ if (isc_refcount_decrement(&acl->refcount) == 1) {
+ destroy(acl);
+ }
+}
+
+static isc_once_t insecure_prefix_once = ISC_ONCE_INIT;
+static isc_mutex_t insecure_prefix_lock;
+static bool insecure_prefix_found;
+
+static void
+initialize_action(void) {
+ isc_mutex_init(&insecure_prefix_lock);
+}
+
+/*
+ * Called via isc_radix_process() to find IP table nodes that are
+ * insecure.
+ */
+static void
+is_insecure(isc_prefix_t *prefix, void **data) {
+ /*
+ * If all nonexistent or negative then this node is secure.
+ */
+ if ((data[0] == NULL || !*(bool *)data[0]) &&
+ (data[1] == NULL || !*(bool *)data[1]))
+ {
+ return;
+ }
+
+ /*
+ * If a loopback address found and the other family
+ * entry doesn't exist or is negative, return.
+ */
+ if (prefix->bitlen == 32 &&
+ htonl(prefix->add.sin.s_addr) == INADDR_LOOPBACK &&
+ (data[1] == NULL || !*(bool *)data[1]))
+ {
+ return;
+ }
+
+ if (prefix->bitlen == 128 && IN6_IS_ADDR_LOOPBACK(&prefix->add.sin6) &&
+ (data[0] == NULL || !*(bool *)data[0]))
+ {
+ return;
+ }
+
+ /* Non-negated, non-loopback */
+ insecure_prefix_found = true; /* LOCKED */
+ return;
+}
+
+/*
+ * Return true iff the acl 'a' is considered insecure, that is,
+ * if it contains IP addresses other than those of the local host.
+ * This is intended for applications such as printing warning
+ * messages for suspect ACLs; it is not intended for making access
+ * control decisions. We make no guarantee that an ACL for which
+ * this function returns false is safe.
+ */
+bool
+dns_acl_isinsecure(const dns_acl_t *a) {
+ unsigned int i;
+ bool insecure;
+
+ RUNTIME_CHECK(isc_once_do(&insecure_prefix_once, initialize_action) ==
+ ISC_R_SUCCESS);
+
+ /*
+ * Walk radix tree to find out if there are any non-negated,
+ * non-loopback prefixes.
+ */
+ LOCK(&insecure_prefix_lock);
+ insecure_prefix_found = false;
+ isc_radix_process(a->iptable->radix, is_insecure);
+ insecure = insecure_prefix_found;
+ UNLOCK(&insecure_prefix_lock);
+ if (insecure) {
+ return (true);
+ }
+
+ /* Now check non-radix elements */
+ for (i = 0; i < a->length; i++) {
+ dns_aclelement_t *e = &a->elements[i];
+
+ /* A negated match can never be insecure. */
+ if (e->negative) {
+ continue;
+ }
+
+ switch (e->type) {
+ case dns_aclelementtype_keyname:
+ case dns_aclelementtype_localhost:
+ continue;
+
+ case dns_aclelementtype_nestedacl:
+ if (dns_acl_isinsecure(e->nestedacl)) {
+ return (true);
+ }
+ continue;
+
+#if defined(HAVE_GEOIP2)
+ case dns_aclelementtype_geoip:
+#endif /* if defined(HAVE_GEOIP2) */
+ case dns_aclelementtype_localnets:
+ return (true);
+
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ /* No insecure elements were found. */
+ return (false);
+}
+
+/*%
+ * Check whether an address/signer is allowed by a given acl/aclenv.
+ */
+bool
+dns_acl_allowed(isc_netaddr_t *addr, const dns_name_t *signer, dns_acl_t *acl,
+ dns_aclenv_t *aclenv) {
+ int match;
+ isc_result_t result;
+
+ if (acl == NULL) {
+ return (true);
+ }
+ result = dns_acl_match(addr, signer, acl, aclenv, &match, NULL);
+ if (result == ISC_R_SUCCESS && match > 0) {
+ return (true);
+ }
+ return (false);
+}
+
+/*
+ * Initialize ACL environment, setting up localhost and localnets ACLs
+ */
+isc_result_t
+dns_aclenv_init(isc_mem_t *mctx, dns_aclenv_t *env) {
+ isc_result_t result;
+
+ env->localhost = NULL;
+ env->localnets = NULL;
+ result = dns_acl_create(mctx, 0, &env->localhost);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_nothing;
+ }
+ result = dns_acl_create(mctx, 0, &env->localnets);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_localhost;
+ }
+ env->match_mapped = false;
+#if defined(HAVE_GEOIP2)
+ env->geoip = NULL;
+#endif /* if defined(HAVE_GEOIP2) */
+ return (ISC_R_SUCCESS);
+
+cleanup_localhost:
+ dns_acl_detach(&env->localhost);
+cleanup_nothing:
+ return (result);
+}
+
+void
+dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s) {
+ dns_acl_detach(&t->localhost);
+ dns_acl_attach(s->localhost, &t->localhost);
+ dns_acl_detach(&t->localnets);
+ dns_acl_attach(s->localnets, &t->localnets);
+ t->match_mapped = s->match_mapped;
+#if defined(HAVE_GEOIP2)
+ t->geoip = s->geoip;
+#endif /* if defined(HAVE_GEOIP2) */
+}
+
+void
+dns_aclenv_destroy(dns_aclenv_t *env) {
+ if (env->localhost != NULL) {
+ dns_acl_detach(&env->localhost);
+ }
+ if (env->localnets != NULL) {
+ dns_acl_detach(&env->localnets);
+ }
+}
diff --git a/lib/dns/adb.c b/lib/dns/adb.c
new file mode 100644
index 0000000..87f0f8b
--- /dev/null
+++ b/lib/dns/adb.c
@@ -0,0 +1,4885 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file
+ *
+ * \note
+ * In finds, if task == NULL, no events will be generated, and no events
+ * have been sent. If task != NULL but taskaction == NULL, an event has been
+ * posted but not yet freed. If neither are NULL, no event was posted.
+ *
+ */
+
+#include <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/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/result.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;
+ dns_view_t *view;
+
+ isc_taskmgr_t *taskmgr;
+ isc_task_t *task;
+ isc_task_t *excl;
+
+ isc_interval_t tick_interval;
+ int next_cleanbucket;
+
+ unsigned int irefcnt;
+ unsigned int erefcnt;
+
+ isc_refcount_t ahrefcnt;
+ isc_refcount_t nhrefcnt;
+
+ /*!
+ * Bucketized locks and lists for names.
+ *
+ * XXXRTH Have a per-bucket structure that contains all of these?
+ */
+ unsigned int nnames;
+ isc_mutex_t namescntlock;
+ unsigned int namescnt;
+ dns_adbnamelist_t *names;
+ dns_adbnamelist_t *deadnames;
+ isc_mutex_t *namelocks;
+ bool *name_sd;
+ unsigned int *name_refcnt;
+
+ /*!
+ * Bucketized locks and lists for entries.
+ *
+ * XXXRTH Have a per-bucket structure that contains all of these?
+ */
+ unsigned int nentries;
+ isc_mutex_t entriescntlock;
+ unsigned int entriescnt;
+ dns_adbentrylist_t *entries;
+ dns_adbentrylist_t *deadentries;
+ isc_mutex_t *entrylocks;
+ bool *entry_sd; /*%< shutting down */
+ unsigned int *entry_refcnt;
+
+ isc_event_t cevent;
+ bool cevent_out;
+ bool shutting_down;
+ isc_eventlist_t whenshutdown;
+ isc_event_t growentries;
+ bool growentries_sent;
+ isc_event_t grownames;
+ bool grownames_sent;
+
+ uint32_t quota;
+ uint32_t atr_freq;
+ double atr_low;
+ double atr_high;
+ double atr_discount;
+};
+
+/*
+ * XXXMLG Document these structures.
+ */
+
+/*% dns_adbname structure */
+struct dns_adbname {
+ unsigned int magic;
+ dns_name_t name;
+ dns_adb_t *adb;
+ unsigned int partial_result;
+ unsigned int flags;
+ int lock_bucket;
+ dns_name_t target;
+ isc_stdtime_t expire_target;
+ isc_stdtime_t expire_v4;
+ isc_stdtime_t expire_v6;
+ unsigned int chains;
+ dns_adbnamehooklist_t v4;
+ dns_adbnamehooklist_t v6;
+ dns_adbfetch_t *fetch_a;
+ dns_adbfetch_t *fetch_aaaa;
+ unsigned int fetch_err;
+ unsigned int fetch6_err;
+ dns_adbfindlist_t finds;
+ /* for LRU-based management */
+ isc_stdtime_t last_used;
+
+ ISC_LINK(dns_adbname_t) plink;
+};
+
+/*% The adbfetch structure */
+struct dns_adbfetch {
+ unsigned int magic;
+ dns_fetch_t *fetch;
+ dns_rdataset_t rdataset;
+ unsigned int depth;
+};
+
+/*%
+ * This is a small widget that dangles off a dns_adbname_t. It contains a
+ * pointer to the address information about this host, and a link to the next
+ * namehook that will contain the next address this host has.
+ */
+struct dns_adbnamehook {
+ unsigned int magic;
+ dns_adbentry_t *entry;
+ ISC_LINK(dns_adbnamehook_t) plink;
+};
+
+/*%
+ * This is a small widget that holds qname-specific information about an
+ * address. Currently limited to lameness, but could just as easily be
+ * extended to other types of information about zones.
+ */
+struct dns_adblameinfo {
+ unsigned int magic;
+
+ dns_name_t qname;
+ dns_rdatatype_t qtype;
+ isc_stdtime_t lame_timer;
+
+ ISC_LINK(dns_adblameinfo_t) plink;
+};
+
+/*%
+ * An address entry. It holds quite a bit of information about addresses,
+ * including edns state (in "flags"), rtt, and of course the address of
+ * the host.
+ */
+struct dns_adbentry {
+ unsigned int magic;
+
+ int lock_bucket;
+ unsigned int refcnt;
+ unsigned int nh;
+
+ unsigned int flags;
+ unsigned int srtt;
+ uint16_t udpsize;
+ unsigned int completed;
+ unsigned int timeouts;
+ unsigned char plain;
+ unsigned char plainto;
+ unsigned char edns;
+ unsigned char to4096; /* Our max. */
+
+ uint8_t mode;
+ atomic_uint_fast32_t quota;
+ atomic_uint_fast32_t active;
+ double atr;
+
+ /*
+ * Allow for encapsulated IPv4/IPv6 UDP packet over ethernet.
+ * Ethernet 1500 - IP(20) - IP6(40) - UDP(8) = 1432.
+ */
+ unsigned char to1432; /* Ethernet */
+ unsigned char to1232; /* IPv6 nofrag */
+ unsigned char to512; /* plain DNS */
+ isc_sockaddr_t sockaddr;
+ unsigned char *cookie;
+ uint16_t cookielen;
+
+ isc_stdtime_t expires;
+ isc_stdtime_t lastage;
+ /*%<
+ * A nonzero 'expires' field indicates that the entry should
+ * persist until that time. This allows entries found
+ * using dns_adb_findaddrinfo() to persist for a limited time
+ * even though they are not necessarily associated with a
+ * name.
+ */
+
+ ISC_LIST(dns_adblameinfo_t) lameinfo;
+ ISC_LINK(dns_adbentry_t) plink;
+};
+
+/*
+ * Internal functions (and prototypes).
+ */
+static dns_adbname_t *
+new_adbname(dns_adb_t *, const dns_name_t *);
+static void
+free_adbname(dns_adb_t *, dns_adbname_t **);
+static dns_adbnamehook_t *
+new_adbnamehook(dns_adb_t *, dns_adbentry_t *);
+static void
+free_adbnamehook(dns_adb_t *, dns_adbnamehook_t **);
+static dns_adblameinfo_t *
+new_adblameinfo(dns_adb_t *, const dns_name_t *, dns_rdatatype_t);
+static void
+free_adblameinfo(dns_adb_t *, dns_adblameinfo_t **);
+static dns_adbentry_t *
+new_adbentry(dns_adb_t *);
+static void
+free_adbentry(dns_adb_t *, dns_adbentry_t **);
+static dns_adbfind_t *
+new_adbfind(dns_adb_t *);
+static bool
+free_adbfind(dns_adb_t *, dns_adbfind_t **);
+static dns_adbaddrinfo_t *
+new_adbaddrinfo(dns_adb_t *, dns_adbentry_t *, in_port_t);
+static dns_adbfetch_t *
+new_adbfetch(dns_adb_t *);
+static void
+free_adbfetch(dns_adb_t *, dns_adbfetch_t **);
+static dns_adbname_t *
+find_name_and_lock(dns_adb_t *, const dns_name_t *, unsigned int, int *);
+static dns_adbentry_t *
+find_entry_and_lock(dns_adb_t *, const isc_sockaddr_t *, int *, isc_stdtime_t);
+static void
+dump_adb(dns_adb_t *, FILE *, bool debug, isc_stdtime_t);
+static void
+print_dns_name(FILE *, const dns_name_t *);
+static void
+print_namehook_list(FILE *, const char *legend, dns_adb_t *adb,
+ dns_adbnamehooklist_t *list, bool debug, isc_stdtime_t now);
+static void
+print_find_list(FILE *, dns_adbname_t *);
+static void
+print_fetch_list(FILE *, dns_adbname_t *);
+static bool
+dec_adb_irefcnt(dns_adb_t *);
+static void
+inc_adb_irefcnt(dns_adb_t *);
+static void
+inc_adb_erefcnt(dns_adb_t *);
+static void
+inc_entry_refcnt(dns_adb_t *, dns_adbentry_t *, bool);
+static bool
+dec_entry_refcnt(dns_adb_t *, bool, dns_adbentry_t *, bool);
+static void
+violate_locking_hierarchy(isc_mutex_t *, isc_mutex_t *);
+static bool
+clean_namehooks(dns_adb_t *, dns_adbnamehooklist_t *);
+static void
+clean_target(dns_adb_t *, dns_name_t *);
+static void
+clean_finds_at_name(dns_adbname_t *, isc_eventtype_t, unsigned int);
+static bool
+check_expire_namehooks(dns_adbname_t *, isc_stdtime_t);
+static bool
+check_expire_entry(dns_adb_t *, dns_adbentry_t **, isc_stdtime_t);
+static void
+cancel_fetches_at_name(dns_adbname_t *);
+static isc_result_t
+dbfind_name(dns_adbname_t *, isc_stdtime_t, dns_rdatatype_t);
+static isc_result_t
+fetch_name(dns_adbname_t *, bool, unsigned int, isc_counter_t *qc,
+ dns_rdatatype_t);
+static void
+check_exit(dns_adb_t *);
+static void
+destroy(dns_adb_t *);
+static bool
+shutdown_names(dns_adb_t *);
+static bool
+shutdown_entries(dns_adb_t *);
+static void
+link_name(dns_adb_t *, int, dns_adbname_t *);
+static bool
+unlink_name(dns_adb_t *, dns_adbname_t *);
+static void
+link_entry(dns_adb_t *, int, dns_adbentry_t *);
+static bool
+unlink_entry(dns_adb_t *, dns_adbentry_t *);
+static bool
+kill_name(dns_adbname_t **, isc_eventtype_t);
+static void
+water(void *, int);
+static void
+dump_entry(FILE *, dns_adb_t *, dns_adbentry_t *, bool, isc_stdtime_t);
+static void
+adjustsrtt(dns_adbaddrinfo_t *addr, unsigned int rtt, unsigned int factor,
+ isc_stdtime_t now);
+static void
+shutdown_task(isc_task_t *task, isc_event_t *ev);
+static void
+log_quota(dns_adbentry_t *entry, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3);
+
+/*
+ * MUST NOT overlap DNS_ADBFIND_* flags!
+ */
+#define FIND_EVENT_SENT 0x40000000
+#define FIND_EVENT_FREED 0x80000000
+#define FIND_EVENTSENT(h) (((h)->flags & FIND_EVENT_SENT) != 0)
+#define FIND_EVENTFREED(h) (((h)->flags & FIND_EVENT_FREED) != 0)
+
+#define NAME_NEEDS_POKE 0x80000000
+#define NAME_IS_DEAD 0x40000000
+#define NAME_HINT_OK DNS_ADBFIND_HINTOK
+#define NAME_GLUE_OK DNS_ADBFIND_GLUEOK
+#define NAME_STARTATZONE DNS_ADBFIND_STARTATZONE
+#define NAME_DEAD(n) (((n)->flags & NAME_IS_DEAD) != 0)
+#define NAME_NEEDSPOKE(n) (((n)->flags & NAME_NEEDS_POKE) != 0)
+#define NAME_GLUEOK(n) (((n)->flags & NAME_GLUE_OK) != 0)
+#define NAME_HINTOK(n) (((n)->flags & NAME_HINT_OK) != 0)
+
+/*
+ * Private flag(s) for entries.
+ * MUST NOT overlap FCTX_ADDRINFO_xxx and DNS_FETCHOPT_NOEDNS0.
+ */
+#define ENTRY_IS_DEAD 0x00400000
+
+/*
+ * To the name, address classes are all that really exist. If it has a
+ * V6 address it doesn't care if it came from a AAAA query.
+ */
+#define NAME_HAS_V4(n) (!ISC_LIST_EMPTY((n)->v4))
+#define NAME_HAS_V6(n) (!ISC_LIST_EMPTY((n)->v6))
+#define NAME_HAS_ADDRS(n) (NAME_HAS_V4(n) || NAME_HAS_V6(n))
+
+/*
+ * Fetches are broken out into A and AAAA types. In some cases,
+ * however, it makes more sense to test for a particular class of fetches,
+ * like V4 or V6 above.
+ * Note: since we have removed the support of A6 in adb, FETCH_A and FETCH_AAAA
+ * are now equal to FETCH_V4 and FETCH_V6, respectively.
+ */
+#define NAME_FETCH_A(n) ((n)->fetch_a != NULL)
+#define NAME_FETCH_AAAA(n) ((n)->fetch_aaaa != NULL)
+#define NAME_FETCH_V4(n) (NAME_FETCH_A(n))
+#define NAME_FETCH_V6(n) (NAME_FETCH_AAAA(n))
+#define NAME_FETCH(n) (NAME_FETCH_V4(n) || NAME_FETCH_V6(n))
+
+/*
+ * Find options and tests to see if there are addresses on the list.
+ */
+#define FIND_WANTEVENT(fn) (((fn)->options & DNS_ADBFIND_WANTEVENT) != 0)
+#define FIND_WANTEMPTYEVENT(fn) (((fn)->options & DNS_ADBFIND_EMPTYEVENT) != 0)
+#define FIND_AVOIDFETCHES(fn) (((fn)->options & DNS_ADBFIND_AVOIDFETCHES) != 0)
+#define FIND_STARTATZONE(fn) (((fn)->options & DNS_ADBFIND_STARTATZONE) != 0)
+#define FIND_HINTOK(fn) (((fn)->options & DNS_ADBFIND_HINTOK) != 0)
+#define FIND_GLUEOK(fn) (((fn)->options & DNS_ADBFIND_GLUEOK) != 0)
+#define FIND_HAS_ADDRS(fn) (!ISC_LIST_EMPTY((fn)->list))
+#define FIND_RETURNLAME(fn) (((fn)->options & DNS_ADBFIND_RETURNLAME) != 0)
+#define FIND_NOFETCH(fn) (((fn)->options & DNS_ADBFIND_NOFETCH) != 0)
+
+/*
+ * These are currently used on simple unsigned ints, so they are
+ * not really associated with any particular type.
+ */
+#define WANT_INET(x) (((x)&DNS_ADBFIND_INET) != 0)
+#define WANT_INET6(x) (((x)&DNS_ADBFIND_INET6) != 0)
+
+#define EXPIRE_OK(exp, now) ((exp == INT_MAX) || (exp < now))
+
+/*
+ * Find out if the flags on a name (nf) indicate if it is a hint or
+ * glue, and compare this to the appropriate bits set in o, to see if
+ * this is ok.
+ */
+#define GLUE_OK(nf, o) (!NAME_GLUEOK(nf) || (((o)&DNS_ADBFIND_GLUEOK) != 0))
+#define HINT_OK(nf, o) (!NAME_HINTOK(nf) || (((o)&DNS_ADBFIND_HINTOK) != 0))
+#define GLUEHINT_OK(nf, o) (GLUE_OK(nf, o) || HINT_OK(nf, o))
+#define STARTATZONE_MATCHES(nf, o) \
+ (((nf)->flags & NAME_STARTATZONE) == ((o)&DNS_ADBFIND_STARTATZONE))
+
+#define ENTER_LEVEL ISC_LOG_DEBUG(50)
+#define EXIT_LEVEL ENTER_LEVEL
+#define CLEAN_LEVEL ISC_LOG_DEBUG(100)
+#define DEF_LEVEL ISC_LOG_DEBUG(5)
+#define NCACHE_LEVEL ISC_LOG_DEBUG(20)
+
+#define NCACHE_RESULT(r) \
+ ((r) == DNS_R_NCACHENXDOMAIN || (r) == DNS_R_NCACHENXRRSET)
+#define AUTH_NX(r) ((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NXRRSET)
+#define NXDOMAIN_RESULT(r) \
+ ((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NCACHENXDOMAIN)
+#define NXRRSET_RESULT(r) \
+ ((r) == DNS_R_NCACHENXRRSET || (r) == DNS_R_NXRRSET || \
+ (r) == DNS_R_HINTNXRRSET)
+
+/*
+ * Error state rankings.
+ */
+
+#define FIND_ERR_SUCCESS 0 /* highest rank */
+#define FIND_ERR_CANCELED 1
+#define FIND_ERR_FAILURE 2
+#define FIND_ERR_NXDOMAIN 3
+#define FIND_ERR_NXRRSET 4
+#define FIND_ERR_UNEXPECTED 5
+#define FIND_ERR_NOTFOUND 6
+#define FIND_ERR_MAX 7
+
+static const char *errnames[] = { "success", "canceled", "failure",
+ "nxdomain", "nxrrset", "unexpected",
+ "not_found" };
+
+#define NEWERR(old, new) (ISC_MIN((old), (new)))
+
+static isc_result_t find_err_map[FIND_ERR_MAX] = {
+ ISC_R_SUCCESS, ISC_R_CANCELED, ISC_R_FAILURE, DNS_R_NXDOMAIN,
+ DNS_R_NXRRSET, ISC_R_UNEXPECTED, ISC_R_NOTFOUND /* not YET found */
+};
+
+static void
+DP(int level, const char *format, ...) ISC_FORMAT_PRINTF(2, 3);
+
+static void
+DP(int level, const char *format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB,
+ level, format, args);
+ va_end(args);
+}
+
+/*%
+ * Increment resolver-related statistics counters.
+ */
+static void
+inc_stats(dns_adb_t *adb, isc_statscounter_t counter) {
+ if (adb->view->resstats != NULL) {
+ isc_stats_increment(adb->view->resstats, counter);
+ }
+}
+
+/*%
+ * Set adb-related statistics counters.
+ */
+static void
+set_adbstat(dns_adb_t *adb, uint64_t val, isc_statscounter_t counter) {
+ if (adb->view->adbstats != NULL) {
+ isc_stats_set(adb->view->adbstats, val, counter);
+ }
+}
+
+static void
+dec_adbstats(dns_adb_t *adb, isc_statscounter_t counter) {
+ if (adb->view->adbstats != NULL) {
+ isc_stats_decrement(adb->view->adbstats, counter);
+ }
+}
+
+static void
+inc_adbstats(dns_adb_t *adb, isc_statscounter_t counter) {
+ if (adb->view->adbstats != NULL) {
+ isc_stats_increment(adb->view->adbstats, counter);
+ }
+}
+
+static dns_ttl_t
+ttlclamp(dns_ttl_t ttl) {
+ if (ttl < ADB_CACHE_MINIMUM) {
+ ttl = ADB_CACHE_MINIMUM;
+ }
+ if (ttl > ADB_CACHE_MAXIMUM) {
+ ttl = ADB_CACHE_MAXIMUM;
+ }
+
+ return (ttl);
+}
+
+/*
+ * Hashing is most efficient if the number of buckets is prime.
+ * The sequence below is the closest previous primes to 2^n and
+ * 1.5 * 2^n, for values of n from 10 to 28. (The tables will
+ * no longer grow beyond 2^28 entries.)
+ */
+static const unsigned nbuckets[] = {
+ 1021, 1531, 2039, 3067, 4093, 6143,
+ 8191, 12281, 16381, 24571, 32749, 49193,
+ 65521, 98299, 131071, 199603, 262139, 393209,
+ 524287, 768431, 1048573, 1572853, 2097143, 3145721,
+ 4194301, 6291449, 8388593, 12582893, 16777213, 25165813,
+ 33554393, 50331599, 67108859, 100663291, 134217689, 201326557,
+ 268535431, 0
+};
+
+static void
+grow_entries(isc_task_t *task, isc_event_t *ev) {
+ dns_adb_t *adb;
+ dns_adbentry_t *e;
+ dns_adbentrylist_t *newdeadentries = NULL;
+ dns_adbentrylist_t *newentries = NULL;
+ bool *newentry_sd = NULL;
+ isc_mutex_t *newentrylocks = NULL;
+ isc_result_t result;
+ unsigned int *newentry_refcnt = NULL;
+ unsigned int i, n, bucket;
+
+ adb = ev->ev_arg;
+ INSIST(DNS_ADB_VALID(adb));
+
+ isc_event_free(&ev);
+
+ result = isc_task_beginexclusive(task);
+ if (result != ISC_R_SUCCESS) {
+ goto check_exit;
+ }
+
+ i = 0;
+ while (nbuckets[i] != 0 && adb->nentries >= nbuckets[i]) {
+ i++;
+ }
+ if (nbuckets[i] != 0) {
+ n = nbuckets[i];
+ } else {
+ goto done;
+ }
+
+ DP(ISC_LOG_INFO, "adb: grow_entries to %u starting", n);
+
+ /*
+ * Are we shutting down?
+ */
+ for (i = 0; i < adb->nentries; i++) {
+ if (adb->entry_sd[i]) {
+ goto cleanup;
+
+ /*
+ * Grab all the resources we need.
+ */
+ }
+ }
+
+ /*
+ * Grab all the resources we need.
+ */
+ newentries = isc_mem_get(adb->mctx, sizeof(*newentries) * n);
+ newdeadentries = isc_mem_get(adb->mctx, sizeof(*newdeadentries) * n);
+ newentrylocks = isc_mem_get(adb->mctx, sizeof(*newentrylocks) * n);
+ newentry_sd = isc_mem_get(adb->mctx, sizeof(*newentry_sd) * n);
+ newentry_refcnt = isc_mem_get(adb->mctx, sizeof(*newentry_refcnt) * n);
+
+ /*
+ * Initialise the new resources.
+ */
+ isc_mutexblock_init(newentrylocks, n);
+
+ for (i = 0; i < n; i++) {
+ ISC_LIST_INIT(newentries[i]);
+ ISC_LIST_INIT(newdeadentries[i]);
+ newentry_sd[i] = false;
+ newentry_refcnt[i] = 0;
+ adb->irefcnt++;
+ }
+
+ /*
+ * Move entries to new arrays.
+ */
+ for (i = 0; i < adb->nentries; i++) {
+ e = ISC_LIST_HEAD(adb->entries[i]);
+ while (e != NULL) {
+ ISC_LIST_UNLINK(adb->entries[i], e, plink);
+ bucket = isc_sockaddr_hash(&e->sockaddr, true) % n;
+ e->lock_bucket = bucket;
+ ISC_LIST_APPEND(newentries[bucket], e, plink);
+ INSIST(adb->entry_refcnt[i] > 0);
+ adb->entry_refcnt[i]--;
+ newentry_refcnt[bucket]++;
+ e = ISC_LIST_HEAD(adb->entries[i]);
+ }
+ e = ISC_LIST_HEAD(adb->deadentries[i]);
+ while (e != NULL) {
+ ISC_LIST_UNLINK(adb->deadentries[i], e, plink);
+ bucket = isc_sockaddr_hash(&e->sockaddr, true) % n;
+ e->lock_bucket = bucket;
+ ISC_LIST_APPEND(newdeadentries[bucket], e, plink);
+ INSIST(adb->entry_refcnt[i] > 0);
+ adb->entry_refcnt[i]--;
+ newentry_refcnt[bucket]++;
+ e = ISC_LIST_HEAD(adb->deadentries[i]);
+ }
+ INSIST(adb->entry_refcnt[i] == 0);
+ adb->irefcnt--;
+ }
+
+ /*
+ * Cleanup old resources.
+ */
+ isc_mutexblock_destroy(adb->entrylocks, adb->nentries);
+ isc_mem_put(adb->mctx, adb->entries,
+ sizeof(*adb->entries) * adb->nentries);
+ isc_mem_put(adb->mctx, adb->deadentries,
+ sizeof(*adb->deadentries) * adb->nentries);
+ isc_mem_put(adb->mctx, adb->entrylocks,
+ sizeof(*adb->entrylocks) * adb->nentries);
+ isc_mem_put(adb->mctx, adb->entry_sd,
+ sizeof(*adb->entry_sd) * adb->nentries);
+ isc_mem_put(adb->mctx, adb->entry_refcnt,
+ sizeof(*adb->entry_refcnt) * adb->nentries);
+
+ /*
+ * Install new resources.
+ */
+ adb->entries = newentries;
+ adb->deadentries = newdeadentries;
+ adb->entrylocks = newentrylocks;
+ adb->entry_sd = newentry_sd;
+ adb->entry_refcnt = newentry_refcnt;
+ adb->nentries = n;
+
+ set_adbstat(adb, adb->nentries, dns_adbstats_nentries);
+
+ /*
+ * Only on success do we set adb->growentries_sent to false.
+ * This will prevent us being continuously being called on error.
+ */
+ adb->growentries_sent = false;
+ goto done;
+
+cleanup:
+ if (newentries != NULL) {
+ isc_mem_put(adb->mctx, newentries, sizeof(*newentries) * n);
+ }
+ if (newdeadentries != NULL) {
+ isc_mem_put(adb->mctx, newdeadentries,
+ sizeof(*newdeadentries) * n);
+ }
+ if (newentrylocks != NULL) {
+ isc_mem_put(adb->mctx, newentrylocks,
+ sizeof(*newentrylocks) * n);
+ }
+ if (newentry_sd != NULL) {
+ isc_mem_put(adb->mctx, newentry_sd, sizeof(*newentry_sd) * n);
+ }
+ if (newentry_refcnt != NULL) {
+ isc_mem_put(adb->mctx, newentry_refcnt,
+ sizeof(*newentry_refcnt) * n);
+ }
+done:
+ isc_task_endexclusive(task);
+
+check_exit:
+ LOCK(&adb->lock);
+ if (dec_adb_irefcnt(adb)) {
+ check_exit(adb);
+ }
+ UNLOCK(&adb->lock);
+ DP(ISC_LOG_INFO, "adb: grow_entries finished");
+}
+
+static void
+grow_names(isc_task_t *task, isc_event_t *ev) {
+ dns_adb_t *adb;
+ dns_adbname_t *name;
+ dns_adbnamelist_t *newdeadnames = NULL;
+ dns_adbnamelist_t *newnames = NULL;
+ bool *newname_sd = NULL;
+ isc_mutex_t *newnamelocks = NULL;
+ isc_result_t result;
+ unsigned int *newname_refcnt = NULL;
+ unsigned int i, n;
+ unsigned int bucket;
+
+ adb = ev->ev_arg;
+ INSIST(DNS_ADB_VALID(adb));
+
+ isc_event_free(&ev);
+
+ result = isc_task_beginexclusive(task);
+ if (result != ISC_R_SUCCESS) {
+ goto check_exit;
+ }
+
+ i = 0;
+ while (nbuckets[i] != 0 && adb->nnames >= nbuckets[i]) {
+ i++;
+ }
+ if (nbuckets[i] != 0) {
+ n = nbuckets[i];
+ } else {
+ goto done;
+ }
+
+ DP(ISC_LOG_INFO, "adb: grow_names to %u starting", n);
+
+ /*
+ * Are we shutting down?
+ */
+ for (i = 0; i < adb->nnames; i++) {
+ if (adb->name_sd[i]) {
+ goto cleanup;
+
+ /*
+ * Grab all the resources we need.
+ */
+ }
+ }
+
+ /*
+ * Grab all the resources we need.
+ */
+ newnames = isc_mem_get(adb->mctx, sizeof(*newnames) * n);
+ newdeadnames = isc_mem_get(adb->mctx, sizeof(*newdeadnames) * n);
+ newnamelocks = isc_mem_get(adb->mctx, sizeof(*newnamelocks) * n);
+ newname_sd = isc_mem_get(adb->mctx, sizeof(*newname_sd) * n);
+ newname_refcnt = isc_mem_get(adb->mctx, sizeof(*newname_refcnt) * n);
+
+ /*
+ * Initialise the new resources.
+ */
+ isc_mutexblock_init(newnamelocks, n);
+
+ for (i = 0; i < n; i++) {
+ ISC_LIST_INIT(newnames[i]);
+ ISC_LIST_INIT(newdeadnames[i]);
+ newname_sd[i] = false;
+ newname_refcnt[i] = 0;
+ adb->irefcnt++;
+ }
+
+ /*
+ * Move names to new arrays.
+ */
+ for (i = 0; i < adb->nnames; i++) {
+ name = ISC_LIST_HEAD(adb->names[i]);
+ while (name != NULL) {
+ ISC_LIST_UNLINK(adb->names[i], name, plink);
+ bucket = dns_name_fullhash(&name->name, true) % n;
+ name->lock_bucket = bucket;
+ ISC_LIST_APPEND(newnames[bucket], name, plink);
+ INSIST(adb->name_refcnt[i] > 0);
+ adb->name_refcnt[i]--;
+ newname_refcnt[bucket]++;
+ name = ISC_LIST_HEAD(adb->names[i]);
+ }
+ name = ISC_LIST_HEAD(adb->deadnames[i]);
+ while (name != NULL) {
+ ISC_LIST_UNLINK(adb->deadnames[i], name, plink);
+ bucket = dns_name_fullhash(&name->name, true) % n;
+ name->lock_bucket = bucket;
+ ISC_LIST_APPEND(newdeadnames[bucket], name, plink);
+ INSIST(adb->name_refcnt[i] > 0);
+ adb->name_refcnt[i]--;
+ newname_refcnt[bucket]++;
+ name = ISC_LIST_HEAD(adb->deadnames[i]);
+ }
+ INSIST(adb->name_refcnt[i] == 0);
+ adb->irefcnt--;
+ }
+
+ /*
+ * Cleanup old resources.
+ */
+ isc_mutexblock_destroy(adb->namelocks, adb->nnames);
+ isc_mem_put(adb->mctx, adb->names, sizeof(*adb->names) * adb->nnames);
+ isc_mem_put(adb->mctx, adb->deadnames,
+ sizeof(*adb->deadnames) * adb->nnames);
+ isc_mem_put(adb->mctx, adb->namelocks,
+ sizeof(*adb->namelocks) * adb->nnames);
+ isc_mem_put(adb->mctx, adb->name_sd,
+ sizeof(*adb->name_sd) * adb->nnames);
+ isc_mem_put(adb->mctx, adb->name_refcnt,
+ sizeof(*adb->name_refcnt) * adb->nnames);
+
+ /*
+ * Install new resources.
+ */
+ adb->names = newnames;
+ adb->deadnames = newdeadnames;
+ adb->namelocks = newnamelocks;
+ adb->name_sd = newname_sd;
+ adb->name_refcnt = newname_refcnt;
+ adb->nnames = n;
+
+ set_adbstat(adb, adb->nnames, dns_adbstats_nnames);
+
+ /*
+ * Only on success do we set adb->grownames_sent to false.
+ * This will prevent us being continuously being called on error.
+ */
+ adb->grownames_sent = false;
+ goto done;
+
+cleanup:
+ if (newnames != NULL) {
+ isc_mem_put(adb->mctx, newnames, sizeof(*newnames) * n);
+ }
+ if (newdeadnames != NULL) {
+ isc_mem_put(adb->mctx, newdeadnames, sizeof(*newdeadnames) * n);
+ }
+ if (newnamelocks != NULL) {
+ isc_mem_put(adb->mctx, newnamelocks, sizeof(*newnamelocks) * n);
+ }
+ if (newname_sd != NULL) {
+ isc_mem_put(adb->mctx, newname_sd, sizeof(*newname_sd) * n);
+ }
+ if (newname_refcnt != NULL) {
+ isc_mem_put(adb->mctx, newname_refcnt,
+ sizeof(*newname_refcnt) * n);
+ }
+done:
+ isc_task_endexclusive(task);
+
+check_exit:
+ LOCK(&adb->lock);
+ if (dec_adb_irefcnt(adb)) {
+ check_exit(adb);
+ }
+ UNLOCK(&adb->lock);
+ DP(ISC_LOG_INFO, "adb: grow_names finished");
+}
+
+/*
+ * Requires the adbname bucket be locked and that no entry buckets be locked.
+ *
+ * This code handles A and AAAA rdatasets only.
+ */
+static isc_result_t
+import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
+ isc_stdtime_t now) {
+ isc_result_t result;
+ dns_adb_t *adb;
+ dns_adbnamehook_t *nh;
+ dns_adbnamehook_t *anh;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ struct in_addr ina;
+ struct in6_addr in6a;
+ isc_sockaddr_t sockaddr;
+ dns_adbentry_t *foundentry; /* NO CLEAN UP! */
+ int addr_bucket;
+ bool new_addresses_added;
+ dns_rdatatype_t rdtype;
+ unsigned int findoptions;
+ dns_adbnamehooklist_t *hookhead;
+
+ INSIST(DNS_ADBNAME_VALID(adbname));
+ adb = adbname->adb;
+ INSIST(DNS_ADB_VALID(adb));
+
+ rdtype = rdataset->type;
+ INSIST((rdtype == dns_rdatatype_a) || (rdtype == dns_rdatatype_aaaa));
+ if (rdtype == dns_rdatatype_a) {
+ findoptions = DNS_ADBFIND_INET;
+ } else {
+ findoptions = DNS_ADBFIND_INET6;
+ }
+
+ addr_bucket = DNS_ADB_INVALIDBUCKET;
+ new_addresses_added = false;
+
+ nh = NULL;
+ result = dns_rdataset_first(rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(rdataset, &rdata);
+ if (rdtype == dns_rdatatype_a) {
+ INSIST(rdata.length == 4);
+ memmove(&ina.s_addr, rdata.data, 4);
+ isc_sockaddr_fromin(&sockaddr, &ina, 0);
+ hookhead = &adbname->v4;
+ } else {
+ INSIST(rdata.length == 16);
+ memmove(in6a.s6_addr, rdata.data, 16);
+ isc_sockaddr_fromin6(&sockaddr, &in6a, 0);
+ hookhead = &adbname->v6;
+ }
+
+ INSIST(nh == NULL);
+ nh = new_adbnamehook(adb, NULL);
+ if (nh == NULL) {
+ adbname->partial_result |= findoptions;
+ result = ISC_R_NOMEMORY;
+ goto fail;
+ }
+
+ foundentry = find_entry_and_lock(adb, &sockaddr, &addr_bucket,
+ now);
+ if (foundentry == NULL) {
+ dns_adbentry_t *entry;
+
+ entry = new_adbentry(adb);
+ if (entry == NULL) {
+ adbname->partial_result |= findoptions;
+ result = ISC_R_NOMEMORY;
+ goto fail;
+ }
+
+ entry->sockaddr = sockaddr;
+ entry->refcnt = 1;
+ entry->nh = 1;
+
+ nh->entry = entry;
+
+ link_entry(adb, addr_bucket, entry);
+ } else {
+ for (anh = ISC_LIST_HEAD(*hookhead); anh != NULL;
+ anh = ISC_LIST_NEXT(anh, plink))
+ {
+ if (anh->entry == foundentry) {
+ break;
+ }
+ }
+ if (anh == NULL) {
+ foundentry->refcnt++;
+ foundentry->nh++;
+ nh->entry = foundentry;
+ } else {
+ free_adbnamehook(adb, &nh);
+ }
+ }
+
+ new_addresses_added = true;
+ if (nh != NULL) {
+ ISC_LIST_APPEND(*hookhead, nh, plink);
+ }
+ nh = NULL;
+ result = dns_rdataset_next(rdataset);
+ }
+
+fail:
+ if (nh != NULL) {
+ free_adbnamehook(adb, &nh);
+ }
+
+ if (addr_bucket != DNS_ADB_INVALIDBUCKET) {
+ UNLOCK(&adb->entrylocks[addr_bucket]);
+ }
+
+ if (rdataset->trust == dns_trust_glue ||
+ rdataset->trust == dns_trust_additional)
+ {
+ rdataset->ttl = ADB_CACHE_MINIMUM;
+ } else if (rdataset->trust == dns_trust_ultimate) {
+ rdataset->ttl = 0;
+ } else {
+ rdataset->ttl = ttlclamp(rdataset->ttl);
+ }
+
+ if (rdtype == dns_rdatatype_a) {
+ DP(NCACHE_LEVEL, "expire_v4 set to MIN(%u,%u) import_rdataset",
+ adbname->expire_v4, now + rdataset->ttl);
+ adbname->expire_v4 = ISC_MIN(
+ adbname->expire_v4,
+ ISC_MIN(now + ADB_ENTRY_WINDOW, now + rdataset->ttl));
+ } else {
+ DP(NCACHE_LEVEL, "expire_v6 set to MIN(%u,%u) import_rdataset",
+ adbname->expire_v6, now + rdataset->ttl);
+ adbname->expire_v6 = ISC_MIN(
+ adbname->expire_v6,
+ ISC_MIN(now + ADB_ENTRY_WINDOW, now + rdataset->ttl));
+ }
+
+ if (new_addresses_added) {
+ /*
+ * Lie a little here. This is more or less so code that cares
+ * can find out if any new information was added or not.
+ */
+ return (ISC_R_SUCCESS);
+ }
+
+ return (result);
+}
+
+/*
+ * Requires the name's bucket be locked.
+ */
+static bool
+kill_name(dns_adbname_t **n, isc_eventtype_t ev) {
+ dns_adbname_t *name;
+ bool result = false;
+ bool result4, result6;
+ int bucket;
+ dns_adb_t *adb;
+
+ INSIST(n != NULL);
+ name = *n;
+ *n = NULL;
+ INSIST(DNS_ADBNAME_VALID(name));
+ adb = name->adb;
+ INSIST(DNS_ADB_VALID(adb));
+
+ DP(DEF_LEVEL, "killing name %p", name);
+
+ /*
+ * If we're dead already, just check to see if we should go
+ * away now or not.
+ */
+ if (NAME_DEAD(name) && !NAME_FETCH(name)) {
+ result = unlink_name(adb, name);
+ free_adbname(adb, &name);
+ if (result) {
+ result = dec_adb_irefcnt(adb);
+ }
+ return (result);
+ }
+
+ /*
+ * Clean up the name's various lists. These two are destructive
+ * in that they will always empty the list.
+ */
+ clean_finds_at_name(name, ev, DNS_ADBFIND_ADDRESSMASK);
+ result4 = clean_namehooks(adb, &name->v4);
+ result6 = clean_namehooks(adb, &name->v6);
+ clean_target(adb, &name->target);
+ result = (result4 || result6);
+
+ /*
+ * If fetches are running, cancel them. If none are running, we can
+ * just kill the name here.
+ */
+ if (!NAME_FETCH(name)) {
+ INSIST(!result);
+ result = unlink_name(adb, name);
+ free_adbname(adb, &name);
+ if (result) {
+ result = dec_adb_irefcnt(adb);
+ }
+ } else {
+ cancel_fetches_at_name(name);
+ if (!NAME_DEAD(name)) {
+ bucket = name->lock_bucket;
+ ISC_LIST_UNLINK(adb->names[bucket], name, plink);
+ ISC_LIST_APPEND(adb->deadnames[bucket], name, plink);
+ name->flags |= NAME_IS_DEAD;
+ }
+ }
+ return (result);
+}
+
+/*
+ * Requires the name's bucket be locked and no entry buckets be locked.
+ */
+static bool
+check_expire_namehooks(dns_adbname_t *name, isc_stdtime_t now) {
+ dns_adb_t *adb;
+ bool result4 = false;
+ bool result6 = false;
+
+ INSIST(DNS_ADBNAME_VALID(name));
+ adb = name->adb;
+ INSIST(DNS_ADB_VALID(adb));
+
+ /*
+ * Check to see if we need to remove the v4 addresses
+ */
+ if (!NAME_FETCH_V4(name) && EXPIRE_OK(name->expire_v4, now)) {
+ if (NAME_HAS_V4(name)) {
+ DP(DEF_LEVEL, "expiring v4 for name %p", name);
+ result4 = clean_namehooks(adb, &name->v4);
+ name->partial_result &= ~DNS_ADBFIND_INET;
+ }
+ name->expire_v4 = INT_MAX;
+ name->fetch_err = FIND_ERR_UNEXPECTED;
+ }
+
+ /*
+ * Check to see if we need to remove the v6 addresses
+ */
+ if (!NAME_FETCH_V6(name) && EXPIRE_OK(name->expire_v6, now)) {
+ if (NAME_HAS_V6(name)) {
+ DP(DEF_LEVEL, "expiring v6 for name %p", name);
+ result6 = clean_namehooks(adb, &name->v6);
+ name->partial_result &= ~DNS_ADBFIND_INET6;
+ }
+ name->expire_v6 = INT_MAX;
+ name->fetch6_err = FIND_ERR_UNEXPECTED;
+ }
+
+ /*
+ * Check to see if we need to remove the alias target.
+ */
+ if (EXPIRE_OK(name->expire_target, now)) {
+ clean_target(adb, &name->target);
+ name->expire_target = INT_MAX;
+ }
+ return (result4 || result6);
+}
+
+/*
+ * Requires the name's bucket be locked.
+ */
+static void
+link_name(dns_adb_t *adb, int bucket, dns_adbname_t *name) {
+ INSIST(name->lock_bucket == DNS_ADB_INVALIDBUCKET);
+
+ ISC_LIST_PREPEND(adb->names[bucket], name, plink);
+ name->lock_bucket = bucket;
+ adb->name_refcnt[bucket]++;
+}
+
+/*
+ * Requires the name's bucket be locked.
+ */
+static bool
+unlink_name(dns_adb_t *adb, dns_adbname_t *name) {
+ int bucket;
+ bool result = false;
+
+ bucket = name->lock_bucket;
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+
+ if (NAME_DEAD(name)) {
+ ISC_LIST_UNLINK(adb->deadnames[bucket], name, plink);
+ } else {
+ ISC_LIST_UNLINK(adb->names[bucket], name, plink);
+ }
+ name->lock_bucket = DNS_ADB_INVALIDBUCKET;
+ INSIST(adb->name_refcnt[bucket] > 0);
+ adb->name_refcnt[bucket]--;
+ if (adb->name_sd[bucket] && adb->name_refcnt[bucket] == 0) {
+ result = true;
+ }
+ return (result);
+}
+
+/*
+ * Requires the entry's bucket be locked.
+ */
+static void
+link_entry(dns_adb_t *adb, int bucket, dns_adbentry_t *entry) {
+ int i;
+ dns_adbentry_t *e;
+
+ if (isc_mem_isovermem(adb->mctx)) {
+ for (i = 0; i < 2; i++) {
+ e = ISC_LIST_TAIL(adb->entries[bucket]);
+ if (e == NULL) {
+ break;
+ }
+ if (e->refcnt == 0) {
+ unlink_entry(adb, e);
+ free_adbentry(adb, &e);
+ continue;
+ }
+ INSIST((e->flags & ENTRY_IS_DEAD) == 0);
+ e->flags |= ENTRY_IS_DEAD;
+ ISC_LIST_UNLINK(adb->entries[bucket], e, plink);
+ ISC_LIST_PREPEND(adb->deadentries[bucket], e, plink);
+ }
+ }
+
+ ISC_LIST_PREPEND(adb->entries[bucket], entry, plink);
+ entry->lock_bucket = bucket;
+ adb->entry_refcnt[bucket]++;
+}
+
+/*
+ * Requires the entry's bucket be locked.
+ */
+static bool
+unlink_entry(dns_adb_t *adb, dns_adbentry_t *entry) {
+ int bucket;
+ bool result = false;
+
+ bucket = entry->lock_bucket;
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+
+ if ((entry->flags & ENTRY_IS_DEAD) != 0) {
+ ISC_LIST_UNLINK(adb->deadentries[bucket], entry, plink);
+ } else {
+ ISC_LIST_UNLINK(adb->entries[bucket], entry, plink);
+ }
+ entry->lock_bucket = DNS_ADB_INVALIDBUCKET;
+ INSIST(adb->entry_refcnt[bucket] > 0);
+ adb->entry_refcnt[bucket]--;
+ if (adb->entry_sd[bucket] && adb->entry_refcnt[bucket] == 0) {
+ result = true;
+ }
+ return (result);
+}
+
+static void
+violate_locking_hierarchy(isc_mutex_t *have, isc_mutex_t *want) {
+ if (isc_mutex_trylock(want) != ISC_R_SUCCESS) {
+ UNLOCK(have);
+ LOCK(want);
+ LOCK(have);
+ }
+}
+
+/*
+ * The ADB _MUST_ be locked before calling. Also, exit conditions must be
+ * checked after calling this function.
+ */
+static bool
+shutdown_names(dns_adb_t *adb) {
+ unsigned int bucket;
+ bool result = false;
+ dns_adbname_t *name;
+ dns_adbname_t *next_name;
+
+ for (bucket = 0; bucket < adb->nnames; bucket++) {
+ LOCK(&adb->namelocks[bucket]);
+ adb->name_sd[bucket] = true;
+
+ name = ISC_LIST_HEAD(adb->names[bucket]);
+ if (name == NULL) {
+ /*
+ * This bucket has no names. We must decrement the
+ * irefcnt ourselves, since it will not be
+ * automatically triggered by a name being unlinked.
+ */
+ INSIST(!result);
+ result = dec_adb_irefcnt(adb);
+ } else {
+ /*
+ * Run through the list. For each name, clean up finds
+ * found there, and cancel any fetches running. When
+ * all the fetches are canceled, the name will destroy
+ * itself.
+ */
+ while (name != NULL) {
+ next_name = ISC_LIST_NEXT(name, plink);
+ INSIST(!result);
+ result = kill_name(&name,
+ DNS_EVENT_ADBSHUTDOWN);
+ name = next_name;
+ }
+ }
+
+ UNLOCK(&adb->namelocks[bucket]);
+ }
+ return (result);
+}
+
+/*
+ * The ADB _MUST_ be locked before calling. Also, exit conditions must be
+ * checked after calling this function.
+ */
+static bool
+shutdown_entries(dns_adb_t *adb) {
+ unsigned int bucket;
+ bool result = false;
+ dns_adbentry_t *entry;
+ dns_adbentry_t *next_entry;
+
+ for (bucket = 0; bucket < adb->nentries; bucket++) {
+ LOCK(&adb->entrylocks[bucket]);
+ adb->entry_sd[bucket] = true;
+
+ entry = ISC_LIST_HEAD(adb->entries[bucket]);
+ if (adb->entry_refcnt[bucket] == 0) {
+ /*
+ * This bucket has no entries. We must decrement the
+ * irefcnt ourselves, since it will not be
+ * automatically triggered by an entry being unlinked.
+ */
+ result = dec_adb_irefcnt(adb);
+ } else {
+ /*
+ * Run through the list. Cleanup any entries not
+ * associated with names, and which are not in use.
+ */
+ while (entry != NULL) {
+ next_entry = ISC_LIST_NEXT(entry, plink);
+ if (entry->refcnt == 0 && entry->expires != 0) {
+ result = unlink_entry(adb, entry);
+ free_adbentry(adb, &entry);
+ if (result) {
+ result = dec_adb_irefcnt(adb);
+ }
+ }
+ entry = next_entry;
+ }
+ }
+
+ UNLOCK(&adb->entrylocks[bucket]);
+ }
+ return (result);
+}
+
+/*
+ * Name bucket must be locked
+ */
+static void
+cancel_fetches_at_name(dns_adbname_t *name) {
+ if (NAME_FETCH_A(name)) {
+ dns_resolver_cancelfetch(name->fetch_a->fetch);
+ }
+
+ if (NAME_FETCH_AAAA(name)) {
+ dns_resolver_cancelfetch(name->fetch_aaaa->fetch);
+ }
+}
+
+/*
+ * Assumes the name bucket is locked.
+ */
+static bool
+clean_namehooks(dns_adb_t *adb, dns_adbnamehooklist_t *namehooks) {
+ dns_adbentry_t *entry;
+ dns_adbnamehook_t *namehook;
+ int addr_bucket;
+ bool result = false;
+ bool overmem = isc_mem_isovermem(adb->mctx);
+
+ addr_bucket = DNS_ADB_INVALIDBUCKET;
+ namehook = ISC_LIST_HEAD(*namehooks);
+ while (namehook != NULL) {
+ INSIST(DNS_ADBNAMEHOOK_VALID(namehook));
+
+ /*
+ * Clean up the entry if needed.
+ */
+ entry = namehook->entry;
+ if (entry != NULL) {
+ INSIST(DNS_ADBENTRY_VALID(entry));
+
+ if (addr_bucket != entry->lock_bucket) {
+ if (addr_bucket != DNS_ADB_INVALIDBUCKET) {
+ UNLOCK(&adb->entrylocks[addr_bucket]);
+ }
+ addr_bucket = entry->lock_bucket;
+ INSIST(addr_bucket != DNS_ADB_INVALIDBUCKET);
+ LOCK(&adb->entrylocks[addr_bucket]);
+ }
+
+ entry->nh--;
+ result = dec_entry_refcnt(adb, overmem, entry, false);
+ }
+
+ /*
+ * Free the namehook
+ */
+ namehook->entry = NULL;
+ ISC_LIST_UNLINK(*namehooks, namehook, plink);
+ free_adbnamehook(adb, &namehook);
+
+ namehook = ISC_LIST_HEAD(*namehooks);
+ }
+
+ if (addr_bucket != DNS_ADB_INVALIDBUCKET) {
+ UNLOCK(&adb->entrylocks[addr_bucket]);
+ }
+ return (result);
+}
+
+static void
+clean_target(dns_adb_t *adb, dns_name_t *target) {
+ if (dns_name_countlabels(target) > 0) {
+ dns_name_free(target, adb->mctx);
+ dns_name_init(target, NULL);
+ }
+}
+
+static isc_result_t
+set_target(dns_adb_t *adb, const dns_name_t *name, const dns_name_t *fname,
+ dns_rdataset_t *rdataset, dns_name_t *target) {
+ isc_result_t result;
+ dns_namereln_t namereln;
+ unsigned int nlabels;
+ int order;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_fixedname_t fixed1, fixed2;
+ dns_name_t *prefix, *new_target;
+
+ REQUIRE(dns_name_countlabels(target) == 0);
+
+ if (rdataset->type == dns_rdatatype_cname) {
+ dns_rdata_cname_t cname;
+
+ /*
+ * Copy the CNAME's target into the target name.
+ */
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_name_dup(&cname.cname, adb->mctx, target);
+ dns_rdata_freestruct(&cname);
+ } else {
+ dns_rdata_dname_t dname;
+
+ INSIST(rdataset->type == dns_rdatatype_dname);
+ namereln = dns_name_fullcompare(name, fname, &order, &nlabels);
+ INSIST(namereln == dns_namereln_subdomain);
+ /*
+ * Get the target name of the DNAME.
+ */
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &dname, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ /*
+ * Construct the new target name.
+ */
+ prefix = dns_fixedname_initname(&fixed1);
+ new_target = dns_fixedname_initname(&fixed2);
+ dns_name_split(name, nlabels, prefix, NULL);
+ result = dns_name_concatenate(prefix, &dname.dname, new_target,
+ NULL);
+ dns_rdata_freestruct(&dname);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_name_dup(new_target, adb->mctx, target);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Assumes nothing is locked, since this is called by the client.
+ */
+static void
+event_free(isc_event_t *event) {
+ dns_adbfind_t *find;
+
+ INSIST(event != NULL);
+ find = event->ev_destroy_arg;
+ INSIST(DNS_ADBFIND_VALID(find));
+
+ LOCK(&find->lock);
+ find->flags |= FIND_EVENT_FREED;
+ event->ev_destroy_arg = NULL;
+ UNLOCK(&find->lock);
+}
+
+/*
+ * Assumes the name bucket is locked.
+ */
+static void
+clean_finds_at_name(dns_adbname_t *name, isc_eventtype_t evtype,
+ unsigned int addrs) {
+ isc_event_t *ev;
+ isc_task_t *task;
+ dns_adbfind_t *find;
+ dns_adbfind_t *next_find;
+ bool process;
+ unsigned int wanted, notify;
+
+ DP(ENTER_LEVEL,
+ "ENTER clean_finds_at_name, name %p, evtype %08x, addrs %08x", name,
+ evtype, addrs);
+
+ find = ISC_LIST_HEAD(name->finds);
+ while (find != NULL) {
+ LOCK(&find->lock);
+ next_find = ISC_LIST_NEXT(find, plink);
+
+ process = false;
+ wanted = find->flags & DNS_ADBFIND_ADDRESSMASK;
+ notify = wanted & addrs;
+
+ switch (evtype) {
+ case DNS_EVENT_ADBMOREADDRESSES:
+ DP(ISC_LOG_DEBUG(3), "DNS_EVENT_ADBMOREADDRESSES");
+ if ((notify) != 0) {
+ find->flags &= ~addrs;
+ process = true;
+ }
+ break;
+ case DNS_EVENT_ADBNOMOREADDRESSES:
+ DP(ISC_LOG_DEBUG(3), "DNS_EVENT_ADBNOMOREADDRESSES");
+ find->flags &= ~addrs;
+ wanted = find->flags & DNS_ADBFIND_ADDRESSMASK;
+ if (wanted == 0) {
+ process = true;
+ }
+ break;
+ default:
+ find->flags &= ~addrs;
+ process = true;
+ }
+
+ if (process) {
+ DP(DEF_LEVEL, "cfan: processing find %p", find);
+ /*
+ * Unlink the find from the name, letting the caller
+ * call dns_adb_destroyfind() on it to clean it up
+ * later.
+ */
+ ISC_LIST_UNLINK(name->finds, find, plink);
+ find->adbname = NULL;
+ find->name_bucket = DNS_ADB_INVALIDBUCKET;
+
+ INSIST(!FIND_EVENTSENT(find));
+
+ ev = &find->event;
+ task = ev->ev_sender;
+ ev->ev_sender = find;
+ find->result_v4 = find_err_map[name->fetch_err];
+ find->result_v6 = find_err_map[name->fetch6_err];
+ ev->ev_type = evtype;
+ ev->ev_destroy = event_free;
+ ev->ev_destroy_arg = find;
+
+ DP(DEF_LEVEL, "sending event %p to task %p for find %p",
+ ev, task, find);
+
+ isc_task_sendanddetach(&task, (isc_event_t **)&ev);
+ find->flags |= FIND_EVENT_SENT;
+ } else {
+ DP(DEF_LEVEL, "cfan: skipping find %p", find);
+ }
+
+ UNLOCK(&find->lock);
+ find = next_find;
+ }
+ DP(ENTER_LEVEL, "EXIT clean_finds_at_name, name %p", name);
+}
+
+static void
+check_exit(dns_adb_t *adb) {
+ isc_event_t *event;
+ /*
+ * The caller must be holding the adb lock.
+ */
+ if (adb->shutting_down) {
+ /*
+ * If there aren't any external references either, we're
+ * done. Send the control event to initiate shutdown.
+ */
+ INSIST(!adb->cevent_out); /* Sanity check. */
+ ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL,
+ DNS_EVENT_ADBCONTROL, shutdown_task, adb, adb,
+ NULL, NULL);
+ event = &adb->cevent;
+ isc_task_send(adb->task, &event);
+ adb->cevent_out = true;
+ }
+}
+
+static bool
+dec_adb_irefcnt(dns_adb_t *adb) {
+ isc_event_t *event;
+ isc_task_t *etask;
+ bool result = false;
+
+ LOCK(&adb->reflock);
+
+ INSIST(adb->irefcnt > 0);
+ adb->irefcnt--;
+
+ if (adb->irefcnt == 0) {
+ event = ISC_LIST_HEAD(adb->whenshutdown);
+ while (event != NULL) {
+ ISC_LIST_UNLINK(adb->whenshutdown, event, ev_link);
+ etask = event->ev_sender;
+ event->ev_sender = adb;
+ isc_task_sendanddetach(&etask, &event);
+ event = ISC_LIST_HEAD(adb->whenshutdown);
+ }
+ }
+
+ if (adb->irefcnt == 0 && adb->erefcnt == 0) {
+ result = true;
+ }
+ UNLOCK(&adb->reflock);
+ return (result);
+}
+
+static void
+inc_adb_irefcnt(dns_adb_t *adb) {
+ LOCK(&adb->reflock);
+ adb->irefcnt++;
+ UNLOCK(&adb->reflock);
+}
+
+static void
+inc_adb_erefcnt(dns_adb_t *adb) {
+ LOCK(&adb->reflock);
+ adb->erefcnt++;
+ UNLOCK(&adb->reflock);
+}
+
+static void
+inc_entry_refcnt(dns_adb_t *adb, dns_adbentry_t *entry, bool lock) {
+ int bucket;
+
+ bucket = entry->lock_bucket;
+
+ if (lock) {
+ LOCK(&adb->entrylocks[bucket]);
+ }
+
+ entry->refcnt++;
+
+ if (lock) {
+ UNLOCK(&adb->entrylocks[bucket]);
+ }
+}
+
+static bool
+dec_entry_refcnt(dns_adb_t *adb, bool overmem, dns_adbentry_t *entry,
+ bool lock) {
+ int bucket;
+ bool destroy_entry;
+ bool result = false;
+
+ bucket = entry->lock_bucket;
+
+ if (lock) {
+ LOCK(&adb->entrylocks[bucket]);
+ }
+
+ INSIST(entry->refcnt > 0);
+ entry->refcnt--;
+
+ destroy_entry = false;
+ if (entry->refcnt == 0 &&
+ (adb->entry_sd[bucket] || entry->expires == 0 || overmem ||
+ (entry->flags & ENTRY_IS_DEAD) != 0))
+ {
+ destroy_entry = true;
+ result = unlink_entry(adb, entry);
+ }
+
+ if (lock) {
+ UNLOCK(&adb->entrylocks[bucket]);
+ }
+
+ if (!destroy_entry) {
+ return (result);
+ }
+
+ entry->lock_bucket = DNS_ADB_INVALIDBUCKET;
+
+ free_adbentry(adb, &entry);
+ if (result) {
+ result = dec_adb_irefcnt(adb);
+ }
+
+ return (result);
+}
+
+static dns_adbname_t *
+new_adbname(dns_adb_t *adb, const dns_name_t *dnsname) {
+ dns_adbname_t *name;
+
+ name = isc_mem_get(adb->mctx, sizeof(*name));
+
+ dns_name_init(&name->name, NULL);
+ dns_name_dup(dnsname, adb->mctx, &name->name);
+ dns_name_init(&name->target, NULL);
+ name->magic = DNS_ADBNAME_MAGIC;
+ name->adb = adb;
+ name->partial_result = 0;
+ name->flags = 0;
+ name->expire_v4 = INT_MAX;
+ name->expire_v6 = INT_MAX;
+ name->expire_target = INT_MAX;
+ name->chains = 0;
+ name->lock_bucket = DNS_ADB_INVALIDBUCKET;
+ ISC_LIST_INIT(name->v4);
+ ISC_LIST_INIT(name->v6);
+ name->fetch_a = NULL;
+ name->fetch_aaaa = NULL;
+ name->fetch_err = FIND_ERR_UNEXPECTED;
+ name->fetch6_err = FIND_ERR_UNEXPECTED;
+ ISC_LIST_INIT(name->finds);
+ ISC_LINK_INIT(name, plink);
+
+ LOCK(&adb->namescntlock);
+ adb->namescnt++;
+ inc_adbstats(adb, dns_adbstats_namescnt);
+ if (!adb->grownames_sent && adb->excl != NULL &&
+ adb->namescnt > (adb->nnames * 8))
+ {
+ isc_event_t *event = &adb->grownames;
+ inc_adb_irefcnt(adb);
+ isc_task_send(adb->excl, &event);
+ adb->grownames_sent = true;
+ }
+ UNLOCK(&adb->namescntlock);
+
+ return (name);
+}
+
+static void
+free_adbname(dns_adb_t *adb, dns_adbname_t **name) {
+ dns_adbname_t *n;
+
+ INSIST(name != NULL && DNS_ADBNAME_VALID(*name));
+ n = *name;
+ *name = NULL;
+
+ INSIST(!NAME_HAS_V4(n));
+ INSIST(!NAME_HAS_V6(n));
+ INSIST(!NAME_FETCH(n));
+ INSIST(ISC_LIST_EMPTY(n->finds));
+ INSIST(!ISC_LINK_LINKED(n, plink));
+ INSIST(n->lock_bucket == DNS_ADB_INVALIDBUCKET);
+ INSIST(n->adb == adb);
+
+ n->magic = 0;
+ dns_name_free(&n->name, adb->mctx);
+
+ isc_mem_put(adb->mctx, n, sizeof(*n));
+ LOCK(&adb->namescntlock);
+ adb->namescnt--;
+ dec_adbstats(adb, dns_adbstats_namescnt);
+ UNLOCK(&adb->namescntlock);
+}
+
+static dns_adbnamehook_t *
+new_adbnamehook(dns_adb_t *adb, dns_adbentry_t *entry) {
+ dns_adbnamehook_t *nh;
+
+ nh = isc_mem_get(adb->mctx, sizeof(*nh));
+ isc_refcount_increment0(&adb->nhrefcnt);
+
+ nh->magic = DNS_ADBNAMEHOOK_MAGIC;
+ nh->entry = entry;
+ ISC_LINK_INIT(nh, plink);
+
+ return (nh);
+}
+
+static void
+free_adbnamehook(dns_adb_t *adb, dns_adbnamehook_t **namehook) {
+ dns_adbnamehook_t *nh;
+
+ INSIST(namehook != NULL && DNS_ADBNAMEHOOK_VALID(*namehook));
+ nh = *namehook;
+ *namehook = NULL;
+
+ INSIST(nh->entry == NULL);
+ INSIST(!ISC_LINK_LINKED(nh, plink));
+
+ nh->magic = 0;
+
+ isc_refcount_decrement(&adb->nhrefcnt);
+ isc_mem_put(adb->mctx, nh, sizeof(*nh));
+}
+
+static dns_adblameinfo_t *
+new_adblameinfo(dns_adb_t *adb, const dns_name_t *qname,
+ dns_rdatatype_t qtype) {
+ dns_adblameinfo_t *li;
+
+ li = isc_mem_get(adb->mctx, sizeof(*li));
+
+ dns_name_init(&li->qname, NULL);
+ dns_name_dup(qname, adb->mctx, &li->qname);
+ li->magic = DNS_ADBLAMEINFO_MAGIC;
+ li->lame_timer = 0;
+ li->qtype = qtype;
+ ISC_LINK_INIT(li, plink);
+
+ return (li);
+}
+
+static void
+free_adblameinfo(dns_adb_t *adb, dns_adblameinfo_t **lameinfo) {
+ dns_adblameinfo_t *li;
+
+ INSIST(lameinfo != NULL && DNS_ADBLAMEINFO_VALID(*lameinfo));
+ li = *lameinfo;
+ *lameinfo = NULL;
+
+ INSIST(!ISC_LINK_LINKED(li, plink));
+
+ dns_name_free(&li->qname, adb->mctx);
+
+ li->magic = 0;
+
+ isc_mem_put(adb->mctx, li, sizeof(*li));
+}
+
+static dns_adbentry_t *
+new_adbentry(dns_adb_t *adb) {
+ dns_adbentry_t *e;
+
+ e = isc_mem_get(adb->mctx, sizeof(*e));
+
+ e->magic = DNS_ADBENTRY_MAGIC;
+ e->lock_bucket = DNS_ADB_INVALIDBUCKET;
+ e->refcnt = 0;
+ e->nh = 0;
+ e->flags = 0;
+ e->udpsize = 0;
+ e->edns = 0;
+ e->completed = 0;
+ e->timeouts = 0;
+ e->plain = 0;
+ e->plainto = 0;
+ e->to4096 = 0;
+ e->to1432 = 0;
+ e->to1232 = 0;
+ e->to512 = 0;
+ e->cookie = NULL;
+ e->cookielen = 0;
+ e->srtt = (isc_random_uniform(0x1f)) + 1;
+ e->lastage = 0;
+ e->expires = 0;
+ atomic_init(&e->active, 0);
+ e->mode = 0;
+ atomic_init(&e->quota, adb->quota);
+ e->atr = 0.0;
+ ISC_LIST_INIT(e->lameinfo);
+ ISC_LINK_INIT(e, plink);
+ LOCK(&adb->entriescntlock);
+ adb->entriescnt++;
+ inc_adbstats(adb, dns_adbstats_entriescnt);
+ if (!adb->growentries_sent && adb->excl != NULL &&
+ adb->entriescnt > (adb->nentries * 8))
+ {
+ isc_event_t *event = &adb->growentries;
+ inc_adb_irefcnt(adb);
+ isc_task_send(adb->excl, &event);
+ adb->growentries_sent = true;
+ }
+ UNLOCK(&adb->entriescntlock);
+
+ return (e);
+}
+
+static void
+free_adbentry(dns_adb_t *adb, dns_adbentry_t **entry) {
+ dns_adbentry_t *e;
+ dns_adblameinfo_t *li;
+
+ INSIST(entry != NULL && DNS_ADBENTRY_VALID(*entry));
+ e = *entry;
+ *entry = NULL;
+
+ INSIST(e->lock_bucket == DNS_ADB_INVALIDBUCKET);
+ INSIST(e->refcnt == 0);
+ INSIST(!ISC_LINK_LINKED(e, plink));
+
+ e->magic = 0;
+
+ if (e->cookie != NULL) {
+ isc_mem_put(adb->mctx, e->cookie, e->cookielen);
+ }
+
+ li = ISC_LIST_HEAD(e->lameinfo);
+ while (li != NULL) {
+ ISC_LIST_UNLINK(e->lameinfo, li, plink);
+ free_adblameinfo(adb, &li);
+ li = ISC_LIST_HEAD(e->lameinfo);
+ }
+
+ isc_mem_put(adb->mctx, e, sizeof(*e));
+ LOCK(&adb->entriescntlock);
+ adb->entriescnt--;
+ dec_adbstats(adb, dns_adbstats_entriescnt);
+ UNLOCK(&adb->entriescntlock);
+}
+
+static dns_adbfind_t *
+new_adbfind(dns_adb_t *adb) {
+ dns_adbfind_t *h;
+
+ h = isc_mem_get(adb->mctx, sizeof(*h));
+ isc_refcount_increment0(&adb->ahrefcnt);
+
+ /*
+ * Public members.
+ */
+ h->magic = 0;
+ h->adb = adb;
+ h->partial_result = 0;
+ h->options = 0;
+ h->flags = 0;
+ h->result_v4 = ISC_R_UNEXPECTED;
+ h->result_v6 = ISC_R_UNEXPECTED;
+ ISC_LINK_INIT(h, publink);
+ ISC_LINK_INIT(h, plink);
+ ISC_LIST_INIT(h->list);
+ h->adbname = NULL;
+ h->name_bucket = DNS_ADB_INVALIDBUCKET;
+
+ /*
+ * private members
+ */
+ isc_mutex_init(&h->lock);
+
+ ISC_EVENT_INIT(&h->event, sizeof(isc_event_t), 0, 0, 0, NULL, NULL,
+ NULL, NULL, h);
+
+ inc_adb_irefcnt(adb);
+ h->magic = DNS_ADBFIND_MAGIC;
+ return (h);
+}
+
+static dns_adbfetch_t *
+new_adbfetch(dns_adb_t *adb) {
+ dns_adbfetch_t *f;
+
+ f = isc_mem_get(adb->mctx, sizeof(*f));
+
+ f->magic = 0;
+ f->fetch = NULL;
+
+ dns_rdataset_init(&f->rdataset);
+
+ f->magic = DNS_ADBFETCH_MAGIC;
+
+ return (f);
+}
+
+static void
+free_adbfetch(dns_adb_t *adb, dns_adbfetch_t **fetch) {
+ dns_adbfetch_t *f;
+
+ INSIST(fetch != NULL && DNS_ADBFETCH_VALID(*fetch));
+ f = *fetch;
+ *fetch = NULL;
+
+ f->magic = 0;
+
+ if (dns_rdataset_isassociated(&f->rdataset)) {
+ dns_rdataset_disassociate(&f->rdataset);
+ }
+
+ isc_mem_put(adb->mctx, f, sizeof(*f));
+}
+
+static bool
+free_adbfind(dns_adb_t *adb, dns_adbfind_t **findp) {
+ dns_adbfind_t *find;
+
+ INSIST(findp != NULL && DNS_ADBFIND_VALID(*findp));
+ find = *findp;
+ *findp = NULL;
+
+ INSIST(!FIND_HAS_ADDRS(find));
+ INSIST(!ISC_LINK_LINKED(find, publink));
+ INSIST(!ISC_LINK_LINKED(find, plink));
+ INSIST(find->name_bucket == DNS_ADB_INVALIDBUCKET);
+ INSIST(find->adbname == NULL);
+
+ find->magic = 0;
+
+ isc_mutex_destroy(&find->lock);
+
+ isc_refcount_decrement(&adb->ahrefcnt);
+ isc_mem_put(adb->mctx, find, sizeof(*find));
+ return (dec_adb_irefcnt(adb));
+}
+
+/*
+ * Copy bits from the entry into the newly allocated addrinfo. The entry
+ * must be locked, and the reference count must be bumped up by one
+ * if this function returns a valid pointer.
+ */
+static dns_adbaddrinfo_t *
+new_adbaddrinfo(dns_adb_t *adb, dns_adbentry_t *entry, in_port_t port) {
+ dns_adbaddrinfo_t *ai;
+
+ ai = isc_mem_get(adb->mctx, sizeof(*ai));
+
+ ai->magic = DNS_ADBADDRINFO_MAGIC;
+ ai->sockaddr = entry->sockaddr;
+ isc_sockaddr_setport(&ai->sockaddr, port);
+ ai->srtt = entry->srtt;
+ ai->flags = entry->flags;
+ ai->entry = entry;
+ ai->dscp = -1;
+ ISC_LINK_INIT(ai, publink);
+
+ return (ai);
+}
+
+static void
+free_adbaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **ainfo) {
+ dns_adbaddrinfo_t *ai;
+
+ INSIST(ainfo != NULL && DNS_ADBADDRINFO_VALID(*ainfo));
+ ai = *ainfo;
+ *ainfo = NULL;
+
+ INSIST(ai->entry == NULL);
+ INSIST(!ISC_LINK_LINKED(ai, publink));
+
+ ai->magic = 0;
+
+ isc_mem_put(adb->mctx, ai, sizeof(*ai));
+}
+
+/*
+ * Search for the name. NOTE: The bucket is kept locked on both
+ * success and failure, so it must always be unlocked by the caller!
+ *
+ * On the first call to this function, *bucketp must be set to
+ * DNS_ADB_INVALIDBUCKET.
+ */
+static dns_adbname_t *
+find_name_and_lock(dns_adb_t *adb, const dns_name_t *name, unsigned int options,
+ int *bucketp) {
+ dns_adbname_t *adbname;
+ int bucket;
+
+ bucket = dns_name_fullhash(name, false) % adb->nnames;
+
+ if (*bucketp == DNS_ADB_INVALIDBUCKET) {
+ LOCK(&adb->namelocks[bucket]);
+ *bucketp = bucket;
+ } else if (*bucketp != bucket) {
+ UNLOCK(&adb->namelocks[*bucketp]);
+ LOCK(&adb->namelocks[bucket]);
+ *bucketp = bucket;
+ }
+
+ adbname = ISC_LIST_HEAD(adb->names[bucket]);
+ while (adbname != NULL) {
+ if (!NAME_DEAD(adbname)) {
+ if (dns_name_equal(name, &adbname->name) &&
+ GLUEHINT_OK(adbname, options) &&
+ STARTATZONE_MATCHES(adbname, options))
+ {
+ return (adbname);
+ }
+ }
+ adbname = ISC_LIST_NEXT(adbname, plink);
+ }
+
+ return (NULL);
+}
+
+/*
+ * Search for the address. NOTE: The bucket is kept locked on both
+ * success and failure, so it must always be unlocked by the caller.
+ *
+ * On the first call to this function, *bucketp must be set to
+ * DNS_ADB_INVALIDBUCKET. This will cause a lock to occur. On
+ * later calls (within the same "lock path") it can be left alone, so
+ * if this function is called multiple times locking is only done if
+ * the bucket changes.
+ */
+static dns_adbentry_t *
+find_entry_and_lock(dns_adb_t *adb, const isc_sockaddr_t *addr, int *bucketp,
+ isc_stdtime_t now) {
+ dns_adbentry_t *entry, *entry_next;
+ int bucket;
+
+ bucket = isc_sockaddr_hash(addr, true) % adb->nentries;
+
+ if (*bucketp == DNS_ADB_INVALIDBUCKET) {
+ LOCK(&adb->entrylocks[bucket]);
+ *bucketp = bucket;
+ } else if (*bucketp != bucket) {
+ UNLOCK(&adb->entrylocks[*bucketp]);
+ LOCK(&adb->entrylocks[bucket]);
+ *bucketp = bucket;
+ }
+
+ /* Search the list, while cleaning up expired entries. */
+ for (entry = ISC_LIST_HEAD(adb->entries[bucket]); entry != NULL;
+ entry = entry_next)
+ {
+ entry_next = ISC_LIST_NEXT(entry, plink);
+ (void)check_expire_entry(adb, &entry, now);
+ if (entry != NULL &&
+ (entry->expires == 0 || entry->expires > now) &&
+ isc_sockaddr_equal(addr, &entry->sockaddr))
+ {
+ ISC_LIST_UNLINK(adb->entries[bucket], entry, plink);
+ ISC_LIST_PREPEND(adb->entries[bucket], entry, plink);
+ return (entry);
+ }
+ }
+
+ return (NULL);
+}
+
+/*
+ * Entry bucket MUST be locked!
+ */
+static bool
+entry_is_lame(dns_adb_t *adb, dns_adbentry_t *entry, const dns_name_t *qname,
+ dns_rdatatype_t qtype, isc_stdtime_t now) {
+ dns_adblameinfo_t *li, *next_li;
+ bool is_bad;
+
+ is_bad = false;
+
+ li = ISC_LIST_HEAD(entry->lameinfo);
+ if (li == NULL) {
+ return (false);
+ }
+ while (li != NULL) {
+ next_li = ISC_LIST_NEXT(li, plink);
+
+ /*
+ * Has the entry expired?
+ */
+ if (li->lame_timer < now) {
+ ISC_LIST_UNLINK(entry->lameinfo, li, plink);
+ free_adblameinfo(adb, &li);
+ }
+
+ /*
+ * Order tests from least to most expensive.
+ *
+ * We do not break out of the main loop here as
+ * we use the loop for house keeping.
+ */
+ if (li != NULL && !is_bad && li->qtype == qtype &&
+ dns_name_equal(qname, &li->qname))
+ {
+ is_bad = true;
+ }
+
+ li = next_li;
+ }
+
+ return (is_bad);
+}
+
+static void
+log_quota(dns_adbentry_t *entry, const char *fmt, ...) {
+ va_list ap;
+ char msgbuf[2048];
+ char addrbuf[ISC_NETADDR_FORMATSIZE];
+ isc_netaddr_t netaddr;
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ isc_netaddr_fromsockaddr(&netaddr, &entry->sockaddr);
+ isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB,
+ ISC_LOG_INFO,
+ "adb: quota %s (%" PRIuFAST32 "/%" PRIuFAST32 "): %s",
+ addrbuf, atomic_load_relaxed(&entry->active),
+ atomic_load_relaxed(&entry->quota), msgbuf);
+}
+
+static void
+copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find,
+ const dns_name_t *qname, dns_rdatatype_t qtype,
+ dns_adbname_t *name, isc_stdtime_t now) {
+ dns_adbnamehook_t *namehook;
+ dns_adbaddrinfo_t *addrinfo;
+ dns_adbentry_t *entry;
+ int bucket;
+
+ bucket = DNS_ADB_INVALIDBUCKET;
+
+ if ((find->options & DNS_ADBFIND_INET) != 0) {
+ namehook = ISC_LIST_HEAD(name->v4);
+ while (namehook != NULL) {
+ entry = namehook->entry;
+ bucket = entry->lock_bucket;
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+ LOCK(&adb->entrylocks[bucket]);
+
+ if (dns_adbentry_overquota(entry)) {
+ find->options |= (DNS_ADBFIND_LAMEPRUNED |
+ DNS_ADBFIND_OVERQUOTA);
+ goto nextv4;
+ }
+
+ if (!FIND_RETURNLAME(find) &&
+ entry_is_lame(adb, entry, qname, qtype, now))
+ {
+ find->options |= DNS_ADBFIND_LAMEPRUNED;
+ goto nextv4;
+ }
+ addrinfo = new_adbaddrinfo(adb, entry, find->port);
+ if (addrinfo == NULL) {
+ find->partial_result |= DNS_ADBFIND_INET;
+ goto out;
+ }
+ /*
+ * Found a valid entry. Add it to the find's list.
+ */
+ inc_entry_refcnt(adb, entry, false);
+ ISC_LIST_APPEND(find->list, addrinfo, publink);
+ addrinfo = NULL;
+ nextv4:
+ UNLOCK(&adb->entrylocks[bucket]);
+ bucket = DNS_ADB_INVALIDBUCKET;
+ namehook = ISC_LIST_NEXT(namehook, plink);
+ }
+ }
+
+ if ((find->options & DNS_ADBFIND_INET6) != 0) {
+ namehook = ISC_LIST_HEAD(name->v6);
+ while (namehook != NULL) {
+ entry = namehook->entry;
+ bucket = entry->lock_bucket;
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+ LOCK(&adb->entrylocks[bucket]);
+
+ if (dns_adbentry_overquota(entry)) {
+ find->options |= (DNS_ADBFIND_LAMEPRUNED |
+ DNS_ADBFIND_OVERQUOTA);
+ goto nextv6;
+ }
+
+ if (!FIND_RETURNLAME(find) &&
+ entry_is_lame(adb, entry, qname, qtype, now))
+ {
+ find->options |= DNS_ADBFIND_LAMEPRUNED;
+ goto nextv6;
+ }
+ addrinfo = new_adbaddrinfo(adb, entry, find->port);
+ if (addrinfo == NULL) {
+ find->partial_result |= DNS_ADBFIND_INET6;
+ goto out;
+ }
+ /*
+ * Found a valid entry. Add it to the find's list.
+ */
+ inc_entry_refcnt(adb, entry, false);
+ ISC_LIST_APPEND(find->list, addrinfo, publink);
+ addrinfo = NULL;
+ nextv6:
+ UNLOCK(&adb->entrylocks[bucket]);
+ bucket = DNS_ADB_INVALIDBUCKET;
+ namehook = ISC_LIST_NEXT(namehook, plink);
+ }
+ }
+
+out:
+ if (bucket != DNS_ADB_INVALIDBUCKET) {
+ UNLOCK(&adb->entrylocks[bucket]);
+ }
+}
+
+static void
+shutdown_task(isc_task_t *task, isc_event_t *ev) {
+ dns_adb_t *adb;
+
+ UNUSED(task);
+
+ adb = ev->ev_arg;
+ INSIST(DNS_ADB_VALID(adb));
+
+ isc_event_free(&ev);
+ /*
+ * Wait for lock around check_exit() call to be released.
+ */
+ LOCK(&adb->lock);
+ UNLOCK(&adb->lock);
+ destroy(adb);
+}
+
+/*
+ * Name bucket must be locked; adb may be locked; no other locks held.
+ */
+static bool
+check_expire_name(dns_adbname_t **namep, isc_stdtime_t now) {
+ dns_adbname_t *name;
+ bool result = false;
+
+ INSIST(namep != NULL && DNS_ADBNAME_VALID(*namep));
+ name = *namep;
+
+ if (NAME_HAS_V4(name) || NAME_HAS_V6(name)) {
+ return (result);
+ }
+ if (NAME_FETCH(name)) {
+ return (result);
+ }
+ if (!EXPIRE_OK(name->expire_v4, now)) {
+ return (result);
+ }
+ if (!EXPIRE_OK(name->expire_v6, now)) {
+ return (result);
+ }
+ if (!EXPIRE_OK(name->expire_target, now)) {
+ return (result);
+ }
+
+ /*
+ * The name is empty. Delete it.
+ */
+ *namep = NULL;
+ result = kill_name(&name, DNS_EVENT_ADBEXPIRED);
+
+ /*
+ * Our caller, or one of its callers, will be calling check_exit() at
+ * some point, so we don't need to do it here.
+ */
+ return (result);
+}
+
+/*%
+ * Examine the tail entry of the LRU list to see if it expires or is stale
+ * (unused for some period); if so, the name entry will be freed. If the ADB
+ * is in the overmem condition, the tail and the next to tail entries
+ * will be unconditionally removed (unless they have an outstanding fetch).
+ * We don't care about a race on 'overmem' at the risk of causing some
+ * collateral damage or a small delay in starting cleanup, so we don't bother
+ * to lock ADB (if it's not locked).
+ *
+ * Name bucket must be locked; adb may be locked; no other locks held.
+ */
+static void
+check_stale_name(dns_adb_t *adb, int bucket, isc_stdtime_t now) {
+ int victims, max_victims;
+ dns_adbname_t *victim, *next_victim;
+ bool overmem = isc_mem_isovermem(adb->mctx);
+ int scans = 0;
+
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+
+ max_victims = overmem ? 2 : 1;
+
+ /*
+ * We limit the number of scanned entries to 10 (arbitrary choice)
+ * in order to avoid examining too many entries when there are many
+ * tail entries that have fetches (this should be rare, but could
+ * happen).
+ */
+ victim = ISC_LIST_TAIL(adb->names[bucket]);
+ for (victims = 0; victim != NULL && victims < max_victims && scans < 10;
+ victim = next_victim)
+ {
+ INSIST(!NAME_DEAD(victim));
+ scans++;
+ next_victim = ISC_LIST_PREV(victim, plink);
+ (void)check_expire_name(&victim, now);
+ if (victim == NULL) {
+ victims++;
+ goto next;
+ }
+
+ if (!NAME_FETCH(victim) &&
+ (overmem || victim->last_used + ADB_STALE_MARGIN <= now))
+ {
+ RUNTIME_CHECK(
+ !kill_name(&victim, DNS_EVENT_ADBCANCELED));
+ victims++;
+ }
+
+ next:
+ if (!overmem) {
+ break;
+ }
+ }
+}
+
+/*
+ * Entry bucket must be locked; adb may be locked; no other locks held.
+ */
+static bool
+check_expire_entry(dns_adb_t *adb, dns_adbentry_t **entryp, isc_stdtime_t now) {
+ dns_adbentry_t *entry;
+ bool result = false;
+
+ INSIST(entryp != NULL && DNS_ADBENTRY_VALID(*entryp));
+ entry = *entryp;
+
+ if (entry->refcnt != 0) {
+ return (result);
+ }
+
+ if (entry->expires == 0 || entry->expires > now) {
+ return (result);
+ }
+
+ /*
+ * The entry is not in use. Delete it.
+ */
+ *entryp = NULL;
+ DP(DEF_LEVEL, "killing entry %p", entry);
+ INSIST(ISC_LINK_LINKED(entry, plink));
+ result = unlink_entry(adb, entry);
+ free_adbentry(adb, &entry);
+ if (result) {
+ dec_adb_irefcnt(adb);
+ }
+ return (result);
+}
+
+/*
+ * ADB must be locked, and no other locks held.
+ */
+static bool
+cleanup_names(dns_adb_t *adb, int bucket, isc_stdtime_t now) {
+ dns_adbname_t *name;
+ dns_adbname_t *next_name;
+ bool result = false;
+
+ DP(CLEAN_LEVEL, "cleaning name bucket %d", bucket);
+
+ LOCK(&adb->namelocks[bucket]);
+ if (adb->name_sd[bucket]) {
+ UNLOCK(&adb->namelocks[bucket]);
+ return (result);
+ }
+
+ name = ISC_LIST_HEAD(adb->names[bucket]);
+ while (name != NULL) {
+ next_name = ISC_LIST_NEXT(name, plink);
+ INSIST(!result);
+ result = check_expire_namehooks(name, now);
+ if (!result) {
+ result = check_expire_name(&name, now);
+ }
+ name = next_name;
+ }
+ UNLOCK(&adb->namelocks[bucket]);
+ return (result);
+}
+
+/*
+ * ADB must be locked, and no other locks held.
+ */
+static bool
+cleanup_entries(dns_adb_t *adb, int bucket, isc_stdtime_t now) {
+ dns_adbentry_t *entry, *next_entry;
+ bool result = false;
+
+ DP(CLEAN_LEVEL, "cleaning entry bucket %d", bucket);
+
+ LOCK(&adb->entrylocks[bucket]);
+ entry = ISC_LIST_HEAD(adb->entries[bucket]);
+ while (entry != NULL) {
+ next_entry = ISC_LIST_NEXT(entry, plink);
+ INSIST(!result);
+ result = check_expire_entry(adb, &entry, now);
+ entry = next_entry;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+ return (result);
+}
+
+static void
+destroy(dns_adb_t *adb) {
+ adb->magic = 0;
+
+ isc_task_detach(&adb->task);
+ if (adb->excl != NULL) {
+ isc_task_detach(&adb->excl);
+ }
+
+ isc_mutexblock_destroy(adb->entrylocks, adb->nentries);
+ isc_mem_put(adb->mctx, adb->entries,
+ sizeof(*adb->entries) * adb->nentries);
+ isc_mem_put(adb->mctx, adb->deadentries,
+ sizeof(*adb->deadentries) * adb->nentries);
+ isc_mem_put(adb->mctx, adb->entrylocks,
+ sizeof(*adb->entrylocks) * adb->nentries);
+ isc_mem_put(adb->mctx, adb->entry_sd,
+ sizeof(*adb->entry_sd) * adb->nentries);
+ isc_mem_put(adb->mctx, adb->entry_refcnt,
+ sizeof(*adb->entry_refcnt) * adb->nentries);
+
+ isc_mutexblock_destroy(adb->namelocks, adb->nnames);
+ isc_mem_put(adb->mctx, adb->names, sizeof(*adb->names) * adb->nnames);
+ isc_mem_put(adb->mctx, adb->deadnames,
+ sizeof(*adb->deadnames) * adb->nnames);
+ isc_mem_put(adb->mctx, adb->namelocks,
+ sizeof(*adb->namelocks) * adb->nnames);
+ isc_mem_put(adb->mctx, adb->name_sd,
+ sizeof(*adb->name_sd) * adb->nnames);
+ isc_mem_put(adb->mctx, adb->name_refcnt,
+ sizeof(*adb->name_refcnt) * adb->nnames);
+
+ isc_mutex_destroy(&adb->reflock);
+ isc_mutex_destroy(&adb->lock);
+ isc_mutex_destroy(&adb->overmemlock);
+ isc_mutex_destroy(&adb->entriescntlock);
+ isc_mutex_destroy(&adb->namescntlock);
+
+ isc_mem_putanddetach(&adb->mctx, adb, sizeof(dns_adb_t));
+}
+
+/*
+ * Public functions.
+ */
+
+isc_result_t
+dns_adb_create(isc_mem_t *mem, dns_view_t *view, isc_timermgr_t *timermgr,
+ isc_taskmgr_t *taskmgr, dns_adb_t **newadb) {
+ dns_adb_t *adb;
+ isc_result_t result;
+ unsigned int i;
+
+ REQUIRE(mem != NULL);
+ REQUIRE(view != NULL);
+ REQUIRE(timermgr != NULL); /* this is actually unused */
+ REQUIRE(taskmgr != NULL);
+ REQUIRE(newadb != NULL && *newadb == NULL);
+
+ UNUSED(timermgr);
+
+ adb = isc_mem_get(mem, sizeof(dns_adb_t));
+
+ /*
+ * Initialize things here that cannot fail, and especially things
+ * that must be NULL for the error return to work properly.
+ */
+ adb->magic = 0;
+ adb->erefcnt = 1;
+ adb->irefcnt = 0;
+ adb->task = NULL;
+ adb->excl = NULL;
+ adb->mctx = NULL;
+ adb->view = view;
+ adb->taskmgr = taskmgr;
+ adb->next_cleanbucket = 0;
+ ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL, 0, NULL,
+ NULL, NULL, NULL, NULL);
+ adb->cevent_out = false;
+ adb->shutting_down = false;
+ ISC_LIST_INIT(adb->whenshutdown);
+
+ adb->nentries = nbuckets[0];
+ adb->entriescnt = 0;
+ adb->entries = NULL;
+ adb->deadentries = NULL;
+ adb->entry_sd = NULL;
+ adb->entry_refcnt = NULL;
+ adb->entrylocks = NULL;
+ ISC_EVENT_INIT(&adb->growentries, sizeof(adb->growentries), 0, NULL,
+ DNS_EVENT_ADBGROWENTRIES, grow_entries, adb, adb, NULL,
+ NULL);
+ adb->growentries_sent = false;
+
+ adb->quota = 0;
+ adb->atr_freq = 0;
+ adb->atr_low = 0.0;
+ adb->atr_high = 0.0;
+ adb->atr_discount = 0.0;
+
+ adb->nnames = nbuckets[0];
+ adb->namescnt = 0;
+ adb->names = NULL;
+ adb->deadnames = NULL;
+ adb->name_sd = NULL;
+ adb->name_refcnt = NULL;
+ adb->namelocks = NULL;
+ ISC_EVENT_INIT(&adb->grownames, sizeof(adb->grownames), 0, NULL,
+ DNS_EVENT_ADBGROWNAMES, grow_names, adb, adb, NULL,
+ NULL);
+ adb->grownames_sent = false;
+
+ result = isc_taskmgr_excltask(adb->taskmgr, &adb->excl);
+ if (result != ISC_R_SUCCESS) {
+ DP(DEF_LEVEL,
+ "adb: task-exclusive mode unavailable, "
+ "initializing table sizes to %u\n",
+ nbuckets[11]);
+ adb->nentries = nbuckets[11];
+ adb->nnames = nbuckets[11];
+ }
+
+ isc_mem_attach(mem, &adb->mctx);
+
+ isc_mutex_init(&adb->lock);
+ isc_mutex_init(&adb->reflock);
+ isc_mutex_init(&adb->overmemlock);
+ isc_mutex_init(&adb->entriescntlock);
+ isc_mutex_init(&adb->namescntlock);
+
+#define ALLOCENTRY(adb, el) \
+ do { \
+ (adb)->el = isc_mem_get((adb)->mctx, \
+ sizeof(*(adb)->el) * (adb)->nentries); \
+ } while (0)
+ ALLOCENTRY(adb, entries);
+ ALLOCENTRY(adb, deadentries);
+ ALLOCENTRY(adb, entrylocks);
+ ALLOCENTRY(adb, entry_sd);
+ ALLOCENTRY(adb, entry_refcnt);
+#undef ALLOCENTRY
+
+#define ALLOCNAME(adb, el) \
+ do { \
+ (adb)->el = isc_mem_get((adb)->mctx, \
+ sizeof(*(adb)->el) * (adb)->nnames); \
+ } while (0)
+ ALLOCNAME(adb, names);
+ ALLOCNAME(adb, deadnames);
+ ALLOCNAME(adb, namelocks);
+ ALLOCNAME(adb, name_sd);
+ ALLOCNAME(adb, name_refcnt);
+#undef ALLOCNAME
+
+ /*
+ * Initialize the bucket locks for names and elements.
+ * May as well initialize the list heads, too.
+ */
+ isc_mutexblock_init(adb->namelocks, adb->nnames);
+
+ for (i = 0; i < adb->nnames; i++) {
+ ISC_LIST_INIT(adb->names[i]);
+ ISC_LIST_INIT(adb->deadnames[i]);
+ adb->name_sd[i] = false;
+ adb->name_refcnt[i] = 0;
+ adb->irefcnt++;
+ }
+ for (i = 0; i < adb->nentries; i++) {
+ ISC_LIST_INIT(adb->entries[i]);
+ ISC_LIST_INIT(adb->deadentries[i]);
+ adb->entry_sd[i] = false;
+ adb->entry_refcnt[i] = 0;
+ adb->irefcnt++;
+ }
+ isc_mutexblock_init(adb->entrylocks, adb->nentries);
+
+ isc_refcount_init(&adb->ahrefcnt, 0);
+ isc_refcount_init(&adb->nhrefcnt, 0);
+
+ /*
+ * Allocate an internal task.
+ */
+ result = isc_task_create(adb->taskmgr, 0, &adb->task);
+ if (result != ISC_R_SUCCESS) {
+ goto fail2;
+ }
+
+ isc_task_setname(adb->task, "ADB", adb);
+
+ result = isc_stats_create(adb->mctx, &view->adbstats, dns_adbstats_max);
+ if (result != ISC_R_SUCCESS) {
+ goto fail2;
+ }
+
+ set_adbstat(adb, adb->nentries, dns_adbstats_nentries);
+ set_adbstat(adb, adb->nnames, dns_adbstats_nnames);
+
+ /*
+ * Normal return.
+ */
+ adb->magic = DNS_ADB_MAGIC;
+ *newadb = adb;
+ return (ISC_R_SUCCESS);
+
+fail2:
+ if (adb->task != NULL) {
+ isc_task_detach(&adb->task);
+ }
+
+ /* clean up entrylocks */
+ isc_mutexblock_destroy(adb->entrylocks, adb->nentries);
+ isc_mutexblock_destroy(adb->namelocks, adb->nnames);
+
+ if (adb->entries != NULL) {
+ isc_mem_put(adb->mctx, adb->entries,
+ sizeof(*adb->entries) * adb->nentries);
+ }
+ if (adb->deadentries != NULL) {
+ isc_mem_put(adb->mctx, adb->deadentries,
+ sizeof(*adb->deadentries) * adb->nentries);
+ }
+ if (adb->entrylocks != NULL) {
+ isc_mem_put(adb->mctx, adb->entrylocks,
+ sizeof(*adb->entrylocks) * adb->nentries);
+ }
+ if (adb->entry_sd != NULL) {
+ isc_mem_put(adb->mctx, adb->entry_sd,
+ sizeof(*adb->entry_sd) * adb->nentries);
+ }
+ if (adb->entry_refcnt != NULL) {
+ isc_mem_put(adb->mctx, adb->entry_refcnt,
+ sizeof(*adb->entry_refcnt) * adb->nentries);
+ }
+ if (adb->names != NULL) {
+ isc_mem_put(adb->mctx, adb->names,
+ sizeof(*adb->names) * adb->nnames);
+ }
+ if (adb->deadnames != NULL) {
+ isc_mem_put(adb->mctx, adb->deadnames,
+ sizeof(*adb->deadnames) * adb->nnames);
+ }
+ if (adb->namelocks != NULL) {
+ isc_mem_put(adb->mctx, adb->namelocks,
+ sizeof(*adb->namelocks) * adb->nnames);
+ }
+ if (adb->name_sd != NULL) {
+ isc_mem_put(adb->mctx, adb->name_sd,
+ sizeof(*adb->name_sd) * adb->nnames);
+ }
+ if (adb->name_refcnt != NULL) {
+ isc_mem_put(adb->mctx, adb->name_refcnt,
+ sizeof(*adb->name_refcnt) * adb->nnames);
+ }
+
+ isc_mutex_destroy(&adb->namescntlock);
+ isc_mutex_destroy(&adb->entriescntlock);
+ isc_mutex_destroy(&adb->overmemlock);
+ isc_mutex_destroy(&adb->reflock);
+ isc_mutex_destroy(&adb->lock);
+ if (adb->excl != NULL) {
+ isc_task_detach(&adb->excl);
+ }
+ isc_mem_putanddetach(&adb->mctx, adb, sizeof(dns_adb_t));
+
+ return (result);
+}
+
+void
+dns_adb_attach(dns_adb_t *adb, dns_adb_t **adbx) {
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(adbx != NULL && *adbx == NULL);
+
+ inc_adb_erefcnt(adb);
+ *adbx = adb;
+}
+
+void
+dns_adb_detach(dns_adb_t **adbx) {
+ dns_adb_t *adb;
+ bool need_exit_check;
+
+ REQUIRE(adbx != NULL && DNS_ADB_VALID(*adbx));
+
+ adb = *adbx;
+ *adbx = NULL;
+
+ LOCK(&adb->reflock);
+ INSIST(adb->erefcnt > 0);
+ adb->erefcnt--;
+ need_exit_check = (adb->erefcnt == 0 && adb->irefcnt == 0);
+ UNLOCK(&adb->reflock);
+
+ if (need_exit_check) {
+ LOCK(&adb->lock);
+ INSIST(adb->shutting_down);
+ check_exit(adb);
+ UNLOCK(&adb->lock);
+ }
+}
+
+void
+dns_adb_whenshutdown(dns_adb_t *adb, isc_task_t *task, isc_event_t **eventp) {
+ isc_task_t *tclone;
+ isc_event_t *event;
+ bool zeroirefcnt;
+
+ /*
+ * Send '*eventp' to 'task' when 'adb' has shutdown.
+ */
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(eventp != NULL);
+
+ event = *eventp;
+ *eventp = NULL;
+
+ LOCK(&adb->lock);
+ LOCK(&adb->reflock);
+
+ zeroirefcnt = (adb->irefcnt == 0);
+
+ if (adb->shutting_down && zeroirefcnt &&
+ isc_refcount_current(&adb->ahrefcnt) == 0)
+ {
+ /*
+ * We're already shutdown. Send the event.
+ */
+ event->ev_sender = adb;
+ isc_task_send(task, &event);
+ } else {
+ tclone = NULL;
+ isc_task_attach(task, &tclone);
+ event->ev_sender = tclone;
+ ISC_LIST_APPEND(adb->whenshutdown, event, ev_link);
+ }
+
+ UNLOCK(&adb->reflock);
+ UNLOCK(&adb->lock);
+}
+
+static void
+shutdown_stage2(isc_task_t *task, isc_event_t *event) {
+ dns_adb_t *adb;
+
+ UNUSED(task);
+
+ adb = event->ev_arg;
+ INSIST(DNS_ADB_VALID(adb));
+
+ LOCK(&adb->lock);
+ INSIST(adb->shutting_down);
+ adb->cevent_out = false;
+ (void)shutdown_names(adb);
+ (void)shutdown_entries(adb);
+ if (dec_adb_irefcnt(adb)) {
+ check_exit(adb);
+ }
+ UNLOCK(&adb->lock);
+}
+
+void
+dns_adb_shutdown(dns_adb_t *adb) {
+ isc_event_t *event;
+
+ /*
+ * Shutdown 'adb'.
+ */
+
+ LOCK(&adb->lock);
+
+ if (!adb->shutting_down) {
+ adb->shutting_down = true;
+ isc_mem_setwater(adb->mctx, water, adb, 0, 0);
+ /*
+ * Isolate shutdown_names and shutdown_entries calls.
+ */
+ inc_adb_irefcnt(adb);
+ ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL,
+ DNS_EVENT_ADBCONTROL, shutdown_stage2, adb, adb,
+ NULL, NULL);
+ adb->cevent_out = true;
+ event = &adb->cevent;
+ isc_task_send(adb->task, &event);
+ }
+
+ UNLOCK(&adb->lock);
+}
+
+isc_result_t
+dns_adb_createfind(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action,
+ void *arg, const dns_name_t *name, const dns_name_t *qname,
+ dns_rdatatype_t qtype, unsigned int options,
+ isc_stdtime_t now, dns_name_t *target, in_port_t port,
+ unsigned int depth, isc_counter_t *qc,
+ dns_adbfind_t **findp) {
+ dns_adbfind_t *find;
+ dns_adbname_t *adbname;
+ int bucket;
+ bool want_event, start_at_zone, alias, have_address;
+ isc_result_t result;
+ unsigned int wanted_addresses;
+ unsigned int wanted_fetches;
+ unsigned int query_pending;
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ if (task != NULL) {
+ REQUIRE(action != NULL);
+ }
+ REQUIRE(name != NULL);
+ REQUIRE(qname != NULL);
+ REQUIRE(findp != NULL && *findp == NULL);
+ REQUIRE(target == NULL || dns_name_hasbuffer(target));
+
+ REQUIRE((options & DNS_ADBFIND_ADDRESSMASK) != 0);
+
+ result = ISC_R_UNEXPECTED;
+ POST(result);
+ wanted_addresses = (options & DNS_ADBFIND_ADDRESSMASK);
+ wanted_fetches = 0;
+ query_pending = 0;
+ want_event = false;
+ start_at_zone = false;
+ alias = false;
+
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+
+ /*
+ * XXXMLG Move this comment somewhere else!
+ *
+ * Look up the name in our internal database.
+ *
+ * Possibilities: Note that these are not always exclusive.
+ *
+ * No name found. In this case, allocate a new name header and
+ * an initial namehook or two. If any of these allocations
+ * fail, clean up and return ISC_R_NOMEMORY.
+ *
+ * Name found, valid addresses present. Allocate one addrinfo
+ * structure for each found and append it to the linked list
+ * of addresses for this header.
+ *
+ * Name found, queries pending. In this case, if a task was
+ * passed in, allocate a job id, attach it to the name's job
+ * list and remember to tell the caller that there will be
+ * more info coming later.
+ */
+
+ find = new_adbfind(adb);
+ if (find == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ find->port = port;
+
+ /*
+ * Remember what types of addresses we are interested in.
+ */
+ find->options = options;
+ find->flags |= wanted_addresses;
+ if (FIND_WANTEVENT(find)) {
+ REQUIRE(task != NULL);
+ }
+
+ if (isc_log_wouldlog(dns_lctx, DEF_LEVEL)) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ } else {
+ namebuf[0] = 0;
+ }
+
+ /*
+ * Try to see if we know anything about this name at all.
+ */
+ bucket = DNS_ADB_INVALIDBUCKET;
+ adbname = find_name_and_lock(adb, name, find->options, &bucket);
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+ if (adb->name_sd[bucket]) {
+ DP(DEF_LEVEL, "dns_adb_createfind: returning "
+ "ISC_R_SHUTTINGDOWN");
+ RUNTIME_CHECK(!free_adbfind(adb, &find));
+ result = ISC_R_SHUTTINGDOWN;
+ goto out;
+ }
+
+ /*
+ * Nothing found. Allocate a new adbname structure for this name.
+ */
+ if (adbname == NULL) {
+ /*
+ * See if there is any stale name at the end of list, and purge
+ * it if so.
+ */
+ check_stale_name(adb, bucket, now);
+
+ adbname = new_adbname(adb, name);
+ if (adbname == NULL) {
+ RUNTIME_CHECK(!free_adbfind(adb, &find));
+ result = ISC_R_NOMEMORY;
+ goto out;
+ }
+ link_name(adb, bucket, adbname);
+ if (FIND_HINTOK(find)) {
+ adbname->flags |= NAME_HINT_OK;
+ }
+ if (FIND_GLUEOK(find)) {
+ adbname->flags |= NAME_GLUE_OK;
+ }
+ if (FIND_STARTATZONE(find)) {
+ adbname->flags |= NAME_STARTATZONE;
+ }
+ } else {
+ /* Move this name forward in the LRU list */
+ ISC_LIST_UNLINK(adb->names[bucket], adbname, plink);
+ ISC_LIST_PREPEND(adb->names[bucket], adbname, plink);
+ }
+ adbname->last_used = now;
+
+ /*
+ * Expire old entries, etc.
+ */
+ RUNTIME_CHECK(!check_expire_namehooks(adbname, now));
+
+ /*
+ * Do we know that the name is an alias?
+ */
+ if (!EXPIRE_OK(adbname->expire_target, now)) {
+ /*
+ * Yes, it is.
+ */
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: name %s (%p) is an alias (cached)",
+ namebuf, adbname);
+ alias = true;
+ goto post_copy;
+ }
+
+ /*
+ * Try to populate the name from the database and/or
+ * start fetches. First try looking for an A record
+ * in the database.
+ */
+ if (!NAME_HAS_V4(adbname) && EXPIRE_OK(adbname->expire_v4, now) &&
+ WANT_INET(wanted_addresses))
+ {
+ result = dbfind_name(adbname, now, dns_rdatatype_a);
+ if (result == ISC_R_SUCCESS) {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: found A for name %s (%p) in db",
+ namebuf, adbname);
+ goto v6;
+ }
+
+ /*
+ * Did we get a CNAME or DNAME?
+ */
+ if (result == DNS_R_ALIAS) {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: name %s (%p) is an alias",
+ namebuf, adbname);
+ alias = true;
+ goto post_copy;
+ }
+
+ /*
+ * If the name doesn't exist at all, don't bother with
+ * v6 queries; they won't work.
+ *
+ * If the name does exist but we didn't get our data, go
+ * ahead and try AAAA.
+ *
+ * If the result is neither of these, try a fetch for A.
+ */
+ if (NXDOMAIN_RESULT(result)) {
+ goto fetch;
+ } else if (NXRRSET_RESULT(result)) {
+ goto v6;
+ }
+
+ if (!NAME_FETCH_V4(adbname)) {
+ wanted_fetches |= DNS_ADBFIND_INET;
+ }
+ }
+
+v6:
+ if (!NAME_HAS_V6(adbname) && EXPIRE_OK(adbname->expire_v6, now) &&
+ WANT_INET6(wanted_addresses))
+ {
+ result = dbfind_name(adbname, now, dns_rdatatype_aaaa);
+ if (result == ISC_R_SUCCESS) {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: found AAAA for name %s (%p)",
+ namebuf, adbname);
+ goto fetch;
+ }
+
+ /*
+ * Did we get a CNAME or DNAME?
+ */
+ if (result == DNS_R_ALIAS) {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: name %s (%p) is an alias",
+ namebuf, adbname);
+ alias = true;
+ goto post_copy;
+ }
+
+ /*
+ * Listen to negative cache hints, and don't start
+ * another query.
+ */
+ if (NCACHE_RESULT(result) || AUTH_NX(result)) {
+ goto fetch;
+ }
+
+ if (!NAME_FETCH_V6(adbname)) {
+ wanted_fetches |= DNS_ADBFIND_INET6;
+ }
+ }
+
+fetch:
+ if ((WANT_INET(wanted_addresses) && NAME_HAS_V4(adbname)) ||
+ (WANT_INET6(wanted_addresses) && NAME_HAS_V6(adbname)))
+ {
+ have_address = true;
+ } else {
+ have_address = false;
+ }
+ if (wanted_fetches != 0 && !(FIND_AVOIDFETCHES(find) && have_address) &&
+ !FIND_NOFETCH(find))
+ {
+ /*
+ * We're missing at least one address family. Either the
+ * caller hasn't instructed us to avoid fetches, or we don't
+ * know anything about any of the address families that would
+ * be acceptable so we have to launch fetches.
+ */
+
+ if (FIND_STARTATZONE(find)) {
+ start_at_zone = true;
+ }
+
+ /*
+ * Start V4.
+ */
+ if (WANT_INET(wanted_fetches) &&
+ fetch_name(adbname, start_at_zone, depth, qc,
+ dns_rdatatype_a) == ISC_R_SUCCESS)
+ {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: "
+ "started A fetch for name %s (%p)",
+ namebuf, adbname);
+ }
+
+ /*
+ * Start V6.
+ */
+ if (WANT_INET6(wanted_fetches) &&
+ fetch_name(adbname, start_at_zone, depth, qc,
+ dns_rdatatype_aaaa) == ISC_R_SUCCESS)
+ {
+ DP(DEF_LEVEL,
+ "dns_adb_createfind: "
+ "started AAAA fetch for name %s (%p)",
+ namebuf, adbname);
+ }
+ }
+
+ /*
+ * Run through the name and copy out the bits we are
+ * interested in.
+ */
+ copy_namehook_lists(adb, find, qname, qtype, adbname, now);
+
+post_copy:
+ if (NAME_FETCH_V4(adbname)) {
+ query_pending |= DNS_ADBFIND_INET;
+ }
+ if (NAME_FETCH_V6(adbname)) {
+ query_pending |= DNS_ADBFIND_INET6;
+ }
+
+ /*
+ * Attach to the name's query list if there are queries
+ * already running, and we have been asked to.
+ */
+ want_event = true;
+ if (!FIND_WANTEVENT(find)) {
+ want_event = false;
+ }
+ if (FIND_WANTEMPTYEVENT(find) && FIND_HAS_ADDRS(find)) {
+ want_event = false;
+ }
+ if ((wanted_addresses & query_pending) == 0) {
+ want_event = false;
+ }
+ if (alias) {
+ want_event = false;
+ }
+ if (want_event) {
+ find->adbname = adbname;
+ find->name_bucket = bucket;
+ bool empty = ISC_LIST_EMPTY(adbname->finds);
+ ISC_LIST_APPEND(adbname->finds, find, plink);
+ find->query_pending = (query_pending & wanted_addresses);
+ find->flags &= ~DNS_ADBFIND_ADDRESSMASK;
+ find->flags |= (find->query_pending & DNS_ADBFIND_ADDRESSMASK);
+ DP(DEF_LEVEL,
+ "createfind: attaching find %p to adbname "
+ "%p %d",
+ find, adbname, empty);
+ } else {
+ /*
+ * Remove the flag so the caller knows there will never
+ * be an event, and set internal flags to fake that
+ * the event was sent and freed, so dns_adb_destroyfind() will
+ * do the right thing.
+ */
+ find->query_pending = (query_pending & wanted_addresses);
+ find->options &= ~DNS_ADBFIND_WANTEVENT;
+ find->flags |= (FIND_EVENT_SENT | FIND_EVENT_FREED);
+ find->flags &= ~DNS_ADBFIND_ADDRESSMASK;
+ }
+
+ find->partial_result |= (adbname->partial_result & wanted_addresses);
+ if (alias) {
+ if (target != NULL) {
+ dns_name_copynf(&adbname->target, target);
+ }
+ result = DNS_R_ALIAS;
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ /*
+ * Copy out error flags from the name structure into the find.
+ */
+ find->result_v4 = find_err_map[adbname->fetch_err];
+ find->result_v6 = find_err_map[adbname->fetch6_err];
+
+out:
+ if (find != NULL) {
+ *findp = find;
+
+ if (want_event) {
+ isc_task_t *taskp;
+
+ INSIST((find->flags & DNS_ADBFIND_ADDRESSMASK) != 0);
+ taskp = NULL;
+ isc_task_attach(task, &taskp);
+ find->event.ev_sender = taskp;
+ find->event.ev_action = action;
+ find->event.ev_arg = arg;
+ }
+ }
+
+ UNLOCK(&adb->namelocks[bucket]);
+
+ return (result);
+}
+
+void
+dns_adb_destroyfind(dns_adbfind_t **findp) {
+ dns_adbfind_t *find;
+ dns_adbentry_t *entry;
+ dns_adbaddrinfo_t *ai;
+ int bucket;
+ dns_adb_t *adb;
+ bool overmem;
+
+ REQUIRE(findp != NULL && DNS_ADBFIND_VALID(*findp));
+ find = *findp;
+ *findp = NULL;
+
+ LOCK(&find->lock);
+
+ DP(DEF_LEVEL, "dns_adb_destroyfind on find %p", find);
+
+ adb = find->adb;
+ REQUIRE(DNS_ADB_VALID(adb));
+
+ REQUIRE(FIND_EVENTFREED(find));
+
+ bucket = find->name_bucket;
+ INSIST(bucket == DNS_ADB_INVALIDBUCKET);
+
+ UNLOCK(&find->lock);
+
+ /*
+ * The find doesn't exist on any list, and nothing is locked.
+ * Return the find to the memory pool, and decrement the adb's
+ * reference count.
+ */
+ overmem = isc_mem_isovermem(adb->mctx);
+ ai = ISC_LIST_HEAD(find->list);
+ while (ai != NULL) {
+ ISC_LIST_UNLINK(find->list, ai, publink);
+ entry = ai->entry;
+ ai->entry = NULL;
+ INSIST(DNS_ADBENTRY_VALID(entry));
+ RUNTIME_CHECK(!dec_entry_refcnt(adb, overmem, entry, true));
+ free_adbaddrinfo(adb, &ai);
+ ai = ISC_LIST_HEAD(find->list);
+ }
+
+ /*
+ * WARNING: The find is freed with the adb locked. This is done
+ * to avoid a race condition where we free the find, some other
+ * thread tests to see if it should be destroyed, detects it should
+ * be, destroys it, and then we try to lock it for our check, but the
+ * lock is destroyed.
+ */
+ LOCK(&adb->lock);
+ if (free_adbfind(adb, &find)) {
+ check_exit(adb);
+ }
+ UNLOCK(&adb->lock);
+}
+
+void
+dns_adb_cancelfind(dns_adbfind_t *find) {
+ isc_event_t *ev;
+ isc_task_t *task;
+ dns_adb_t *adb;
+ int bucket;
+ int unlock_bucket;
+
+ LOCK(&find->lock);
+
+ DP(DEF_LEVEL, "dns_adb_cancelfind on find %p", find);
+
+ adb = find->adb;
+ REQUIRE(DNS_ADB_VALID(adb));
+
+ REQUIRE(!FIND_EVENTFREED(find));
+ REQUIRE(FIND_WANTEVENT(find));
+
+ bucket = find->name_bucket;
+ if (bucket == DNS_ADB_INVALIDBUCKET) {
+ goto cleanup;
+ }
+
+ /*
+ * We need to get the adbname's lock to unlink the find.
+ */
+ unlock_bucket = bucket;
+ violate_locking_hierarchy(&find->lock, &adb->namelocks[unlock_bucket]);
+ bucket = find->name_bucket;
+ if (bucket != DNS_ADB_INVALIDBUCKET) {
+ ISC_LIST_UNLINK(find->adbname->finds, find, plink);
+ find->adbname = NULL;
+ find->name_bucket = DNS_ADB_INVALIDBUCKET;
+ }
+ UNLOCK(&adb->namelocks[unlock_bucket]);
+ bucket = DNS_ADB_INVALIDBUCKET;
+ POST(bucket);
+
+cleanup:
+
+ if (!FIND_EVENTSENT(find)) {
+ ev = &find->event;
+ task = ev->ev_sender;
+ ev->ev_sender = find;
+ ev->ev_type = DNS_EVENT_ADBCANCELED;
+ ev->ev_destroy = event_free;
+ ev->ev_destroy_arg = find;
+ find->result_v4 = ISC_R_CANCELED;
+ find->result_v6 = ISC_R_CANCELED;
+
+ DP(DEF_LEVEL, "sending event %p to task %p for find %p", ev,
+ task, find);
+
+ isc_task_sendanddetach(&task, (isc_event_t **)&ev);
+ }
+
+ UNLOCK(&find->lock);
+}
+
+void
+dns_adb_dump(dns_adb_t *adb, FILE *f) {
+ unsigned int i;
+ isc_stdtime_t now;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(f != NULL);
+
+ /*
+ * Lock the adb itself, lock all the name buckets, then lock all
+ * the entry buckets. This should put the adb into a state where
+ * nothing can change, so we can iterate through everything and
+ * print at our leisure.
+ */
+
+ LOCK(&adb->lock);
+ isc_stdtime_get(&now);
+
+ for (i = 0; i < adb->nnames; i++) {
+ RUNTIME_CHECK(!cleanup_names(adb, i, now));
+ }
+ for (i = 0; i < adb->nentries; i++) {
+ RUNTIME_CHECK(!cleanup_entries(adb, i, now));
+ }
+
+ dump_adb(adb, f, false, now);
+ UNLOCK(&adb->lock);
+}
+
+static void
+dump_ttl(FILE *f, const char *legend, isc_stdtime_t value, isc_stdtime_t now) {
+ if (value == INT_MAX) {
+ return;
+ }
+ fprintf(f, " [%s TTL %d]", legend, (int)(value - now));
+}
+
+static void
+dump_adb(dns_adb_t *adb, FILE *f, bool debug, isc_stdtime_t now) {
+ dns_adbname_t *name;
+ dns_adbentry_t *entry;
+
+ fprintf(f, ";\n; Address database dump\n;\n");
+ fprintf(f, "; [edns success/4096 timeout/1432 timeout/1232 timeout/"
+ "512 timeout]\n");
+ fprintf(f, "; [plain success/timeout]\n;\n");
+ if (debug) {
+ LOCK(&adb->reflock);
+ fprintf(f,
+ "; addr %p, erefcnt %u, irefcnt %u, finds out "
+ "%" PRIuFAST32 "\n",
+ adb, adb->erefcnt, adb->irefcnt,
+ isc_refcount_current(&adb->nhrefcnt));
+ UNLOCK(&adb->reflock);
+ }
+
+/*
+ * In TSAN mode we need to lock the locks individually, as TSAN
+ * can't handle more than 64 locks locked by one thread.
+ * In regular mode we want a consistent dump so we need to
+ * lock everything.
+ */
+#ifndef __SANITIZE_THREAD__
+ for (size_t i = 0; i < adb->nnames; i++) {
+ LOCK(&adb->namelocks[i]);
+ }
+ for (size_t i = 0; i < adb->nentries; i++) {
+ LOCK(&adb->entrylocks[i]);
+ }
+#endif /* ifndef __SANITIZE_THREAD__ */
+
+ /*
+ * Dump the names
+ */
+ for (size_t i = 0; i < adb->nnames; i++) {
+#ifdef __SANITIZE_THREAD__
+ LOCK(&adb->namelocks[i]);
+#endif /* ifdef __SANITIZE_THREAD__ */
+ name = ISC_LIST_HEAD(adb->names[i]);
+ if (name == NULL) {
+#ifdef __SANITIZE_THREAD__
+ UNLOCK(&adb->namelocks[i]);
+#endif /* ifdef __SANITIZE_THREAD__ */
+ continue;
+ }
+ if (debug) {
+ fprintf(f, "; bucket %zu\n", i);
+ }
+ for (; name != NULL; name = ISC_LIST_NEXT(name, plink)) {
+ if (debug) {
+ fprintf(f, "; name %p (flags %08x)\n", name,
+ name->flags);
+ }
+ fprintf(f, "; ");
+ print_dns_name(f, &name->name);
+ if (dns_name_countlabels(&name->target) > 0) {
+ fprintf(f, " alias ");
+ print_dns_name(f, &name->target);
+ }
+
+ dump_ttl(f, "v4", name->expire_v4, now);
+ dump_ttl(f, "v6", name->expire_v6, now);
+ dump_ttl(f, "target", name->expire_target, now);
+
+ fprintf(f, " [v4 %s] [v6 %s]",
+ errnames[name->fetch_err],
+ errnames[name->fetch6_err]);
+
+ fprintf(f, "\n");
+
+ print_namehook_list(f, "v4", adb, &name->v4, debug,
+ now);
+ print_namehook_list(f, "v6", adb, &name->v6, debug,
+ now);
+
+ if (debug) {
+ print_fetch_list(f, name);
+ print_find_list(f, name);
+ }
+ }
+#ifdef __SANITIZE_THREAD__
+ UNLOCK(&adb->namelocks[i]);
+#endif /* ifdef __SANITIZE_THREAD__ */
+ }
+
+ fprintf(f, ";\n; Unassociated entries\n;\n");
+
+ for (size_t i = 0; i < adb->nentries; i++) {
+#ifdef __SANITIZE_THREAD__
+ LOCK(&adb->entrylocks[i]);
+#endif /* ifdef __SANITIZE_THREAD__ */
+ entry = ISC_LIST_HEAD(adb->entries[i]);
+ while (entry != NULL) {
+ if (entry->nh == 0) {
+ dump_entry(f, adb, entry, debug, now);
+ }
+ entry = ISC_LIST_NEXT(entry, plink);
+ }
+#ifdef __SANITIZE_THREAD__
+ UNLOCK(&adb->entrylocks[i]);
+#endif /* ifdef __SANITIZE_THREAD__ */
+ }
+
+#ifndef __SANITIZE_THREAD__
+ /*
+ * Unlock everything
+ */
+ for (ssize_t i = adb->nentries - 1; i >= 0; i--) {
+ UNLOCK(&adb->entrylocks[i]);
+ }
+ for (ssize_t i = adb->nnames - 1; i >= 0; i--) {
+ UNLOCK(&adb->namelocks[i]);
+ }
+#endif /* ifndef __SANITIZE_THREAD__ */
+}
+
+static void
+dump_entry(FILE *f, dns_adb_t *adb, dns_adbentry_t *entry, bool debug,
+ isc_stdtime_t now) {
+ char addrbuf[ISC_NETADDR_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ isc_netaddr_t netaddr;
+ dns_adblameinfo_t *li;
+
+ isc_netaddr_fromsockaddr(&netaddr, &entry->sockaddr);
+ isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
+
+ if (debug) {
+ fprintf(f, ";\t%p: refcnt %u\n", entry, entry->refcnt);
+ }
+
+ fprintf(f,
+ ";\t%s [srtt %u] [flags %08x] [edns %u/%u/%u/%u/%u] "
+ "[plain %u/%u]",
+ addrbuf, entry->srtt, entry->flags, entry->edns, entry->to4096,
+ entry->to1432, entry->to1232, entry->to512, entry->plain,
+ entry->plainto);
+ if (entry->udpsize != 0U) {
+ fprintf(f, " [udpsize %u]", entry->udpsize);
+ }
+ if (entry->cookie != NULL) {
+ unsigned int i;
+ fprintf(f, " [cookie=");
+ for (i = 0; i < entry->cookielen; i++) {
+ fprintf(f, "%02x", entry->cookie[i]);
+ }
+ fprintf(f, "]");
+ }
+ if (entry->expires != 0) {
+ fprintf(f, " [ttl %d]", (int)(entry->expires - now));
+ }
+
+ if (adb != NULL && adb->quota != 0 && adb->atr_freq != 0) {
+ uint_fast32_t quota = atomic_load_relaxed(&entry->quota);
+ fprintf(f, " [atr %0.2f] [quota %" PRIuFAST32 "]", entry->atr,
+ quota);
+ }
+
+ fprintf(f, "\n");
+ for (li = ISC_LIST_HEAD(entry->lameinfo); li != NULL;
+ li = ISC_LIST_NEXT(li, plink))
+ {
+ fprintf(f, ";\t\t");
+ print_dns_name(f, &li->qname);
+ dns_rdatatype_format(li->qtype, typebuf, sizeof(typebuf));
+ fprintf(f, " %s [lame TTL %d]\n", typebuf,
+ (int)(li->lame_timer - now));
+ }
+}
+
+void
+dns_adb_dumpfind(dns_adbfind_t *find, FILE *f) {
+ char tmp[512];
+ const char *tmpp;
+ dns_adbaddrinfo_t *ai;
+ isc_sockaddr_t *sa;
+
+ /*
+ * Not used currently, in the API Just In Case we
+ * want to dump out the name and/or entries too.
+ */
+
+ LOCK(&find->lock);
+
+ fprintf(f, ";Find %p\n", find);
+ fprintf(f, ";\tqpending %08x partial %08x options %08x flags %08x\n",
+ find->query_pending, find->partial_result, find->options,
+ find->flags);
+ fprintf(f, ";\tname_bucket %d, name %p, event sender %p\n",
+ find->name_bucket, find->adbname, find->event.ev_sender);
+
+ ai = ISC_LIST_HEAD(find->list);
+ if (ai != NULL) {
+ fprintf(f, "\tAddresses:\n");
+ }
+ while (ai != NULL) {
+ sa = &ai->sockaddr;
+ switch (sa->type.sa.sa_family) {
+ case AF_INET:
+ tmpp = inet_ntop(AF_INET, &sa->type.sin.sin_addr, tmp,
+ sizeof(tmp));
+ break;
+ case AF_INET6:
+ tmpp = inet_ntop(AF_INET6, &sa->type.sin6.sin6_addr,
+ tmp, sizeof(tmp));
+ break;
+ default:
+ tmpp = "UnkFamily";
+ }
+
+ if (tmpp == NULL) {
+ tmpp = "BadAddress";
+ }
+
+ fprintf(f,
+ "\t\tentry %p, flags %08x"
+ " srtt %u addr %s\n",
+ ai->entry, ai->flags, ai->srtt, tmpp);
+
+ ai = ISC_LIST_NEXT(ai, publink);
+ }
+
+ UNLOCK(&find->lock);
+}
+
+static void
+print_dns_name(FILE *f, const dns_name_t *name) {
+ char buf[DNS_NAME_FORMATSIZE];
+
+ INSIST(f != NULL);
+
+ dns_name_format(name, buf, sizeof(buf));
+ fprintf(f, "%s", buf);
+}
+
+static void
+print_namehook_list(FILE *f, const char *legend, dns_adb_t *adb,
+ dns_adbnamehooklist_t *list, bool debug,
+ isc_stdtime_t now) {
+ dns_adbnamehook_t *nh;
+
+ for (nh = ISC_LIST_HEAD(*list); nh != NULL;
+ nh = ISC_LIST_NEXT(nh, plink))
+ {
+ if (debug) {
+ fprintf(f, ";\tHook(%s) %p\n", legend, nh);
+ }
+#ifdef __SANITIZE_THREAD__
+ LOCK(&adb->entrylocks[nh->entry->lock_bucket]);
+#endif
+ dump_entry(f, adb, nh->entry, debug, now);
+#ifdef __SANITIZE_THREAD__
+ UNLOCK(&adb->entrylocks[nh->entry->lock_bucket]);
+#endif
+ }
+}
+
+static void
+print_fetch(FILE *f, dns_adbfetch_t *ft, const char *type) {
+ fprintf(f, "\t\tFetch(%s): %p -> { fetch %p }\n", type, ft, ft->fetch);
+}
+
+static void
+print_fetch_list(FILE *f, dns_adbname_t *n) {
+ if (NAME_FETCH_A(n)) {
+ print_fetch(f, n->fetch_a, "A");
+ }
+ if (NAME_FETCH_AAAA(n)) {
+ print_fetch(f, n->fetch_aaaa, "AAAA");
+ }
+}
+
+static void
+print_find_list(FILE *f, dns_adbname_t *name) {
+ dns_adbfind_t *find;
+
+ find = ISC_LIST_HEAD(name->finds);
+ while (find != NULL) {
+ dns_adb_dumpfind(find, f);
+ find = ISC_LIST_NEXT(find, plink);
+ }
+}
+
+static isc_result_t
+dbfind_name(dns_adbname_t *adbname, isc_stdtime_t now, dns_rdatatype_t rdtype) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+ dns_adb_t *adb;
+ dns_fixedname_t foundname;
+ dns_name_t *fname;
+
+ INSIST(DNS_ADBNAME_VALID(adbname));
+ adb = adbname->adb;
+ INSIST(DNS_ADB_VALID(adb));
+ INSIST(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
+
+ fname = dns_fixedname_initname(&foundname);
+ dns_rdataset_init(&rdataset);
+
+ if (rdtype == dns_rdatatype_a) {
+ adbname->fetch_err = FIND_ERR_UNEXPECTED;
+ } else {
+ adbname->fetch6_err = FIND_ERR_UNEXPECTED;
+ }
+
+ /*
+ * We need to specify whether to search static-stub zones (if
+ * configured) depending on whether this is a "start at zone" lookup,
+ * i.e., whether it's a "bailiwick" glue. If it's bailiwick (in which
+ * case NAME_STARTATZONE is set) we need to stop the search at any
+ * matching static-stub zone without looking into the cache to honor
+ * the configuration on which server we should send queries to.
+ */
+ result = dns_view_find(adb->view, &adbname->name, rdtype, now,
+ NAME_GLUEOK(adbname) ? DNS_DBFIND_GLUEOK : 0,
+ NAME_HINTOK(adbname),
+ ((adbname->flags & NAME_STARTATZONE) != 0), NULL,
+ NULL, fname, &rdataset, NULL);
+
+ /* XXXVIX this switch statement is too sparse to gen a jump table. */
+ switch (result) {
+ case DNS_R_GLUE:
+ case DNS_R_HINT:
+ case ISC_R_SUCCESS:
+ /*
+ * Found in the database. Even if we can't copy out
+ * any information, return success, or else a fetch
+ * will be made, which will only make things worse.
+ */
+ if (rdtype == dns_rdatatype_a) {
+ adbname->fetch_err = FIND_ERR_SUCCESS;
+ } else {
+ adbname->fetch6_err = FIND_ERR_SUCCESS;
+ }
+ result = import_rdataset(adbname, &rdataset, now);
+ break;
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NXRRSET:
+ /*
+ * We're authoritative and the data doesn't exist.
+ * Make up a negative cache entry so we don't ask again
+ * for a while.
+ *
+ * XXXRTH What time should we use? I'm putting in 30 seconds
+ * for now.
+ */
+ if (rdtype == dns_rdatatype_a) {
+ adbname->expire_v4 = now + 30;
+ DP(NCACHE_LEVEL,
+ "adb name %p: Caching auth negative entry for A",
+ adbname);
+ if (result == DNS_R_NXDOMAIN) {
+ adbname->fetch_err = FIND_ERR_NXDOMAIN;
+ } else {
+ adbname->fetch_err = FIND_ERR_NXRRSET;
+ }
+ } else {
+ DP(NCACHE_LEVEL,
+ "adb name %p: Caching auth negative entry for AAAA",
+ adbname);
+ adbname->expire_v6 = now + 30;
+ if (result == DNS_R_NXDOMAIN) {
+ adbname->fetch6_err = FIND_ERR_NXDOMAIN;
+ } else {
+ adbname->fetch6_err = FIND_ERR_NXRRSET;
+ }
+ }
+ break;
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ /*
+ * We found a negative cache entry. Pull the TTL from it
+ * so we won't ask again for a while.
+ */
+ rdataset.ttl = ttlclamp(rdataset.ttl);
+ if (rdtype == dns_rdatatype_a) {
+ adbname->expire_v4 = rdataset.ttl + now;
+ if (result == DNS_R_NCACHENXDOMAIN) {
+ adbname->fetch_err = FIND_ERR_NXDOMAIN;
+ } else {
+ adbname->fetch_err = FIND_ERR_NXRRSET;
+ }
+ DP(NCACHE_LEVEL,
+ "adb name %p: Caching negative entry for A (ttl %u)",
+ adbname, rdataset.ttl);
+ } else {
+ DP(NCACHE_LEVEL,
+ "adb name %p: Caching negative entry for AAAA (ttl "
+ "%u)",
+ adbname, rdataset.ttl);
+ adbname->expire_v6 = rdataset.ttl + now;
+ if (result == DNS_R_NCACHENXDOMAIN) {
+ adbname->fetch6_err = FIND_ERR_NXDOMAIN;
+ } else {
+ adbname->fetch6_err = FIND_ERR_NXRRSET;
+ }
+ }
+ break;
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ /*
+ * Clear the hint and glue flags, so this will match
+ * more often.
+ */
+ adbname->flags &= ~(DNS_ADBFIND_GLUEOK | DNS_ADBFIND_HINTOK);
+
+ rdataset.ttl = ttlclamp(rdataset.ttl);
+ clean_target(adb, &adbname->target);
+ adbname->expire_target = INT_MAX;
+ result = set_target(adb, &adbname->name, fname, &rdataset,
+ &adbname->target);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_ALIAS;
+ DP(NCACHE_LEVEL, "adb name %p: caching alias target",
+ adbname);
+ adbname->expire_target = rdataset.ttl + now;
+ }
+ if (rdtype == dns_rdatatype_a) {
+ adbname->fetch_err = FIND_ERR_SUCCESS;
+ } else {
+ adbname->fetch6_err = FIND_ERR_SUCCESS;
+ }
+ break;
+ }
+
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ return (result);
+}
+
+static void
+fetch_callback(isc_task_t *task, isc_event_t *ev) {
+ dns_fetchevent_t *dev;
+ dns_adbname_t *name;
+ dns_adb_t *adb;
+ dns_adbfetch_t *fetch;
+ int bucket;
+ isc_eventtype_t ev_status;
+ isc_stdtime_t now;
+ isc_result_t result;
+ unsigned int address_type;
+ bool want_check_exit = false;
+
+ UNUSED(task);
+
+ INSIST(ev->ev_type == DNS_EVENT_FETCHDONE);
+ dev = (dns_fetchevent_t *)ev;
+ name = ev->ev_arg;
+ INSIST(DNS_ADBNAME_VALID(name));
+ adb = name->adb;
+ INSIST(DNS_ADB_VALID(adb));
+
+ bucket = name->lock_bucket;
+ LOCK(&adb->namelocks[bucket]);
+
+ INSIST(NAME_FETCH_A(name) || NAME_FETCH_AAAA(name));
+ address_type = 0;
+ if (NAME_FETCH_A(name) && (name->fetch_a->fetch == dev->fetch)) {
+ address_type = DNS_ADBFIND_INET;
+ fetch = name->fetch_a;
+ name->fetch_a = NULL;
+ } else if (NAME_FETCH_AAAA(name) &&
+ (name->fetch_aaaa->fetch == dev->fetch))
+ {
+ address_type = DNS_ADBFIND_INET6;
+ fetch = name->fetch_aaaa;
+ name->fetch_aaaa = NULL;
+ } else {
+ fetch = NULL;
+ }
+
+ INSIST(address_type != 0 && fetch != NULL);
+
+ dns_resolver_destroyfetch(&fetch->fetch);
+ dev->fetch = NULL;
+
+ ev_status = DNS_EVENT_ADBNOMOREADDRESSES;
+
+ /*
+ * Cleanup things we don't care about.
+ */
+ if (dev->node != NULL) {
+ dns_db_detachnode(dev->db, &dev->node);
+ }
+ if (dev->db != NULL) {
+ dns_db_detach(&dev->db);
+ }
+
+ /*
+ * If this name is marked as dead, clean up, throwing away
+ * potentially good data.
+ */
+ if (NAME_DEAD(name)) {
+ free_adbfetch(adb, &fetch);
+ isc_event_free(&ev);
+
+ want_check_exit = kill_name(&name, DNS_EVENT_ADBCANCELED);
+
+ UNLOCK(&adb->namelocks[bucket]);
+
+ if (want_check_exit) {
+ LOCK(&adb->lock);
+ check_exit(adb);
+ UNLOCK(&adb->lock);
+ }
+
+ return;
+ }
+
+ isc_stdtime_get(&now);
+
+ /*
+ * If we got a negative cache response, remember it.
+ */
+ if (NCACHE_RESULT(dev->result)) {
+ dev->rdataset->ttl = ttlclamp(dev->rdataset->ttl);
+ if (address_type == DNS_ADBFIND_INET) {
+ DP(NCACHE_LEVEL,
+ "adb fetch name %p: "
+ "caching negative entry for A (ttl %u)",
+ name, dev->rdataset->ttl);
+ name->expire_v4 = ISC_MIN(name->expire_v4,
+ dev->rdataset->ttl + now);
+ if (dev->result == DNS_R_NCACHENXDOMAIN) {
+ name->fetch_err = FIND_ERR_NXDOMAIN;
+ } else {
+ name->fetch_err = FIND_ERR_NXRRSET;
+ }
+ inc_stats(adb, dns_resstatscounter_gluefetchv4fail);
+ } else {
+ DP(NCACHE_LEVEL,
+ "adb fetch name %p: "
+ "caching negative entry for AAAA (ttl %u)",
+ name, dev->rdataset->ttl);
+ name->expire_v6 = ISC_MIN(name->expire_v6,
+ dev->rdataset->ttl + now);
+ if (dev->result == DNS_R_NCACHENXDOMAIN) {
+ name->fetch6_err = FIND_ERR_NXDOMAIN;
+ } else {
+ name->fetch6_err = FIND_ERR_NXRRSET;
+ }
+ inc_stats(adb, dns_resstatscounter_gluefetchv6fail);
+ }
+ goto out;
+ }
+
+ /*
+ * Handle CNAME/DNAME.
+ */
+ if (dev->result == DNS_R_CNAME || dev->result == DNS_R_DNAME) {
+ dev->rdataset->ttl = ttlclamp(dev->rdataset->ttl);
+ clean_target(adb, &name->target);
+ name->expire_target = INT_MAX;
+ result = set_target(adb, &name->name,
+ dns_fixedname_name(&dev->foundname),
+ dev->rdataset, &name->target);
+ if (result == ISC_R_SUCCESS) {
+ DP(NCACHE_LEVEL,
+ "adb fetch name %p: caching alias target", name);
+ name->expire_target = dev->rdataset->ttl + now;
+ }
+ goto check_result;
+ }
+
+ /*
+ * Did we get back junk? If so, and there are no more fetches
+ * sitting out there, tell all the finds about it.
+ */
+ if (dev->result != ISC_R_SUCCESS) {
+ char buf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(&name->name, buf, sizeof(buf));
+ DP(DEF_LEVEL, "adb: fetch of '%s' %s failed: %s", buf,
+ address_type == DNS_ADBFIND_INET ? "A" : "AAAA",
+ dns_result_totext(dev->result));
+ /*
+ * Don't record a failure unless this is the initial
+ * fetch of a chain.
+ */
+ if (fetch->depth > 1) {
+ goto out;
+ }
+ /* XXXMLG Don't pound on bad servers. */
+ if (address_type == DNS_ADBFIND_INET) {
+ name->expire_v4 = ISC_MIN(name->expire_v4, now + 10);
+ name->fetch_err = FIND_ERR_FAILURE;
+ inc_stats(adb, dns_resstatscounter_gluefetchv4fail);
+ } else {
+ name->expire_v6 = ISC_MIN(name->expire_v6, now + 10);
+ name->fetch6_err = FIND_ERR_FAILURE;
+ inc_stats(adb, dns_resstatscounter_gluefetchv6fail);
+ }
+ goto out;
+ }
+
+ /*
+ * We got something potentially useful.
+ */
+ result = import_rdataset(name, &fetch->rdataset, now);
+
+check_result:
+ if (result == ISC_R_SUCCESS) {
+ ev_status = DNS_EVENT_ADBMOREADDRESSES;
+ if (address_type == DNS_ADBFIND_INET) {
+ name->fetch_err = FIND_ERR_SUCCESS;
+ } else {
+ name->fetch6_err = FIND_ERR_SUCCESS;
+ }
+ }
+
+out:
+ free_adbfetch(adb, &fetch);
+ isc_event_free(&ev);
+
+ clean_finds_at_name(name, ev_status, address_type);
+
+ UNLOCK(&adb->namelocks[bucket]);
+}
+
+static isc_result_t
+fetch_name(dns_adbname_t *adbname, bool start_at_zone, unsigned int depth,
+ isc_counter_t *qc, dns_rdatatype_t type) {
+ isc_result_t result;
+ dns_adbfetch_t *fetch = NULL;
+ dns_adb_t *adb;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ dns_rdataset_t rdataset;
+ dns_rdataset_t *nameservers;
+ unsigned int options;
+
+ INSIST(DNS_ADBNAME_VALID(adbname));
+ adb = adbname->adb;
+ INSIST(DNS_ADB_VALID(adb));
+
+ INSIST((type == dns_rdatatype_a && !NAME_FETCH_V4(adbname)) ||
+ (type == dns_rdatatype_aaaa && !NAME_FETCH_V6(adbname)));
+
+ adbname->fetch_err = FIND_ERR_NOTFOUND;
+
+ name = NULL;
+ nameservers = NULL;
+ dns_rdataset_init(&rdataset);
+
+ options = DNS_FETCHOPT_NOVALIDATE;
+ if (start_at_zone) {
+ DP(ENTER_LEVEL, "fetch_name: starting at zone for name %p",
+ adbname);
+ name = dns_fixedname_initname(&fixed);
+ result = dns_view_findzonecut(adb->view, &adbname->name, name,
+ NULL, 0, 0, true, false,
+ &rdataset, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_HINT) {
+ goto cleanup;
+ }
+ nameservers = &rdataset;
+ options |= DNS_FETCHOPT_UNSHARED;
+ }
+
+ fetch = new_adbfetch(adb);
+ if (fetch == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ fetch->depth = depth;
+
+ /*
+ * We're not minimizing this query, as nothing user-related should
+ * be leaked here.
+ * However, if we'd ever want to change it we'd have to modify
+ * createfetch to find deepest cached name when we're providing
+ * domain and nameservers.
+ */
+ result = dns_resolver_createfetch(
+ adb->view->resolver, &adbname->name, type, name, nameservers,
+ NULL, NULL, 0, options, depth, qc, adb->task, fetch_callback,
+ adbname, &fetch->rdataset, NULL, &fetch->fetch);
+ if (result != ISC_R_SUCCESS) {
+ DP(ENTER_LEVEL, "fetch_name: createfetch failed with %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ if (type == dns_rdatatype_a) {
+ adbname->fetch_a = fetch;
+ inc_stats(adb, dns_resstatscounter_gluefetchv4);
+ } else {
+ adbname->fetch_aaaa = fetch;
+ inc_stats(adb, dns_resstatscounter_gluefetchv6);
+ }
+ fetch = NULL; /* Keep us from cleaning this up below. */
+
+cleanup:
+ if (fetch != NULL) {
+ free_adbfetch(adb, &fetch);
+ }
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ return (result);
+}
+
+/*
+ * XXXMLG Needs to take a find argument and an address info, no zone or adb,
+ * since these can be extracted from the find itself.
+ */
+isc_result_t
+dns_adb_marklame(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ const dns_name_t *qname, dns_rdatatype_t qtype,
+ isc_stdtime_t expire_time) {
+ dns_adblameinfo_t *li;
+ int bucket;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+ REQUIRE(qname != NULL);
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+ li = ISC_LIST_HEAD(addr->entry->lameinfo);
+ while (li != NULL &&
+ (li->qtype != qtype || !dns_name_equal(qname, &li->qname)))
+ {
+ li = ISC_LIST_NEXT(li, plink);
+ }
+ if (li != NULL) {
+ if (expire_time > li->lame_timer) {
+ li->lame_timer = expire_time;
+ }
+ goto unlock;
+ }
+ li = new_adblameinfo(adb, qname, qtype);
+ if (li == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto unlock;
+ }
+
+ li->lame_timer = expire_time;
+
+ ISC_LIST_PREPEND(addr->entry->lameinfo, li, plink);
+unlock:
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ return (result);
+}
+
+void
+dns_adb_adjustsrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int rtt,
+ unsigned int factor) {
+ int bucket;
+ isc_stdtime_t now = 0;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+ REQUIRE(factor <= 10);
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ if (addr->entry->expires == 0 || factor == DNS_ADB_RTTADJAGE) {
+ isc_stdtime_get(&now);
+ }
+ adjustsrtt(addr, rtt, factor, now);
+
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+void
+dns_adb_agesrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, isc_stdtime_t now) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ adjustsrtt(addr, 0, DNS_ADB_RTTADJAGE, now);
+
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+static void
+adjustsrtt(dns_adbaddrinfo_t *addr, unsigned int rtt, unsigned int factor,
+ isc_stdtime_t now) {
+ uint64_t new_srtt;
+
+ if (factor == DNS_ADB_RTTADJAGE) {
+ if (addr->entry->lastage != now) {
+ new_srtt = addr->entry->srtt;
+ new_srtt <<= 9;
+ new_srtt -= addr->entry->srtt;
+ new_srtt >>= 9;
+ addr->entry->lastage = now;
+ } else {
+ new_srtt = addr->entry->srtt;
+ }
+ } else {
+ new_srtt = ((uint64_t)addr->entry->srtt / 10 * factor) +
+ ((uint64_t)rtt / 10 * (10 - factor));
+ }
+
+ addr->entry->srtt = (unsigned int)new_srtt;
+ addr->srtt = (unsigned int)new_srtt;
+
+ if (addr->entry->expires == 0) {
+ addr->entry->expires = now + ADB_ENTRY_WINDOW;
+ }
+}
+
+void
+dns_adb_changeflags(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int bits,
+ unsigned int mask) {
+ int bucket;
+ isc_stdtime_t now;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ REQUIRE((bits & ENTRY_IS_DEAD) == 0);
+ REQUIRE((mask & ENTRY_IS_DEAD) == 0);
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ addr->entry->flags = (addr->entry->flags & ~mask) | (bits & mask);
+ if (addr->entry->expires == 0) {
+ isc_stdtime_get(&now);
+ addr->entry->expires = now + ADB_ENTRY_WINDOW;
+ }
+
+ /*
+ * Note that we do not update the other bits in addr->flags with
+ * the most recent values from addr->entry->flags.
+ */
+ addr->flags = (addr->flags & ~mask) | (bits & mask);
+
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+/*
+ * The polynomial backoff curve (10000 / ((10 + n) / 10)^(3/2)) <0..99> drops
+ * fairly aggressively at first, then slows down and tails off at around 2-3%.
+ *
+ * These will be used to make quota adjustments.
+ */
+static int quota_adj[] = {
+ 10000, 8668, 7607, 6747, 6037, 5443, 4941, 4512, 4141, 3818, 3536,
+ 3286, 3065, 2867, 2690, 2530, 2385, 2254, 2134, 2025, 1925, 1832,
+ 1747, 1668, 1595, 1527, 1464, 1405, 1350, 1298, 1250, 1205, 1162,
+ 1121, 1083, 1048, 1014, 981, 922, 894, 868, 843, 820, 797,
+ 775, 755, 735, 716, 698, 680, 664, 648, 632, 618, 603,
+ 590, 577, 564, 552, 540, 529, 518, 507, 497, 487, 477,
+ 468, 459, 450, 442, 434, 426, 418, 411, 404, 397, 390,
+ 383, 377, 370, 364, 358, 353, 347, 342, 336, 331, 326,
+ 321, 316, 312, 307, 303, 298, 294, 290, 286, 282, 278
+};
+
+#define QUOTA_ADJ_SIZE (sizeof(quota_adj) / sizeof(quota_adj[0]))
+
+/*
+ * Caller must hold adbentry lock
+ */
+static void
+maybe_adjust_quota(dns_adb_t *adb, dns_adbaddrinfo_t *addr, bool timeout) {
+ double tr;
+
+ UNUSED(adb);
+
+ if (adb->quota == 0 || adb->atr_freq == 0) {
+ return;
+ }
+
+ if (timeout) {
+ addr->entry->timeouts++;
+ }
+
+ if (addr->entry->completed++ <= adb->atr_freq) {
+ return;
+ }
+
+ /*
+ * Calculate an exponential rolling average of the timeout ratio
+ *
+ * XXX: Integer arithmetic might be better than floating point
+ */
+ tr = (double)addr->entry->timeouts / addr->entry->completed;
+ addr->entry->timeouts = addr->entry->completed = 0;
+ INSIST(addr->entry->atr >= 0.0);
+ INSIST(addr->entry->atr <= 1.0);
+ INSIST(adb->atr_discount >= 0.0);
+ INSIST(adb->atr_discount <= 1.0);
+ addr->entry->atr *= 1.0 - adb->atr_discount;
+ addr->entry->atr += tr * adb->atr_discount;
+ addr->entry->atr = ISC_CLAMP(addr->entry->atr, 0.0, 1.0);
+
+ if (addr->entry->atr < adb->atr_low && addr->entry->mode > 0) {
+ uint_fast32_t new_quota =
+ adb->quota * quota_adj[--addr->entry->mode] / 10000;
+ atomic_store_release(&addr->entry->quota,
+ ISC_MAX(1, new_quota));
+ log_quota(addr->entry,
+ "atr %0.2f, quota increased to %" PRIuFAST32,
+ addr->entry->atr, new_quota);
+ } else if (addr->entry->atr > adb->atr_high &&
+ addr->entry->mode < (QUOTA_ADJ_SIZE - 1))
+ {
+ uint_fast32_t new_quota =
+ adb->quota * quota_adj[++addr->entry->mode] / 10000;
+ atomic_store_release(&addr->entry->quota,
+ ISC_MAX(1, new_quota));
+ log_quota(addr->entry,
+ "atr %0.2f, quota decreased to %" PRIuFAST32,
+ addr->entry->atr, new_quota);
+ }
+}
+
+#define EDNSTOS 3U
+bool
+dns_adb_noedns(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ int bucket;
+ bool noedns = false;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ if (addr->entry->edns == 0U &&
+ (addr->entry->plain > EDNSTOS || addr->entry->to4096 > EDNSTOS))
+ {
+ if (((addr->entry->plain + addr->entry->to4096) & 0x3f) != 0) {
+ noedns = true;
+ } else {
+ /*
+ * Increment plain so we don't get stuck.
+ */
+ addr->entry->plain++;
+ if (addr->entry->plain == 0xff) {
+ addr->entry->edns >>= 1;
+ addr->entry->to4096 >>= 1;
+ addr->entry->to1432 >>= 1;
+ addr->entry->to1232 >>= 1;
+ addr->entry->to512 >>= 1;
+ addr->entry->plain >>= 1;
+ addr->entry->plainto >>= 1;
+ }
+ }
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+ return (noedns);
+}
+
+void
+dns_adb_plainresponse(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ maybe_adjust_quota(adb, addr, false);
+
+ addr->entry->plain++;
+ if (addr->entry->plain == 0xff) {
+ addr->entry->edns >>= 1;
+ addr->entry->to4096 >>= 1;
+ addr->entry->to1432 >>= 1;
+ addr->entry->to1232 >>= 1;
+ addr->entry->to512 >>= 1;
+ addr->entry->plain >>= 1;
+ addr->entry->plainto >>= 1;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+void
+dns_adb_timeout(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ maybe_adjust_quota(adb, addr, true);
+
+ /*
+ * If we have not had a successful query then clear all
+ * edns timeout information.
+ */
+ if (addr->entry->edns == 0 && addr->entry->plain == 0) {
+ addr->entry->to512 = 0;
+ addr->entry->to1232 = 0;
+ addr->entry->to1432 = 0;
+ addr->entry->to4096 = 0;
+ } else {
+ addr->entry->to512 >>= 1;
+ addr->entry->to1232 >>= 1;
+ addr->entry->to1432 >>= 1;
+ addr->entry->to4096 >>= 1;
+ }
+
+ addr->entry->plainto++;
+ if (addr->entry->plainto == 0xff) {
+ addr->entry->edns >>= 1;
+ addr->entry->plain >>= 1;
+ addr->entry->plainto >>= 1;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+void
+dns_adb_ednsto(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int size) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ maybe_adjust_quota(adb, addr, true);
+
+ if (size <= 512U) {
+ if (addr->entry->to512 <= EDNSTOS) {
+ addr->entry->to512++;
+ addr->entry->to1232++;
+ addr->entry->to1432++;
+ addr->entry->to4096++;
+ }
+ } else if (size <= 1232U) {
+ if (addr->entry->to1232 <= EDNSTOS) {
+ addr->entry->to1232++;
+ addr->entry->to1432++;
+ addr->entry->to4096++;
+ }
+ } else if (size <= 1432U) {
+ if (addr->entry->to1432 <= EDNSTOS) {
+ addr->entry->to1432++;
+ addr->entry->to4096++;
+ }
+ } else {
+ if (addr->entry->to4096 <= EDNSTOS) {
+ addr->entry->to4096++;
+ }
+ }
+
+ if (addr->entry->to4096 == 0xff) {
+ addr->entry->edns >>= 1;
+ addr->entry->to4096 >>= 1;
+ addr->entry->to1432 >>= 1;
+ addr->entry->to1232 >>= 1;
+ addr->entry->to512 >>= 1;
+ addr->entry->plain >>= 1;
+ addr->entry->plainto >>= 1;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+void
+dns_adb_setudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int size) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+ if (size < 512U) {
+ size = 512U;
+ }
+ if (size > addr->entry->udpsize) {
+ addr->entry->udpsize = size;
+ }
+
+ maybe_adjust_quota(adb, addr, false);
+
+ addr->entry->edns++;
+ if (addr->entry->edns == 0xff) {
+ addr->entry->edns >>= 1;
+ addr->entry->to4096 >>= 1;
+ addr->entry->to1432 >>= 1;
+ addr->entry->to1232 >>= 1;
+ addr->entry->to512 >>= 1;
+ addr->entry->plain >>= 1;
+ addr->entry->plainto >>= 1;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+unsigned int
+dns_adb_getudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ int bucket;
+ unsigned int size;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+ size = addr->entry->udpsize;
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ return (size);
+}
+
+unsigned int
+dns_adb_probesize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, int lookups) {
+ int bucket;
+ unsigned int size;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+ if (addr->entry->to1232 > EDNSTOS || lookups >= 2) {
+ size = 512;
+ } else if (addr->entry->to1432 > EDNSTOS || lookups >= 1) {
+ size = 1232;
+ } else if (addr->entry->to4096 > EDNSTOS) {
+ size = 1432;
+ } else {
+ size = 4096;
+ }
+ /*
+ * Don't shrink probe size below what we have seen due to multiple
+ * lookups.
+ */
+ if (lookups > 0 && size < addr->entry->udpsize &&
+ addr->entry->udpsize < 4096)
+ {
+ size = addr->entry->udpsize;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ return (size);
+}
+
+void
+dns_adb_setcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ const unsigned char *cookie, size_t len) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ if (addr->entry->cookie != NULL &&
+ (cookie == NULL || len != addr->entry->cookielen))
+ {
+ isc_mem_put(adb->mctx, addr->entry->cookie,
+ addr->entry->cookielen);
+ addr->entry->cookie = NULL;
+ addr->entry->cookielen = 0;
+ }
+
+ if (addr->entry->cookie == NULL && cookie != NULL && len != 0U) {
+ addr->entry->cookie = isc_mem_get(adb->mctx, len);
+ addr->entry->cookielen = (uint16_t)len;
+ }
+
+ if (addr->entry->cookie != NULL) {
+ memmove(addr->entry->cookie, cookie, len);
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+}
+
+size_t
+dns_adb_getcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ unsigned char *cookie, size_t len) {
+ int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+ if (cookie != NULL && addr->entry->cookie != NULL &&
+ len >= addr->entry->cookielen)
+ {
+ memmove(cookie, addr->entry->cookie, addr->entry->cookielen);
+ len = addr->entry->cookielen;
+ } else {
+ len = 0;
+ }
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ return (len);
+}
+
+isc_result_t
+dns_adb_findaddrinfo(dns_adb_t *adb, const isc_sockaddr_t *sa,
+ dns_adbaddrinfo_t **addrp, isc_stdtime_t now) {
+ int bucket;
+ dns_adbentry_t *entry;
+ dns_adbaddrinfo_t *addr;
+ isc_result_t result;
+ in_port_t port;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(addrp != NULL && *addrp == NULL);
+
+ UNUSED(now);
+
+ result = ISC_R_SUCCESS;
+ bucket = DNS_ADB_INVALIDBUCKET;
+ entry = find_entry_and_lock(adb, sa, &bucket, now);
+ INSIST(bucket != DNS_ADB_INVALIDBUCKET);
+ if (adb->entry_sd[bucket]) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto unlock;
+ }
+ if (entry == NULL) {
+ /*
+ * We don't know anything about this address.
+ */
+ entry = new_adbentry(adb);
+ if (entry == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto unlock;
+ }
+ entry->sockaddr = *sa;
+ link_entry(adb, bucket, entry);
+ DP(ENTER_LEVEL, "findaddrinfo: new entry %p", entry);
+ } else {
+ DP(ENTER_LEVEL, "findaddrinfo: found entry %p", entry);
+ }
+
+ port = isc_sockaddr_getport(sa);
+ addr = new_adbaddrinfo(adb, entry, port);
+ if (addr == NULL) {
+ result = ISC_R_NOMEMORY;
+ } else {
+ inc_entry_refcnt(adb, entry, false);
+ *addrp = addr;
+ }
+
+unlock:
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ return (result);
+}
+
+void
+dns_adb_freeaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **addrp) {
+ dns_adbaddrinfo_t *addr;
+ dns_adbentry_t *entry;
+ int bucket;
+ isc_stdtime_t now;
+ bool want_check_exit = false;
+ bool overmem;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(addrp != NULL);
+ addr = *addrp;
+ *addrp = NULL;
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+ entry = addr->entry;
+ REQUIRE(DNS_ADBENTRY_VALID(entry));
+
+ overmem = isc_mem_isovermem(adb->mctx);
+
+ bucket = addr->entry->lock_bucket;
+ LOCK(&adb->entrylocks[bucket]);
+
+ if (entry->expires == 0) {
+ isc_stdtime_get(&now);
+ entry->expires = now + ADB_ENTRY_WINDOW;
+ }
+
+ want_check_exit = dec_entry_refcnt(adb, overmem, entry, false);
+
+ UNLOCK(&adb->entrylocks[bucket]);
+
+ addr->entry = NULL;
+ free_adbaddrinfo(adb, &addr);
+
+ if (want_check_exit) {
+ LOCK(&adb->lock);
+ check_exit(adb);
+ UNLOCK(&adb->lock);
+ }
+}
+
+void
+dns_adb_flush(dns_adb_t *adb) {
+ unsigned int i;
+
+ INSIST(DNS_ADB_VALID(adb));
+
+ LOCK(&adb->lock);
+
+ /*
+ * Call our cleanup routines.
+ */
+ for (i = 0; i < adb->nnames; i++) {
+ RUNTIME_CHECK(!cleanup_names(adb, i, INT_MAX));
+ }
+ for (i = 0; i < adb->nentries; i++) {
+ RUNTIME_CHECK(!cleanup_entries(adb, i, INT_MAX));
+ }
+
+#ifdef DUMP_ADB_AFTER_CLEANING
+ dump_adb(adb, stdout, true, INT_MAX);
+#endif /* ifdef DUMP_ADB_AFTER_CLEANING */
+
+ UNLOCK(&adb->lock);
+}
+
+void
+dns_adb_flushname(dns_adb_t *adb, const dns_name_t *name) {
+ dns_adbname_t *adbname;
+ dns_adbname_t *nextname;
+ unsigned int bucket;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(name != NULL);
+
+ LOCK(&adb->lock);
+ bucket = dns_name_hash(name, false) % adb->nnames;
+ LOCK(&adb->namelocks[bucket]);
+ adbname = ISC_LIST_HEAD(adb->names[bucket]);
+ while (adbname != NULL) {
+ nextname = ISC_LIST_NEXT(adbname, plink);
+ if (!NAME_DEAD(adbname) && dns_name_equal(name, &adbname->name))
+ {
+ RUNTIME_CHECK(
+ !kill_name(&adbname, DNS_EVENT_ADBCANCELED));
+ }
+ adbname = nextname;
+ }
+ UNLOCK(&adb->namelocks[bucket]);
+ UNLOCK(&adb->lock);
+}
+
+void
+dns_adb_flushnames(dns_adb_t *adb, const dns_name_t *name) {
+ dns_adbname_t *adbname, *nextname;
+ unsigned int i;
+
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(name != NULL);
+
+ LOCK(&adb->lock);
+ for (i = 0; i < adb->nnames; i++) {
+ LOCK(&adb->namelocks[i]);
+ adbname = ISC_LIST_HEAD(adb->names[i]);
+ while (adbname != NULL) {
+ bool ret;
+ nextname = ISC_LIST_NEXT(adbname, plink);
+ if (!NAME_DEAD(adbname) &&
+ dns_name_issubdomain(&adbname->name, name))
+ {
+ ret = kill_name(&adbname,
+ DNS_EVENT_ADBCANCELED);
+ RUNTIME_CHECK(!ret);
+ }
+ adbname = nextname;
+ }
+ UNLOCK(&adb->namelocks[i]);
+ }
+ UNLOCK(&adb->lock);
+}
+
+static void
+water(void *arg, int mark) {
+ /*
+ * We're going to change the way to handle overmem condition: use
+ * isc_mem_isovermem() instead of storing the state via this callback,
+ * since the latter way tends to cause race conditions.
+ * To minimize the change, and in case we re-enable the callback
+ * approach, however, keep this function at the moment.
+ */
+
+ dns_adb_t *adb = arg;
+ bool overmem = (mark == ISC_MEM_HIWATER);
+
+ REQUIRE(DNS_ADB_VALID(adb));
+
+ DP(ISC_LOG_DEBUG(1), "adb reached %s water mark",
+ overmem ? "high" : "low");
+}
+
+void
+dns_adb_setadbsize(dns_adb_t *adb, size_t size) {
+ size_t hiwater, lowater;
+
+ INSIST(DNS_ADB_VALID(adb));
+
+ if (size != 0U && size < DNS_ADB_MINADBSIZE) {
+ size = DNS_ADB_MINADBSIZE;
+ }
+
+ hiwater = size - (size >> 3); /* Approximately 7/8ths. */
+ lowater = size - (size >> 2); /* Approximately 3/4ths. */
+
+ if (size == 0U || hiwater == 0U || lowater == 0U) {
+ isc_mem_setwater(adb->mctx, water, adb, 0, 0);
+ } else {
+ isc_mem_setwater(adb->mctx, water, adb, hiwater, lowater);
+ }
+}
+
+void
+dns_adb_setquota(dns_adb_t *adb, uint32_t quota, uint32_t freq, double low,
+ double high, double discount) {
+ REQUIRE(DNS_ADB_VALID(adb));
+
+ adb->quota = quota;
+ adb->atr_freq = freq;
+ adb->atr_low = low;
+ adb->atr_high = high;
+ adb->atr_discount = discount;
+}
+
+bool
+dns_adbentry_overquota(dns_adbentry_t *entry) {
+ REQUIRE(DNS_ADBENTRY_VALID(entry));
+
+ uint_fast32_t quota = atomic_load_relaxed(&entry->quota);
+ uint_fast32_t active = atomic_load_acquire(&entry->active);
+
+ return (quota != 0 && active >= quota);
+}
+
+void
+dns_adb_beginudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ INSIST(atomic_fetch_add_relaxed(&addr->entry->active, 1) != UINT32_MAX);
+}
+
+void
+dns_adb_endudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
+ REQUIRE(DNS_ADB_VALID(adb));
+ REQUIRE(DNS_ADBADDRINFO_VALID(addr));
+
+ INSIST(atomic_fetch_sub_release(&addr->entry->active, 1) != 0);
+}
diff --git a/lib/dns/badcache.c b/lib/dns/badcache.c
new file mode 100644
index 0000000..92116c0
--- /dev/null
+++ b/lib/dns/badcache.c
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/platform.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/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_name_t name;
+};
+
+static void
+badcache_resize(dns_badcache_t *bc, isc_time_t *now);
+
+isc_result_t
+dns_badcache_init(isc_mem_t *mctx, unsigned int size, dns_badcache_t **bcp) {
+ dns_badcache_t *bc = NULL;
+ unsigned int i;
+
+ REQUIRE(bcp != NULL && *bcp == NULL);
+ REQUIRE(mctx != NULL);
+
+ bc = isc_mem_get(mctx, sizeof(dns_badcache_t));
+ memset(bc, 0, sizeof(dns_badcache_t));
+
+ isc_mem_attach(mctx, &bc->mctx);
+ isc_rwlock_init(&bc->lock, 0, 0);
+
+ bc->table = isc_mem_get(bc->mctx, sizeof(*bc->table) * size);
+ bc->tlocks = isc_mem_get(bc->mctx, sizeof(isc_mutex_t) * size);
+ for (i = 0; i < size; i++) {
+ isc_mutex_init(&bc->tlocks[i]);
+ }
+ bc->size = bc->minsize = size;
+ memset(bc->table, 0, bc->size * sizeof(dns_bcentry_t *));
+
+ atomic_init(&bc->count, 0);
+ atomic_init(&bc->sweep, 0);
+ bc->magic = BADCACHE_MAGIC;
+
+ *bcp = bc;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_badcache_destroy(dns_badcache_t **bcp) {
+ dns_badcache_t *bc;
+ unsigned int i;
+
+ REQUIRE(bcp != NULL && *bcp != NULL);
+ bc = *bcp;
+ *bcp = NULL;
+
+ dns_badcache_flush(bc);
+
+ bc->magic = 0;
+ isc_rwlock_destroy(&bc->lock);
+ for (i = 0; i < bc->size; i++) {
+ isc_mutex_destroy(&bc->tlocks[i]);
+ }
+ isc_mem_put(bc->mctx, bc->table, sizeof(dns_bcentry_t *) * bc->size);
+ isc_mem_put(bc->mctx, bc->tlocks, sizeof(isc_mutex_t) * bc->size);
+ isc_mem_putanddetach(&bc->mctx, bc, sizeof(dns_badcache_t));
+}
+
+static void
+badcache_resize(dns_badcache_t *bc, isc_time_t *now) {
+ dns_bcentry_t **newtable, *bad, *next;
+ isc_mutex_t *newlocks;
+ unsigned int newsize, i;
+ bool grow;
+
+ RWLOCK(&bc->lock, isc_rwlocktype_write);
+
+ /*
+ * XXXWPK we will have a thundering herd problem here,
+ * as all threads will wait on the RWLOCK when there's
+ * a need to resize badcache.
+ * However, it happens so rarely it should not be a
+ * performance issue. This is because we double the
+ * size every time we grow it, and we don't shrink
+ * unless the number of entries really shrunk. In a
+ * high load situation, the number of badcache entries
+ * will eventually stabilize.
+ */
+ if (atomic_load_relaxed(&bc->count) > bc->size * 8) {
+ grow = true;
+ } else if (atomic_load_relaxed(&bc->count) < bc->size * 2 &&
+ bc->size > bc->minsize)
+ {
+ grow = false;
+ } else {
+ /* Someone resized it already, bail. */
+ RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+ return;
+ }
+
+ if (grow) {
+ newsize = bc->size * 2 + 1;
+ } else {
+ newsize = (bc->size - 1) / 2;
+#ifdef __clang_analyzer__
+ /*
+ * XXXWPK there's a bug in clang static analyzer -
+ * `value % newsize` is considered undefined even though
+ * we check if newsize is larger than 0. This helps.
+ */
+ newsize += 1;
+#endif
+ }
+ RUNTIME_CHECK(newsize > 0);
+
+ newtable = isc_mem_get(bc->mctx, sizeof(dns_bcentry_t *) * newsize);
+ memset(newtable, 0, sizeof(dns_bcentry_t *) * newsize);
+
+ newlocks = isc_mem_get(bc->mctx, sizeof(isc_mutex_t) * newsize);
+
+ /* Copy existing mutexes */
+ for (i = 0; i < newsize && i < bc->size; i++) {
+ newlocks[i] = bc->tlocks[i];
+ }
+ /* Initialize additional mutexes if we're growing */
+ for (i = bc->size; i < newsize; i++) {
+ isc_mutex_init(&newlocks[i]);
+ }
+ /* Destroy extra mutexes if we're shrinking */
+ for (i = newsize; i < bc->size; i++) {
+ isc_mutex_destroy(&bc->tlocks[i]);
+ }
+
+ for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
+ for (bad = bc->table[i]; bad != NULL; bad = next) {
+ next = bad->next;
+ if (isc_time_compare(&bad->expire, now) < 0) {
+ isc_mem_put(bc->mctx, bad,
+ sizeof(*bad) + bad->name.length);
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ } else {
+ bad->next = newtable[bad->hashval % newsize];
+ newtable[bad->hashval % newsize] = bad;
+ }
+ }
+ bc->table[i] = NULL;
+ }
+
+ isc_mem_put(bc->mctx, bc->tlocks, sizeof(isc_mutex_t) * bc->size);
+ bc->tlocks = newlocks;
+
+ isc_mem_put(bc->mctx, bc->table, sizeof(*bc->table) * bc->size);
+ bc->size = newsize;
+ bc->table = newtable;
+
+ RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+}
+
+void
+dns_badcache_add(dns_badcache_t *bc, const dns_name_t *name,
+ dns_rdatatype_t type, bool update, uint32_t flags,
+ isc_time_t *expire) {
+ isc_result_t result;
+ unsigned int hashval, hash;
+ dns_bcentry_t *bad, *prev, *next;
+ isc_time_t now;
+ bool resize = false;
+
+ REQUIRE(VALID_BADCACHE(bc));
+ REQUIRE(name != NULL);
+ REQUIRE(expire != NULL);
+
+ RWLOCK(&bc->lock, isc_rwlocktype_read);
+
+ result = isc_time_now(&now);
+ if (result != ISC_R_SUCCESS) {
+ isc_time_settoepoch(&now);
+ }
+
+ hashval = dns_name_hash(name, false);
+ hash = hashval % bc->size;
+ LOCK(&bc->tlocks[hash]);
+ prev = NULL;
+ for (bad = bc->table[hash]; bad != NULL; bad = next) {
+ next = bad->next;
+ if (bad->type == type && dns_name_equal(name, &bad->name)) {
+ if (update) {
+ bad->expire = *expire;
+ bad->flags = flags;
+ }
+ break;
+ }
+ if (isc_time_compare(&bad->expire, &now) < 0) {
+ if (prev == NULL) {
+ bc->table[hash] = bad->next;
+ } else {
+ prev->next = bad->next;
+ }
+ isc_mem_put(bc->mctx, bad,
+ sizeof(*bad) + bad->name.length);
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ } else {
+ prev = bad;
+ }
+ }
+
+ if (bad == NULL) {
+ isc_buffer_t buffer;
+ bad = isc_mem_get(bc->mctx, sizeof(*bad) + name->length);
+ bad->type = type;
+ bad->hashval = hashval;
+ bad->expire = *expire;
+ bad->flags = flags;
+ isc_buffer_init(&buffer, bad + 1, name->length);
+ dns_name_init(&bad->name, NULL);
+ dns_name_copy(name, &bad->name, &buffer);
+ bad->next = bc->table[hash];
+ bc->table[hash] = bad;
+ unsigned count = atomic_fetch_add_relaxed(&bc->count, 1);
+ if ((count > bc->size * 8) ||
+ (count < bc->size * 2 && bc->size > bc->minsize))
+ {
+ resize = true;
+ }
+ } else {
+ bad->expire = *expire;
+ }
+
+ UNLOCK(&bc->tlocks[hash]);
+ RWUNLOCK(&bc->lock, isc_rwlocktype_read);
+ if (resize) {
+ badcache_resize(bc, &now);
+ }
+}
+
+bool
+dns_badcache_find(dns_badcache_t *bc, const dns_name_t *name,
+ dns_rdatatype_t type, uint32_t *flagp, isc_time_t *now) {
+ dns_bcentry_t *bad, *prev, *next;
+ bool answer = false;
+ unsigned int i;
+ unsigned int hash;
+
+ REQUIRE(VALID_BADCACHE(bc));
+ REQUIRE(name != NULL);
+ REQUIRE(now != NULL);
+
+ RWLOCK(&bc->lock, isc_rwlocktype_read);
+
+ /*
+ * XXXMUKS: dns_name_equal() is expensive as it does a
+ * octet-by-octet comparison, and it can be made better in two
+ * ways here. First, lowercase the names (use
+ * dns_name_downcase() instead of dns_name_copy() in
+ * dns_badcache_add()) so that dns_name_caseequal() can be used
+ * which the compiler will emit as SIMD instructions. Second,
+ * don't put multiple copies of the same name in the chain (or
+ * multiple names will have to be matched for equality), but use
+ * name->link to store the type specific part.
+ */
+
+ if (atomic_load_relaxed(&bc->count) == 0) {
+ goto skip;
+ }
+
+ hash = dns_name_hash(name, false) % bc->size;
+ prev = NULL;
+ LOCK(&bc->tlocks[hash]);
+ for (bad = bc->table[hash]; bad != NULL; bad = next) {
+ next = bad->next;
+ /*
+ * Search the hash list. Clean out expired records as we go.
+ */
+ if (isc_time_compare(&bad->expire, now) < 0) {
+ if (prev != NULL) {
+ prev->next = bad->next;
+ } else {
+ bc->table[hash] = bad->next;
+ }
+
+ isc_mem_put(bc->mctx, bad,
+ sizeof(*bad) + bad->name.length);
+ atomic_fetch_sub(&bc->count, 1);
+ continue;
+ }
+ if (bad->type == type && dns_name_equal(name, &bad->name)) {
+ if (flagp != NULL) {
+ *flagp = bad->flags;
+ }
+ answer = true;
+ break;
+ }
+ prev = bad;
+ }
+ UNLOCK(&bc->tlocks[hash]);
+skip:
+
+ /*
+ * Slow sweep to clean out stale records.
+ */
+ i = atomic_fetch_add(&bc->sweep, 1) % bc->size;
+ if (isc_mutex_trylock(&bc->tlocks[i]) == ISC_R_SUCCESS) {
+ bad = bc->table[i];
+ if (bad != NULL && isc_time_compare(&bad->expire, now) < 0) {
+ bc->table[i] = bad->next;
+ isc_mem_put(bc->mctx, bad,
+ sizeof(*bad) + bad->name.length);
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ }
+ UNLOCK(&bc->tlocks[i]);
+ }
+
+ RWUNLOCK(&bc->lock, isc_rwlocktype_read);
+ return (answer);
+}
+
+void
+dns_badcache_flush(dns_badcache_t *bc) {
+ dns_bcentry_t *entry, *next;
+ unsigned int i;
+
+ RWLOCK(&bc->lock, isc_rwlocktype_write);
+ REQUIRE(VALID_BADCACHE(bc));
+
+ for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
+ for (entry = bc->table[i]; entry != NULL; entry = next) {
+ next = entry->next;
+ isc_mem_put(bc->mctx, entry,
+ sizeof(*entry) + entry->name.length);
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ }
+ bc->table[i] = NULL;
+ }
+ RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+}
+
+void
+dns_badcache_flushname(dns_badcache_t *bc, const dns_name_t *name) {
+ dns_bcentry_t *bad, *prev, *next;
+ isc_result_t result;
+ isc_time_t now;
+ unsigned int hash;
+
+ REQUIRE(VALID_BADCACHE(bc));
+ REQUIRE(name != NULL);
+
+ RWLOCK(&bc->lock, isc_rwlocktype_read);
+
+ result = isc_time_now(&now);
+ if (result != ISC_R_SUCCESS) {
+ isc_time_settoepoch(&now);
+ }
+ hash = dns_name_hash(name, false) % bc->size;
+ LOCK(&bc->tlocks[hash]);
+ prev = NULL;
+ for (bad = bc->table[hash]; bad != NULL; bad = next) {
+ int n;
+ next = bad->next;
+ n = isc_time_compare(&bad->expire, &now);
+ if (n < 0 || dns_name_equal(name, &bad->name)) {
+ if (prev == NULL) {
+ bc->table[hash] = bad->next;
+ } else {
+ prev->next = bad->next;
+ }
+
+ isc_mem_put(bc->mctx, bad,
+ sizeof(*bad) + bad->name.length);
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ } else {
+ prev = bad;
+ }
+ }
+ UNLOCK(&bc->tlocks[hash]);
+
+ RWUNLOCK(&bc->lock, isc_rwlocktype_read);
+}
+
+void
+dns_badcache_flushtree(dns_badcache_t *bc, const dns_name_t *name) {
+ dns_bcentry_t *bad, *prev, *next;
+ unsigned int i;
+ int n;
+ isc_time_t now;
+ isc_result_t result;
+
+ REQUIRE(VALID_BADCACHE(bc));
+ REQUIRE(name != NULL);
+
+ /*
+ * We write lock the tree to avoid relocking every node
+ * individually.
+ */
+ RWLOCK(&bc->lock, isc_rwlocktype_write);
+
+ result = isc_time_now(&now);
+ if (result != ISC_R_SUCCESS) {
+ isc_time_settoepoch(&now);
+ }
+
+ for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
+ prev = NULL;
+ for (bad = bc->table[i]; bad != NULL; bad = next) {
+ next = bad->next;
+ n = isc_time_compare(&bad->expire, &now);
+ if (n < 0 || dns_name_issubdomain(&bad->name, name)) {
+ if (prev == NULL) {
+ bc->table[i] = bad->next;
+ } else {
+ prev->next = bad->next;
+ }
+
+ isc_mem_put(bc->mctx, bad,
+ sizeof(*bad) + bad->name.length);
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ } else {
+ prev = bad;
+ }
+ }
+ }
+
+ RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+}
+
+void
+dns_badcache_print(dns_badcache_t *bc, const char *cachename, FILE *fp) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ dns_bcentry_t *bad, *next, *prev;
+ isc_time_t now;
+ unsigned int i;
+ uint64_t t;
+
+ REQUIRE(VALID_BADCACHE(bc));
+ REQUIRE(cachename != NULL);
+ REQUIRE(fp != NULL);
+
+ /*
+ * We write lock the tree to avoid relocking every node
+ * individually.
+ */
+ RWLOCK(&bc->lock, isc_rwlocktype_write);
+ fprintf(fp, ";\n; %s\n;\n", cachename);
+
+ TIME_NOW(&now);
+ for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
+ prev = NULL;
+ for (bad = bc->table[i]; bad != NULL; bad = next) {
+ next = bad->next;
+ if (isc_time_compare(&bad->expire, &now) < 0) {
+ if (prev != NULL) {
+ prev->next = bad->next;
+ } else {
+ bc->table[i] = bad->next;
+ }
+
+ isc_mem_put(bc->mctx, bad,
+ sizeof(*bad) + bad->name.length);
+ atomic_fetch_sub_relaxed(&bc->count, 1);
+ continue;
+ }
+ prev = bad;
+ dns_name_format(&bad->name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(bad->type, typebuf,
+ sizeof(typebuf));
+ t = isc_time_microdiff(&bad->expire, &now);
+ t /= 1000;
+ fprintf(fp,
+ "; %s/%s [ttl "
+ "%" PRIu64 "]\n",
+ namebuf, typebuf, t);
+ }
+ }
+ RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+}
diff --git a/lib/dns/byaddr.c b/lib/dns/byaddr.c
new file mode 100644
index 0000000..c6f471b
--- /dev/null
+++ b/lib/dns/byaddr.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/print.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/result.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..417cdaf
--- /dev/null
+++ b/lib/dns/cache.c
@@ -0,0 +1,1510 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/refcount.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/lib.h>
+#include <dns/log.h>
+#include <dns/masterdump.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/result.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_mutex_t filelock;
+ isc_mem_t *mctx; /* Main cache memory */
+ isc_mem_t *hmctx; /* Heap memory */
+ char *name;
+ isc_refcount_t references;
+ isc_refcount_t live_tasks;
+
+ /* Locked by 'lock'. */
+ dns_rdataclass_t rdclass;
+ dns_db_t *db;
+ cache_cleaner_t cleaner;
+ char *db_type;
+ int db_argc;
+ char **db_argv;
+ size_t size;
+ dns_ttl_t serve_stale_ttl;
+ dns_ttl_t serve_stale_refresh;
+ isc_stats_t *stats;
+
+ /* Locked by 'filelock'. */
+ char *filename;
+ /* Access to the on-disk cache file is also locked by 'filelock'. */
+};
+
+/***
+ *** Functions
+ ***/
+
+static isc_result_t
+cache_cleaner_init(dns_cache_t *cache, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, cache_cleaner_t *cleaner);
+
+static void
+incremental_cleaning_action(isc_task_t *task, isc_event_t *event);
+
+static void
+cleaner_shutdown_action(isc_task_t *task, isc_event_t *event);
+
+static void
+overmem_cleaning_action(isc_task_t *task, isc_event_t *event);
+
+static isc_result_t
+cache_create_db(dns_cache_t *cache, dns_db_t **db) {
+ isc_result_t result;
+ result = dns_db_create(cache->mctx, cache->db_type, dns_rootname,
+ dns_dbtype_cache, cache->rdclass, cache->db_argc,
+ cache->db_argv, db);
+ if (result == ISC_R_SUCCESS) {
+ dns_db_setservestalettl(*db, cache->serve_stale_ttl);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_cache_create(isc_mem_t *cmctx, isc_mem_t *hmctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, dns_rdataclass_t rdclass,
+ const char *cachename, const char *db_type,
+ unsigned int db_argc, char **db_argv, dns_cache_t **cachep) {
+ isc_result_t result;
+ dns_cache_t *cache;
+ int i, extra = 0;
+ isc_task_t *dbtask;
+
+ REQUIRE(cachep != NULL);
+ REQUIRE(*cachep == NULL);
+ REQUIRE(cmctx != NULL);
+ REQUIRE(hmctx != NULL);
+ REQUIRE(cachename != NULL);
+
+ cache = isc_mem_get(cmctx, sizeof(*cache));
+
+ cache->mctx = cache->hmctx = NULL;
+ isc_mem_attach(cmctx, &cache->mctx);
+ isc_mem_attach(hmctx, &cache->hmctx);
+
+ cache->name = NULL;
+ if (cachename != NULL) {
+ cache->name = isc_mem_strdup(cmctx, cachename);
+ }
+
+ isc_mutex_init(&cache->lock);
+ isc_mutex_init(&cache->filelock);
+
+ isc_refcount_init(&cache->references, 1);
+ isc_refcount_init(&cache->live_tasks, 1);
+ cache->rdclass = rdclass;
+ cache->serve_stale_ttl = 0;
+
+ cache->stats = NULL;
+ result = isc_stats_create(cmctx, &cache->stats,
+ dns_cachestatscounter_max);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_filelock;
+ }
+
+ cache->db_type = isc_mem_strdup(cmctx, db_type);
+
+ /*
+ * For databases of type "rbt" we pass hmctx to dns_db_create()
+ * via cache->db_argv, followed by the rest of the arguments in
+ * db_argv (of which there really shouldn't be any).
+ */
+ if (strcmp(cache->db_type, "rbt") == 0) {
+ extra = 1;
+ }
+
+ cache->db_argc = db_argc + extra;
+ cache->db_argv = NULL;
+
+ if (cache->db_argc != 0) {
+ cache->db_argv = isc_mem_get(cmctx,
+ cache->db_argc * sizeof(char *));
+
+ for (i = 0; i < cache->db_argc; i++) {
+ cache->db_argv[i] = NULL;
+ }
+
+ cache->db_argv[0] = (char *)hmctx;
+ for (i = extra; i < cache->db_argc; i++) {
+ cache->db_argv[i] = isc_mem_strdup(cmctx,
+ db_argv[i - extra]);
+ }
+ }
+
+ /*
+ * Create the database
+ */
+ cache->db = NULL;
+ result = cache_create_db(cache, &cache->db);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_dbargv;
+ }
+ if (taskmgr != NULL) {
+ dbtask = NULL;
+ result = isc_task_create(taskmgr, 1, &dbtask);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_db;
+ }
+
+ isc_task_setname(dbtask, "cache_dbtask", NULL);
+ dns_db_settask(cache->db, dbtask);
+ isc_task_detach(&dbtask);
+ }
+
+ cache->filename = NULL;
+
+ cache->magic = CACHE_MAGIC;
+
+ /*
+ * RBT-type cache DB has its own mechanism of cache cleaning and doesn't
+ * need the control of the generic cleaner.
+ */
+ if (strcmp(db_type, "rbt") == 0) {
+ result = cache_cleaner_init(cache, NULL, NULL, &cache->cleaner);
+ } else {
+ result = cache_cleaner_init(cache, taskmgr, timermgr,
+ &cache->cleaner);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_db;
+ }
+
+ result = dns_db_setcachestats(cache->db, cache->stats);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_db;
+ }
+
+ *cachep = cache;
+ return (ISC_R_SUCCESS);
+
+cleanup_db:
+ dns_db_detach(&cache->db);
+cleanup_dbargv:
+ for (i = extra; i < cache->db_argc; i++) {
+ if (cache->db_argv[i] != NULL) {
+ isc_mem_free(cmctx, cache->db_argv[i]);
+ }
+ }
+ if (cache->db_argv != NULL) {
+ isc_mem_put(cmctx, cache->db_argv,
+ cache->db_argc * sizeof(char *));
+ }
+ isc_mem_free(cmctx, cache->db_type);
+cleanup_filelock:
+ isc_mutex_destroy(&cache->filelock);
+ isc_stats_detach(&cache->stats);
+ isc_mutex_destroy(&cache->lock);
+ if (cache->name != NULL) {
+ isc_mem_free(cmctx, cache->name);
+ }
+ isc_mem_detach(&cache->hmctx);
+ isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
+ return (result);
+}
+
+static void
+cache_free(dns_cache_t *cache) {
+ REQUIRE(VALID_CACHE(cache));
+
+ isc_refcount_destroy(&cache->references);
+ isc_refcount_destroy(&cache->live_tasks);
+
+ isc_mem_setwater(cache->mctx, NULL, NULL, 0, 0);
+
+ if (cache->cleaner.task != NULL) {
+ isc_task_detach(&cache->cleaner.task);
+ }
+
+ if (cache->cleaner.overmem_event != NULL) {
+ isc_event_free(&cache->cleaner.overmem_event);
+ }
+
+ if (cache->cleaner.resched_event != NULL) {
+ isc_event_free(&cache->cleaner.resched_event);
+ }
+
+ if (cache->cleaner.iterator != NULL) {
+ dns_dbiterator_destroy(&cache->cleaner.iterator);
+ }
+
+ isc_mutex_destroy(&cache->cleaner.lock);
+
+ if (cache->filename) {
+ isc_mem_free(cache->mctx, cache->filename);
+ cache->filename = NULL;
+ }
+
+ if (cache->db != NULL) {
+ dns_db_detach(&cache->db);
+ }
+
+ if (cache->db_argv != NULL) {
+ /*
+ * We don't free db_argv[0] in "rbt" cache databases
+ * as it's a pointer to hmctx
+ */
+ int extra = 0;
+ if (strcmp(cache->db_type, "rbt") == 0) {
+ extra = 1;
+ }
+ for (int i = extra; i < cache->db_argc; i++) {
+ if (cache->db_argv[i] != NULL) {
+ isc_mem_free(cache->mctx, cache->db_argv[i]);
+ }
+ }
+ isc_mem_put(cache->mctx, cache->db_argv,
+ cache->db_argc * sizeof(char *));
+ }
+
+ if (cache->db_type != NULL) {
+ isc_mem_free(cache->mctx, cache->db_type);
+ }
+
+ if (cache->name != NULL) {
+ isc_mem_free(cache->mctx, cache->name);
+ }
+
+ if (cache->stats != NULL) {
+ isc_stats_detach(&cache->stats);
+ }
+
+ isc_mutex_destroy(&cache->lock);
+ isc_mutex_destroy(&cache->filelock);
+
+ cache->magic = 0;
+ isc_mem_detach(&cache->hmctx);
+ isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
+}
+
+void
+dns_cache_attach(dns_cache_t *cache, dns_cache_t **targetp) {
+ REQUIRE(VALID_CACHE(cache));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&cache->references);
+
+ *targetp = cache;
+}
+
+void
+dns_cache_detach(dns_cache_t **cachep) {
+ dns_cache_t *cache;
+
+ REQUIRE(cachep != NULL);
+ cache = *cachep;
+ *cachep = NULL;
+ REQUIRE(VALID_CACHE(cache));
+
+ if (isc_refcount_decrement(&cache->references) == 1) {
+ cache->cleaner.overmem = false;
+ /*
+ * When the cache is shut down, dump it to a file if one is
+ * specified.
+ */
+ isc_result_t result = dns_cache_dump(cache);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "error dumping cache: %s ",
+ isc_result_totext(result));
+ }
+
+ /*
+ * If the cleaner task exists, let it free the cache.
+ */
+ if (isc_refcount_decrement(&cache->live_tasks) > 1) {
+ isc_task_shutdown(cache->cleaner.task);
+ } else {
+ cache_free(cache);
+ }
+ }
+}
+
+void
+dns_cache_attachdb(dns_cache_t *cache, dns_db_t **dbp) {
+ REQUIRE(VALID_CACHE(cache));
+ REQUIRE(dbp != NULL && *dbp == NULL);
+ REQUIRE(cache->db != NULL);
+
+ LOCK(&cache->lock);
+ dns_db_attach(cache->db, dbp);
+ UNLOCK(&cache->lock);
+}
+
+isc_result_t
+dns_cache_setfilename(dns_cache_t *cache, const char *filename) {
+ char *newname;
+
+ REQUIRE(VALID_CACHE(cache));
+ REQUIRE(filename != NULL);
+
+ newname = isc_mem_strdup(cache->mctx, filename);
+
+ LOCK(&cache->filelock);
+ if (cache->filename) {
+ isc_mem_free(cache->mctx, cache->filename);
+ }
+ cache->filename = newname;
+ UNLOCK(&cache->filelock);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_cache_load(dns_cache_t *cache) {
+ isc_result_t result;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ if (cache->filename == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ LOCK(&cache->filelock);
+ result = dns_db_load(cache->db, cache->filename, dns_masterformat_text,
+ 0);
+ UNLOCK(&cache->filelock);
+
+ return (result);
+}
+
+isc_result_t
+dns_cache_dump(dns_cache_t *cache) {
+ isc_result_t result;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ if (cache->filename == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ LOCK(&cache->filelock);
+ result = dns_master_dump(cache->mctx, cache->db, NULL,
+ &dns_master_style_cache, cache->filename,
+ dns_masterformat_text, NULL);
+ UNLOCK(&cache->filelock);
+ return (result);
+}
+
+const char *
+dns_cache_getname(dns_cache_t *cache) {
+ REQUIRE(VALID_CACHE(cache));
+
+ return (cache->name);
+}
+
+/*
+ * Initialize the cache cleaner object at *cleaner.
+ * Space for the object must be allocated by the caller.
+ */
+
+static isc_result_t
+cache_cleaner_init(dns_cache_t *cache, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, cache_cleaner_t *cleaner) {
+ isc_result_t result;
+
+ isc_mutex_init(&cleaner->lock);
+
+ cleaner->increment = DNS_CACHE_CLEANERINCREMENT;
+ cleaner->state = cleaner_s_idle;
+ cleaner->cache = cache;
+ cleaner->iterator = NULL;
+ cleaner->overmem = false;
+ cleaner->replaceiterator = false;
+
+ cleaner->task = NULL;
+ cleaner->resched_event = NULL;
+ cleaner->overmem_event = NULL;
+
+ result = dns_db_createiterator(cleaner->cache->db, false,
+ &cleaner->iterator);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (taskmgr != NULL && timermgr != NULL) {
+ result = isc_task_create(taskmgr, 1, &cleaner->task);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_task_create() failed: %s",
+ dns_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+ isc_refcount_increment(&cleaner->cache->live_tasks);
+ isc_task_setname(cleaner->task, "cachecleaner", cleaner);
+
+ result = isc_task_onshutdown(cleaner->task,
+ cleaner_shutdown_action, cache);
+ if (result != ISC_R_SUCCESS) {
+ isc_refcount_decrement0(&cleaner->cache->live_tasks);
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "cache cleaner: "
+ "isc_task_onshutdown() failed: %s",
+ dns_result_totext(result));
+ goto cleanup;
+ }
+
+ cleaner->resched_event = isc_event_allocate(
+ cache->mctx, cleaner, DNS_EVENT_CACHECLEAN,
+ incremental_cleaning_action, cleaner,
+ sizeof(isc_event_t));
+
+ cleaner->overmem_event = isc_event_allocate(
+ cache->mctx, cleaner, DNS_EVENT_CACHEOVERMEM,
+ overmem_cleaning_action, cleaner, sizeof(isc_event_t));
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (cleaner->overmem_event != NULL) {
+ isc_event_free(&cleaner->overmem_event);
+ }
+ if (cleaner->resched_event != NULL) {
+ isc_event_free(&cleaner->resched_event);
+ }
+ if (cleaner->task != NULL) {
+ isc_task_detach(&cleaner->task);
+ }
+ if (cleaner->iterator != NULL) {
+ dns_dbiterator_destroy(&cleaner->iterator);
+ }
+ isc_mutex_destroy(&cleaner->lock);
+
+ return (result);
+}
+
+static void
+begin_cleaning(cache_cleaner_t *cleaner) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(CLEANER_IDLE(cleaner));
+
+ /*
+ * Create an iterator, if it does not already exist, and
+ * position it at the beginning of the cache.
+ */
+ if (cleaner->iterator == NULL) {
+ result = dns_db_createiterator(cleaner->cache->db, false,
+ &cleaner->iterator);
+ }
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "cache cleaner could not create "
+ "iterator: %s",
+ isc_result_totext(result));
+ } else {
+ dns_dbiterator_setcleanmode(cleaner->iterator, true);
+ result = dns_dbiterator_first(cleaner->iterator);
+ }
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * If the result is ISC_R_NOMORE, the database is empty,
+ * so there is nothing to be cleaned.
+ */
+ if (result != ISC_R_NOMORE && cleaner->iterator != NULL) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "cache cleaner: "
+ "dns_dbiterator_first() failed: %s",
+ dns_result_totext(result));
+ dns_dbiterator_destroy(&cleaner->iterator);
+ } else if (cleaner->iterator != NULL) {
+ result = dns_dbiterator_pause(cleaner->iterator);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ } else {
+ /*
+ * Pause the iterator to free its lock.
+ */
+ result = dns_dbiterator_pause(cleaner->iterator);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
+ ISC_LOG_DEBUG(1), "begin cache cleaning, mem inuse %lu",
+ (unsigned long)isc_mem_inuse(cleaner->cache->mctx));
+ cleaner->state = cleaner_s_busy;
+ isc_task_send(cleaner->task, &cleaner->resched_event);
+ }
+
+ return;
+}
+
+static void
+end_cleaning(cache_cleaner_t *cleaner, isc_event_t *event) {
+ isc_result_t result;
+
+ REQUIRE(CLEANER_BUSY(cleaner));
+ REQUIRE(event != NULL);
+
+ result = dns_dbiterator_pause(cleaner->iterator);
+ if (result != ISC_R_SUCCESS) {
+ dns_dbiterator_destroy(&cleaner->iterator);
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
+ ISC_LOG_DEBUG(1), "end cache cleaning, mem inuse %lu",
+ (unsigned long)isc_mem_inuse(cleaner->cache->mctx));
+
+ cleaner->state = cleaner_s_idle;
+ cleaner->resched_event = event;
+}
+
+/*
+ * This is called when the cache either surpasses its upper limit
+ * or shrinks beyond its lower limit.
+ */
+static void
+overmem_cleaning_action(isc_task_t *task, isc_event_t *event) {
+ cache_cleaner_t *cleaner = event->ev_arg;
+ bool want_cleaning = false;
+
+ UNUSED(task);
+
+ INSIST(task == cleaner->task);
+ INSIST(event->ev_type == DNS_EVENT_CACHEOVERMEM);
+ INSIST(cleaner->overmem_event == NULL);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
+ ISC_LOG_DEBUG(1),
+ "overmem_cleaning_action called, "
+ "overmem = %d, state = %d",
+ cleaner->overmem, cleaner->state);
+
+ LOCK(&cleaner->lock);
+
+ if (cleaner->overmem) {
+ if (cleaner->state == cleaner_s_idle) {
+ want_cleaning = true;
+ }
+ } else {
+ if (cleaner->state == cleaner_s_busy) {
+ /*
+ * end_cleaning() can't be called here because
+ * then both cleaner->overmem_event and
+ * cleaner->resched_event will point to this
+ * event. Set the state to done, and then
+ * when the incremental_cleaning_action() event
+ * is posted, it will handle the end_cleaning.
+ */
+ cleaner->state = cleaner_s_done;
+ }
+ }
+
+ cleaner->overmem_event = event;
+
+ UNLOCK(&cleaner->lock);
+
+ if (want_cleaning) {
+ begin_cleaning(cleaner);
+ }
+}
+
+/*
+ * Do incremental cleaning.
+ */
+static void
+incremental_cleaning_action(isc_task_t *task, isc_event_t *event) {
+ cache_cleaner_t *cleaner = event->ev_arg;
+ isc_result_t result;
+ unsigned int n_names;
+ isc_time_t start;
+
+ UNUSED(task);
+
+ INSIST(task == cleaner->task);
+ INSIST(event->ev_type == DNS_EVENT_CACHECLEAN);
+
+ if (cleaner->state == cleaner_s_done) {
+ cleaner->state = cleaner_s_busy;
+ end_cleaning(cleaner, event);
+ LOCK(&cleaner->cache->lock);
+ LOCK(&cleaner->lock);
+ if (cleaner->replaceiterator) {
+ dns_dbiterator_destroy(&cleaner->iterator);
+ (void)dns_db_createiterator(cleaner->cache->db, false,
+ &cleaner->iterator);
+ cleaner->replaceiterator = false;
+ }
+ UNLOCK(&cleaner->lock);
+ UNLOCK(&cleaner->cache->lock);
+ return;
+ }
+
+ INSIST(CLEANER_BUSY(cleaner));
+
+ n_names = cleaner->increment;
+
+ REQUIRE(DNS_DBITERATOR_VALID(cleaner->iterator));
+
+ isc_time_now(&start);
+ while (n_names-- > 0) {
+ dns_dbnode_t *node = NULL;
+
+ result = dns_dbiterator_current(cleaner->iterator, &node, NULL);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "cache cleaner: "
+ "dns_dbiterator_current() "
+ "failed: %s",
+ dns_result_totext(result));
+
+ end_cleaning(cleaner, event);
+ return;
+ }
+
+ /*
+ * The node was not needed, but was required by
+ * dns_dbiterator_current(). Give up its reference.
+ */
+ dns_db_detachnode(cleaner->cache->db, &node);
+
+ /*
+ * Step to the next node.
+ */
+ result = dns_dbiterator_next(cleaner->iterator);
+
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Either the end was reached (ISC_R_NOMORE) or
+ * some error was signaled. If the cache is still
+ * overmem and no error was encountered,
+ * keep trying to clean it, otherwise stop cleaning.
+ */
+ if (result != ISC_R_NOMORE) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "cache cleaner: "
+ "dns_dbiterator_next() "
+ "failed: %s",
+ dns_result_totext(result));
+ } else if (cleaner->overmem) {
+ result =
+ dns_dbiterator_first(cleaner->iterator);
+ if (result == ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE,
+ ISC_LOG_DEBUG(1),
+ "cache cleaner: "
+ "still overmem, "
+ "reset and try again");
+ continue;
+ }
+ }
+
+ end_cleaning(cleaner, event);
+ return;
+ }
+ }
+
+ /*
+ * We have successfully performed a cleaning increment but have
+ * not gone through the entire cache. Free the iterator locks
+ * and reschedule another batch. If it fails, just try to continue
+ * anyway.
+ */
+ result = dns_dbiterator_pause(cleaner->iterator);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
+ ISC_LOG_DEBUG(1),
+ "cache cleaner: checked %u nodes, "
+ "mem inuse %lu, sleeping",
+ cleaner->increment,
+ (unsigned long)isc_mem_inuse(cleaner->cache->mctx));
+
+ isc_task_send(task, &event);
+ INSIST(CLEANER_BUSY(cleaner));
+ return;
+}
+
+/*
+ * Do immediate cleaning.
+ */
+isc_result_t
+dns_cache_clean(dns_cache_t *cache, isc_stdtime_t now) {
+ isc_result_t result;
+ dns_dbiterator_t *iterator = NULL;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ result = dns_db_createiterator(cache->db, 0, &iterator);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_dbiterator_first(iterator);
+
+ while (result == ISC_R_SUCCESS) {
+ dns_dbnode_t *node = NULL;
+ result = dns_dbiterator_current(iterator, &node,
+ (dns_name_t *)NULL);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ /*
+ * Check TTLs, mark expired rdatasets stale.
+ */
+ result = dns_db_expirenode(cache->db, node, now);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "cache cleaner: dns_db_expirenode() "
+ "failed: %s",
+ dns_result_totext(result));
+ /*
+ * Continue anyway.
+ */
+ }
+
+ /*
+ * This is where the actual freeing takes place.
+ */
+ dns_db_detachnode(cache->db, &node);
+
+ result = dns_dbiterator_next(iterator);
+ }
+
+ dns_dbiterator_destroy(&iterator);
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static void
+water(void *arg, int mark) {
+ dns_cache_t *cache = arg;
+ bool overmem = (mark == ISC_MEM_HIWATER);
+
+ REQUIRE(VALID_CACHE(cache));
+
+ LOCK(&cache->cleaner.lock);
+
+ if (overmem != cache->cleaner.overmem) {
+ dns_db_overmem(cache->db, overmem);
+ cache->cleaner.overmem = overmem;
+ isc_mem_waterack(cache->mctx, mark);
+ }
+
+ if (cache->cleaner.overmem_event != NULL) {
+ isc_task_send(cache->cleaner.task,
+ &cache->cleaner.overmem_event);
+ }
+
+ UNLOCK(&cache->cleaner.lock);
+}
+
+void
+dns_cache_setcachesize(dns_cache_t *cache, size_t size) {
+ size_t hiwater, lowater;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ /*
+ * Impose a minimum cache size; pathological things happen if there
+ * is too little room.
+ */
+ if (size != 0U && size < DNS_CACHE_MINSIZE) {
+ size = DNS_CACHE_MINSIZE;
+ }
+
+ LOCK(&cache->lock);
+ cache->size = size;
+ UNLOCK(&cache->lock);
+
+ hiwater = size - (size >> 3); /* Approximately 7/8ths. */
+ lowater = size - (size >> 2); /* Approximately 3/4ths. */
+
+ /*
+ * If the cache was overmem and cleaning, but now with the new limits
+ * it is no longer in an overmem condition, then the next
+ * isc_mem_put for cache memory will do the right thing and trigger
+ * water().
+ */
+
+ if (size == 0U || hiwater == 0U || lowater == 0U) {
+ /*
+ * Disable cache memory limiting.
+ */
+ isc_mem_setwater(cache->mctx, water, cache, 0, 0);
+ } else {
+ /*
+ * Establish new cache memory limits (either for the first
+ * time, or replacing other limits).
+ */
+ isc_mem_setwater(cache->mctx, water, cache, hiwater, lowater);
+ }
+
+ dns_db_adjusthashsize(cache->db, size);
+}
+
+size_t
+dns_cache_getcachesize(dns_cache_t *cache) {
+ size_t size;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ LOCK(&cache->lock);
+ size = cache->size;
+ UNLOCK(&cache->lock);
+
+ return (size);
+}
+
+void
+dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl) {
+ REQUIRE(VALID_CACHE(cache));
+
+ LOCK(&cache->lock);
+ cache->serve_stale_ttl = ttl;
+ UNLOCK(&cache->lock);
+
+ (void)dns_db_setservestalettl(cache->db, ttl);
+}
+
+dns_ttl_t
+dns_cache_getservestalettl(dns_cache_t *cache) {
+ dns_ttl_t ttl;
+ isc_result_t result;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ /*
+ * Could get it straight from the dns_cache_t, but use db
+ * to confirm the value that the db is really using.
+ */
+ result = dns_db_getservestalettl(cache->db, &ttl);
+ return (result == ISC_R_SUCCESS ? ttl : 0);
+}
+
+void
+dns_cache_setservestalerefresh(dns_cache_t *cache, dns_ttl_t interval) {
+ REQUIRE(VALID_CACHE(cache));
+
+ LOCK(&cache->lock);
+ cache->serve_stale_refresh = interval;
+ UNLOCK(&cache->lock);
+
+ (void)dns_db_setservestalerefresh(cache->db, interval);
+}
+
+dns_ttl_t
+dns_cache_getservestalerefresh(dns_cache_t *cache) {
+ isc_result_t result;
+ dns_ttl_t interval;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ result = dns_db_getservestalerefresh(cache->db, &interval);
+ return (result == ISC_R_SUCCESS ? interval : 0);
+}
+
+/*
+ * The cleaner task is shutting down; do the necessary cleanup.
+ */
+static void
+cleaner_shutdown_action(isc_task_t *task, isc_event_t *event) {
+ dns_cache_t *cache = event->ev_arg;
+
+ UNUSED(task);
+
+ INSIST(task == cache->cleaner.task);
+ INSIST(event->ev_type == ISC_TASKEVENT_SHUTDOWN);
+
+ if (CLEANER_BUSY(&cache->cleaner)) {
+ end_cleaning(&cache->cleaner, event);
+ } else {
+ isc_event_free(&event);
+ }
+
+ /* Make sure we don't reschedule anymore. */
+ (void)isc_task_purge(task, NULL, DNS_EVENT_CACHECLEAN, NULL);
+
+ isc_refcount_decrementz(&cache->live_tasks);
+
+ cache_free(cache);
+}
+
+isc_result_t
+dns_cache_flush(dns_cache_t *cache) {
+ dns_db_t *db = NULL, *olddb;
+ dns_dbiterator_t *dbiterator = NULL, *olddbiterator = NULL;
+ isc_result_t result;
+
+ result = cache_create_db(cache, &db);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_createiterator(db, false, &dbiterator);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detach(&db);
+ return (result);
+ }
+
+ LOCK(&cache->lock);
+ LOCK(&cache->cleaner.lock);
+ if (cache->cleaner.state == cleaner_s_idle) {
+ olddbiterator = cache->cleaner.iterator;
+ cache->cleaner.iterator = dbiterator;
+ dbiterator = NULL;
+ } else {
+ if (cache->cleaner.state == cleaner_s_busy) {
+ cache->cleaner.state = cleaner_s_done;
+ }
+ cache->cleaner.replaceiterator = true;
+ }
+ olddb = cache->db;
+ cache->db = db;
+ dns_db_setcachestats(cache->db, cache->stats);
+ UNLOCK(&cache->cleaner.lock);
+ UNLOCK(&cache->lock);
+
+ if (dbiterator != NULL) {
+ dns_dbiterator_destroy(&dbiterator);
+ }
+ if (olddbiterator != NULL) {
+ dns_dbiterator_destroy(&olddbiterator);
+ }
+ dns_db_detach(&olddb);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+clearnode(dns_db_t *db, dns_dbnode_t *node) {
+ isc_result_t result;
+ dns_rdatasetiter_t *iter = NULL;
+
+ result = dns_db_allrdatasets(db, node, NULL, DNS_DB_STALEOK,
+ (isc_stdtime_t)0, &iter);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(iter))
+ {
+ dns_rdataset_t rdataset;
+ dns_rdataset_init(&rdataset);
+
+ dns_rdatasetiter_current(iter, &rdataset);
+ result = dns_db_deleterdataset(db, node, NULL, rdataset.type,
+ rdataset.covers);
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) {
+ break;
+ }
+ }
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ dns_rdatasetiter_destroy(&iter);
+ return (result);
+}
+
+static isc_result_t
+cleartree(dns_db_t *db, const dns_name_t *name) {
+ isc_result_t result, answer = ISC_R_SUCCESS;
+ dns_dbiterator_t *iter = NULL;
+ dns_dbnode_t *node = NULL, *top = NULL;
+ dns_fixedname_t fnodename;
+ dns_name_t *nodename;
+
+ /*
+ * Create the node if it doesn't exist so dns_dbiterator_seek()
+ * can find it. We will continue even if this fails.
+ */
+ (void)dns_db_findnode(db, name, true, &top);
+
+ nodename = dns_fixedname_initname(&fnodename);
+
+ result = dns_db_createiterator(db, 0, &iter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_dbiterator_seek(iter, name);
+ if (result == DNS_R_PARTIALMATCH) {
+ result = dns_dbiterator_next(iter);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ while (result == ISC_R_SUCCESS) {
+ result = dns_dbiterator_current(iter, &node, nodename);
+ if (result == DNS_R_NEWORIGIN) {
+ result = ISC_R_SUCCESS;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ /*
+ * Are we done?
+ */
+ if (!dns_name_issubdomain(nodename, name)) {
+ goto cleanup;
+ }
+
+ /*
+ * If clearnode fails record and move onto the next node.
+ */
+ result = clearnode(db, node);
+ if (result != ISC_R_SUCCESS && answer == ISC_R_SUCCESS) {
+ answer = result;
+ }
+ dns_db_detachnode(db, &node);
+ result = dns_dbiterator_next(iter);
+ }
+
+cleanup:
+ if (result == ISC_R_NOMORE || result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ if (result != ISC_R_SUCCESS && answer == ISC_R_SUCCESS) {
+ answer = result;
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (iter != NULL) {
+ dns_dbiterator_destroy(&iter);
+ }
+ if (top != NULL) {
+ dns_db_detachnode(db, &top);
+ }
+
+ return (answer);
+}
+
+isc_result_t
+dns_cache_flushname(dns_cache_t *cache, const dns_name_t *name) {
+ return (dns_cache_flushnode(cache, name, false));
+}
+
+isc_result_t
+dns_cache_flushnode(dns_cache_t *cache, const dns_name_t *name, bool tree) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_db_t *db = NULL;
+
+ if (tree && dns_name_equal(name, dns_rootname)) {
+ return (dns_cache_flush(cache));
+ }
+
+ LOCK(&cache->lock);
+ if (cache->db != NULL) {
+ dns_db_attach(cache->db, &db);
+ }
+ UNLOCK(&cache->lock);
+ if (db == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (tree) {
+ result = cleartree(cache->db, name);
+ } else {
+ result = dns_db_findnode(cache->db, name, false, &node);
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ goto cleanup_db;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_db;
+ }
+ result = clearnode(cache->db, node);
+ dns_db_detachnode(cache->db, &node);
+ }
+
+cleanup_db:
+ dns_db_detach(&db);
+ return (result);
+}
+
+isc_stats_t *
+dns_cache_getstats(dns_cache_t *cache) {
+ REQUIRE(VALID_CACHE(cache));
+ return (cache->stats);
+}
+
+void
+dns_cache_updatestats(dns_cache_t *cache, isc_result_t result) {
+ REQUIRE(VALID_CACHE(cache));
+ if (cache->stats == NULL) {
+ return;
+ }
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ case DNS_R_GLUE:
+ case DNS_R_ZONECUT:
+ isc_stats_increment(cache->stats,
+ dns_cachestatscounter_queryhits);
+ break;
+ default:
+ isc_stats_increment(cache->stats,
+ dns_cachestatscounter_querymisses);
+ }
+}
+
+/*
+ * XXX: Much of the following code has been copied in from statschannel.c.
+ * We should refactor this into a generic function in stats.c that can be
+ * called from both places.
+ */
+typedef struct cache_dumparg {
+ isc_statsformat_t type;
+ void *arg; /* type dependent argument */
+ int ncounters; /* for general statistics */
+ int *counterindices; /* for general statistics */
+ uint64_t *countervalues; /* for general statistics */
+ isc_result_t result;
+} cache_dumparg_t;
+
+static void
+getcounter(isc_statscounter_t counter, uint64_t val, void *arg) {
+ cache_dumparg_t *dumparg = arg;
+
+ REQUIRE(counter < dumparg->ncounters);
+ dumparg->countervalues[counter] = val;
+}
+
+static void
+getcounters(isc_stats_t *stats, isc_statsformat_t type, int ncounters,
+ int *indices, uint64_t *values) {
+ cache_dumparg_t dumparg;
+
+ memset(values, 0, sizeof(values[0]) * ncounters);
+
+ dumparg.type = type;
+ dumparg.ncounters = ncounters;
+ dumparg.counterindices = indices;
+ dumparg.countervalues = values;
+
+ isc_stats_dump(stats, getcounter, &dumparg, ISC_STATSDUMP_VERBOSE);
+}
+
+void
+dns_cache_dumpstats(dns_cache_t *cache, FILE *fp) {
+ int indices[dns_cachestatscounter_max];
+ uint64_t values[dns_cachestatscounter_max];
+
+ REQUIRE(VALID_CACHE(cache));
+
+ getcounters(cache->stats, isc_statsformat_file,
+ dns_cachestatscounter_max, indices, values);
+
+ fprintf(fp, "%20" PRIu64 " %s\n", values[dns_cachestatscounter_hits],
+ "cache hits");
+ fprintf(fp, "%20" PRIu64 " %s\n", values[dns_cachestatscounter_misses],
+ "cache misses");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ values[dns_cachestatscounter_queryhits],
+ "cache hits (from query)");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ values[dns_cachestatscounter_querymisses],
+ "cache misses (from query)");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ values[dns_cachestatscounter_deletelru],
+ "cache records deleted due to memory exhaustion");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ values[dns_cachestatscounter_deletettl],
+ "cache records deleted due to TTL expiration");
+ fprintf(fp, "%20u %s\n", dns_db_nodecount(cache->db),
+ "cache database nodes");
+ fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)dns_db_hashsize(cache->db),
+ "cache database hash buckets");
+
+ fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_total(cache->mctx),
+ "cache tree memory total");
+ fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_inuse(cache->mctx),
+ "cache tree memory in use");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ (uint64_t)isc_mem_maxinuse(cache->mctx),
+ "cache tree highest memory in use");
+
+ fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_total(cache->hmctx),
+ "cache heap memory total");
+ fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_inuse(cache->hmctx),
+ "cache heap memory in use");
+ fprintf(fp, "%20" PRIu64 " %s\n",
+ (uint64_t)isc_mem_maxinuse(cache->hmctx),
+ "cache heap highest memory in use");
+}
+
+#ifdef HAVE_LIBXML2
+#define TRY0(a) \
+ do { \
+ xmlrc = (a); \
+ if (xmlrc < 0) \
+ goto error; \
+ } while (0)
+static int
+renderstat(const char *name, uint64_t value, xmlTextWriterPtr writer) {
+ int xmlrc;
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter"));
+ TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name",
+ ISC_XMLCHAR name));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", value));
+ TRY0(xmlTextWriterEndElement(writer)); /* counter */
+
+error:
+ return (xmlrc);
+}
+
+int
+dns_cache_renderxml(dns_cache_t *cache, void *writer0) {
+ int indices[dns_cachestatscounter_max];
+ uint64_t values[dns_cachestatscounter_max];
+ int xmlrc;
+ xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ getcounters(cache->stats, isc_statsformat_file,
+ dns_cachestatscounter_max, indices, values);
+ TRY0(renderstat("CacheHits", values[dns_cachestatscounter_hits],
+ writer));
+ TRY0(renderstat("CacheMisses", values[dns_cachestatscounter_misses],
+ writer));
+ TRY0(renderstat("QueryHits", values[dns_cachestatscounter_queryhits],
+ writer));
+ TRY0(renderstat("QueryMisses",
+ values[dns_cachestatscounter_querymisses], writer));
+ TRY0(renderstat("DeleteLRU", values[dns_cachestatscounter_deletelru],
+ writer));
+ TRY0(renderstat("DeleteTTL", values[dns_cachestatscounter_deletettl],
+ writer));
+
+ TRY0(renderstat("CacheNodes", dns_db_nodecount(cache->db), writer));
+ TRY0(renderstat("CacheBuckets", dns_db_hashsize(cache->db), writer));
+
+ TRY0(renderstat("TreeMemTotal", isc_mem_total(cache->mctx), writer));
+ TRY0(renderstat("TreeMemInUse", isc_mem_inuse(cache->mctx), writer));
+ TRY0(renderstat("TreeMemMax", isc_mem_maxinuse(cache->mctx), writer));
+
+ TRY0(renderstat("HeapMemTotal", isc_mem_total(cache->hmctx), writer));
+ TRY0(renderstat("HeapMemInUse", isc_mem_inuse(cache->hmctx), writer));
+ TRY0(renderstat("HeapMemMax", isc_mem_maxinuse(cache->hmctx), writer));
+error:
+ return (xmlrc);
+}
+#endif /* ifdef HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+#define CHECKMEM(m) \
+ do { \
+ if (m == NULL) { \
+ result = ISC_R_NOMEMORY; \
+ goto error; \
+ } \
+ } while (0)
+
+isc_result_t
+dns_cache_renderjson(dns_cache_t *cache, void *cstats0) {
+ isc_result_t result = ISC_R_SUCCESS;
+ int indices[dns_cachestatscounter_max];
+ uint64_t values[dns_cachestatscounter_max];
+ json_object *obj;
+ json_object *cstats = (json_object *)cstats0;
+
+ REQUIRE(VALID_CACHE(cache));
+
+ getcounters(cache->stats, isc_statsformat_file,
+ dns_cachestatscounter_max, indices, values);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_hits]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "CacheHits", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_misses]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "CacheMisses", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_queryhits]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "QueryHits", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_querymisses]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "QueryMisses", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_deletelru]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "DeleteLRU", obj);
+
+ obj = json_object_new_int64(values[dns_cachestatscounter_deletettl]);
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "DeleteTTL", obj);
+
+ obj = json_object_new_int64(dns_db_nodecount(cache->db));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "CacheNodes", obj);
+
+ obj = json_object_new_int64(dns_db_hashsize(cache->db));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "CacheBuckets", obj);
+
+ obj = json_object_new_int64(isc_mem_total(cache->mctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "TreeMemTotal", obj);
+
+ obj = json_object_new_int64(isc_mem_inuse(cache->mctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "TreeMemInUse", obj);
+
+ obj = json_object_new_int64(isc_mem_maxinuse(cache->mctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "TreeMemMax", obj);
+
+ obj = json_object_new_int64(isc_mem_total(cache->hmctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "HeapMemTotal", obj);
+
+ obj = json_object_new_int64(isc_mem_inuse(cache->hmctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "HeapMemInUse", obj);
+
+ obj = json_object_new_int64(isc_mem_maxinuse(cache->hmctx));
+ CHECKMEM(obj);
+ json_object_object_add(cstats, "HeapMemMax", obj);
+
+ result = ISC_R_SUCCESS;
+error:
+ return (result);
+}
+#endif /* ifdef HAVE_JSON_C */
diff --git a/lib/dns/callbacks.c b/lib/dns/callbacks.c
new file mode 100644
index 0000000..5200b72
--- /dev/null
+++ b/lib/dns/callbacks.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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..2c00d6e
--- /dev/null
+++ b/lib/dns/catz.c
@@ -0,0 +1,2105 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.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/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_ZONE_VALID(catz) ISC_MAGIC_VALID(catz, DNS_CATZ_ZONE_MAGIC)
+#define DNS_CATZ_ZONES_VALID(catzs) ISC_MAGIC_VALID(catzs, DNS_CATZ_ZONES_MAGIC)
+#define DNS_CATZ_ENTRY_VALID(entry) ISC_MAGIC_VALID(entry, DNS_CATZ_ENTRY_MAGIC)
+
+/*%
+ * Single member zone in a catalog
+ */
+struct dns_catz_entry {
+ unsigned int magic;
+ dns_name_t name;
+ dns_catz_options_t opts;
+ isc_refcount_t refs;
+};
+
+/*%
+ * Catalog zone
+ */
+struct dns_catz_zone {
+ unsigned int magic;
+ dns_name_t name;
+ dns_catz_zones_t *catzs;
+ dns_rdata_t soa;
+ /* key in entries is 'mhash', not domain name! */
+ isc_ht_t *entries;
+ /*
+ * defoptions are taken from named.conf
+ * zoneoptions are global options from zone
+ */
+ dns_catz_options_t defoptions;
+ dns_catz_options_t zoneoptions;
+ isc_time_t lastupdated;
+ bool updatepending;
+ uint32_t version;
+
+ dns_db_t *db;
+ dns_dbversion_t *dbversion;
+
+ isc_timer_t *updatetimer;
+ isc_event_t updateevent;
+
+ bool active;
+ bool db_registered;
+
+ isc_refcount_t refs;
+};
+
+static isc_result_t
+catz_process_zones_entry(dns_catz_zone_t *zone, dns_rdataset_t *value,
+ dns_label_t *mhash);
+static isc_result_t
+catz_process_zones_suboption(dns_catz_zone_t *zone, dns_rdataset_t *value,
+ dns_label_t *mhash, dns_name_t *name);
+static void
+catz_entry_add_or_mod(dns_catz_zone_t *target, isc_ht_t *ht, unsigned char *key,
+ size_t keysize, dns_catz_entry_t *nentry,
+ dns_catz_entry_t *oentry, const char *msg,
+ const char *zname, const char *czname);
+
+/*%
+ * Collection of catalog zones for a view
+ */
+struct dns_catz_zones {
+ unsigned int magic;
+ isc_ht_t *zones;
+ isc_mem_t *mctx;
+ isc_refcount_t refs;
+ isc_mutex_t lock;
+ dns_catz_zonemodmethods_t *zmm;
+ isc_taskmgr_t *taskmgr;
+ isc_timermgr_t *timermgr;
+ dns_view_t *view;
+ isc_task_t *updater;
+};
+
+void
+dns_catz_options_init(dns_catz_options_t *options) {
+ REQUIRE(options != NULL);
+
+ dns_ipkeylist_init(&options->masters);
+
+ options->allow_query = NULL;
+ options->allow_transfer = NULL;
+
+ options->allow_query = NULL;
+ options->allow_transfer = NULL;
+
+ options->in_memory = false;
+ options->min_update_interval = 5;
+ options->zonedir = NULL;
+}
+
+void
+dns_catz_options_free(dns_catz_options_t *options, isc_mem_t *mctx) {
+ REQUIRE(options != NULL);
+ REQUIRE(mctx != NULL);
+
+ if (options->masters.count != 0) {
+ dns_ipkeylist_clear(mctx, &options->masters);
+ }
+ if (options->zonedir != NULL) {
+ isc_mem_free(mctx, options->zonedir);
+ options->zonedir = NULL;
+ }
+ if (options->allow_query != NULL) {
+ isc_buffer_free(&options->allow_query);
+ }
+ if (options->allow_transfer != NULL) {
+ isc_buffer_free(&options->allow_transfer);
+ }
+}
+
+isc_result_t
+dns_catz_options_copy(isc_mem_t *mctx, const dns_catz_options_t *src,
+ dns_catz_options_t *dst) {
+ REQUIRE(mctx != NULL);
+ REQUIRE(src != NULL);
+ REQUIRE(dst != NULL);
+ REQUIRE(dst->masters.count == 0);
+ REQUIRE(dst->allow_query == NULL);
+ REQUIRE(dst->allow_transfer == NULL);
+
+ if (src->masters.count != 0) {
+ dns_ipkeylist_copy(mctx, &src->masters, &dst->masters);
+ }
+
+ if (dst->zonedir != NULL) {
+ isc_mem_free(mctx, dst->zonedir);
+ dst->zonedir = NULL;
+ }
+
+ if (src->zonedir != NULL) {
+ dst->zonedir = isc_mem_strdup(mctx, src->zonedir);
+ }
+
+ if (src->allow_query != NULL) {
+ isc_buffer_dup(mctx, &dst->allow_query, src->allow_query);
+ }
+
+ if (src->allow_transfer != NULL) {
+ isc_buffer_dup(mctx, &dst->allow_transfer, src->allow_transfer);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_catz_options_setdefault(isc_mem_t *mctx, const dns_catz_options_t *defaults,
+ dns_catz_options_t *opts) {
+ REQUIRE(mctx != NULL);
+ REQUIRE(defaults != NULL);
+ REQUIRE(opts != NULL);
+
+ if (opts->masters.count == 0 && defaults->masters.count != 0) {
+ dns_ipkeylist_copy(mctx, &defaults->masters, &opts->masters);
+ }
+
+ if (defaults->zonedir != NULL) {
+ opts->zonedir = isc_mem_strdup(mctx, defaults->zonedir);
+ }
+
+ if (opts->allow_query == NULL && defaults->allow_query != NULL) {
+ isc_buffer_dup(mctx, &opts->allow_query, defaults->allow_query);
+ }
+ if (opts->allow_transfer == NULL && defaults->allow_transfer != NULL) {
+ isc_buffer_dup(mctx, &opts->allow_transfer,
+ defaults->allow_transfer);
+ }
+
+ /* This option is always taken from config, so it's always 'default' */
+ opts->in_memory = defaults->in_memory;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_catz_entry_new(isc_mem_t *mctx, const dns_name_t *domain,
+ dns_catz_entry_t **nentryp) {
+ dns_catz_entry_t *nentry;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(nentryp != NULL && *nentryp == NULL);
+
+ nentry = isc_mem_get(mctx, sizeof(dns_catz_entry_t));
+
+ dns_name_init(&nentry->name, NULL);
+ if (domain != NULL) {
+ dns_name_dup(domain, mctx, &nentry->name);
+ }
+
+ dns_catz_options_init(&nentry->opts);
+ isc_refcount_init(&nentry->refs, 1);
+ nentry->magic = DNS_CATZ_ENTRY_MAGIC;
+ *nentryp = nentry;
+ return (ISC_R_SUCCESS);
+}
+
+dns_name_t *
+dns_catz_entry_getname(dns_catz_entry_t *entry) {
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ return (&entry->name);
+}
+
+isc_result_t
+dns_catz_entry_copy(dns_catz_zone_t *zone, const dns_catz_entry_t *entry,
+ dns_catz_entry_t **nentryp) {
+ isc_result_t result;
+ dns_catz_entry_t *nentry = NULL;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ REQUIRE(nentryp != NULL && *nentryp == NULL);
+
+ result = dns_catz_entry_new(zone->catzs->mctx, &entry->name, &nentry);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_catz_options_copy(zone->catzs->mctx, &entry->opts,
+ &nentry->opts);
+ if (result != ISC_R_SUCCESS) {
+ dns_catz_entry_detach(zone, &nentry);
+ }
+
+ *nentryp = nentry;
+ return (result);
+}
+
+void
+dns_catz_entry_attach(dns_catz_entry_t *entry, dns_catz_entry_t **entryp) {
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ REQUIRE(entryp != NULL && *entryp == NULL);
+
+ isc_refcount_increment(&entry->refs);
+ *entryp = entry;
+}
+
+void
+dns_catz_entry_detach(dns_catz_zone_t *zone, dns_catz_entry_t **entryp) {
+ dns_catz_entry_t *entry;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(entryp != NULL);
+ entry = *entryp;
+ *entryp = NULL;
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+
+ if (isc_refcount_decrement(&entry->refs) == 1) {
+ isc_mem_t *mctx = zone->catzs->mctx;
+ entry->magic = 0;
+ isc_refcount_destroy(&entry->refs);
+ dns_catz_options_free(&entry->opts, mctx);
+ if (dns_name_dynamic(&entry->name)) {
+ dns_name_free(&entry->name, mctx);
+ }
+ isc_mem_put(mctx, entry, sizeof(dns_catz_entry_t));
+ }
+}
+
+bool
+dns_catz_entry_validate(const dns_catz_entry_t *entry) {
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ UNUSED(entry);
+
+ return (true);
+}
+
+bool
+dns_catz_entry_cmp(const dns_catz_entry_t *ea, const dns_catz_entry_t *eb) {
+ isc_region_t ra, rb;
+
+ REQUIRE(DNS_CATZ_ENTRY_VALID(ea));
+ REQUIRE(DNS_CATZ_ENTRY_VALID(eb));
+
+ if (ea == eb) {
+ return (true);
+ }
+
+ if (ea->opts.masters.count != eb->opts.masters.count) {
+ return (false);
+ }
+
+ if (memcmp(ea->opts.masters.addrs, eb->opts.masters.addrs,
+ ea->opts.masters.count * sizeof(isc_sockaddr_t)))
+ {
+ return (false);
+ }
+
+ for (size_t i = 0; i < eb->opts.masters.count; i++) {
+ if ((ea->opts.masters.keys[i] == NULL) !=
+ (eb->opts.masters.keys[i] == NULL))
+ {
+ return (false);
+ }
+ if (ea->opts.masters.keys[i] == NULL) {
+ continue;
+ }
+ if (!dns_name_equal(ea->opts.masters.keys[i],
+ eb->opts.masters.keys[i]))
+ {
+ return (false);
+ }
+ }
+
+ /* If one is NULL and the other isn't, the entries don't match */
+ if ((ea->opts.allow_query == NULL) != (eb->opts.allow_query == NULL)) {
+ return (false);
+ }
+
+ /* If one is non-NULL, then they both are */
+ if (ea->opts.allow_query != NULL) {
+ isc_buffer_usedregion(ea->opts.allow_query, &ra);
+ isc_buffer_usedregion(eb->opts.allow_query, &rb);
+ if (isc_region_compare(&ra, &rb)) {
+ return (false);
+ }
+ }
+
+ /* Repeat the above checks with allow_transfer */
+ if ((ea->opts.allow_transfer == NULL) !=
+ (eb->opts.allow_transfer == NULL))
+ {
+ return (false);
+ }
+
+ if (ea->opts.allow_transfer != NULL) {
+ isc_buffer_usedregion(ea->opts.allow_transfer, &ra);
+ isc_buffer_usedregion(eb->opts.allow_transfer, &rb);
+ if (isc_region_compare(&ra, &rb)) {
+ return (false);
+ }
+ }
+
+ /* xxxwpk TODO compare dscps! */
+ return (true);
+}
+
+dns_name_t *
+dns_catz_zone_getname(dns_catz_zone_t *zone) {
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+
+ return (&zone->name);
+}
+
+dns_catz_options_t *
+dns_catz_zone_getdefoptions(dns_catz_zone_t *zone) {
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+
+ return (&zone->defoptions);
+}
+
+void
+dns_catz_zone_resetdefoptions(dns_catz_zone_t *zone) {
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+
+ dns_catz_options_free(&zone->defoptions, zone->catzs->mctx);
+ dns_catz_options_init(&zone->defoptions);
+}
+
+isc_result_t
+dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) {
+ isc_result_t result;
+ isc_ht_iter_t *iter1 = NULL, *iter2 = NULL;
+ isc_ht_iter_t *iteradd = NULL, *itermod = NULL;
+ isc_ht_t *toadd = NULL, *tomod = NULL;
+ bool delcur = false;
+ char czname[DNS_NAME_FORMATSIZE];
+ char zname[DNS_NAME_FORMATSIZE];
+ dns_catz_zoneop_fn_t addzone, modzone, delzone;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(newzone));
+ REQUIRE(DNS_CATZ_ZONE_VALID(target));
+
+ /* TODO verify the new zone first! */
+
+ addzone = target->catzs->zmm->addzone;
+ modzone = target->catzs->zmm->modzone;
+ delzone = target->catzs->zmm->delzone;
+
+ /* Copy zoneoptions from newzone into target. */
+
+ dns_catz_options_free(&target->zoneoptions, target->catzs->mctx);
+ dns_catz_options_copy(target->catzs->mctx, &newzone->zoneoptions,
+ &target->zoneoptions);
+ dns_catz_options_setdefault(target->catzs->mctx, &target->defoptions,
+ &target->zoneoptions);
+
+ dns_name_format(&target->name, czname, DNS_NAME_FORMATSIZE);
+
+ isc_ht_init(&toadd, target->catzs->mctx, 16);
+
+ isc_ht_init(&tomod, target->catzs->mctx, 16);
+
+ isc_ht_iter_create(newzone->entries, &iter1);
+
+ isc_ht_iter_create(target->entries, &iter2);
+
+ /*
+ * We can create those iterators now, even though toadd and tomod are
+ * empty
+ */
+ isc_ht_iter_create(toadd, &iteradd);
+
+ isc_ht_iter_create(tomod, &itermod);
+
+ /*
+ * First - walk the new zone and find all nodes that are not in the
+ * old zone, or are in both zones and are modified.
+ */
+ for (result = isc_ht_iter_first(iter1); result == ISC_R_SUCCESS;
+ result = delcur ? isc_ht_iter_delcurrent_next(iter1)
+ : isc_ht_iter_next(iter1))
+ {
+ dns_catz_entry_t *nentry = NULL;
+ dns_catz_entry_t *oentry = NULL;
+ dns_zone_t *zone = NULL;
+ unsigned char *key = NULL;
+ size_t keysize;
+ delcur = false;
+
+ isc_ht_iter_current(iter1, (void **)&nentry);
+ isc_ht_iter_currentkey(iter1, &key, &keysize);
+
+ /*
+ * Spurious record that came from suboption without main
+ * record, removed.
+ * xxxwpk: make it a separate verification phase?
+ */
+ if (dns_name_countlabels(&nentry->name) == 0) {
+ dns_catz_entry_detach(newzone, &nentry);
+ delcur = true;
+ continue;
+ }
+
+ dns_name_format(&nentry->name, zname, DNS_NAME_FORMATSIZE);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
+ "catz: iterating over '%s' from catalog '%s'",
+ zname, czname);
+ dns_catz_options_setdefault(target->catzs->mctx,
+ &target->zoneoptions,
+ &nentry->opts);
+
+ result = isc_ht_find(target->entries, key, (uint32_t)keysize,
+ (void **)&oentry);
+ if (result != ISC_R_SUCCESS) {
+ catz_entry_add_or_mod(target, toadd, key, keysize,
+ nentry, NULL, "adding", zname,
+ czname);
+ continue;
+ }
+
+ result = dns_zt_find(target->catzs->view->zonetable,
+ dns_catz_entry_getname(nentry), 0, NULL,
+ &zone);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
+ "catz: zone '%s' was expected to exist "
+ "but can not be found, will be restored",
+ zname);
+ catz_entry_add_or_mod(target, toadd, key, keysize,
+ nentry, oentry, "adding", zname,
+ czname);
+ continue;
+ }
+ dns_zone_detach(&zone);
+
+ if (dns_catz_entry_cmp(oentry, nentry) != true) {
+ catz_entry_add_or_mod(target, tomod, key, keysize,
+ nentry, oentry, "modifying",
+ zname, czname);
+ continue;
+ }
+
+ /*
+ * Delete the old entry so that it won't accidentally be
+ * removed as a non-existing entry below.
+ */
+ dns_catz_entry_detach(target, &oentry);
+ result = isc_ht_delete(target->entries, key, (uint32_t)keysize);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ RUNTIME_CHECK(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter1);
+
+ /*
+ * Then - walk the old zone; only deleted entries should remain.
+ */
+ for (result = isc_ht_iter_first(iter2); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(iter2))
+ {
+ dns_catz_entry_t *entry = NULL;
+ isc_ht_iter_current(iter2, (void **)&entry);
+
+ dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE);
+ result = delzone(entry, target, target->catzs->view,
+ target->catzs->taskmgr,
+ target->catzs->zmm->udata);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: deleting zone '%s' from catalog '%s' - %s",
+ zname, czname, isc_result_totext(result));
+ dns_catz_entry_detach(target, &entry);
+ }
+ RUNTIME_CHECK(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter2);
+ /* At this moment target->entries has to be be empty. */
+ INSIST(isc_ht_count(target->entries) == 0);
+ isc_ht_destroy(&target->entries);
+
+ for (result = isc_ht_iter_first(iteradd); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(iteradd))
+ {
+ dns_catz_entry_t *entry = NULL;
+ isc_ht_iter_current(iteradd, (void **)&entry);
+
+ dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE);
+ result = addzone(entry, target, target->catzs->view,
+ target->catzs->taskmgr,
+ target->catzs->zmm->udata);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: adding zone '%s' from catalog "
+ "'%s' - %s",
+ zname, czname, isc_result_totext(result));
+ }
+
+ for (result = isc_ht_iter_first(itermod); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(itermod))
+ {
+ dns_catz_entry_t *entry = NULL;
+ isc_ht_iter_current(itermod, (void **)&entry);
+
+ dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE);
+ result = modzone(entry, target, target->catzs->view,
+ target->catzs->taskmgr,
+ target->catzs->zmm->udata);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: modifying zone '%s' from catalog "
+ "'%s' - %s",
+ zname, czname, isc_result_totext(result));
+ }
+
+ target->entries = newzone->entries;
+ newzone->entries = NULL;
+
+ result = ISC_R_SUCCESS;
+
+ isc_ht_iter_destroy(&iteradd);
+ isc_ht_iter_destroy(&itermod);
+ isc_ht_destroy(&toadd);
+ isc_ht_destroy(&tomod);
+
+ return (result);
+}
+
+isc_result_t
+dns_catz_new_zones(dns_catz_zones_t **catzsp, dns_catz_zonemodmethods_t *zmm,
+ isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr) {
+ dns_catz_zones_t *new_zones;
+ isc_result_t result;
+
+ REQUIRE(catzsp != NULL && *catzsp == NULL);
+ REQUIRE(zmm != NULL);
+
+ new_zones = isc_mem_get(mctx, sizeof(*new_zones));
+ memset(new_zones, 0, sizeof(*new_zones));
+
+ isc_mutex_init(&new_zones->lock);
+
+ isc_refcount_init(&new_zones->refs, 1);
+
+ isc_ht_init(&new_zones->zones, mctx, 4);
+
+ isc_mem_attach(mctx, &new_zones->mctx);
+ new_zones->zmm = zmm;
+ new_zones->timermgr = timermgr;
+ new_zones->taskmgr = taskmgr;
+
+ result = isc_task_create(taskmgr, 0, &new_zones->updater);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_ht;
+ }
+ new_zones->magic = DNS_CATZ_ZONES_MAGIC;
+
+ *catzsp = new_zones;
+ return (ISC_R_SUCCESS);
+
+cleanup_ht:
+ isc_ht_destroy(&new_zones->zones);
+ isc_refcount_destroy(&new_zones->refs);
+ isc_mutex_destroy(&new_zones->lock);
+ isc_mem_putanddetach(&new_zones->mctx, new_zones, sizeof(*new_zones));
+
+ return (result);
+}
+
+void
+dns_catz_catzs_set_view(dns_catz_zones_t *catzs, dns_view_t *view) {
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+ REQUIRE(DNS_VIEW_VALID(view));
+ /* Either it's a new one or it's being reconfigured. */
+ REQUIRE(catzs->view == NULL || !strcmp(catzs->view->name, view->name));
+
+ catzs->view = view;
+}
+
+isc_result_t
+dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **zonep,
+ const dns_name_t *name) {
+ isc_result_t result;
+ dns_catz_zone_t *new_zone;
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+ REQUIRE(zonep != NULL && *zonep == NULL);
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+
+ new_zone = isc_mem_get(catzs->mctx, sizeof(*new_zone));
+
+ memset(new_zone, 0, sizeof(*new_zone));
+
+ dns_name_init(&new_zone->name, NULL);
+ dns_name_dup(name, catzs->mctx, &new_zone->name);
+
+ isc_ht_init(&new_zone->entries, catzs->mctx, 16);
+
+ new_zone->updatetimer = NULL;
+ result = isc_timer_create(catzs->timermgr, isc_timertype_inactive, NULL,
+ NULL, catzs->updater,
+ dns_catz_update_taskaction, new_zone,
+ &new_zone->updatetimer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_ht;
+ }
+
+ isc_time_settoepoch(&new_zone->lastupdated);
+ new_zone->updatepending = false;
+ new_zone->db = NULL;
+ new_zone->dbversion = NULL;
+ new_zone->catzs = catzs;
+ dns_catz_options_init(&new_zone->defoptions);
+ dns_catz_options_init(&new_zone->zoneoptions);
+ new_zone->active = true;
+ new_zone->db_registered = false;
+ new_zone->version = (uint32_t)(-1);
+ isc_refcount_init(&new_zone->refs, 1);
+ new_zone->magic = DNS_CATZ_ZONE_MAGIC;
+
+ *zonep = new_zone;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_ht:
+ isc_ht_destroy(&new_zone->entries);
+ dns_name_free(&new_zone->name, catzs->mctx);
+ isc_mem_put(catzs->mctx, new_zone, sizeof(*new_zone));
+
+ return (result);
+}
+
+isc_result_t
+dns_catz_add_zone(dns_catz_zones_t *catzs, const dns_name_t *name,
+ dns_catz_zone_t **zonep) {
+ dns_catz_zone_t *new_zone = NULL;
+ isc_result_t result, tresult;
+ char zname[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+ REQUIRE(zonep != NULL && *zonep == NULL);
+
+ dns_name_format(name, zname, DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_DEBUG(3), "catz: dns_catz_add_zone %s", zname);
+
+ LOCK(&catzs->lock);
+
+ result = dns_catz_new_zone(catzs, &new_zone, name);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = isc_ht_add(catzs->zones, new_zone->name.ndata,
+ new_zone->name.length, new_zone);
+ if (result != ISC_R_SUCCESS) {
+ dns_catz_zone_detach(&new_zone);
+ if (result != ISC_R_EXISTS) {
+ goto cleanup;
+ }
+ }
+
+ if (result == ISC_R_EXISTS) {
+ tresult = isc_ht_find(catzs->zones, name->ndata, name->length,
+ (void **)&new_zone);
+ INSIST(tresult == ISC_R_SUCCESS && !new_zone->active);
+ new_zone->active = true;
+ }
+
+ *zonep = new_zone;
+
+cleanup:
+ UNLOCK(&catzs->lock);
+
+ return (result);
+}
+
+dns_catz_zone_t *
+dns_catz_get_zone(dns_catz_zones_t *catzs, const dns_name_t *name) {
+ isc_result_t result;
+ dns_catz_zone_t *found = NULL;
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+
+ LOCK(&catzs->lock);
+ result = isc_ht_find(catzs->zones, name->ndata, name->length,
+ (void **)&found);
+ UNLOCK(&catzs->lock);
+ if (result != ISC_R_SUCCESS) {
+ return (NULL);
+ }
+
+ return (found);
+}
+
+void
+dns_catz_catzs_attach(dns_catz_zones_t *catzs, dns_catz_zones_t **catzsp) {
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+ REQUIRE(catzsp != NULL && *catzsp == NULL);
+
+ isc_refcount_increment(&catzs->refs);
+ *catzsp = catzs;
+}
+
+void
+dns_catz_zone_attach(dns_catz_zone_t *zone, dns_catz_zone_t **zonep) {
+ REQUIRE(zonep != NULL && *zonep == NULL);
+
+ isc_refcount_increment(&zone->refs);
+ *zonep = zone;
+}
+
+void
+dns_catz_zone_detach(dns_catz_zone_t **zonep) {
+ REQUIRE(zonep != NULL && *zonep != NULL);
+ dns_catz_zone_t *zone = *zonep;
+ *zonep = NULL;
+
+ if (isc_refcount_decrement(&zone->refs) == 1) {
+ isc_mem_t *mctx = zone->catzs->mctx;
+ isc_refcount_destroy(&zone->refs);
+ if (zone->entries != NULL) {
+ isc_ht_iter_t *iter = NULL;
+ isc_result_t result;
+ isc_ht_iter_create(zone->entries, &iter);
+ for (result = isc_ht_iter_first(iter);
+ result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(iter))
+ {
+ dns_catz_entry_t *entry = NULL;
+
+ isc_ht_iter_current(iter, (void **)&entry);
+ dns_catz_entry_detach(zone, &entry);
+ }
+ INSIST(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter);
+
+ /* The hashtable has to be empty now. */
+ INSIST(isc_ht_count(zone->entries) == 0);
+ isc_ht_destroy(&zone->entries);
+ }
+ zone->magic = 0;
+ isc_timer_destroy(&zone->updatetimer);
+ if (zone->db_registered) {
+ dns_db_updatenotify_unregister(
+ zone->db, dns_catz_dbupdate_callback,
+ zone->catzs);
+ }
+ if (zone->dbversion) {
+ dns_db_closeversion(zone->db, &zone->dbversion, false);
+ }
+ if (zone->db != NULL) {
+ dns_db_detach(&zone->db);
+ }
+
+ dns_name_free(&zone->name, mctx);
+ dns_catz_options_free(&zone->defoptions, mctx);
+ dns_catz_options_free(&zone->zoneoptions, mctx);
+
+ zone->catzs = NULL;
+ isc_mem_put(mctx, zone, sizeof(dns_catz_zone_t));
+ }
+}
+
+void
+dns_catz_catzs_detach(dns_catz_zones_t **catzsp) {
+ dns_catz_zones_t *catzs;
+
+ REQUIRE(catzsp != NULL && DNS_CATZ_ZONES_VALID(*catzsp));
+
+ catzs = *catzsp;
+ *catzsp = NULL;
+
+ if (isc_refcount_decrement(&catzs->refs) == 1) {
+ catzs->magic = 0;
+ isc_task_destroy(&catzs->updater);
+ isc_mutex_destroy(&catzs->lock);
+ if (catzs->zones != NULL) {
+ isc_ht_iter_t *iter = NULL;
+ isc_result_t result;
+ isc_ht_iter_create(catzs->zones, &iter);
+ for (result = isc_ht_iter_first(iter);
+ result == ISC_R_SUCCESS;)
+ {
+ dns_catz_zone_t *zone = NULL;
+ isc_ht_iter_current(iter, (void **)&zone);
+ result = isc_ht_iter_delcurrent_next(iter);
+ dns_catz_zone_detach(&zone);
+ }
+ INSIST(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter);
+ INSIST(isc_ht_count(catzs->zones) == 0);
+ isc_ht_destroy(&catzs->zones);
+ }
+ isc_refcount_destroy(&catzs->refs);
+ isc_mem_putanddetach(&catzs->mctx, catzs, sizeof(*catzs));
+ }
+}
+
+typedef enum {
+ CATZ_OPT_NONE,
+ CATZ_OPT_ZONES,
+ CATZ_OPT_MASTERS,
+ CATZ_OPT_ALLOW_QUERY,
+ CATZ_OPT_ALLOW_TRANSFER,
+ CATZ_OPT_VERSION,
+} catz_opt_t;
+
+static bool
+catz_opt_cmp(const dns_label_t *option, const char *opt) {
+ unsigned int l = strlen(opt);
+ if (option->length - 1 == l &&
+ memcmp(opt, option->base + 1, l - 1) == 0)
+ {
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+static catz_opt_t
+catz_get_option(const dns_label_t *option) {
+ if (catz_opt_cmp(option, "zones")) {
+ return (CATZ_OPT_ZONES);
+ } else if (catz_opt_cmp(option, "masters")) {
+ return (CATZ_OPT_MASTERS);
+ } else if (catz_opt_cmp(option, "allow-query")) {
+ return (CATZ_OPT_ALLOW_QUERY);
+ } else if (catz_opt_cmp(option, "allow-transfer")) {
+ return (CATZ_OPT_ALLOW_TRANSFER);
+ } else if (catz_opt_cmp(option, "version")) {
+ return (CATZ_OPT_VERSION);
+ } else {
+ return (CATZ_OPT_NONE);
+ }
+}
+
+static isc_result_t
+catz_process_zones(dns_catz_zone_t *zone, dns_rdataset_t *value,
+ dns_name_t *name) {
+ dns_label_t mhash;
+ dns_name_t opt;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(DNS_RDATASET_VALID(value));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+
+ if (value->rdclass != dns_rdataclass_in) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (name->labels == 0) {
+ return (ISC_R_FAILURE);
+ }
+
+ dns_name_getlabel(name, name->labels - 1, &mhash);
+
+ if (name->labels == 1) {
+ return (catz_process_zones_entry(zone, value, &mhash));
+ } else {
+ dns_name_init(&opt, NULL);
+ dns_name_split(name, 1, &opt, NULL);
+ return (catz_process_zones_suboption(zone, value, &mhash,
+ &opt));
+ }
+}
+
+static isc_result_t
+catz_process_zones_entry(dns_catz_zone_t *zone, dns_rdataset_t *value,
+ dns_label_t *mhash) {
+ isc_result_t result;
+ dns_rdata_t rdata;
+ dns_rdata_ptr_t ptr;
+ dns_catz_entry_t *entry = NULL;
+
+ /*
+ * We only take -first- value, as mhash must be
+ * different.
+ */
+ if (value->type != dns_rdatatype_ptr) {
+ return (ISC_R_FAILURE);
+ }
+
+ result = dns_rdataset_first(value);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_FAILURE);
+ }
+
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &ptr, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ result = isc_ht_find(zone->entries, mhash->base, mhash->length,
+ (void **)&entry);
+ if (result == ISC_R_SUCCESS) {
+ if (dns_name_countlabels(&entry->name) != 0) {
+ /* We have a duplicate. */
+ dns_rdata_freestruct(&ptr);
+ return (ISC_R_FAILURE);
+ } else {
+ dns_name_dup(&ptr.ptr, zone->catzs->mctx, &entry->name);
+ }
+ } else {
+ result = dns_catz_entry_new(zone->catzs->mctx, &ptr.ptr,
+ &entry);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdata_freestruct(&ptr);
+ return (result);
+ }
+
+ result = isc_ht_add(zone->entries, mhash->base, mhash->length,
+ entry);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdata_freestruct(&ptr);
+ dns_catz_entry_detach(zone, &entry);
+ return (result);
+ }
+ }
+
+ dns_rdata_freestruct(&ptr);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+catz_process_version(dns_catz_zone_t *zone, dns_rdataset_t *value) {
+ isc_result_t result;
+ dns_rdata_t rdata;
+ dns_rdata_txt_t rdatatxt;
+ dns_rdata_txt_string_t rdatastr;
+ uint32_t tversion;
+ char t[16];
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(DNS_RDATASET_VALID(value));
+
+ if (value->rdclass != dns_rdataclass_in ||
+ value->type != dns_rdatatype_txt)
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ result = dns_rdataset_first(value);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &rdatatxt, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ result = dns_rdata_txt_first(&rdatatxt);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_rdata_txt_current(&rdatatxt, &rdatastr);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_rdata_txt_next(&rdatatxt);
+ if (result != ISC_R_NOMORE) {
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ if (rdatastr.length > 15) {
+ result = ISC_R_BADNUMBER;
+ goto cleanup;
+ }
+ memmove(t, rdatastr.data, rdatastr.length);
+ t[rdatastr.length] = 0;
+ result = isc_parse_uint32(&tversion, t, 10);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ zone->version = tversion;
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ dns_rdata_freestruct(&rdatatxt);
+ return (result);
+}
+
+static isc_result_t
+catz_process_masters(dns_catz_zone_t *zone, dns_ipkeylist_t *ipkl,
+ dns_rdataset_t *value, dns_name_t *name) {
+ isc_result_t result;
+ dns_rdata_t rdata;
+ dns_rdata_in_a_t rdata_a;
+ dns_rdata_in_aaaa_t rdata_aaaa;
+ dns_rdata_txt_t rdata_txt;
+ dns_rdata_txt_string_t rdatastr;
+ dns_name_t *keyname = NULL;
+ isc_mem_t *mctx;
+ char keycbuf[DNS_NAME_FORMATSIZE];
+ isc_buffer_t keybuf;
+ unsigned int rcount;
+ unsigned int i;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(ipkl != NULL);
+ REQUIRE(DNS_RDATASET_VALID(value));
+ REQUIRE(dns_rdataset_isassociated(value));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+
+ mctx = zone->catzs->mctx;
+ memset(&rdata_a, 0, sizeof(rdata_a));
+ memset(&rdata_aaaa, 0, sizeof(rdata_aaaa));
+ memset(&rdata_txt, 0, sizeof(rdata_txt));
+ isc_buffer_init(&keybuf, keycbuf, sizeof(keycbuf));
+
+ /*
+ * We have three possibilities here:
+ * - either empty name and IN A/IN AAAA record
+ * - label and IN A/IN AAAA
+ * - label and IN TXT - TSIG key name
+ */
+ if (value->rdclass != dns_rdataclass_in) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (name->labels > 0) {
+ isc_sockaddr_t sockaddr;
+
+ /*
+ * We're pre-preparing the data once, we'll put it into
+ * the right spot in the masters array once we find it.
+ */
+ result = dns_rdataset_first(value);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+ switch (value->type) {
+ case dns_rdatatype_a:
+ result = dns_rdata_tostruct(&rdata, &rdata_a, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_sockaddr_fromin(&sockaddr, &rdata_a.in_addr, 0);
+ break;
+ case dns_rdatatype_aaaa:
+ result = dns_rdata_tostruct(&rdata, &rdata_aaaa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_sockaddr_fromin6(&sockaddr, &rdata_aaaa.in6_addr,
+ 0);
+ break;
+ case dns_rdatatype_txt:
+ result = dns_rdata_tostruct(&rdata, &rdata_txt, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ result = dns_rdata_txt_first(&rdata_txt);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_rdata_txt_current(&rdata_txt, &rdatastr);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_rdata_txt_next(&rdata_txt);
+ if (result != ISC_R_NOMORE) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* rdatastr.length < DNS_NAME_MAXTEXT */
+ keyname = isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(keyname, 0);
+ memmove(keycbuf, rdatastr.data, rdatastr.length);
+ keycbuf[rdatastr.length] = 0;
+ result = dns_name_fromstring(keyname, keycbuf, 0, mctx);
+ if (result != ISC_R_SUCCESS) {
+ dns_name_free(keyname, mctx);
+ isc_mem_put(mctx, keyname, sizeof(dns_name_t));
+ return (result);
+ }
+ break;
+ default:
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * We have to find the appropriate labeled record in masters
+ * if it exists.
+ * In common case we'll have no more than 3-4 records here so
+ * no optimization.
+ */
+ for (i = 0; i < ipkl->count; i++) {
+ if (ipkl->labels[i] != NULL &&
+ !dns_name_compare(name, ipkl->labels[i]))
+ {
+ break;
+ }
+ }
+
+ if (i < ipkl->count) { /* we have this record already */
+ if (value->type == dns_rdatatype_txt) {
+ ipkl->keys[i] = keyname;
+ } else { /* A/AAAA */
+ memmove(&ipkl->addrs[i], &sockaddr,
+ sizeof(isc_sockaddr_t));
+ }
+ } else {
+ result = dns_ipkeylist_resize(mctx, ipkl, i + 1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ ipkl->labels[i] = isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(ipkl->labels[i], NULL);
+ dns_name_dup(name, mctx, ipkl->labels[i]);
+
+ if (value->type == dns_rdatatype_txt) {
+ ipkl->keys[i] = keyname;
+ } else { /* A/AAAA */
+ memmove(&ipkl->addrs[i], &sockaddr,
+ sizeof(isc_sockaddr_t));
+ }
+ ipkl->count++;
+ }
+ return (ISC_R_SUCCESS);
+ }
+ /* else - 'simple' case - without labels */
+
+ if (value->type != dns_rdatatype_a && value->type != dns_rdatatype_aaaa)
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ rcount = dns_rdataset_count(value) + ipkl->count;
+
+ result = dns_ipkeylist_resize(mctx, ipkl, rcount);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ for (result = dns_rdataset_first(value); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(value))
+ {
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+ /*
+ * port 0 == take the default
+ */
+ if (value->type == dns_rdatatype_a) {
+ result = dns_rdata_tostruct(&rdata, &rdata_a, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_sockaddr_fromin(&ipkl->addrs[ipkl->count],
+ &rdata_a.in_addr, 0);
+ } else {
+ result = dns_rdata_tostruct(&rdata, &rdata_aaaa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_sockaddr_fromin6(&ipkl->addrs[ipkl->count],
+ &rdata_aaaa.in6_addr, 0);
+ }
+ ipkl->keys[ipkl->count] = NULL;
+ ipkl->labels[ipkl->count] = NULL;
+ ipkl->count++;
+ dns_rdata_freestruct(&rdata_a);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+catz_process_apl(dns_catz_zone_t *zone, isc_buffer_t **aclbp,
+ dns_rdataset_t *value) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdata_t rdata;
+ dns_rdata_in_apl_t rdata_apl;
+ dns_rdata_apl_ent_t apl_ent;
+ isc_netaddr_t addr;
+ isc_buffer_t *aclb = NULL;
+ unsigned char buf[256]; /* larger than INET6_ADDRSTRLEN */
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(aclbp != NULL);
+ REQUIRE(*aclbp == NULL);
+ REQUIRE(DNS_RDATASET_VALID(value));
+ REQUIRE(dns_rdataset_isassociated(value));
+
+ if (value->rdclass != dns_rdataclass_in ||
+ value->type != dns_rdatatype_apl)
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ if (dns_rdataset_count(value) > 1) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
+ "catz: more than one APL entry for member zone, "
+ "result is undefined");
+ }
+ result = dns_rdataset_first(value);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(value, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rdata_apl, zone->catzs->mctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_allocate(zone->catzs->mctx, &aclb, 16);
+ isc_buffer_setautorealloc(aclb, true);
+ for (result = dns_rdata_apl_first(&rdata_apl); result == ISC_R_SUCCESS;
+ result = dns_rdata_apl_next(&rdata_apl))
+ {
+ result = dns_rdata_apl_current(&rdata_apl, &apl_ent);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ memset(buf, 0, sizeof(buf));
+ if (apl_ent.data != NULL && apl_ent.length > 0) {
+ memmove(buf, apl_ent.data, apl_ent.length);
+ }
+ if (apl_ent.family == 1) {
+ isc_netaddr_fromin(&addr, (struct in_addr *)buf);
+ } else if (apl_ent.family == 2) {
+ isc_netaddr_fromin6(&addr, (struct in6_addr *)buf);
+ } else {
+ continue; /* xxxwpk log it or simply ignore? */
+ }
+ if (apl_ent.negative) {
+ isc_buffer_putuint8(aclb, '!');
+ }
+ isc_buffer_reserve(&aclb, INET6_ADDRSTRLEN);
+ result = isc_netaddr_totext(&addr, aclb);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if ((apl_ent.family == 1 && apl_ent.prefix < 32) ||
+ (apl_ent.family == 2 && apl_ent.prefix < 128))
+ {
+ isc_buffer_putuint8(aclb, '/');
+ isc_buffer_putdecint(aclb, apl_ent.prefix);
+ }
+ isc_buffer_putstr(aclb, "; ");
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ } else {
+ goto cleanup;
+ }
+ *aclbp = aclb;
+ aclb = NULL;
+cleanup:
+ if (aclb != NULL) {
+ isc_buffer_free(&aclb);
+ }
+ dns_rdata_freestruct(&rdata_apl);
+ return (result);
+}
+
+static isc_result_t
+catz_process_zones_suboption(dns_catz_zone_t *zone, dns_rdataset_t *value,
+ dns_label_t *mhash, dns_name_t *name) {
+ isc_result_t result;
+ dns_catz_entry_t *entry = NULL;
+ dns_label_t option;
+ dns_name_t prefix;
+ catz_opt_t opt;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(mhash != NULL);
+ REQUIRE(DNS_RDATASET_VALID(value));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+
+ if (name->labels == 0) {
+ return (ISC_R_FAILURE);
+ }
+ dns_name_getlabel(name, name->labels - 1, &option);
+ opt = catz_get_option(&option);
+
+ /*
+ * We're adding this entry now, in case the option is invalid we'll get
+ * rid of it in verification phase.
+ */
+ result = isc_ht_find(zone->entries, mhash->base, mhash->length,
+ (void **)&entry);
+ if (result != ISC_R_SUCCESS) {
+ result = dns_catz_entry_new(zone->catzs->mctx, NULL, &entry);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = isc_ht_add(zone->entries, mhash->base, mhash->length,
+ entry);
+ if (result != ISC_R_SUCCESS) {
+ dns_catz_entry_detach(zone, &entry);
+ return (result);
+ }
+ }
+
+ dns_name_init(&prefix, NULL);
+ dns_name_split(name, 1, &prefix, NULL);
+ switch (opt) {
+ case CATZ_OPT_MASTERS:
+ return (catz_process_masters(zone, &entry->opts.masters, value,
+ &prefix));
+ case CATZ_OPT_ALLOW_QUERY:
+ if (prefix.labels != 0) {
+ return (ISC_R_FAILURE);
+ }
+ return (catz_process_apl(zone, &entry->opts.allow_query,
+ value));
+ case CATZ_OPT_ALLOW_TRANSFER:
+ if (prefix.labels != 0) {
+ return (ISC_R_FAILURE);
+ }
+ return (catz_process_apl(zone, &entry->opts.allow_transfer,
+ value));
+ default:
+ return (ISC_R_FAILURE);
+ }
+
+ return (ISC_R_FAILURE);
+}
+
+static void
+catz_entry_add_or_mod(dns_catz_zone_t *target, isc_ht_t *ht, unsigned char *key,
+ size_t keysize, dns_catz_entry_t *nentry,
+ dns_catz_entry_t *oentry, const char *msg,
+ const char *zname, const char *czname) {
+ isc_result_t result = isc_ht_add(ht, key, (uint32_t)keysize, nentry);
+
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: error %s zone '%s' from catalog '%s' - %s",
+ msg, zname, czname, isc_result_totext(result));
+ }
+ if (oentry != NULL) {
+ dns_catz_entry_detach(target, &oentry);
+ result = isc_ht_delete(target->entries, key, (uint32_t)keysize);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+catz_process_value(dns_catz_zone_t *zone, dns_name_t *name,
+ dns_rdataset_t *rdataset) {
+ dns_label_t option;
+ dns_name_t prefix;
+ catz_opt_t opt;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+
+ dns_name_getlabel(name, name->labels - 1, &option);
+ opt = catz_get_option(&option);
+ dns_name_init(&prefix, NULL);
+ dns_name_split(name, 1, &prefix, NULL);
+ switch (opt) {
+ case CATZ_OPT_ZONES:
+ return (catz_process_zones(zone, rdataset, &prefix));
+ case CATZ_OPT_MASTERS:
+ return (catz_process_masters(zone, &zone->zoneoptions.masters,
+ rdataset, &prefix));
+ case CATZ_OPT_ALLOW_QUERY:
+ if (prefix.labels != 0) {
+ return (ISC_R_FAILURE);
+ }
+ return (catz_process_apl(zone, &zone->zoneoptions.allow_query,
+ rdataset));
+ case CATZ_OPT_ALLOW_TRANSFER:
+ if (prefix.labels != 0) {
+ return (ISC_R_FAILURE);
+ }
+ return (catz_process_apl(
+ zone, &zone->zoneoptions.allow_transfer, rdataset));
+ case CATZ_OPT_VERSION:
+ if (prefix.labels != 0) {
+ return (ISC_R_FAILURE);
+ }
+ return (catz_process_version(zone, rdataset));
+ default:
+ return (ISC_R_FAILURE);
+ }
+}
+
+isc_result_t
+dns_catz_update_process(dns_catz_zones_t *catzs, dns_catz_zone_t *zone,
+ const dns_name_t *src_name, dns_rdataset_t *rdataset) {
+ isc_result_t result;
+ int order;
+ unsigned int nlabels;
+ dns_namereln_t nrres;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_soa_t soa;
+ dns_name_t prefix;
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(ISC_MAGIC_VALID(src_name, DNS_NAME_MAGIC));
+
+ nrres = dns_name_fullcompare(src_name, &zone->name, &order, &nlabels);
+ if (nrres == dns_namereln_equal) {
+ if (rdataset->type == dns_rdatatype_soa) {
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * xxxwpk TODO do we want to save something from SOA?
+ */
+ return (result);
+ } else if (rdataset->type == dns_rdatatype_ns) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_UNEXPECTED);
+ }
+ } else if (nrres != dns_namereln_subdomain) {
+ return (ISC_R_UNEXPECTED);
+ }
+
+ dns_name_init(&prefix, NULL);
+ dns_name_split(src_name, zone->name.labels, &prefix, NULL);
+ result = catz_process_value(zone, &prefix, rdataset);
+
+ return (result);
+}
+
+static isc_result_t
+digest2hex(unsigned char *digest, unsigned int digestlen, char *hash,
+ size_t hashlen) {
+ unsigned int i;
+ int ret;
+ for (i = 0; i < digestlen; i++) {
+ size_t left = hashlen - i * 2;
+ ret = snprintf(hash + i * 2, left, "%02x", digest[i]);
+ if (ret < 0 || (size_t)ret >= left) {
+ return (ISC_R_NOSPACE);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_catz_generate_masterfilename(dns_catz_zone_t *zone, dns_catz_entry_t *entry,
+ isc_buffer_t **buffer) {
+ isc_buffer_t *tbuf = NULL;
+ isc_region_t r;
+ isc_result_t result;
+ size_t rlen;
+ bool special = false;
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ REQUIRE(buffer != NULL && *buffer != NULL);
+
+ isc_buffer_allocate(zone->catzs->mctx, &tbuf,
+ strlen(zone->catzs->view->name) +
+ 2 * DNS_NAME_FORMATSIZE + 2);
+
+ isc_buffer_putstr(tbuf, zone->catzs->view->name);
+ isc_buffer_putstr(tbuf, "_");
+ result = dns_name_totext(&zone->name, true, tbuf);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_buffer_putstr(tbuf, "_");
+ result = dns_name_totext(&entry->name, true, tbuf);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Search for slash and other special characters in the view and
+ * zone names. Add a null terminator so we can use strpbrk(), then
+ * remove it.
+ */
+ isc_buffer_putuint8(tbuf, 0);
+ if (strpbrk(isc_buffer_base(tbuf), "\\/:") != NULL) {
+ special = true;
+ }
+ isc_buffer_subtract(tbuf, 1);
+
+ /* __catz__<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);
+}
+
+isc_result_t
+dns_catz_generate_zonecfg(dns_catz_zone_t *zone, dns_catz_entry_t *entry,
+ isc_buffer_t **buf) {
+ /*
+ * We have to generate a text buffer with regular zone config:
+ * zone "foo.bar" {
+ * type slave;
+ * masters [ dscp X ] { ip1 port port1; ip2 port port2; };
+ * }
+ */
+ isc_buffer_t *buffer = NULL;
+ isc_region_t region;
+ isc_result_t result;
+ uint32_t i;
+ isc_netaddr_t netaddr;
+ char pbuf[sizeof("65535")]; /* used both for port number and DSCP */
+ char zname[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+ REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
+ REQUIRE(buf != NULL && *buf == NULL);
+
+ /*
+ * The buffer will be reallocated if something won't fit,
+ * ISC_BUFFER_INCR seems like a good start.
+ */
+ isc_buffer_allocate(zone->catzs->mctx, &buffer, ISC_BUFFER_INCR);
+
+ isc_buffer_setautorealloc(buffer, true);
+ isc_buffer_putstr(buffer, "zone \"");
+ dns_name_totext(&entry->name, true, buffer);
+ isc_buffer_putstr(buffer, "\" { type slave; masters");
+
+ /*
+ * DSCP value has no default, but when it is specified, it is identical
+ * for all masters and cannot be overridden for a specific master IP, so
+ * use the DSCP value set for the first master
+ */
+ if (entry->opts.masters.count > 0 && entry->opts.masters.dscps[0] >= 0)
+ {
+ isc_buffer_putstr(buffer, " dscp ");
+ snprintf(pbuf, sizeof(pbuf), "%hd",
+ entry->opts.masters.dscps[0]);
+ isc_buffer_putstr(buffer, pbuf);
+ }
+
+ isc_buffer_putstr(buffer, " { ");
+ for (i = 0; i < entry->opts.masters.count; i++) {
+ /*
+ * Every master must have an IP address assigned.
+ */
+ switch (entry->opts.masters.addrs[i].type.sa.sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ break;
+ default:
+ dns_name_format(&entry->name, zname,
+ DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: zone '%s' uses an invalid master "
+ "(no IP address assigned)",
+ zname);
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ isc_netaddr_fromsockaddr(&netaddr,
+ &entry->opts.masters.addrs[i]);
+ isc_buffer_reserve(&buffer, INET6_ADDRSTRLEN);
+ result = isc_netaddr_totext(&netaddr, buffer);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ isc_buffer_putstr(buffer, " port ");
+ snprintf(pbuf, sizeof(pbuf), "%u",
+ isc_sockaddr_getport(&entry->opts.masters.addrs[i]));
+ isc_buffer_putstr(buffer, pbuf);
+
+ if (entry->opts.masters.keys[i] != NULL) {
+ isc_buffer_putstr(buffer, " key ");
+ result = dns_name_totext(entry->opts.masters.keys[i],
+ true, buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ isc_buffer_putstr(buffer, "; ");
+ }
+ isc_buffer_putstr(buffer, "}; ");
+ if (!entry->opts.in_memory) {
+ isc_buffer_putstr(buffer, "file \"");
+ result = dns_catz_generate_masterfilename(zone, entry, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_buffer_putstr(buffer, "\"; ");
+ }
+ if (entry->opts.allow_query != NULL) {
+ isc_buffer_putstr(buffer, "allow-query { ");
+ isc_buffer_usedregion(entry->opts.allow_query, &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);
+}
+
+void
+dns_catz_update_taskaction(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ dns_catz_zone_t *zone;
+ (void)task;
+
+ REQUIRE(event != NULL);
+ zone = event->ev_arg;
+ REQUIRE(DNS_CATZ_ZONE_VALID(zone));
+
+ LOCK(&zone->catzs->lock);
+ zone->updatepending = false;
+ dns_catz_update_from_db(zone->db, zone->catzs);
+ result = isc_timer_reset(zone->updatetimer, isc_timertype_inactive,
+ NULL, NULL, true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_event_free(&event);
+ result = isc_time_now(&zone->lastupdated);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ UNLOCK(&zone->catzs->lock);
+}
+
+isc_result_t
+dns_catz_dbupdate_callback(dns_db_t *db, void *fn_arg) {
+ dns_catz_zones_t *catzs;
+ dns_catz_zone_t *zone = NULL;
+ isc_time_t now;
+ uint64_t tdiff;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_region_t r;
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(DNS_CATZ_ZONES_VALID(fn_arg));
+ catzs = (dns_catz_zones_t *)fn_arg;
+
+ dns_name_toregion(&db->origin, &r);
+
+ LOCK(&catzs->lock);
+ result = isc_ht_find(catzs->zones, r.base, r.length, (void **)&zone);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* New zone came as AXFR */
+ if (zone->db != NULL && zone->db != db) {
+ if (zone->dbversion != NULL) {
+ dns_db_closeversion(zone->db, &zone->dbversion, false);
+ }
+ dns_db_updatenotify_unregister(
+ zone->db, dns_catz_dbupdate_callback, zone->catzs);
+ dns_db_detach(&zone->db);
+ /*
+ * We're not registering db update callback, it will be
+ * registered at the end of update_from_db
+ */
+ zone->db_registered = false;
+ }
+ if (zone->db == NULL) {
+ dns_db_attach(db, &zone->db);
+ }
+
+ if (!zone->updatepending) {
+ zone->updatepending = true;
+ isc_time_now(&now);
+ tdiff = isc_time_microdiff(&now, &zone->lastupdated) / 1000000;
+ if (tdiff < zone->defoptions.min_update_interval) {
+ isc_interval_t interval;
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: new zone version came too soon, "
+ "deferring update");
+ isc_interval_set(&interval,
+ zone->defoptions.min_update_interval -
+ (unsigned int)tdiff,
+ 0);
+ dns_db_currentversion(db, &zone->dbversion);
+ result = isc_timer_reset(zone->updatetimer,
+ isc_timertype_once, NULL,
+ &interval, true);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ } else {
+ isc_event_t *event;
+
+ dns_db_currentversion(db, &zone->dbversion);
+ ISC_EVENT_INIT(&zone->updateevent,
+ sizeof(zone->updateevent), 0, NULL,
+ DNS_EVENT_CATZUPDATED,
+ dns_catz_update_taskaction, zone, zone,
+ NULL, NULL);
+ event = &zone->updateevent;
+ isc_task_send(catzs->updater, &event);
+ }
+ } else {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
+ "catz: update already queued");
+ if (zone->dbversion != NULL) {
+ dns_db_closeversion(zone->db, &zone->dbversion, false);
+ }
+ dns_db_currentversion(zone->db, &zone->dbversion);
+ }
+
+cleanup:
+ UNLOCK(&catzs->lock);
+
+ return (result);
+}
+
+static bool
+catz_rdatatype_is_processable(const dns_rdatatype_t type) {
+ return (!dns_rdatatype_isdnssec(type) && type != dns_rdatatype_cds &&
+ type != dns_rdatatype_cdnskey && type != dns_rdatatype_zonemd);
+}
+
+void
+dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs) {
+ dns_catz_zone_t *oldzone = NULL, *newzone = NULL;
+ isc_result_t result;
+ isc_region_t r;
+ dns_dbnode_t *node = NULL;
+ dns_dbiterator_t *it = NULL;
+ dns_fixedname_t fixname;
+ dns_name_t *name;
+ dns_rdatasetiter_t *rdsiter = NULL;
+ dns_rdataset_t rdataset;
+ char bname[DNS_NAME_FORMATSIZE];
+ isc_buffer_t ibname;
+ uint32_t vers;
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+
+ /*
+ * Create a new catz in the same context as current catz.
+ */
+ dns_name_toregion(&db->origin, &r);
+ result = isc_ht_find(catzs->zones, r.base, r.length, (void **)&oldzone);
+ if (result != ISC_R_SUCCESS) {
+ /* This can happen if we remove the zone in the meantime. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: zone '%s' not in config", bname);
+ return;
+ }
+
+ if (!oldzone->active) {
+ /* This can happen during a reconfiguration. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "catz: zone '%s' is no longer active", bname);
+ return;
+ }
+
+ isc_buffer_init(&ibname, bname, DNS_NAME_FORMATSIZE);
+ result = dns_name_totext(&db->origin, true, &ibname);
+ INSIST(result == ISC_R_SUCCESS);
+
+ result = dns_db_getsoaserial(db, oldzone->dbversion, &vers);
+ if (result != ISC_R_SUCCESS) {
+ /* A zone without SOA record?!? */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: zone '%s' has no SOA record (%s)", bname,
+ isc_result_totext(result));
+ return;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_INFO,
+ "catz: updating catalog zone '%s' with serial %" PRIu32,
+ bname, vers);
+
+ result = dns_catz_new_zone(catzs, &newzone, &db->origin);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_closeversion(db, &oldzone->dbversion, false);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed to create new zone - %s",
+ isc_result_totext(result));
+ return;
+ }
+
+ result = dns_db_createiterator(db, DNS_DB_NONSEC3, &it);
+ if (result != ISC_R_SUCCESS) {
+ dns_catz_zone_detach(&newzone);
+ dns_db_closeversion(db, &oldzone->dbversion, false);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed to create DB iterator - %s",
+ isc_result_totext(result));
+ return;
+ }
+
+ name = dns_fixedname_initname(&fixname);
+
+ /*
+ * Iterate over database to fill the new zone.
+ */
+ result = dns_dbiterator_first(it);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed to get db iterator - %s",
+ isc_result_totext(result));
+ }
+
+ while (result == ISC_R_SUCCESS) {
+ result = dns_dbiterator_current(it, &node, name);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed to get db iterator - %s",
+ isc_result_totext(result));
+ break;
+ }
+
+ result = dns_db_allrdatasets(db, node, oldzone->dbversion, 0, 0,
+ &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed to fetch rrdatasets - %s",
+ isc_result_totext(result));
+ dns_db_detachnode(db, &node);
+ break;
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_rdatasetiter_first(rdsiter);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdatasetiter_current(rdsiter, &rdataset);
+
+ /*
+ * Skip processing DNSSEC-related and ZONEMD types,
+ * because we are not interested in them in the context
+ * of a catalog zone, and processing them will fail
+ * and produce an unnecessary warning message.
+ */
+ if (!catz_rdatatype_is_processable(rdataset.type)) {
+ goto next;
+ }
+
+ result = dns_catz_update_process(catzs, newzone, name,
+ &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ char cname[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+
+ dns_name_format(name, cname,
+ DNS_NAME_FORMATSIZE);
+ dns_rdataclass_format(rdataset.rdclass,
+ classbuf,
+ sizeof(classbuf));
+ dns_rdatatype_format(rdataset.type, typebuf,
+ sizeof(typebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER,
+ ISC_LOG_WARNING,
+ "catz: unknown record in catalog "
+ "zone - %s %s %s(%s) - ignoring",
+ cname, classbuf, typebuf,
+ isc_result_totext(result));
+ }
+ next:
+ dns_rdataset_disassociate(&rdataset);
+ result = dns_rdatasetiter_next(rdsiter);
+ }
+
+ dns_rdatasetiter_destroy(&rdsiter);
+
+ dns_db_detachnode(db, &node);
+ result = dns_dbiterator_next(it);
+ }
+
+ dns_dbiterator_destroy(&it);
+ dns_db_closeversion(db, &oldzone->dbversion, false);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_DEBUG(3),
+ "catz: update_from_db: iteration finished");
+
+ /*
+ * Finally merge new zone into old zone.
+ */
+ result = dns_catz_zones_merge(oldzone, newzone);
+ dns_catz_zone_detach(&newzone);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "catz: failed merging zones: %s",
+ isc_result_totext(result));
+
+ return;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_DEBUG(3),
+ "catz: update_from_db: new zone merged");
+
+ /*
+ * When we're doing reconfig and setting a new catalog zone
+ * from an existing zone we won't have a chance to set up
+ * update callback in zone_startload or axfr_makedb, but we will
+ * call onupdate() artificially so we can register the callback here.
+ */
+ if (!oldzone->db_registered) {
+ result = dns_db_updatenotify_register(
+ db, dns_catz_dbupdate_callback, oldzone->catzs);
+ if (result == ISC_R_SUCCESS) {
+ oldzone->db_registered = true;
+ }
+ }
+}
+
+void
+dns_catz_prereconfig(dns_catz_zones_t *catzs) {
+ isc_result_t result;
+ isc_ht_iter_t *iter = NULL;
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+
+ LOCK(&catzs->lock);
+ isc_ht_iter_create(catzs->zones, &iter);
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_next(iter))
+ {
+ dns_catz_zone_t *zone = NULL;
+ isc_ht_iter_current(iter, (void **)&zone);
+ zone->active = false;
+ }
+ UNLOCK(&catzs->lock);
+ INSIST(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter);
+}
+
+void
+dns_catz_postreconfig(dns_catz_zones_t *catzs) {
+ isc_result_t result;
+ dns_catz_zone_t *newzone = NULL;
+ isc_ht_iter_t *iter = NULL;
+
+ REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
+
+ LOCK(&catzs->lock);
+ isc_ht_iter_create(catzs->zones, &iter);
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;) {
+ dns_catz_zone_t *zone = NULL;
+
+ isc_ht_iter_current(iter, (void **)&zone);
+ if (!zone->active) {
+ char cname[DNS_NAME_FORMATSIZE];
+ dns_name_format(&zone->name, cname,
+ DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
+ "catz: removing catalog zone %s", cname);
+
+ /*
+ * Merge the old zone with an empty one to remove
+ * all members.
+ */
+ result = dns_catz_new_zone(catzs, &newzone,
+ &zone->name);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_catz_zones_merge(zone, newzone);
+ dns_catz_zone_detach(&newzone);
+
+ /* Make sure that we have an empty catalog zone. */
+ INSIST(isc_ht_count(zone->entries) == 0);
+ result = isc_ht_iter_delcurrent_next(iter);
+ dns_catz_zone_detach(&zone);
+ } else {
+ result = isc_ht_iter_next(iter);
+ }
+ }
+ UNLOCK(&catzs->lock);
+ RUNTIME_CHECK(result == ISC_R_NOMORE);
+ isc_ht_iter_destroy(&iter);
+}
+
+void
+dns_catz_get_iterator(dns_catz_zone_t *catz, isc_ht_iter_t **itp) {
+ REQUIRE(DNS_CATZ_ZONE_VALID(catz));
+
+ isc_ht_iter_create(catz->entries, itp);
+}
diff --git a/lib/dns/client.c b/lib/dns/client.c
new file mode 100644
index 0000000..9cfc810
--- /dev/null
+++ b/lib/dns/client.c
@@ -0,0 +1,1353 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <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/safe.h>
+#include <isc/sockaddr.h>
+#include <isc/socket.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/result.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_socketmgr_t *socketmgr;
+ isc_timermgr_t *timermgr;
+ dns_dispatchmgr_t *dispatchmgr;
+ dns_dispatch_t *dispatchv4;
+ dns_dispatch_t *dispatchv6;
+
+ unsigned int find_timeout;
+ unsigned int find_udpretries;
+
+ isc_refcount_t references;
+
+ /* Locked */
+ dns_viewlist_t viewlist;
+ ISC_LIST(struct resctx) resctxs;
+};
+
+#define DEF_FIND_TIMEOUT 5
+#define DEF_FIND_UDPRETRIES 3
+
+/*%
+ * Internal state for a single name resolution procedure
+ */
+typedef struct resctx {
+ /* Unlocked */
+ unsigned int magic;
+ isc_mutex_t lock;
+ dns_client_t *client;
+ bool want_dnssec;
+ bool want_validation;
+ bool want_cdflag;
+ bool want_tcp;
+
+ /* Locked */
+ ISC_LINK(struct resctx) link;
+ isc_task_t *task;
+ dns_view_t *view;
+ unsigned int restarts;
+ dns_fixedname_t name;
+ dns_rdatatype_t type;
+ dns_fetch_t *fetch;
+ dns_namelist_t namelist;
+ isc_result_t result;
+ dns_clientresevent_t *event;
+ bool canceled;
+ dns_rdataset_t *rdataset;
+ dns_rdataset_t *sigrdataset;
+} resctx_t;
+
+/*%
+ * Argument of an internal event for synchronous name resolution.
+ */
+typedef struct resarg {
+ /* Unlocked */
+ isc_appctx_t *actx;
+ dns_client_t *client;
+ isc_mutex_t lock;
+
+ /* Locked */
+ isc_result_t result;
+ isc_result_t vresult;
+ dns_namelist_t *namelist;
+ dns_clientrestrans_t *trans;
+ bool canceled;
+} resarg_t;
+
+static void
+client_resfind(resctx_t *rctx, dns_fetchevent_t *event);
+
+/*
+ * Try honoring the operating system's preferred ephemeral port range.
+ */
+static isc_result_t
+setsourceports(isc_mem_t *mctx, dns_dispatchmgr_t *manager) {
+ isc_portset_t *v4portset = NULL, *v6portset = NULL;
+ in_port_t udpport_low, udpport_high;
+ isc_result_t result;
+
+ result = isc_portset_create(mctx, &v4portset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = isc_net_getudpportrange(AF_INET, &udpport_low, &udpport_high);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_portset_addrange(v4portset, udpport_low, udpport_high);
+
+ result = isc_portset_create(mctx, &v6portset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = isc_net_getudpportrange(AF_INET6, &udpport_low, &udpport_high);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_portset_addrange(v6portset, udpport_low, udpport_high);
+
+ result = dns_dispatchmgr_setavailports(manager, v4portset, v6portset);
+
+cleanup:
+ if (v4portset != NULL) {
+ isc_portset_destroy(mctx, &v4portset);
+ }
+ if (v6portset != NULL) {
+ isc_portset_destroy(mctx, &v6portset);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+getudpdispatch(int family, dns_dispatchmgr_t *dispatchmgr,
+ isc_socketmgr_t *socketmgr, isc_taskmgr_t *taskmgr,
+ bool is_shared, dns_dispatch_t **dispp,
+ const isc_sockaddr_t *localaddr) {
+ unsigned int attrs, attrmask;
+ dns_dispatch_t *disp;
+ unsigned buffersize, maxbuffers, maxrequests, buckets, increment;
+ isc_result_t result;
+ isc_sockaddr_t anyaddr;
+
+ attrs = 0;
+ attrs |= DNS_DISPATCHATTR_UDP;
+ switch (family) {
+ case AF_INET:
+ attrs |= DNS_DISPATCHATTR_IPV4;
+ break;
+ case AF_INET6:
+ attrs |= DNS_DISPATCHATTR_IPV6;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ attrmask = 0;
+ attrmask |= DNS_DISPATCHATTR_UDP;
+ attrmask |= DNS_DISPATCHATTR_TCP;
+ attrmask |= DNS_DISPATCHATTR_IPV4;
+ attrmask |= DNS_DISPATCHATTR_IPV6;
+
+ if (localaddr == NULL) {
+ isc_sockaddr_anyofpf(&anyaddr, family);
+ localaddr = &anyaddr;
+ }
+
+ buffersize = 4096;
+ maxbuffers = is_shared ? 1000 : 8;
+ maxrequests = 32768;
+ buckets = is_shared ? 16411 : 3;
+ increment = is_shared ? 16433 : 5;
+
+ disp = NULL;
+ result = dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, localaddr,
+ buffersize, maxbuffers, maxrequests,
+ buckets, increment, attrs, attrmask,
+ &disp);
+ if (result == ISC_R_SUCCESS) {
+ *dispp = disp;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, unsigned int options,
+ isc_taskmgr_t *taskmgr, unsigned int ntasks,
+ isc_socketmgr_t *socketmgr, isc_timermgr_t *timermgr,
+ dns_dispatchmgr_t *dispatchmgr, dns_dispatch_t *dispatchv4,
+ dns_dispatch_t *dispatchv6, dns_view_t **viewp) {
+ isc_result_t result;
+ dns_view_t *view = NULL;
+ const char *dbtype;
+
+ result = dns_view_create(mctx, rdclass, DNS_CLIENTVIEW_NAME, &view);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /* Initialize view security roots */
+ result = dns_view_initsecroots(view, mctx);
+ if (result != ISC_R_SUCCESS) {
+ dns_view_detach(&view);
+ return (result);
+ }
+
+ result = dns_view_createresolver(view, taskmgr, ntasks, 1, socketmgr,
+ timermgr, 0, dispatchmgr, dispatchv4,
+ dispatchv6);
+ if (result != ISC_R_SUCCESS) {
+ dns_view_detach(&view);
+ return (result);
+ }
+
+ /*
+ * Set cache DB.
+ * XXX: it may be better if specific DB implementations can be
+ * specified via some configuration knob.
+ */
+ if ((options & DNS_CLIENTCREATEOPT_USECACHE) != 0) {
+ dbtype = "rbt";
+ } else {
+ dbtype = "ecdb";
+ }
+ result = dns_db_create(mctx, dbtype, dns_rootname, dns_dbtype_cache,
+ rdclass, 0, NULL, &view->cachedb);
+ if (result != ISC_R_SUCCESS) {
+ dns_view_detach(&view);
+ return (result);
+ }
+
+ *viewp = view;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_client_create(isc_mem_t *mctx, isc_appctx_t *actx, isc_taskmgr_t *taskmgr,
+ isc_socketmgr_t *socketmgr, isc_timermgr_t *timermgr,
+ unsigned int options, dns_client_t **clientp,
+ const isc_sockaddr_t *localaddr4,
+ const isc_sockaddr_t *localaddr6) {
+ isc_result_t result;
+ dns_client_t *client = NULL;
+ dns_dispatchmgr_t *dispatchmgr = NULL;
+ dns_dispatch_t *dispatchv4 = NULL;
+ dns_dispatch_t *dispatchv6 = NULL;
+ dns_view_t *view = NULL;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(taskmgr != NULL);
+ REQUIRE(timermgr != NULL);
+ REQUIRE(socketmgr != NULL);
+ REQUIRE(clientp != NULL && *clientp == NULL);
+
+ client = isc_mem_get(mctx, sizeof(*client));
+
+ isc_mutex_init(&client->lock);
+
+ client->actx = actx;
+ client->taskmgr = taskmgr;
+ client->socketmgr = socketmgr;
+ client->timermgr = timermgr;
+
+ client->task = NULL;
+ result = isc_task_create(client->taskmgr, 0, &client->task);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_lock;
+ }
+
+ result = dns_dispatchmgr_create(mctx, &dispatchmgr);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_task;
+ }
+ client->dispatchmgr = dispatchmgr;
+ (void)setsourceports(mctx, dispatchmgr);
+
+ /*
+ * If only one address family is specified, use it.
+ * If neither family is specified, or if both are, use both.
+ */
+ client->dispatchv4 = NULL;
+ if (localaddr4 != NULL || localaddr6 == NULL) {
+ result = getudpdispatch(AF_INET, dispatchmgr, socketmgr,
+ taskmgr, true, &dispatchv4, localaddr4);
+ if (result == ISC_R_SUCCESS) {
+ client->dispatchv4 = dispatchv4;
+ }
+ }
+
+ client->dispatchv6 = NULL;
+ if (localaddr6 != NULL || localaddr4 == NULL) {
+ result = getudpdispatch(AF_INET6, dispatchmgr, socketmgr,
+ taskmgr, true, &dispatchv6, localaddr6);
+ if (result == ISC_R_SUCCESS) {
+ client->dispatchv6 = dispatchv6;
+ }
+ }
+
+ /* We need at least one of the dispatchers */
+ if (dispatchv4 == NULL && dispatchv6 == NULL) {
+ INSIST(result != ISC_R_SUCCESS);
+ goto cleanup_dispatchmgr;
+ }
+
+ isc_refcount_init(&client->references, 1);
+
+ /* Create the default view for class IN */
+ result = createview(mctx, dns_rdataclass_in, options, taskmgr,
+ RESOLVER_NTASKS, socketmgr, timermgr, dispatchmgr,
+ dispatchv4, dispatchv6, &view);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_references;
+ }
+
+ ISC_LIST_INIT(client->viewlist);
+ ISC_LIST_APPEND(client->viewlist, view, link);
+
+ dns_view_freeze(view); /* too early? */
+
+ ISC_LIST_INIT(client->resctxs);
+
+ client->mctx = NULL;
+ isc_mem_attach(mctx, &client->mctx);
+
+ client->find_timeout = DEF_FIND_TIMEOUT;
+ client->find_udpretries = DEF_FIND_UDPRETRIES;
+ client->attributes = 0;
+
+ client->magic = DNS_CLIENT_MAGIC;
+
+ *clientp = client;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_references:
+ isc_refcount_decrementz(&client->references);
+ isc_refcount_destroy(&client->references);
+cleanup_dispatchmgr:
+ if (dispatchv4 != NULL) {
+ dns_dispatch_detach(&dispatchv4);
+ }
+ if (dispatchv6 != NULL) {
+ dns_dispatch_detach(&dispatchv6);
+ }
+ dns_dispatchmgr_destroy(&dispatchmgr);
+cleanup_task:
+ isc_task_detach(&client->task);
+cleanup_lock:
+ isc_mutex_destroy(&client->lock);
+ isc_mem_put(mctx, client, sizeof(*client));
+
+ return (result);
+}
+
+static void
+destroyclient(dns_client_t *client) {
+ dns_view_t *view;
+
+ isc_refcount_destroy(&client->references);
+
+ while ((view = ISC_LIST_HEAD(client->viewlist)) != NULL) {
+ ISC_LIST_UNLINK(client->viewlist, view, link);
+ dns_view_detach(&view);
+ }
+
+ if (client->dispatchv4 != NULL) {
+ dns_dispatch_detach(&client->dispatchv4);
+ }
+ if (client->dispatchv6 != NULL) {
+ dns_dispatch_detach(&client->dispatchv6);
+ }
+
+ dns_dispatchmgr_destroy(&client->dispatchmgr);
+
+ isc_task_detach(&client->task);
+
+ isc_mutex_destroy(&client->lock);
+ client->magic = 0;
+
+ isc_mem_putanddetach(&client->mctx, client, sizeof(*client));
+}
+
+void
+dns_client_destroy(dns_client_t **clientp) {
+ dns_client_t *client;
+
+ REQUIRE(clientp != NULL);
+ client = *clientp;
+ *clientp = NULL;
+ REQUIRE(DNS_CLIENT_VALID(client));
+
+ if (isc_refcount_decrement(&client->references) == 1) {
+ destroyclient(client);
+ }
+}
+
+isc_result_t
+dns_client_setservers(dns_client_t *client, dns_rdataclass_t rdclass,
+ const dns_name_t *name_space, isc_sockaddrlist_t *addrs) {
+ isc_result_t result;
+ dns_view_t *view = NULL;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+ REQUIRE(addrs != NULL);
+
+ if (name_space == NULL) {
+ name_space = dns_rootname;
+ }
+
+ LOCK(&client->lock);
+ result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME,
+ rdclass, &view);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK(&client->lock);
+ return (result);
+ }
+ UNLOCK(&client->lock);
+
+ result = dns_fwdtable_add(view->fwdtable, name_space, addrs,
+ dns_fwdpolicy_only);
+
+ dns_view_detach(&view);
+
+ return (result);
+}
+
+isc_result_t
+dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass,
+ const dns_name_t *name_space) {
+ isc_result_t result;
+ dns_view_t *view = NULL;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+
+ if (name_space == NULL) {
+ name_space = dns_rootname;
+ }
+
+ LOCK(&client->lock);
+ result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME,
+ rdclass, &view);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK(&client->lock);
+ return (result);
+ }
+ UNLOCK(&client->lock);
+
+ result = dns_fwdtable_delete(view->fwdtable, name_space);
+
+ dns_view_detach(&view);
+
+ return (result);
+}
+
+static isc_result_t
+getrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) {
+ dns_rdataset_t *rdataset;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(rdatasetp != NULL && *rdatasetp == NULL);
+
+ rdataset = isc_mem_get(mctx, sizeof(*rdataset));
+
+ dns_rdataset_init(rdataset);
+
+ *rdatasetp = rdataset;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+putrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) {
+ dns_rdataset_t *rdataset;
+
+ REQUIRE(rdatasetp != NULL);
+ rdataset = *rdatasetp;
+ *rdatasetp = NULL;
+ REQUIRE(rdataset != NULL);
+
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+
+ isc_mem_put(mctx, rdataset, sizeof(*rdataset));
+}
+
+static void
+fetch_done(isc_task_t *task, isc_event_t *event) {
+ resctx_t *rctx = event->ev_arg;
+ dns_fetchevent_t *fevent;
+
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+ REQUIRE(RCTX_VALID(rctx));
+ REQUIRE(rctx->task == task);
+ fevent = (dns_fetchevent_t *)event;
+
+ client_resfind(rctx, fevent);
+}
+
+static isc_result_t
+start_fetch(resctx_t *rctx) {
+ isc_result_t result;
+ int fopts = 0;
+
+ /*
+ * The caller must be holding the rctx's lock.
+ */
+
+ REQUIRE(rctx->fetch == NULL);
+
+ if (!rctx->want_cdflag) {
+ fopts |= DNS_FETCHOPT_NOCDFLAG;
+ }
+ if (!rctx->want_validation) {
+ fopts |= DNS_FETCHOPT_NOVALIDATE;
+ }
+ if (rctx->want_tcp) {
+ fopts |= DNS_FETCHOPT_TCP;
+ }
+
+ result = dns_resolver_createfetch(
+ rctx->view->resolver, dns_fixedname_name(&rctx->name),
+ rctx->type, NULL, NULL, NULL, NULL, 0, fopts, 0, NULL,
+ rctx->task, fetch_done, rctx, rctx->rdataset, rctx->sigrdataset,
+ &rctx->fetch);
+
+ return (result);
+}
+
+static isc_result_t
+view_find(resctx_t *rctx, dns_db_t **dbp, dns_dbnode_t **nodep,
+ dns_name_t *foundname) {
+ isc_result_t result;
+ dns_name_t *name = dns_fixedname_name(&rctx->name);
+ dns_rdatatype_t type;
+
+ if (rctx->type == dns_rdatatype_rrsig) {
+ type = dns_rdatatype_any;
+ } else {
+ type = rctx->type;
+ }
+
+ result = dns_view_find(rctx->view, name, type, 0, 0, false, false, dbp,
+ nodep, foundname, rctx->rdataset,
+ rctx->sigrdataset);
+
+ return (result);
+}
+
+static void
+client_resfind(resctx_t *rctx, dns_fetchevent_t *event) {
+ isc_mem_t *mctx;
+ isc_result_t tresult, result = ISC_R_SUCCESS;
+ isc_result_t vresult = ISC_R_SUCCESS;
+ bool want_restart;
+ bool send_event = false;
+ dns_name_t *name, *prefix;
+ dns_fixedname_t foundname, fixed;
+ dns_rdataset_t *trdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned int nlabels;
+ int order;
+ dns_namereln_t namereln;
+ dns_rdata_cname_t cname;
+ dns_rdata_dname_t dname;
+
+ REQUIRE(RCTX_VALID(rctx));
+
+ LOCK(&rctx->lock);
+
+ mctx = rctx->view->mctx;
+
+ name = dns_fixedname_name(&rctx->name);
+
+ do {
+ dns_name_t *fname = NULL;
+ dns_name_t *ansname = NULL;
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+
+ rctx->restarts++;
+ want_restart = false;
+
+ if (event == NULL && !rctx->canceled) {
+ fname = dns_fixedname_initname(&foundname);
+ INSIST(!dns_rdataset_isassociated(rctx->rdataset));
+ INSIST(rctx->sigrdataset == NULL ||
+ !dns_rdataset_isassociated(rctx->sigrdataset));
+ result = view_find(rctx, &db, &node, fname);
+ if (result == ISC_R_NOTFOUND) {
+ /*
+ * We don't know anything about the name.
+ * Launch a fetch.
+ */
+ if (node != NULL) {
+ INSIST(db != NULL);
+ dns_db_detachnode(db, &node);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ result = start_fetch(rctx);
+ if (result != ISC_R_SUCCESS) {
+ putrdataset(mctx, &rctx->rdataset);
+ if (rctx->sigrdataset != NULL) {
+ putrdataset(mctx,
+ &rctx->sigrdataset);
+ }
+ send_event = true;
+ }
+ goto done;
+ }
+ } else {
+ INSIST(event != NULL);
+ INSIST(event->fetch == rctx->fetch);
+ dns_resolver_destroyfetch(&rctx->fetch);
+ db = event->db;
+ node = event->node;
+ result = event->result;
+ vresult = event->vresult;
+ fname = dns_fixedname_name(&event->foundname);
+ INSIST(event->rdataset == rctx->rdataset);
+ INSIST(event->sigrdataset == rctx->sigrdataset);
+ }
+
+ /*
+ * If we've been canceled, forget about the result.
+ */
+ if (rctx->canceled) {
+ result = ISC_R_CANCELED;
+ } else {
+ /*
+ * Otherwise, get some resource for copying the
+ * result.
+ */
+ dns_name_t *aname = dns_fixedname_name(&rctx->name);
+
+ ansname = isc_mem_get(mctx, sizeof(*ansname));
+ dns_name_init(ansname, NULL);
+
+ dns_name_dup(aname, mctx, ansname);
+ }
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ send_event = true;
+ /*
+ * This case is handled in the main line below.
+ */
+ break;
+ case DNS_R_CNAME:
+ /*
+ * Add the CNAME to the answer list.
+ */
+ trdataset = rctx->rdataset;
+ ISC_LIST_APPEND(ansname->list, rctx->rdataset, link);
+ rctx->rdataset = NULL;
+ if (rctx->sigrdataset != NULL) {
+ ISC_LIST_APPEND(ansname->list,
+ rctx->sigrdataset, link);
+ rctx->sigrdataset = NULL;
+ }
+ ISC_LIST_APPEND(rctx->namelist, ansname, link);
+ ansname = NULL;
+
+ /*
+ * Copy the CNAME's target into the lookup's
+ * query name and start over.
+ */
+ tresult = dns_rdataset_first(trdataset);
+ if (tresult != ISC_R_SUCCESS) {
+ goto done;
+ }
+ dns_rdataset_current(trdataset, &rdata);
+ tresult = dns_rdata_tostruct(&rdata, &cname, NULL);
+ dns_rdata_reset(&rdata);
+ if (tresult != ISC_R_SUCCESS) {
+ goto done;
+ }
+ dns_name_copynf(&cname.cname, name);
+ dns_rdata_freestruct(&cname);
+ want_restart = true;
+ goto done;
+ case DNS_R_DNAME:
+ /*
+ * Add the DNAME to the answer list.
+ */
+ trdataset = rctx->rdataset;
+ ISC_LIST_APPEND(ansname->list, rctx->rdataset, link);
+ rctx->rdataset = NULL;
+ if (rctx->sigrdataset != NULL) {
+ ISC_LIST_APPEND(ansname->list,
+ rctx->sigrdataset, link);
+ rctx->sigrdataset = NULL;
+ }
+ ISC_LIST_APPEND(rctx->namelist, ansname, link);
+ ansname = NULL;
+
+ namereln = dns_name_fullcompare(name, fname, &order,
+ &nlabels);
+ INSIST(namereln == dns_namereln_subdomain);
+ /*
+ * Get the target name of the DNAME.
+ */
+ tresult = dns_rdataset_first(trdataset);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto done;
+ }
+ dns_rdataset_current(trdataset, &rdata);
+ tresult = dns_rdata_tostruct(&rdata, &dname, NULL);
+ dns_rdata_reset(&rdata);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto done;
+ }
+ /*
+ * Construct the new query name and start over.
+ */
+ prefix = dns_fixedname_initname(&fixed);
+ dns_name_split(name, nlabels, prefix, NULL);
+ tresult = dns_name_concatenate(prefix, &dname.dname,
+ name, NULL);
+ dns_rdata_freestruct(&dname);
+ if (tresult == ISC_R_SUCCESS) {
+ want_restart = true;
+ } else {
+ result = tresult;
+ }
+ goto done;
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ ISC_LIST_APPEND(ansname->list, rctx->rdataset, link);
+ ISC_LIST_APPEND(rctx->namelist, ansname, link);
+ ansname = NULL;
+ rctx->rdataset = NULL;
+ /* What about sigrdataset? */
+ if (rctx->sigrdataset != NULL) {
+ putrdataset(mctx, &rctx->sigrdataset);
+ }
+ send_event = true;
+ goto done;
+ default:
+ if (rctx->rdataset != NULL) {
+ putrdataset(mctx, &rctx->rdataset);
+ }
+ if (rctx->sigrdataset != NULL) {
+ putrdataset(mctx, &rctx->sigrdataset);
+ }
+ send_event = true;
+ goto done;
+ }
+
+ if (rctx->type == dns_rdatatype_any) {
+ int n = 0;
+ dns_rdatasetiter_t *rdsiter = NULL;
+
+ tresult = dns_db_allrdatasets(db, node, NULL, 0, 0,
+ &rdsiter);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto done;
+ }
+
+ tresult = dns_rdatasetiter_first(rdsiter);
+ while (tresult == ISC_R_SUCCESS) {
+ dns_rdatasetiter_current(rdsiter,
+ rctx->rdataset);
+ if (rctx->rdataset->type != 0) {
+ ISC_LIST_APPEND(ansname->list,
+ rctx->rdataset, link);
+ n++;
+ rctx->rdataset = NULL;
+ } else {
+ /*
+ * We're not interested in this
+ * rdataset.
+ */
+ dns_rdataset_disassociate(
+ rctx->rdataset);
+ }
+ tresult = dns_rdatasetiter_next(rdsiter);
+
+ if (tresult == ISC_R_SUCCESS &&
+ rctx->rdataset == NULL)
+ {
+ tresult = getrdataset(mctx,
+ &rctx->rdataset);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ POST(result);
+ break;
+ }
+ }
+ }
+ if (rctx->rdataset != NULL) {
+ putrdataset(mctx, &rctx->rdataset);
+ }
+ if (rctx->sigrdataset != NULL) {
+ putrdataset(mctx, &rctx->sigrdataset);
+ }
+ if (n == 0) {
+ /*
+ * We didn't match any rdatasets (which means
+ * something went wrong in this
+ * implementation).
+ */
+ result = DNS_R_SERVFAIL; /* better code? */
+ POST(result);
+ } else {
+ ISC_LIST_APPEND(rctx->namelist, ansname, link);
+ ansname = NULL;
+ }
+ dns_rdatasetiter_destroy(&rdsiter);
+ if (tresult != ISC_R_NOMORE) {
+ result = DNS_R_SERVFAIL; /* ditto */
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ goto done;
+ } else {
+ /*
+ * This is the "normal" case -- an ordinary question
+ * to which we've got the answer.
+ */
+ ISC_LIST_APPEND(ansname->list, rctx->rdataset, link);
+ rctx->rdataset = NULL;
+ if (rctx->sigrdataset != NULL) {
+ ISC_LIST_APPEND(ansname->list,
+ rctx->sigrdataset, link);
+ rctx->sigrdataset = NULL;
+ }
+ ISC_LIST_APPEND(rctx->namelist, ansname, link);
+ ansname = NULL;
+ }
+
+ done:
+ /*
+ * Free temporary resources
+ */
+ if (ansname != NULL) {
+ dns_rdataset_t *rdataset;
+
+ while ((rdataset = ISC_LIST_HEAD(ansname->list)) !=
+ NULL)
+ {
+ ISC_LIST_UNLINK(ansname->list, rdataset, link);
+ putrdataset(mctx, &rdataset);
+ }
+ dns_name_free(ansname, mctx);
+ isc_mem_put(mctx, ansname, sizeof(*ansname));
+ }
+
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ if (event != NULL) {
+ isc_event_free(ISC_EVENT_PTR(&event));
+ }
+
+ /*
+ * Limit the number of restarts.
+ */
+ if (want_restart && rctx->restarts == MAX_RESTARTS) {
+ want_restart = false;
+ result = ISC_R_QUOTA;
+ send_event = true;
+ }
+
+ /*
+ * Prepare further find with new resources
+ */
+ if (want_restart) {
+ INSIST(rctx->rdataset == NULL &&
+ rctx->sigrdataset == NULL);
+
+ result = getrdataset(mctx, &rctx->rdataset);
+ if (result == ISC_R_SUCCESS && rctx->want_dnssec) {
+ result = getrdataset(mctx, &rctx->sigrdataset);
+ if (result != ISC_R_SUCCESS) {
+ putrdataset(mctx, &rctx->rdataset);
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ want_restart = false;
+ send_event = true;
+ }
+ }
+ } while (want_restart);
+
+ if (send_event) {
+ isc_task_t *task;
+
+ while ((name = ISC_LIST_HEAD(rctx->namelist)) != NULL) {
+ ISC_LIST_UNLINK(rctx->namelist, name, link);
+ ISC_LIST_APPEND(rctx->event->answerlist, name, link);
+ }
+
+ rctx->event->result = result;
+ rctx->event->vresult = vresult;
+ task = rctx->event->ev_sender;
+ rctx->event->ev_sender = rctx;
+ isc_task_sendanddetach(&task, ISC_EVENT_PTR(&rctx->event));
+ }
+
+ UNLOCK(&rctx->lock);
+}
+
+static void
+suspend(isc_task_t *task, isc_event_t *event) {
+ isc_appctx_t *actx = event->ev_arg;
+
+ UNUSED(task);
+
+ isc_app_ctxsuspend(actx);
+ isc_event_free(&event);
+}
+
+static void
+resolve_done(isc_task_t *task, isc_event_t *event) {
+ resarg_t *resarg = event->ev_arg;
+ dns_clientresevent_t *rev = (dns_clientresevent_t *)event;
+ dns_name_t *name;
+ isc_result_t result;
+
+ UNUSED(task);
+
+ LOCK(&resarg->lock);
+
+ resarg->result = rev->result;
+ resarg->vresult = rev->vresult;
+ while ((name = ISC_LIST_HEAD(rev->answerlist)) != NULL) {
+ ISC_LIST_UNLINK(rev->answerlist, name, link);
+ ISC_LIST_APPEND(*resarg->namelist, name, link);
+ }
+
+ dns_client_destroyrestrans(&resarg->trans);
+ isc_event_free(&event);
+
+ if (!resarg->canceled) {
+ UNLOCK(&resarg->lock);
+
+ /*
+ * We may or may not be running. isc__appctx_onrun will
+ * fail if we are currently running otherwise we post a
+ * action to call isc_app_ctxsuspend when we do start
+ * running.
+ */
+ result = isc_app_ctxonrun(resarg->actx, resarg->client->mctx,
+ task, suspend, resarg->actx);
+ if (result == ISC_R_ALREADYRUNNING) {
+ isc_app_ctxsuspend(resarg->actx);
+ }
+ } else {
+ /*
+ * We have already exited from the loop (due to some
+ * unexpected event). Just clean the arg up.
+ */
+ UNLOCK(&resarg->lock);
+ isc_mutex_destroy(&resarg->lock);
+ isc_mem_put(resarg->client->mctx, resarg, sizeof(*resarg));
+ }
+}
+
+isc_result_t
+dns_client_resolve(dns_client_t *client, const dns_name_t *name,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int options, dns_namelist_t *namelist) {
+ isc_result_t result;
+ resarg_t *resarg;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+ REQUIRE(client->actx != NULL);
+ REQUIRE(namelist != NULL && ISC_LIST_EMPTY(*namelist));
+
+ resarg = isc_mem_get(client->mctx, sizeof(*resarg));
+
+ *resarg = (resarg_t){
+ .actx = client->actx,
+ .client = client,
+ .result = DNS_R_SERVFAIL,
+ .namelist = namelist,
+ };
+
+ isc_mutex_init(&resarg->lock);
+
+ result = dns_client_startresolve(client, name, rdclass, type, options,
+ client->task, resolve_done, resarg,
+ &resarg->trans);
+ if (result != ISC_R_SUCCESS) {
+ isc_mutex_destroy(&resarg->lock);
+ isc_mem_put(client->mctx, resarg, sizeof(*resarg));
+ return (result);
+ }
+
+ /*
+ * Start internal event loop. It blocks until the entire process
+ * is completed.
+ */
+ result = isc_app_ctxrun(client->actx);
+
+ LOCK(&resarg->lock);
+ if (result == ISC_R_SUCCESS || result == ISC_R_SUSPEND) {
+ result = resarg->result;
+ }
+ if (result != ISC_R_SUCCESS && resarg->vresult != ISC_R_SUCCESS) {
+ /*
+ * If this lookup failed due to some error in DNSSEC
+ * validation, return the validation error code.
+ * XXX: or should we pass the validation result separately?
+ */
+ result = resarg->vresult;
+ }
+ if (resarg->trans != NULL) {
+ /*
+ * Unusual termination (perhaps due to signal). We need some
+ * tricky cleanup process.
+ */
+ resarg->canceled = true;
+ dns_client_cancelresolve(resarg->trans);
+
+ UNLOCK(&resarg->lock);
+
+ /* resarg will be freed in the event handler. */
+ } else {
+ UNLOCK(&resarg->lock);
+
+ isc_mutex_destroy(&resarg->lock);
+ isc_mem_put(client->mctx, resarg, sizeof(*resarg));
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_client_startresolve(dns_client_t *client, const dns_name_t *name,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int options, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_clientrestrans_t **transp) {
+ dns_view_t *view = NULL;
+ dns_clientresevent_t *event = NULL;
+ resctx_t *rctx = NULL;
+ isc_task_t *tclone = NULL;
+ isc_mem_t *mctx;
+ isc_result_t result;
+ dns_rdataset_t *rdataset, *sigrdataset;
+ bool want_dnssec, want_validation, want_cdflag, want_tcp;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+ REQUIRE(transp != NULL && *transp == NULL);
+
+ LOCK(&client->lock);
+ result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME,
+ rdclass, &view);
+ UNLOCK(&client->lock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ mctx = client->mctx;
+ rdataset = NULL;
+ sigrdataset = NULL;
+ want_dnssec = ((options & DNS_CLIENTRESOPT_NODNSSEC) == 0);
+ want_validation = ((options & DNS_CLIENTRESOPT_NOVALIDATE) == 0);
+ want_cdflag = ((options & DNS_CLIENTRESOPT_NOCDFLAG) == 0);
+ want_tcp = ((options & DNS_CLIENTRESOPT_TCP) != 0);
+
+ /*
+ * Prepare some intermediate resources
+ */
+ tclone = NULL;
+ isc_task_attach(task, &tclone);
+ event = (dns_clientresevent_t *)isc_event_allocate(
+ mctx, tclone, DNS_EVENT_CLIENTRESDONE, action, arg,
+ sizeof(*event));
+ event->result = DNS_R_SERVFAIL;
+ ISC_LIST_INIT(event->answerlist);
+
+ rctx = isc_mem_get(mctx, sizeof(*rctx));
+ isc_mutex_init(&rctx->lock);
+
+ result = getrdataset(mctx, &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ rctx->rdataset = rdataset;
+
+ if (want_dnssec) {
+ result = getrdataset(mctx, &sigrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ rctx->sigrdataset = sigrdataset;
+
+ dns_fixedname_init(&rctx->name);
+ dns_name_copynf(name, dns_fixedname_name(&rctx->name));
+
+ rctx->client = client;
+ ISC_LINK_INIT(rctx, link);
+ rctx->canceled = false;
+ rctx->task = client->task;
+ rctx->type = type;
+ rctx->view = view;
+ rctx->restarts = 0;
+ rctx->fetch = NULL;
+ rctx->want_dnssec = want_dnssec;
+ rctx->want_validation = want_validation;
+ rctx->want_cdflag = want_cdflag;
+ rctx->want_tcp = want_tcp;
+ ISC_LIST_INIT(rctx->namelist);
+ rctx->event = event;
+
+ rctx->magic = RCTX_MAGIC;
+ isc_refcount_increment(&client->references);
+
+ LOCK(&client->lock);
+ ISC_LIST_APPEND(client->resctxs, rctx, link);
+ UNLOCK(&client->lock);
+
+ *transp = (dns_clientrestrans_t *)rctx;
+ client_resfind(rctx, NULL);
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (rdataset != NULL) {
+ putrdataset(client->mctx, &rdataset);
+ }
+ if (sigrdataset != NULL) {
+ putrdataset(client->mctx, &sigrdataset);
+ }
+ isc_mutex_destroy(&rctx->lock);
+ isc_mem_put(mctx, rctx, sizeof(*rctx));
+ isc_event_free(ISC_EVENT_PTR(&event));
+ isc_task_detach(&tclone);
+ dns_view_detach(&view);
+
+ return (result);
+}
+
+void
+dns_client_cancelresolve(dns_clientrestrans_t *trans) {
+ resctx_t *rctx;
+
+ REQUIRE(trans != NULL);
+ rctx = (resctx_t *)trans;
+ REQUIRE(RCTX_VALID(rctx));
+
+ LOCK(&rctx->lock);
+
+ if (!rctx->canceled) {
+ rctx->canceled = true;
+ if (rctx->fetch != NULL) {
+ dns_resolver_cancelfetch(rctx->fetch);
+ }
+ }
+
+ UNLOCK(&rctx->lock);
+}
+
+void
+dns_client_freeresanswer(dns_client_t *client, dns_namelist_t *namelist) {
+ dns_name_t *name;
+ dns_rdataset_t *rdataset;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+ REQUIRE(namelist != NULL);
+
+ while ((name = ISC_LIST_HEAD(*namelist)) != NULL) {
+ ISC_LIST_UNLINK(*namelist, name, link);
+ while ((rdataset = ISC_LIST_HEAD(name->list)) != NULL) {
+ ISC_LIST_UNLINK(name->list, rdataset, link);
+ putrdataset(client->mctx, &rdataset);
+ }
+ dns_name_free(name, client->mctx);
+ isc_mem_put(client->mctx, name, sizeof(*name));
+ }
+}
+
+void
+dns_client_destroyrestrans(dns_clientrestrans_t **transp) {
+ resctx_t *rctx;
+ isc_mem_t *mctx;
+ dns_client_t *client;
+
+ REQUIRE(transp != NULL);
+ rctx = (resctx_t *)*transp;
+ *transp = NULL;
+ REQUIRE(RCTX_VALID(rctx));
+ REQUIRE(rctx->fetch == NULL);
+ REQUIRE(rctx->event == NULL);
+ client = rctx->client;
+ REQUIRE(DNS_CLIENT_VALID(client));
+
+ mctx = client->mctx;
+ dns_view_detach(&rctx->view);
+
+ /*
+ * Wait for the lock in client_resfind to be released before
+ * destroying the lock.
+ */
+ LOCK(&rctx->lock);
+ UNLOCK(&rctx->lock);
+
+ LOCK(&client->lock);
+
+ INSIST(ISC_LINK_LINKED(rctx, link));
+ ISC_LIST_UNLINK(client->resctxs, rctx, link);
+
+ UNLOCK(&client->lock);
+
+ INSIST(ISC_LIST_EMPTY(rctx->namelist));
+
+ isc_mutex_destroy(&rctx->lock);
+ rctx->magic = 0;
+
+ isc_mem_put(mctx, rctx, sizeof(*rctx));
+
+ dns_client_destroy(&client);
+}
+
+isc_result_t
+dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass,
+ dns_rdatatype_t rdtype, const dns_name_t *keyname,
+ isc_buffer_t *databuf) {
+ isc_result_t result;
+ dns_view_t *view = NULL;
+ dns_keytable_t *secroots = NULL;
+ dns_name_t *name = NULL;
+ char rdatabuf[DST_KEY_MAXSIZE];
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ dns_rdata_ds_t ds;
+ dns_decompress_t dctx;
+ dns_rdata_t rdata;
+ isc_buffer_t b;
+
+ REQUIRE(DNS_CLIENT_VALID(client));
+
+ LOCK(&client->lock);
+ result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME,
+ rdclass, &view);
+ UNLOCK(&client->lock);
+ CHECK(result);
+
+ CHECK(dns_view_getsecroots(view, &secroots));
+
+ DE_CONST(keyname, name);
+
+ if (rdtype != dns_rdatatype_dnskey && rdtype != dns_rdatatype_ds) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+
+ isc_buffer_init(&b, rdatabuf, sizeof(rdatabuf));
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE);
+ dns_rdata_init(&rdata);
+ isc_buffer_setactive(databuf, isc_buffer_usedlength(databuf));
+ CHECK(dns_rdata_fromwire(&rdata, rdclass, rdtype, databuf, &dctx, 0,
+ &b));
+ dns_decompress_invalidate(&dctx);
+
+ if (rdtype == dns_rdatatype_ds) {
+ CHECK(dns_rdata_tostruct(&rdata, &ds, NULL));
+ } else {
+ CHECK(dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256,
+ digest, &ds));
+ }
+
+ CHECK(dns_keytable_add(secroots, false, false, name, &ds));
+
+cleanup:
+ if (view != NULL) {
+ dns_view_detach(&view);
+ }
+ if (secroots != NULL) {
+ dns_keytable_detach(&secroots);
+ }
+ return (result);
+}
diff --git a/lib/dns/clientinfo.c b/lib/dns/clientinfo.c
new file mode 100644
index 0000000..fe81d59
--- /dev/null
+++ b/lib/dns/clientinfo.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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, dns_ecs_t *ecs,
+ void *versionp) {
+ ci->version = DNS_CLIENTINFO_VERSION;
+ ci->data = data;
+ ci->dbversion = versionp;
+ if (ecs != NULL) {
+ ci->ecs = *ecs;
+ } else {
+ dns_ecs_init(&ci->ecs);
+ }
+}
diff --git a/lib/dns/compress.c b/lib/dns/compress.c
new file mode 100644
index 0000000..e8e8954
--- /dev/null
+++ b/lib/dns/compress.c
@@ -0,0 +1,584 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#define DNS_NAME_USEINLINE 1
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/compress.h>
+#include <dns/fixedname.h>
+#include <dns/rbt.h>
+#include <dns/result.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 (ISC_UNLIKELY((cctx->allowed & DNS_COMPRESS_ENABLED) == 0)) {
+ return (false);
+ }
+
+ if (cctx->count == 0) {
+ return (false);
+ }
+
+ labels = dns_name_countlabels(name);
+ INSIST(labels > 0);
+
+ dns_name_init(&tname, NULL);
+
+ numlabels = labels > 3U ? 3U : labels;
+ p = name->ndata;
+
+ for (n = 0; n < numlabels - 1; n++) {
+ unsigned char ch, llen;
+ unsigned int firstoffset, length;
+
+ firstoffset = (unsigned int)(p - name->ndata);
+ length = name->length - firstoffset;
+
+ /*
+ * We calculate the table index using the first
+ * character in the first label of the suffix name.
+ */
+ ch = p[1];
+ i = tableindex[ch];
+ if (ISC_LIKELY((cctx->allowed & DNS_COMPRESS_CASESENSITIVE) !=
+ 0))
+ {
+ for (node = cctx->table[i]; node != NULL;
+ node = node->next)
+ {
+ if (ISC_UNLIKELY(node->name.length != length)) {
+ continue;
+ }
+
+ if (ISC_LIKELY(memcmp(node->name.ndata, p,
+ length) == 0))
+ {
+ goto found;
+ }
+ }
+ } else {
+ for (node = cctx->table[i]; node != NULL;
+ node = node->next)
+ {
+ unsigned int l, count;
+ unsigned char c;
+ unsigned char *label1, *label2;
+
+ if (ISC_UNLIKELY(node->name.length != length)) {
+ continue;
+ }
+
+ l = labels - n;
+ if (ISC_UNLIKELY(node->name.labels != l)) {
+ continue;
+ }
+
+ label1 = node->name.ndata;
+ label2 = p;
+ while (ISC_LIKELY(l-- > 0)) {
+ count = *label1++;
+ if (count != *label2++) {
+ goto cont1;
+ }
+
+ /* no bitstring support */
+ INSIST(count <= 63);
+
+ /* Loop unrolled for performance */
+ while (ISC_LIKELY(count > 3)) {
+ c = maptolower[label1[0]];
+ if (c != maptolower[label2[0]])
+ {
+ goto cont1;
+ }
+ c = maptolower[label1[1]];
+ if (c != maptolower[label2[1]])
+ {
+ goto cont1;
+ }
+ c = maptolower[label1[2]];
+ if (c != maptolower[label2[2]])
+ {
+ goto cont1;
+ }
+ c = maptolower[label1[3]];
+ if (c != maptolower[label2[3]])
+ {
+ goto cont1;
+ }
+ count -= 4;
+ label1 += 4;
+ label2 += 4;
+ }
+ while (ISC_LIKELY(count-- > 0)) {
+ c = maptolower[*label1++];
+ if (c != maptolower[*label2++])
+ {
+ goto cont1;
+ }
+ }
+ }
+ break;
+ cont1:
+ continue;
+ }
+ }
+
+ if (node != NULL) {
+ break;
+ }
+
+ llen = *p;
+ p += llen + 1;
+ }
+
+found:
+ /*
+ * If node == NULL, we found no match at all.
+ */
+ if (node == NULL) {
+ return (false);
+ }
+
+ if (n == 0) {
+ dns_name_reset(prefix);
+ } else {
+ dns_name_getlabelsequence(name, 0, n, prefix);
+ }
+
+ *offset = (node->offset & 0x7fff);
+ return (true);
+}
+
+static unsigned int
+name_length(const dns_name_t *name) {
+ isc_region_t r;
+ dns_name_toregion(name, &r);
+ return (r.length);
+}
+
+void
+dns_compress_add(dns_compress_t *cctx, const dns_name_t *name,
+ const dns_name_t *prefix, uint16_t offset) {
+ dns_name_t tname, xname;
+ unsigned int start;
+ unsigned int n;
+ unsigned int count;
+ unsigned int i;
+ dns_compressnode_t *node;
+ unsigned int length;
+ unsigned int tlength;
+ uint16_t toffset;
+ unsigned char *tmp;
+ isc_region_t r;
+ bool allocated = false;
+
+ REQUIRE(VALID_CCTX(cctx));
+ REQUIRE(dns_name_isabsolute(name));
+
+ if (ISC_UNLIKELY((cctx->allowed & DNS_COMPRESS_ENABLED) == 0)) {
+ return;
+ }
+
+ if (offset >= 0x4000) {
+ return;
+ }
+ dns_name_init(&tname, NULL);
+ dns_name_init(&xname, NULL);
+
+ n = dns_name_countlabels(name);
+ count = dns_name_countlabels(prefix);
+ if (dns_name_isabsolute(prefix)) {
+ count--;
+ }
+ if (count == 0) {
+ return;
+ }
+ start = 0;
+ dns_name_toregion(name, &r);
+ length = r.length;
+ if (cctx->arena_off + length < DNS_COMPRESS_ARENA_SIZE) {
+ tmp = &cctx->arena[cctx->arena_off];
+ cctx->arena_off += length;
+ } else {
+ allocated = true;
+ tmp = isc_mem_get(cctx->mctx, length);
+ }
+ /*
+ * Copy name data to 'tmp' and make 'r' use 'tmp'.
+ */
+ memmove(tmp, r.base, r.length);
+ r.base = tmp;
+ dns_name_fromregion(&xname, &r);
+
+ if (count > 2U) {
+ count = 2U;
+ }
+
+ while (count > 0) {
+ unsigned char ch;
+
+ dns_name_getlabelsequence(&xname, start, n, &tname);
+ /*
+ * We calculate the table index using the first
+ * character in the first label of tname.
+ */
+ ch = tname.ndata[1];
+ i = tableindex[ch];
+ tlength = name_length(&tname);
+ toffset = (uint16_t)(offset + (length - tlength));
+ if (toffset >= 0x4000) {
+ break;
+ }
+ /*
+ * Create a new node and add it.
+ */
+ if (cctx->count < DNS_COMPRESS_INITIALNODES) {
+ node = &cctx->initialnodes[cctx->count];
+ } else {
+ node = isc_mem_get(cctx->mctx,
+ sizeof(dns_compressnode_t));
+ }
+ node->count = cctx->count++;
+ /*
+ * 'node->r.base' becomes 'tmp' when start == 0.
+ * Record this by setting 0x8000 so it can be freed later.
+ */
+ if (start == 0 && allocated) {
+ toffset |= 0x8000;
+ }
+ node->offset = toffset;
+ dns_name_toregion(&tname, &node->r);
+ dns_name_init(&node->name, NULL);
+ node->name.length = node->r.length;
+ node->name.ndata = node->r.base;
+ node->name.labels = tname.labels;
+ node->name.attributes = DNS_NAMEATTR_ABSOLUTE;
+ node->next = cctx->table[i];
+ cctx->table[i] = node;
+ start++;
+ n--;
+ count--;
+ }
+
+ if (start == 0) {
+ if (!allocated) {
+ cctx->arena_off -= length;
+ } else {
+ isc_mem_put(cctx->mctx, tmp, length);
+ }
+ }
+}
+
+void
+dns_compress_rollback(dns_compress_t *cctx, uint16_t offset) {
+ unsigned int i;
+ dns_compressnode_t *node;
+
+ REQUIRE(VALID_CCTX(cctx));
+
+ if (ISC_UNLIKELY((cctx->allowed & DNS_COMPRESS_ENABLED) == 0)) {
+ return;
+ }
+
+ for (i = 0; i < DNS_COMPRESS_TABLESIZE; i++) {
+ node = cctx->table[i];
+ /*
+ * This relies on nodes with greater offsets being
+ * closer to the beginning of the list, and the
+ * items with the greatest offsets being at the end
+ * of the initialnodes[] array.
+ */
+ while (node != NULL && (node->offset & 0x7fff) >= offset) {
+ cctx->table[i] = node->next;
+ if ((node->offset & 0x8000) != 0) {
+ isc_mem_put(cctx->mctx, node->r.base,
+ node->r.length);
+ }
+ if (node->count >= DNS_COMPRESS_INITIALNODES) {
+ isc_mem_put(cctx->mctx, node, sizeof(*node));
+ }
+ cctx->count--;
+ node = cctx->table[i];
+ }
+ }
+}
+
+/***
+ *** Decompression
+ ***/
+
+void
+dns_decompress_init(dns_decompress_t *dctx, int edns,
+ dns_decompresstype_t type) {
+ REQUIRE(dctx != NULL);
+ REQUIRE(edns >= -1 && edns <= 255);
+
+ dctx->allowed = DNS_COMPRESS_NONE;
+ dctx->edns = edns;
+ dctx->type = type;
+ dctx->magic = DCTX_MAGIC;
+}
+
+void
+dns_decompress_invalidate(dns_decompress_t *dctx) {
+ REQUIRE(VALID_DCTX(dctx));
+
+ dctx->magic = 0;
+}
+
+void
+dns_decompress_setmethods(dns_decompress_t *dctx, unsigned int allowed) {
+ REQUIRE(VALID_DCTX(dctx));
+
+ switch (dctx->type) {
+ case DNS_DECOMPRESS_ANY:
+ dctx->allowed = DNS_COMPRESS_ALL;
+ break;
+ case DNS_DECOMPRESS_NONE:
+ dctx->allowed = DNS_COMPRESS_NONE;
+ break;
+ case DNS_DECOMPRESS_STRICT:
+ dctx->allowed = allowed;
+ break;
+ }
+}
+
+unsigned int
+dns_decompress_getmethods(dns_decompress_t *dctx) {
+ REQUIRE(VALID_DCTX(dctx));
+
+ return (dctx->allowed);
+}
+
+int
+dns_decompress_edns(dns_decompress_t *dctx) {
+ REQUIRE(VALID_DCTX(dctx));
+
+ return (dctx->edns);
+}
+
+dns_decompresstype_t
+dns_decompress_type(dns_decompress_t *dctx) {
+ REQUIRE(VALID_DCTX(dctx));
+
+ return (dctx->type);
+}
diff --git a/lib/dns/db.c b/lib/dns/db.c
new file mode 100644
index 0000000..41dbaa3
--- /dev/null
+++ b/lib/dns/db.c
@@ -0,0 +1,1139 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/once.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>
+#include <dns/result.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"
+
+static ISC_LIST(dns_dbimplementation_t) implementations;
+static isc_rwlock_t implock;
+static isc_once_t once = ISC_ONCE_INIT;
+
+static dns_dbimplementation_t rbtimp;
+
+static void
+initialize(void) {
+ isc_rwlock_init(&implock, 0, 0);
+
+ rbtimp.name = "rbt";
+ rbtimp.create = dns_rbtdb_create;
+ rbtimp.mctx = NULL;
+ rbtimp.driverarg = NULL;
+ ISC_LINK_INIT(&rbtimp, link);
+
+ ISC_LIST_INIT(implementations);
+ ISC_LIST_APPEND(implementations, &rbtimp, link);
+}
+
+static dns_dbimplementation_t *
+impfind(const char *name) {
+ dns_dbimplementation_t *imp;
+
+ for (imp = ISC_LIST_HEAD(implementations); imp != NULL;
+ imp = ISC_LIST_NEXT(imp, link))
+ {
+ if (strcasecmp(name, imp->name) == 0) {
+ return (imp);
+ }
+ }
+ return (NULL);
+}
+
+/***
+ *** Basic DB Methods
+ ***/
+
+isc_result_t
+dns_db_create(isc_mem_t *mctx, const char *db_type, const dns_name_t *origin,
+ dns_dbtype_t type, dns_rdataclass_t rdclass, unsigned int argc,
+ char *argv[], dns_db_t **dbp) {
+ dns_dbimplementation_t *impinfo;
+
+ RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS);
+
+ /*
+ * Create a new database using implementation 'db_type'.
+ */
+
+ REQUIRE(dbp != NULL && *dbp == NULL);
+ REQUIRE(dns_name_isabsolute(origin));
+
+ RWLOCK(&implock, isc_rwlocktype_read);
+ impinfo = impfind(db_type);
+ if (impinfo != NULL) {
+ isc_result_t result;
+ result = ((impinfo->create)(mctx, origin, type, rdclass, argc,
+ argv, impinfo->driverarg, dbp));
+ RWUNLOCK(&implock, isc_rwlocktype_read);
+ return (result);
+ }
+
+ RWUNLOCK(&implock, isc_rwlocktype_read);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DB,
+ ISC_LOG_ERROR, "unsupported database type '%s'", db_type);
+
+ return (ISC_R_NOTFOUND);
+}
+
+void
+dns_db_attach(dns_db_t *source, dns_db_t **targetp) {
+ /*
+ * Attach *targetp to source.
+ */
+
+ REQUIRE(DNS_DB_VALID(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ (source->methods->attach)(source, targetp);
+
+ ENSURE(*targetp == source);
+}
+
+void
+dns_db_detach(dns_db_t **dbp) {
+ /*
+ * Detach *dbp from its database.
+ */
+
+ REQUIRE(dbp != NULL);
+ REQUIRE(DNS_DB_VALID(*dbp));
+
+ ((*dbp)->methods->detach)(dbp);
+
+ ENSURE(*dbp == NULL);
+}
+
+bool
+dns_db_iscache(dns_db_t *db) {
+ /*
+ * Does 'db' have cache semantics?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ if ((db->attributes & DNS_DBATTR_CACHE) != 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+dns_db_iszone(dns_db_t *db) {
+ /*
+ * Does 'db' have zone semantics?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ if ((db->attributes & (DNS_DBATTR_CACHE | DNS_DBATTR_STUB)) == 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+dns_db_isstub(dns_db_t *db) {
+ /*
+ * Does 'db' have stub semantics?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ if ((db->attributes & DNS_DBATTR_STUB) != 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+dns_db_isdnssec(dns_db_t *db) {
+ /*
+ * Is 'db' secure or partially secure?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+
+ if (db->methods->isdnssec != NULL) {
+ return ((db->methods->isdnssec)(db));
+ }
+ return ((db->methods->issecure)(db));
+}
+
+bool
+dns_db_issecure(dns_db_t *db) {
+ /*
+ * Is 'db' secure?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+
+ return ((db->methods->issecure)(db));
+}
+
+bool
+dns_db_ispersistent(dns_db_t *db) {
+ /*
+ * Is 'db' persistent?
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ return ((db->methods->ispersistent)(db));
+}
+
+dns_name_t *
+dns_db_origin(dns_db_t *db) {
+ /*
+ * The origin of the database.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ return (&db->origin);
+}
+
+dns_rdataclass_t
+dns_db_class(dns_db_t *db) {
+ /*
+ * The class of the database.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ return (db->rdclass);
+}
+
+isc_result_t
+dns_db_beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ /*
+ * Begin loading 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(DNS_CALLBACK_VALID(callbacks));
+
+ return ((db->methods->beginload)(db, callbacks));
+}
+
+isc_result_t
+dns_db_endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ dns_dbonupdatelistener_t *listener;
+
+ /*
+ * Finish loading 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(DNS_CALLBACK_VALID(callbacks));
+ REQUIRE(callbacks->add_private != NULL);
+
+ for (listener = ISC_LIST_HEAD(db->update_listeners); listener != NULL;
+ listener = ISC_LIST_NEXT(listener, link))
+ {
+ listener->onupdate(db, listener->onupdate_arg);
+ }
+
+ return ((db->methods->endload)(db, callbacks));
+}
+
+isc_result_t
+dns_db_load(dns_db_t *db, const char *filename, dns_masterformat_t format,
+ unsigned int options) {
+ isc_result_t result, eresult;
+ dns_rdatacallbacks_t callbacks;
+
+ /*
+ * Load master file 'filename' into 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+
+ if ((db->attributes & DNS_DBATTR_CACHE) != 0) {
+ options |= DNS_MASTER_AGETTL;
+ }
+
+ dns_rdatacallbacks_init(&callbacks);
+ result = dns_db_beginload(db, &callbacks);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dns_master_loadfile(filename, &db->origin, &db->origin,
+ db->rdclass, options, 0, &callbacks, NULL,
+ NULL, db->mctx, format, 0);
+ eresult = dns_db_endload(db, &callbacks);
+ /*
+ * We always call dns_db_endload(), but we only want to return its
+ * result if dns_master_loadfile() succeeded. If dns_master_loadfile()
+ * failed, we want to return the result code it gave us.
+ */
+ if (eresult != ISC_R_SUCCESS &&
+ (result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE))
+ {
+ result = eresult;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_db_serialize(dns_db_t *db, dns_dbversion_t *version, FILE *file) {
+ REQUIRE(DNS_DB_VALID(db));
+ if (db->methods->serialize == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((db->methods->serialize)(db, version, file));
+}
+
+isc_result_t
+dns_db_dump(dns_db_t *db, dns_dbversion_t *version, const char *filename) {
+ return ((db->methods->dump)(db, version, filename,
+ dns_masterformat_text));
+}
+
+/***
+ *** Version Methods
+ ***/
+
+void
+dns_db_currentversion(dns_db_t *db, dns_dbversion_t **versionp) {
+ /*
+ * Open the current version for reading.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+ REQUIRE(versionp != NULL && *versionp == NULL);
+
+ (db->methods->currentversion)(db, versionp);
+}
+
+isc_result_t
+dns_db_newversion(dns_db_t *db, dns_dbversion_t **versionp) {
+ /*
+ * Open a new version for reading and writing.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+ REQUIRE(versionp != NULL && *versionp == NULL);
+
+ return ((db->methods->newversion)(db, versionp));
+}
+
+void
+dns_db_attachversion(dns_db_t *db, dns_dbversion_t *source,
+ dns_dbversion_t **targetp) {
+ /*
+ * Attach '*targetp' to 'source'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+ REQUIRE(source != NULL);
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ (db->methods->attachversion)(db, source, targetp);
+
+ ENSURE(*targetp != NULL);
+}
+
+void
+dns_db_closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit) {
+ dns_dbonupdatelistener_t *listener;
+
+ /*
+ * Close version '*versionp'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
+ REQUIRE(versionp != NULL && *versionp != NULL);
+
+ (db->methods->closeversion)(db, versionp, commit);
+
+ if (commit) {
+ for (listener = ISC_LIST_HEAD(db->update_listeners);
+ listener != NULL; listener = ISC_LIST_NEXT(listener, link))
+ {
+ listener->onupdate(db, listener->onupdate_arg);
+ }
+ }
+
+ ENSURE(*versionp == NULL);
+}
+
+/***
+ *** Node Methods
+ ***/
+
+isc_result_t
+dns_db_findnode(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep) {
+ /*
+ * Find the node with name 'name'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ if (db->methods->findnode != NULL) {
+ return ((db->methods->findnode)(db, name, create, nodep));
+ } else {
+ return ((db->methods->findnodeext)(db, name, create, NULL, NULL,
+ nodep));
+ }
+}
+
+isc_result_t
+dns_db_findnodeext(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo, dns_dbnode_t **nodep) {
+ /*
+ * Find the node with name 'name', passing 'arg' to the database
+ * implementation.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ if (db->methods->findnodeext != NULL) {
+ return ((db->methods->findnodeext)(db, name, create, methods,
+ clientinfo, nodep));
+ } else {
+ return ((db->methods->findnode)(db, name, create, nodep));
+ }
+}
+
+isc_result_t
+dns_db_findnsec3node(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep) {
+ /*
+ * Find the node with name 'name'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ return ((db->methods->findnsec3node)(db, name, create, nodep));
+}
+
+isc_result_t
+dns_db_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ /*
+ * Find the best match for 'name' and 'type' in version 'version'
+ * of 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(type != dns_rdatatype_rrsig);
+ REQUIRE(nodep == NULL || *nodep == NULL);
+ REQUIRE(dns_name_hasbuffer(foundname));
+ REQUIRE(rdataset == NULL || (DNS_RDATASET_VALID(rdataset) &&
+ !dns_rdataset_isassociated(rdataset)));
+ REQUIRE(sigrdataset == NULL ||
+ (DNS_RDATASET_VALID(sigrdataset) &&
+ !dns_rdataset_isassociated(sigrdataset)));
+
+ if (db->methods->find != NULL) {
+ return ((db->methods->find)(db, name, version, type, options,
+ now, nodep, foundname, rdataset,
+ sigrdataset));
+ } else {
+ return ((db->methods->findext)(db, name, version, type, options,
+ now, nodep, foundname, NULL,
+ NULL, rdataset, sigrdataset));
+ }
+}
+
+isc_result_t
+dns_db_findext(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ /*
+ * Find the best match for 'name' and 'type' in version 'version'
+ * of 'db', passing in 'arg'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(type != dns_rdatatype_rrsig);
+ REQUIRE(nodep == NULL || *nodep == NULL);
+ REQUIRE(dns_name_hasbuffer(foundname));
+ REQUIRE(rdataset == NULL || (DNS_RDATASET_VALID(rdataset) &&
+ !dns_rdataset_isassociated(rdataset)));
+ REQUIRE(sigrdataset == NULL ||
+ (DNS_RDATASET_VALID(sigrdataset) &&
+ !dns_rdataset_isassociated(sigrdataset)));
+
+ if (db->methods->findext != NULL) {
+ return ((db->methods->findext)(
+ db, name, version, type, options, now, nodep, foundname,
+ methods, clientinfo, rdataset, sigrdataset));
+ } else {
+ return ((db->methods->find)(db, name, version, type, options,
+ now, nodep, foundname, rdataset,
+ sigrdataset));
+ }
+}
+
+isc_result_t
+dns_db_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_name_t *dcname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ /*
+ * Find the deepest known zonecut which encloses 'name' in 'db'.
+ * foundname is the zonecut, dcname is the deepest name we have
+ * in database that is part of queried name.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+ REQUIRE(nodep == NULL || *nodep == NULL);
+ REQUIRE(dns_name_hasbuffer(foundname));
+ REQUIRE(sigrdataset == NULL ||
+ (DNS_RDATASET_VALID(sigrdataset) &&
+ !dns_rdataset_isassociated(sigrdataset)));
+
+ return ((db->methods->findzonecut)(db, name, options, now, nodep,
+ foundname, dcname, rdataset,
+ sigrdataset));
+}
+
+void
+dns_db_attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) {
+ /*
+ * Attach *targetp to source.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(source != NULL);
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ (db->methods->attachnode)(db, source, targetp);
+}
+
+void
+dns_db_detachnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ /*
+ * Detach *nodep from its node.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(nodep != NULL && *nodep != NULL);
+
+ (db->methods->detachnode)(db, nodep);
+
+ ENSURE(*nodep == NULL);
+}
+
+void
+dns_db_transfernode(dns_db_t *db, dns_dbnode_t **sourcep,
+ dns_dbnode_t **targetp) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+ /*
+ * This doesn't check the implementation magic. If we find that
+ * we need such checks in future then this will be done in the
+ * method.
+ */
+ REQUIRE(sourcep != NULL && *sourcep != NULL);
+
+ UNUSED(db);
+
+ if (db->methods->transfernode == NULL) {
+ *targetp = *sourcep;
+ *sourcep = NULL;
+ } else {
+ (db->methods->transfernode)(db, sourcep, targetp);
+ }
+
+ ENSURE(*sourcep == NULL);
+}
+
+isc_result_t
+dns_db_expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) {
+ /*
+ * Mark as stale all records at 'node' which expire at or before 'now'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+ REQUIRE(node != NULL);
+
+ return ((db->methods->expirenode)(db, node, now));
+}
+
+void
+dns_db_printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) {
+ /*
+ * Print a textual representation of the contents of the node to
+ * 'out'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(node != NULL);
+
+ (db->methods->printnode)(db, node, out);
+}
+
+/***
+ *** DB Iterator Creation
+ ***/
+
+isc_result_t
+dns_db_createiterator(dns_db_t *db, unsigned int flags,
+ dns_dbiterator_t **iteratorp) {
+ /*
+ * Create an iterator for version 'version' of 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(iteratorp != NULL && *iteratorp == NULL);
+
+ return (db->methods->createiterator(db, flags, iteratorp));
+}
+
+/***
+ *** Rdataset Methods
+ ***/
+
+isc_result_t
+dns_db_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(node != NULL);
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(!dns_rdataset_isassociated(rdataset));
+ REQUIRE(covers == 0 || type == dns_rdatatype_rrsig);
+ REQUIRE(type != dns_rdatatype_any);
+ REQUIRE(sigrdataset == NULL ||
+ (DNS_RDATASET_VALID(sigrdataset) &&
+ !dns_rdataset_isassociated(sigrdataset)));
+
+ return ((db->methods->findrdataset)(db, node, version, type, covers,
+ now, rdataset, sigrdataset));
+}
+
+isc_result_t
+dns_db_allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ /*
+ * Make '*iteratorp' an rdataset iteratator for all rdatasets at
+ * 'node' in version 'version' of 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(iteratorp != NULL && *iteratorp == NULL);
+
+ return ((db->methods->allrdatasets)(db, node, version, options, now,
+ iteratorp));
+}
+
+isc_result_t
+dns_db_addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ unsigned int options, dns_rdataset_t *addedrdataset) {
+ /*
+ * Add 'rdataset' to 'node' in version 'version' of 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(node != NULL);
+ REQUIRE(((db->attributes & DNS_DBATTR_CACHE) == 0 && version != NULL) ||
+ ((db->attributes & DNS_DBATTR_CACHE) != 0 && version == NULL &&
+ (options & DNS_DBADD_MERGE) == 0));
+ REQUIRE((options & DNS_DBADD_EXACT) == 0 ||
+ (options & DNS_DBADD_MERGE) != 0);
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(dns_rdataset_isassociated(rdataset));
+ REQUIRE(rdataset->rdclass == db->rdclass);
+ REQUIRE(addedrdataset == NULL ||
+ (DNS_RDATASET_VALID(addedrdataset) &&
+ !dns_rdataset_isassociated(addedrdataset)));
+
+ return ((db->methods->addrdataset)(db, node, version, now, rdataset,
+ options, addedrdataset));
+}
+
+isc_result_t
+dns_db_subtractrdataset(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, dns_rdataset_t *rdataset,
+ unsigned int options, dns_rdataset_t *newrdataset) {
+ /*
+ * Remove any rdata in 'rdataset' from 'node' in version 'version' of
+ * 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(node != NULL);
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0 && version != NULL);
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(dns_rdataset_isassociated(rdataset));
+ REQUIRE(rdataset->rdclass == db->rdclass);
+ REQUIRE(newrdataset == NULL ||
+ (DNS_RDATASET_VALID(newrdataset) &&
+ !dns_rdataset_isassociated(newrdataset)));
+
+ return ((db->methods->subtractrdataset)(db, node, version, rdataset,
+ options, newrdataset));
+}
+
+isc_result_t
+dns_db_deleterdataset(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, dns_rdatatype_t type,
+ dns_rdatatype_t covers) {
+ /*
+ * Make it so that no rdataset of type 'type' exists at 'node' in
+ * version version 'version' of 'db'.
+ */
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(node != NULL);
+ REQUIRE(((db->attributes & DNS_DBATTR_CACHE) == 0 && version != NULL) ||
+ ((db->attributes & DNS_DBATTR_CACHE) != 0 && version == NULL));
+
+ return ((db->methods->deleterdataset)(db, node, version, type, covers));
+}
+
+void
+dns_db_overmem(dns_db_t *db, bool overmem) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ (db->methods->overmem)(db, overmem);
+}
+
+isc_result_t
+dns_db_getsoaserial(dns_db_t *db, dns_dbversion_t *ver, uint32_t *serialp) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_buffer_t buffer;
+
+ REQUIRE(dns_db_iszone(db) || dns_db_isstub(db));
+
+ result = dns_db_findnode(db, dns_db_origin(db), false, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_soa, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto freenode;
+ }
+
+ result = dns_rdataset_first(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto freerdataset;
+ }
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdataset_next(&rdataset);
+ INSIST(result == ISC_R_NOMORE);
+
+ INSIST(rdata.length > 20);
+ isc_buffer_init(&buffer, rdata.data, rdata.length);
+ isc_buffer_add(&buffer, rdata.length);
+ isc_buffer_forward(&buffer, rdata.length - 20);
+ *serialp = isc_buffer_getuint32(&buffer);
+
+ result = ISC_R_SUCCESS;
+
+freerdataset:
+ dns_rdataset_disassociate(&rdataset);
+
+freenode:
+ dns_db_detachnode(db, &node);
+ return (result);
+}
+
+unsigned int
+dns_db_nodecount(dns_db_t *db) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ return ((db->methods->nodecount)(db));
+}
+
+size_t
+dns_db_hashsize(dns_db_t *db) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ if (db->methods->hashsize == NULL) {
+ return (0);
+ }
+
+ return ((db->methods->hashsize)(db));
+}
+
+isc_result_t
+dns_db_adjusthashsize(dns_db_t *db, size_t size) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ if (db->methods->adjusthashsize != NULL) {
+ return ((db->methods->adjusthashsize)(db, size));
+ }
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+void
+dns_db_settask(dns_db_t *db, isc_task_t *task) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ (db->methods->settask)(db, task);
+}
+
+isc_result_t
+dns_db_register(const char *name, dns_dbcreatefunc_t create, void *driverarg,
+ isc_mem_t *mctx, dns_dbimplementation_t **dbimp) {
+ dns_dbimplementation_t *imp;
+
+ REQUIRE(name != NULL);
+ REQUIRE(dbimp != NULL && *dbimp == NULL);
+
+ RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS);
+
+ RWLOCK(&implock, isc_rwlocktype_write);
+ imp = impfind(name);
+ if (imp != NULL) {
+ RWUNLOCK(&implock, isc_rwlocktype_write);
+ return (ISC_R_EXISTS);
+ }
+
+ imp = isc_mem_get(mctx, sizeof(dns_dbimplementation_t));
+ imp->name = name;
+ imp->create = create;
+ imp->mctx = NULL;
+ imp->driverarg = driverarg;
+ isc_mem_attach(mctx, &imp->mctx);
+ ISC_LINK_INIT(imp, link);
+ ISC_LIST_APPEND(implementations, imp, link);
+ RWUNLOCK(&implock, isc_rwlocktype_write);
+
+ *dbimp = imp;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_db_unregister(dns_dbimplementation_t **dbimp) {
+ dns_dbimplementation_t *imp;
+
+ REQUIRE(dbimp != NULL && *dbimp != NULL);
+
+ RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS);
+
+ imp = *dbimp;
+ *dbimp = NULL;
+ RWLOCK(&implock, isc_rwlocktype_write);
+ ISC_LIST_UNLINK(implementations, imp, link);
+ isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_dbimplementation_t));
+ RWUNLOCK(&implock, isc_rwlocktype_write);
+ ENSURE(*dbimp == NULL);
+}
+
+isc_result_t
+dns_db_getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(dns_db_iszone(db));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ if (db->methods->getoriginnode != NULL) {
+ return ((db->methods->getoriginnode)(db, nodep));
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+dns_stats_t *
+dns_db_getrrsetstats(dns_db_t *db) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ if (db->methods->getrrsetstats != NULL) {
+ return ((db->methods->getrrsetstats)(db));
+ }
+
+ return (NULL);
+}
+
+isc_result_t
+dns_db_setcachestats(dns_db_t *db, isc_stats_t *stats) {
+ REQUIRE(DNS_DB_VALID(db));
+
+ if (db->methods->setcachestats != NULL) {
+ return ((db->methods->setcachestats)(db, stats));
+ }
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version,
+ dns_hash_t *hash, uint8_t *flags,
+ uint16_t *iterations, unsigned char *salt,
+ size_t *salt_length) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(dns_db_iszone(db));
+
+ if (db->methods->getnsec3parameters != NULL) {
+ return ((db->methods->getnsec3parameters)(db, version, hash,
+ flags, iterations,
+ salt, salt_length));
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_db_getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records,
+ uint64_t *bytes) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(dns_db_iszone(db));
+
+ if (db->methods->getsize != NULL) {
+ return ((db->methods->getsize)(db, version, records, bytes));
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_db_setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset,
+ isc_stdtime_t resign) {
+ if (db->methods->setsigningtime != NULL) {
+ return ((db->methods->setsigningtime)(db, rdataset, resign));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_getsigningtime(dns_db_t *db, dns_rdataset_t *rdataset,
+ dns_name_t *name) {
+ if (db->methods->getsigningtime != NULL) {
+ return ((db->methods->getsigningtime)(db, rdataset, name));
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+void
+dns_db_resigned(dns_db_t *db, dns_rdataset_t *rdataset,
+ dns_dbversion_t *version) {
+ if (db->methods->resigned != NULL) {
+ (db->methods->resigned)(db, rdataset, version);
+ }
+}
+
+/*
+ * Attach a database to policy zone databases.
+ * This should only happen when the caller has already ensured that
+ * it is dealing with a database that understands response policy zones.
+ */
+void
+dns_db_rpz_attach(dns_db_t *db, void *rpzs, uint8_t rpz_num) {
+ REQUIRE(db->methods->rpz_attach != NULL);
+ (db->methods->rpz_attach)(db, rpzs, rpz_num);
+}
+
+/*
+ * Finish loading a response policy zone.
+ */
+isc_result_t
+dns_db_rpz_ready(dns_db_t *db) {
+ if (db->methods->rpz_ready == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ return ((db->methods->rpz_ready)(db));
+}
+
+/*
+ * Attach a notify-on-update function the database
+ */
+isc_result_t
+dns_db_updatenotify_register(dns_db_t *db, dns_dbupdate_callback_t fn,
+ void *fn_arg) {
+ dns_dbonupdatelistener_t *listener;
+
+ REQUIRE(db != NULL);
+ REQUIRE(fn != NULL);
+
+ for (listener = ISC_LIST_HEAD(db->update_listeners); listener != NULL;
+ listener = ISC_LIST_NEXT(listener, link))
+ {
+ if ((listener->onupdate == fn) &&
+ (listener->onupdate_arg == fn_arg))
+ {
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ listener = isc_mem_get(db->mctx, sizeof(dns_dbonupdatelistener_t));
+
+ listener->onupdate = fn;
+ listener->onupdate_arg = fn_arg;
+
+ ISC_LINK_INIT(listener, link);
+ ISC_LIST_APPEND(db->update_listeners, listener, link);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_db_updatenotify_unregister(dns_db_t *db, dns_dbupdate_callback_t fn,
+ void *fn_arg) {
+ dns_dbonupdatelistener_t *listener;
+
+ REQUIRE(db != NULL);
+
+ for (listener = ISC_LIST_HEAD(db->update_listeners); listener != NULL;
+ listener = ISC_LIST_NEXT(listener, link))
+ {
+ if ((listener->onupdate == fn) &&
+ (listener->onupdate_arg == fn_arg))
+ {
+ ISC_LIST_UNLINK(db->update_listeners, listener, link);
+ isc_mem_put(db->mctx, listener,
+ sizeof(dns_dbonupdatelistener_t));
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) {
+ REQUIRE(db != NULL);
+ REQUIRE(node != NULL);
+ REQUIRE(name != NULL);
+
+ if (db->methods->nodefullname == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((db->methods->nodefullname)(db, node, name));
+}
+
+isc_result_t
+dns_db_setservestalettl(dns_db_t *db, dns_ttl_t ttl) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+
+ if (db->methods->setservestalettl != NULL) {
+ return ((db->methods->setservestalettl)(db, ttl));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_getservestalettl(dns_db_t *db, dns_ttl_t *ttl) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+
+ if (db->methods->getservestalettl != NULL) {
+ return ((db->methods->getservestalettl)(db, ttl));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_setservestalerefresh(dns_db_t *db, uint32_t interval) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+
+ if (db->methods->setservestalerefresh != NULL) {
+ return ((db->methods->setservestalerefresh)(db, interval));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_getservestalerefresh(dns_db_t *db, uint32_t *interval) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+
+ if (db->methods->getservestalerefresh != NULL) {
+ return ((db->methods->getservestalerefresh)(db, interval));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_setgluecachestats(dns_db_t *db, isc_stats_t *stats) {
+ REQUIRE(dns_db_iszone(db));
+ REQUIRE(stats != NULL);
+
+ if (db->methods->setgluecachestats != NULL) {
+ return ((db->methods->setgluecachestats)(db, stats));
+ }
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
diff --git a/lib/dns/dbiterator.c b/lib/dns/dbiterator.c
new file mode 100644
index 0000000..39d9471
--- /dev/null
+++ b/lib/dns/dbiterator.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/dbtable.c b/lib/dns/dbtable.c
new file mode 100644
index 0000000..e36594b
--- /dev/null
+++ b/lib/dns/dbtable.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/rwlock.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbtable.h>
+#include <dns/rbt.h>
+#include <dns/result.h>
+
+struct dns_dbtable {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_rdataclass_t rdclass;
+ isc_rwlock_t tree_lock;
+ /* Protected by atomics */
+ isc_refcount_t references;
+ /* Locked by tree_lock. */
+ dns_rbt_t *rbt;
+ dns_db_t *default_db;
+};
+
+#define DBTABLE_MAGIC ISC_MAGIC('D', 'B', '-', '-')
+#define VALID_DBTABLE(dbtable) ISC_MAGIC_VALID(dbtable, DBTABLE_MAGIC)
+
+static void
+dbdetach(void *data, void *arg) {
+ dns_db_t *db = data;
+
+ UNUSED(arg);
+
+ dns_db_detach(&db);
+}
+
+isc_result_t
+dns_dbtable_create(isc_mem_t *mctx, dns_rdataclass_t rdclass,
+ dns_dbtable_t **dbtablep) {
+ dns_dbtable_t *dbtable;
+ isc_result_t result;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(dbtablep != NULL && *dbtablep == NULL);
+
+ dbtable = isc_mem_get(mctx, sizeof(*dbtable));
+
+ dbtable->rbt = NULL;
+ result = dns_rbt_create(mctx, dbdetach, NULL, &dbtable->rbt);
+ if (result != ISC_R_SUCCESS) {
+ goto clean1;
+ }
+
+ isc_rwlock_init(&dbtable->tree_lock, 0, 0);
+ dbtable->default_db = NULL;
+ dbtable->mctx = NULL;
+ isc_mem_attach(mctx, &dbtable->mctx);
+ dbtable->rdclass = rdclass;
+ dbtable->magic = DBTABLE_MAGIC;
+ isc_refcount_init(&dbtable->references, 1);
+
+ *dbtablep = dbtable;
+
+ return (ISC_R_SUCCESS);
+
+clean1:
+ isc_mem_putanddetach(&mctx, dbtable, sizeof(*dbtable));
+
+ return (result);
+}
+
+static void
+dbtable_free(dns_dbtable_t *dbtable) {
+ /*
+ * Caller must ensure that it is safe to call.
+ */
+
+ RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
+
+ if (dbtable->default_db != NULL) {
+ dns_db_detach(&dbtable->default_db);
+ }
+
+ dns_rbt_destroy(&dbtable->rbt);
+
+ RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
+
+ isc_rwlock_destroy(&dbtable->tree_lock);
+
+ dbtable->magic = 0;
+
+ isc_mem_putanddetach(&dbtable->mctx, dbtable, sizeof(*dbtable));
+}
+
+void
+dns_dbtable_attach(dns_dbtable_t *source, dns_dbtable_t **targetp) {
+ REQUIRE(VALID_DBTABLE(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+void
+dns_dbtable_detach(dns_dbtable_t **dbtablep) {
+ dns_dbtable_t *dbtable;
+
+ REQUIRE(dbtablep != NULL);
+ dbtable = *dbtablep;
+ *dbtablep = NULL;
+ REQUIRE(VALID_DBTABLE(dbtable));
+
+ if (isc_refcount_decrement(&dbtable->references) == 1) {
+ dbtable_free(dbtable);
+ }
+}
+
+isc_result_t
+dns_dbtable_add(dns_dbtable_t *dbtable, dns_db_t *db) {
+ isc_result_t result;
+ dns_db_t *dbclone;
+
+ REQUIRE(VALID_DBTABLE(dbtable));
+ REQUIRE(dns_db_class(db) == dbtable->rdclass);
+
+ dbclone = NULL;
+ dns_db_attach(db, &dbclone);
+
+ RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
+ result = dns_rbt_addname(dbtable->rbt, dns_db_origin(dbclone), dbclone);
+ RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+void
+dns_dbtable_remove(dns_dbtable_t *dbtable, dns_db_t *db) {
+ dns_db_t *stored_data = NULL;
+ isc_result_t result;
+ dns_name_t *name;
+
+ REQUIRE(VALID_DBTABLE(dbtable));
+
+ name = dns_db_origin(db);
+
+ /*
+ * There is a requirement that the association of name with db
+ * be verified. With the current rbt.c this is expensive to do,
+ * because effectively two find operations are being done, but
+ * deletion is relatively infrequent.
+ * XXXDCL ... this could be cheaper now with dns_rbt_deletenode.
+ */
+
+ RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
+
+ result = dns_rbt_findname(dbtable->rbt, name, 0, NULL,
+ (void **)(void *)&stored_data);
+
+ if (result == ISC_R_SUCCESS) {
+ INSIST(stored_data == db);
+
+ (void)dns_rbt_deletename(dbtable->rbt, name, false);
+ }
+
+ RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
+}
+
+void
+dns_dbtable_adddefault(dns_dbtable_t *dbtable, dns_db_t *db) {
+ REQUIRE(VALID_DBTABLE(dbtable));
+ REQUIRE(dbtable->default_db == NULL);
+ REQUIRE(dns_name_compare(dns_db_origin(db), dns_rootname) == 0);
+
+ RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
+
+ dbtable->default_db = NULL;
+ dns_db_attach(db, &dbtable->default_db);
+
+ RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
+}
+
+void
+dns_dbtable_getdefault(dns_dbtable_t *dbtable, dns_db_t **dbp) {
+ REQUIRE(VALID_DBTABLE(dbtable));
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ RWLOCK(&dbtable->tree_lock, isc_rwlocktype_read);
+
+ dns_db_attach(dbtable->default_db, dbp);
+
+ RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_read);
+}
+
+void
+dns_dbtable_removedefault(dns_dbtable_t *dbtable) {
+ REQUIRE(VALID_DBTABLE(dbtable));
+
+ RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
+
+ dns_db_detach(&dbtable->default_db);
+
+ RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
+}
+
+isc_result_t
+dns_dbtable_find(dns_dbtable_t *dbtable, const dns_name_t *name,
+ unsigned int options, dns_db_t **dbp) {
+ dns_db_t *stored_data = NULL;
+ isc_result_t result;
+ unsigned int rbtoptions = 0;
+
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ if ((options & DNS_DBTABLEFIND_NOEXACT) != 0) {
+ rbtoptions |= DNS_RBTFIND_NOEXACT;
+ }
+
+ RWLOCK(&dbtable->tree_lock, isc_rwlocktype_read);
+
+ result = dns_rbt_findname(dbtable->rbt, name, rbtoptions, NULL,
+ (void **)(void *)&stored_data);
+
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ dns_db_attach(stored_data, dbp);
+ } else if (dbtable->default_db != NULL) {
+ dns_db_attach(dbtable->default_db, dbp);
+ result = DNS_R_PARTIALMATCH;
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+
+ RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_read);
+
+ return (result);
+}
diff --git a/lib/dns/diff.c b/lib/dns/diff.c
new file mode 100644
index 0000000..200d711
--- /dev/null
+++ b/lib/dns/diff.c
@@ -0,0 +1,688 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/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/result.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(__FILE__, __LINE__,
+ "unexpected non-minimal diff");
+ } else {
+ dns_difftuple_free(tuplep);
+ }
+ dns_difftuple_free(&ot);
+ break;
+ }
+ }
+
+ if (*tuplep != NULL) {
+ ISC_LIST_APPEND(diff->tuples, *tuplep, link);
+ *tuplep = NULL;
+ }
+}
+
+static isc_stdtime_t
+setresign(dns_rdataset_t *modified) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_rrsig_t sig;
+ int64_t when;
+ isc_result_t result;
+
+ result = dns_rdataset_first(modified);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(modified, &rdata);
+ (void)dns_rdata_tostruct(&rdata, &sig, NULL);
+ if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) {
+ when = 0;
+ } else {
+ when = dns_time64_from32(sig.timeexpire);
+ }
+ dns_rdata_reset(&rdata);
+
+ result = dns_rdataset_next(modified);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(modified, &rdata);
+ (void)dns_rdata_tostruct(&rdata, &sig, NULL);
+ if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) {
+ goto next_rr;
+ }
+ if (when == 0 || dns_time64_from32(sig.timeexpire) < when) {
+ when = dns_time64_from32(sig.timeexpire);
+ }
+ next_rr:
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(modified);
+ }
+ INSIST(result == ISC_R_NOMORE);
+ return ((isc_stdtime_t)when);
+}
+
+static void
+getownercase(dns_rdataset_t *rdataset, dns_name_t *name) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_getownercase(rdataset, name);
+ }
+}
+
+static void
+setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_setownercase(rdataset, name);
+ }
+}
+
+static isc_result_t
+diff_apply(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver, bool warn) {
+ dns_difftuple_t *t;
+ dns_dbnode_t *node = NULL;
+ isc_result_t result;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+
+ REQUIRE(DNS_DIFF_VALID(diff));
+ REQUIRE(DNS_DB_VALID(db));
+
+ t = ISC_LIST_HEAD(diff->tuples);
+ while (t != NULL) {
+ dns_name_t *name;
+
+ INSIST(node == NULL);
+ name = &t->name;
+ /*
+ * Find the node.
+ * We create the node if it does not exist.
+ * This will cause an empty node to be created if the diff
+ * contains a deletion of an RR at a nonexistent name,
+ * but such diffs should never be created in the first
+ * place.
+ */
+
+ while (t != NULL && dns_name_equal(&t->name, name)) {
+ dns_rdatatype_t type, covers;
+ dns_diffop_t op;
+ dns_rdatalist_t rdl;
+ dns_rdataset_t rds;
+ dns_rdataset_t ardataset;
+ unsigned int options;
+
+ op = t->op;
+ type = t->rdata.type;
+ covers = rdata_covers(&t->rdata);
+
+ /*
+ * Collect a contiguous set of updates with
+ * the same operation (add/delete) and RR type
+ * into a single rdatalist so that the
+ * database rrset merging/subtraction code
+ * can work more efficiently than if each
+ * RR were merged into / subtracted from
+ * the database separately.
+ *
+ * This is done by linking rdata structures from the
+ * diff into "rdatalist". This uses the rdata link
+ * field, not the diff link field, so the structure
+ * of the diff itself is not affected.
+ */
+
+ dns_rdatalist_init(&rdl);
+ rdl.type = type;
+ rdl.covers = covers;
+ rdl.rdclass = t->rdata.rdclass;
+ rdl.ttl = t->ttl;
+
+ node = NULL;
+ if (type != dns_rdatatype_nsec3 &&
+ covers != dns_rdatatype_nsec3)
+ {
+ CHECK(dns_db_findnode(db, name, true, &node));
+ } else {
+ CHECK(dns_db_findnsec3node(db, name, true,
+ &node));
+ }
+
+ while (t != NULL && dns_name_equal(&t->name, name) &&
+ t->op == op && t->rdata.type == type &&
+ rdata_covers(&t->rdata) == covers)
+ {
+ /*
+ * Remember the add name for
+ * dns_rdataset_setownercase.
+ */
+ name = &t->name;
+ if (t->ttl != rdl.ttl && warn) {
+ dns_name_format(name, namebuf,
+ sizeof(namebuf));
+ dns_rdatatype_format(t->rdata.type,
+ typebuf,
+ sizeof(typebuf));
+ dns_rdataclass_format(t->rdata.rdclass,
+ classbuf,
+ sizeof(classbuf));
+ isc_log_write(DIFF_COMMON_LOGARGS,
+ ISC_LOG_WARNING,
+ "'%s/%s/%s': TTL differs "
+ "in "
+ "rdataset, adjusting "
+ "%lu -> %lu",
+ namebuf, typebuf,
+ classbuf,
+ (unsigned long)t->ttl,
+ (unsigned long)rdl.ttl);
+ }
+ ISC_LIST_APPEND(rdl.rdata, &t->rdata, link);
+ t = ISC_LIST_NEXT(t, link);
+ }
+
+ /*
+ * Convert the rdatalist into a rdataset.
+ */
+ dns_rdataset_init(&rds);
+ dns_rdataset_init(&ardataset);
+ CHECK(dns_rdatalist_tordataset(&rdl, &rds));
+ rds.trust = dns_trust_ultimate;
+
+ /*
+ * Merge the rdataset into the database.
+ */
+ switch (op) {
+ case DNS_DIFFOP_ADD:
+ case DNS_DIFFOP_ADDRESIGN:
+ options = DNS_DBADD_MERGE | DNS_DBADD_EXACT |
+ DNS_DBADD_EXACTTTL;
+ result = dns_db_addrdataset(db, node, ver, 0,
+ &rds, options,
+ &ardataset);
+ break;
+ case DNS_DIFFOP_DEL:
+ case DNS_DIFFOP_DELRESIGN:
+ options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD;
+ result = dns_db_subtractrdataset(db, node, ver,
+ &rds, options,
+ &ardataset);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ if (rds.type == dns_rdatatype_rrsig &&
+ (op == DNS_DIFFOP_DELRESIGN ||
+ op == DNS_DIFFOP_ADDRESIGN))
+ {
+ isc_stdtime_t resign;
+ resign = setresign(&ardataset);
+ dns_db_setsigningtime(db, &ardataset,
+ resign);
+ }
+ if (op == DNS_DIFFOP_ADD ||
+ op == DNS_DIFFOP_ADDRESIGN)
+ {
+ setownercase(&ardataset, name);
+ }
+ if (op == DNS_DIFFOP_DEL ||
+ op == DNS_DIFFOP_DELRESIGN)
+ {
+ getownercase(&ardataset, name);
+ }
+ } else if (result == DNS_R_UNCHANGED) {
+ /*
+ * This will not happen when executing a
+ * dynamic update, because that code will
+ * generate strictly minimal diffs.
+ * It may happen when receiving an IXFR
+ * from a server that is not as careful.
+ * Issue a warning and continue.
+ */
+ if (warn) {
+ dns_name_format(dns_db_origin(db),
+ namebuf,
+ sizeof(namebuf));
+ dns_rdataclass_format(dns_db_class(db),
+ classbuf,
+ sizeof(classbuf));
+ isc_log_write(DIFF_COMMON_LOGARGS,
+ ISC_LOG_WARNING,
+ "%s/%s: dns_diff_apply: "
+ "update with no effect",
+ namebuf, classbuf);
+ }
+ if (op == DNS_DIFFOP_ADD ||
+ op == DNS_DIFFOP_ADDRESIGN)
+ {
+ setownercase(&ardataset, name);
+ }
+ if (op == DNS_DIFFOP_DEL ||
+ op == DNS_DIFFOP_DELRESIGN)
+ {
+ getownercase(&ardataset, name);
+ }
+ } else if (result == DNS_R_NXRRSET) {
+ /*
+ * OK.
+ */
+ if (op == DNS_DIFFOP_DEL ||
+ op == DNS_DIFFOP_DELRESIGN)
+ {
+ getownercase(&ardataset, name);
+ }
+ if (dns_rdataset_isassociated(&ardataset)) {
+ dns_rdataset_disassociate(&ardataset);
+ }
+ } else {
+ if (dns_rdataset_isassociated(&ardataset)) {
+ dns_rdataset_disassociate(&ardataset);
+ }
+ CHECK(result);
+ }
+ dns_db_detachnode(db, &node);
+ if (dns_rdataset_isassociated(&ardataset)) {
+ dns_rdataset_disassociate(&ardataset);
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_diff_apply(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) {
+ return (diff_apply(diff, db, ver, true));
+}
+
+isc_result_t
+dns_diff_applysilently(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) {
+ return (diff_apply(diff, db, ver, false));
+}
+
+/* XXX this duplicates lots of code in diff_apply(). */
+
+isc_result_t
+dns_diff_load(dns_diff_t *diff, dns_addrdatasetfunc_t addfunc,
+ void *add_private) {
+ dns_difftuple_t *t;
+ isc_result_t result;
+
+ REQUIRE(DNS_DIFF_VALID(diff));
+
+ t = ISC_LIST_HEAD(diff->tuples);
+ while (t != NULL) {
+ dns_name_t *name;
+
+ name = &t->name;
+ while (t != NULL && dns_name_caseequal(&t->name, name)) {
+ dns_rdatatype_t type, covers;
+ dns_diffop_t op;
+ dns_rdatalist_t rdl;
+ dns_rdataset_t rds;
+
+ op = t->op;
+ type = t->rdata.type;
+ covers = rdata_covers(&t->rdata);
+
+ dns_rdatalist_init(&rdl);
+ rdl.type = type;
+ rdl.covers = covers;
+ rdl.rdclass = t->rdata.rdclass;
+ rdl.ttl = t->ttl;
+
+ while (t != NULL &&
+ dns_name_caseequal(&t->name, name) &&
+ t->op == op && t->rdata.type == type &&
+ rdata_covers(&t->rdata) == covers)
+ {
+ ISC_LIST_APPEND(rdl.rdata, &t->rdata, link);
+ t = ISC_LIST_NEXT(t, link);
+ }
+
+ /*
+ * Convert the rdatalist into a rdataset.
+ */
+ dns_rdataset_init(&rds);
+ CHECK(dns_rdatalist_tordataset(&rdl, &rds));
+ rds.trust = dns_trust_ultimate;
+
+ INSIST(op == DNS_DIFFOP_ADD);
+ result = (*addfunc)(add_private, name, &rds);
+ if (result == DNS_R_UNCHANGED) {
+ isc_log_write(DIFF_COMMON_LOGARGS,
+ ISC_LOG_WARNING,
+ "dns_diff_load: "
+ "update with no effect");
+ } else if (result == ISC_R_SUCCESS ||
+ result == DNS_R_NXRRSET)
+ {
+ /*
+ * OK.
+ */
+ } else {
+ CHECK(result);
+ }
+ }
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/*
+ * XXX uses qsort(); a merge sort would be more natural for lists,
+ * and perhaps safer wrt thread stack overflow.
+ */
+isc_result_t
+dns_diff_sort(dns_diff_t *diff, dns_diff_compare_func *compare) {
+ unsigned int length = 0;
+ unsigned int i;
+ dns_difftuple_t **v;
+ dns_difftuple_t *p;
+ REQUIRE(DNS_DIFF_VALID(diff));
+
+ for (p = ISC_LIST_HEAD(diff->tuples); p != NULL;
+ p = ISC_LIST_NEXT(p, link))
+ {
+ length++;
+ }
+ if (length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ v = isc_mem_get(diff->mctx, length * sizeof(dns_difftuple_t *));
+ for (i = 0; i < length; i++) {
+ p = ISC_LIST_HEAD(diff->tuples);
+ v[i] = p;
+ ISC_LIST_UNLINK(diff->tuples, p, link);
+ }
+ INSIST(ISC_LIST_HEAD(diff->tuples) == NULL);
+ qsort(v, length, sizeof(v[0]), compare);
+ for (i = 0; i < length; i++) {
+ ISC_LIST_APPEND(diff->tuples, v[i], link);
+ }
+ isc_mem_put(diff->mctx, v, length * sizeof(dns_difftuple_t *));
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Create an rdataset containing the single RR of the given
+ * tuple. The caller must allocate the rdata, rdataset and
+ * an rdatalist structure for it to refer to.
+ */
+
+static isc_result_t
+diff_tuple_tordataset(dns_difftuple_t *t, dns_rdata_t *rdata,
+ dns_rdatalist_t *rdl, dns_rdataset_t *rds) {
+ REQUIRE(DNS_DIFFTUPLE_VALID(t));
+ REQUIRE(rdl != NULL);
+ REQUIRE(rds != NULL);
+
+ dns_rdatalist_init(rdl);
+ rdl->type = t->rdata.type;
+ rdl->rdclass = t->rdata.rdclass;
+ rdl->ttl = t->ttl;
+ dns_rdataset_init(rds);
+ ISC_LINK_INIT(rdata, link);
+ dns_rdata_clone(&t->rdata, rdata);
+ ISC_LIST_APPEND(rdl->rdata, rdata, link);
+ return (dns_rdatalist_tordataset(rdl, rds));
+}
+
+isc_result_t
+dns_diff_print(dns_diff_t *diff, FILE *file) {
+ isc_result_t result;
+ dns_difftuple_t *t;
+ char *mem = NULL;
+ unsigned int size = 2048;
+ const char *op = NULL;
+
+ REQUIRE(DNS_DIFF_VALID(diff));
+
+ mem = isc_mem_get(diff->mctx, size);
+
+ for (t = ISC_LIST_HEAD(diff->tuples); t != NULL;
+ t = ISC_LIST_NEXT(t, link))
+ {
+ isc_buffer_t buf;
+ isc_region_t r;
+
+ dns_rdatalist_t rdl;
+ dns_rdataset_t rds;
+ dns_rdata_t rd = DNS_RDATA_INIT;
+
+ result = diff_tuple_tordataset(t, &rd, &rdl, &rds);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "diff_tuple_tordataset failed: %s",
+ dns_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+ again:
+ isc_buffer_init(&buf, mem, size);
+ result = dns_rdataset_totext(&rds, &t->name, false, false,
+ &buf);
+
+ if (result == ISC_R_NOSPACE) {
+ isc_mem_put(diff->mctx, mem, size);
+ size += 1024;
+ mem = isc_mem_get(diff->mctx, size);
+ goto again;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ /*
+ * Get rid of final newline.
+ */
+ INSIST(buf.used >= 1 &&
+ ((char *)buf.base)[buf.used - 1] == '\n');
+ buf.used--;
+
+ isc_buffer_usedregion(&buf, &r);
+ switch (t->op) {
+ case DNS_DIFFOP_EXISTS:
+ op = "exists";
+ break;
+ case DNS_DIFFOP_ADD:
+ op = "add";
+ break;
+ case DNS_DIFFOP_DEL:
+ op = "del";
+ break;
+ case DNS_DIFFOP_ADDRESIGN:
+ op = "add re-sign";
+ break;
+ case DNS_DIFFOP_DELRESIGN:
+ op = "del re-sign";
+ break;
+ }
+ if (file != NULL) {
+ fprintf(file, "%s %.*s\n", op, (int)r.length,
+ (char *)r.base);
+ } else {
+ isc_log_write(DIFF_COMMON_LOGARGS, ISC_LOG_DEBUG(7),
+ "%s %.*s", op, (int)r.length,
+ (char *)r.base);
+ }
+ }
+ result = ISC_R_SUCCESS;
+cleanup:
+ if (mem != NULL) {
+ isc_mem_put(diff->mctx, mem, size);
+ }
+ return (result);
+}
diff --git a/lib/dns/dispatch.c b/lib/dns/dispatch.c
new file mode 100644
index 0000000..5f27d63
--- /dev/null
+++ b/lib/dns/dispatch.c
@@ -0,0 +1,3591 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/portset.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/socket.h>
+#include <isc/stats.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/dispatch.h>
+#include <dns/events.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/portlist.h>
+#include <dns/stats.h>
+#include <dns/tcpmsg.h>
+#include <dns/types.h>
+
+typedef ISC_LIST(dns_dispentry_t) dns_displist_t;
+
+typedef struct dispsocket dispsocket_t;
+typedef ISC_LIST(dispsocket_t) dispsocketlist_t;
+
+typedef struct dispportentry dispportentry_t;
+typedef ISC_LIST(dispportentry_t) dispportlist_t;
+
+typedef struct dns_qid {
+ unsigned int magic;
+ unsigned int qid_nbuckets; /*%< hash table size */
+ unsigned int qid_increment; /*%< id increment on collision */
+ isc_mutex_t lock;
+ dns_displist_t *qid_table; /*%< the table itself */
+ dispsocketlist_t *sock_table; /*%< socket table */
+} dns_qid_t;
+
+struct dns_dispatchmgr {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_acl_t *blackhole;
+ dns_portlist_t *portlist;
+ isc_stats_t *stats;
+
+ /* Locked by "lock". */
+ isc_mutex_t lock;
+ unsigned int state;
+ ISC_LIST(dns_dispatch_t) list;
+
+ /* locked by buffer_lock */
+ dns_qid_t *qid;
+ isc_mutex_t buffer_lock;
+ unsigned int buffers; /*%< allocated buffers */
+ unsigned int buffersize; /*%< size of each buffer */
+ unsigned int maxbuffers; /*%< max buffers */
+
+ isc_refcount_t irefs;
+
+ /*%
+ * Locked by qid->lock if qid exists; otherwise, can be used without
+ * being locked.
+ * Memory footprint considerations: this is a simple implementation of
+ * available ports, i.e., an ordered array of the actual port numbers.
+ * This will require about 256KB of memory in the worst case (128KB for
+ * each of IPv4 and IPv6). We could reduce it by representing it as a
+ * more sophisticated way such as a list (or array) of ranges that are
+ * searched to identify a specific port. Our decision here is the saved
+ * memory isn't worth the implementation complexity, considering the
+ * fact that the whole BIND9 process (which is mainly named) already
+ * requires a pretty large memory footprint. We may, however, have to
+ * revisit the decision when we want to use it as a separate module for
+ * an environment where memory requirement is severer.
+ */
+ in_port_t *v4ports; /*%< available ports for IPv4 */
+ unsigned int nv4ports; /*%< # of available ports for IPv4 */
+ in_port_t *v6ports; /*%< available ports for IPv4 */
+ unsigned int nv6ports; /*%< # of available ports for IPv4 */
+};
+
+#define MGR_SHUTTINGDOWN 0x00000001U
+#define MGR_IS_SHUTTINGDOWN(l) (((l)->state & MGR_SHUTTINGDOWN) != 0)
+
+#define IS_PRIVATE(d) (((d)->attributes & DNS_DISPATCHATTR_PRIVATE) != 0)
+
+struct dns_dispentry {
+ unsigned int magic;
+ dns_dispatch_t *disp;
+ dns_messageid_t id;
+ in_port_t port;
+ unsigned int bucket;
+ isc_sockaddr_t host;
+ isc_task_t *task;
+ isc_taskaction_t action;
+ void *arg;
+ bool item_out;
+ dispsocket_t *dispsocket;
+ ISC_LIST(dns_dispatchevent_t) items;
+ ISC_LINK(dns_dispentry_t) link;
+};
+
+/*%
+ * Maximum number of dispatch sockets that can be pooled for reuse. The
+ * appropriate value may vary, but experiments have shown a busy caching server
+ * may need more than 1000 sockets concurrently opened. The maximum allowable
+ * number of dispatch sockets (per manager) will be set to the double of this
+ * value.
+ */
+#ifndef DNS_DISPATCH_POOLSOCKS
+#define DNS_DISPATCH_POOLSOCKS 2048
+#endif /* ifndef DNS_DISPATCH_POOLSOCKS */
+
+/*%
+ * Quota to control the number of dispatch sockets. If a dispatch has more
+ * than the quota of sockets, new queries will purge oldest ones, so that
+ * a massive number of outstanding queries won't prevent subsequent queries
+ * (especially if the older ones take longer time and result in timeout).
+ */
+#ifndef DNS_DISPATCH_SOCKSQUOTA
+#define DNS_DISPATCH_SOCKSQUOTA 3072
+#endif /* ifndef DNS_DISPATCH_SOCKSQUOTA */
+
+struct dispsocket {
+ unsigned int magic;
+ isc_socket_t *socket;
+ dns_dispatch_t *disp;
+ isc_sockaddr_t host;
+ in_port_t localport; /* XXX: should be removed later */
+ dispportentry_t *portentry;
+ dns_dispentry_t *resp;
+ isc_task_t *task;
+ ISC_LINK(dispsocket_t) link;
+ unsigned int bucket;
+ ISC_LINK(dispsocket_t) blink;
+};
+
+/*%
+ * A port table entry. We remember every port we first open in a table with a
+ * reference counter so that we can 'reuse' the same port (with different
+ * destination addresses) using the SO_REUSEADDR socket option.
+ */
+struct dispportentry {
+ in_port_t port;
+ isc_refcount_t refs;
+ ISC_LINK(struct dispportentry) link;
+};
+
+#ifndef DNS_DISPATCH_PORTTABLESIZE
+#define DNS_DISPATCH_PORTTABLESIZE 1024
+#endif /* ifndef DNS_DISPATCH_PORTTABLESIZE */
+
+#define INVALID_BUCKET (0xffffdead)
+
+/*%
+ * Number of tasks for each dispatch that use separate sockets for different
+ * transactions. This must be a power of 2 as it will divide 32 bit numbers
+ * to get an uniformly random tasks selection. See get_dispsocket().
+ */
+#define MAX_INTERNAL_TASKS 64
+
+struct dns_dispatch {
+ /* Unlocked. */
+ unsigned int magic; /*%< magic */
+ dns_dispatchmgr_t *mgr; /*%< dispatch manager */
+ int ntasks;
+ /*%
+ * internal task buckets. We use multiple tasks to distribute various
+ * socket events well when using separate dispatch sockets. We use the
+ * 1st task (task[0]) for internal control events.
+ */
+ isc_task_t *task[MAX_INTERNAL_TASKS];
+ isc_socket_t *socket; /*%< isc socket attached to */
+ isc_sockaddr_t local; /*%< local address */
+ in_port_t localport; /*%< local UDP port */
+ isc_sockaddr_t peer; /*%< peer address (TCP) */
+ isc_dscp_t dscp; /*%< "listen-on" DSCP value */
+ unsigned int maxrequests; /*%< max requests */
+ isc_event_t *ctlevent;
+
+ isc_mem_t *sepool; /*%< pool for socket events */
+
+ /*% Locked by mgr->lock. */
+ ISC_LINK(dns_dispatch_t) link;
+
+ /* Locked by "lock". */
+ isc_mutex_t lock; /*%< locks all below */
+ isc_sockettype_t socktype;
+ unsigned int attributes;
+ unsigned int refcount; /*%< number of users */
+ dns_dispatchevent_t *failsafe_ev; /*%< failsafe cancel event */
+ unsigned int shutting_down : 1, shutdown_out : 1, connected : 1,
+ tcpmsg_valid : 1, recv_pending : 1; /*%< is a
+ * recv()
+ * pending?
+ * */
+ isc_result_t shutdown_why;
+ ISC_LIST(dispsocket_t) activesockets;
+ ISC_LIST(dispsocket_t) inactivesockets;
+ unsigned int nsockets;
+ unsigned int requests; /*%< how many requests we have */
+ unsigned int tcpbuffers; /*%< allocated buffers */
+ dns_tcpmsg_t tcpmsg; /*%< for tcp streams */
+ dns_qid_t *qid;
+ dispportlist_t *port_table; /*%< hold ports 'owned' by us */
+};
+
+#define QID_MAGIC ISC_MAGIC('Q', 'i', 'd', ' ')
+#define VALID_QID(e) ISC_MAGIC_VALID((e), QID_MAGIC)
+
+#define RESPONSE_MAGIC ISC_MAGIC('D', 'r', 's', 'p')
+#define VALID_RESPONSE(e) ISC_MAGIC_VALID((e), RESPONSE_MAGIC)
+
+#define DISPSOCK_MAGIC ISC_MAGIC('D', 's', 'o', 'c')
+#define VALID_DISPSOCK(e) ISC_MAGIC_VALID((e), DISPSOCK_MAGIC)
+
+#define DISPATCH_MAGIC ISC_MAGIC('D', 'i', 's', 'p')
+#define VALID_DISPATCH(e) ISC_MAGIC_VALID((e), DISPATCH_MAGIC)
+
+#define DNS_DISPATCHMGR_MAGIC ISC_MAGIC('D', 'M', 'g', 'r')
+#define VALID_DISPATCHMGR(e) ISC_MAGIC_VALID((e), DNS_DISPATCHMGR_MAGIC)
+
+#define DNS_QID(disp) \
+ ((disp)->socktype == isc_sockettype_tcp) ? (disp)->qid \
+ : (disp)->mgr->qid
+
+/*%
+ * Locking a query port buffer is a bit tricky. We access the buffer without
+ * locking until qid is created. Technically, there is a possibility of race
+ * between the creation of qid and access to the port buffer; in practice,
+ * however, this should be safe because qid isn't created until the first
+ * dispatch is created and there should be no contending situation until then.
+ */
+#define PORTBUFLOCK(mgr) \
+ if ((mgr)->qid != NULL) \
+ LOCK(&((mgr)->qid->lock))
+#define PORTBUFUNLOCK(mgr) \
+ if ((mgr)->qid != NULL) \
+ UNLOCK((&(mgr)->qid->lock))
+
+/*
+ * Statics.
+ */
+static dns_dispentry_t *
+entry_search(dns_qid_t *, const isc_sockaddr_t *, dns_messageid_t, in_port_t,
+ unsigned int);
+static bool
+destroy_disp_ok(dns_dispatch_t *);
+static void
+destroy_disp(isc_task_t *task, isc_event_t *event);
+static void
+destroy_dispsocket(dns_dispatch_t *, dispsocket_t **);
+static void
+deactivate_dispsocket(dns_dispatch_t *, dispsocket_t *);
+static void
+udp_exrecv(isc_task_t *, isc_event_t *);
+static void
+udp_shrecv(isc_task_t *, isc_event_t *);
+static void
+udp_recv(isc_event_t *, dns_dispatch_t *, dispsocket_t *);
+static void
+tcp_recv(isc_task_t *, isc_event_t *);
+static isc_result_t
+startrecv(dns_dispatch_t *, dispsocket_t *);
+static uint32_t
+dns_hash(dns_qid_t *, const isc_sockaddr_t *, dns_messageid_t, in_port_t);
+static void
+free_buffer(dns_dispatch_t *disp, void *buf, unsigned int len);
+static void *
+allocate_udp_buffer(dns_dispatch_t *disp);
+static void
+free_devent(dns_dispatch_t *disp, dns_dispatchevent_t *ev);
+static dns_dispatchevent_t *
+allocate_devent(dns_dispatch_t *disp);
+static void
+do_cancel(dns_dispatch_t *disp);
+static dns_dispentry_t *
+linear_first(dns_qid_t *disp);
+static dns_dispentry_t *
+linear_next(dns_qid_t *disp, dns_dispentry_t *resp);
+static void
+dispatch_free(dns_dispatch_t **dispp);
+static isc_result_t
+get_udpsocket(dns_dispatchmgr_t *mgr, dns_dispatch_t *disp,
+ isc_socketmgr_t *sockmgr, const isc_sockaddr_t *localaddr,
+ isc_socket_t **sockp, isc_socket_t *dup_socket, bool duponly);
+static isc_result_t
+dispatch_createudp(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr,
+ isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr,
+ unsigned int maxrequests, unsigned int attributes,
+ dns_dispatch_t **dispp, isc_socket_t *dup_socket);
+static bool
+destroy_mgr_ok(dns_dispatchmgr_t *mgr);
+static void
+destroy_mgr(dns_dispatchmgr_t **mgrp);
+static isc_result_t
+qid_allocate(dns_dispatchmgr_t *mgr, unsigned int buckets,
+ unsigned int increment, dns_qid_t **qidp, bool needaddrtable);
+static void
+qid_destroy(isc_mem_t *mctx, dns_qid_t **qidp);
+static isc_result_t
+open_socket(isc_socketmgr_t *mgr, const isc_sockaddr_t *local,
+ unsigned int options, isc_socket_t **sockp,
+ isc_socket_t *dup_socket, bool duponly);
+static bool
+portavailable(dns_dispatchmgr_t *mgr, isc_socket_t *sock,
+ isc_sockaddr_t *sockaddrp);
+
+#define LVL(x) ISC_LOG_DEBUG(x)
+
+static void
+mgr_log(dns_dispatchmgr_t *mgr, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+static void
+mgr_log(dns_dispatchmgr_t *mgr, int level, const char *fmt, ...) {
+ char msgbuf[2048];
+ va_list ap;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH,
+ DNS_LOGMODULE_DISPATCH, level, "dispatchmgr %p: %s", mgr,
+ msgbuf);
+}
+
+static void
+inc_stats(dns_dispatchmgr_t *mgr, isc_statscounter_t counter) {
+ if (mgr->stats != NULL) {
+ isc_stats_increment(mgr->stats, counter);
+ }
+}
+
+static void
+dec_stats(dns_dispatchmgr_t *mgr, isc_statscounter_t counter) {
+ if (mgr->stats != NULL) {
+ isc_stats_decrement(mgr->stats, counter);
+ }
+}
+
+static void
+dispatch_log(dns_dispatch_t *disp, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+static void
+dispatch_log(dns_dispatch_t *disp, int level, const char *fmt, ...) {
+ char msgbuf[2048];
+ va_list ap;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH,
+ DNS_LOGMODULE_DISPATCH, level, "dispatch %p: %s", disp,
+ msgbuf);
+}
+
+static void
+request_log(dns_dispatch_t *disp, dns_dispentry_t *resp, int level,
+ const char *fmt, ...) ISC_FORMAT_PRINTF(4, 5);
+
+static void
+request_log(dns_dispatch_t *disp, dns_dispentry_t *resp, int level,
+ const char *fmt, ...) {
+ char msgbuf[2048];
+ char peerbuf[256];
+ va_list ap;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ if (VALID_RESPONSE(resp)) {
+ isc_sockaddr_format(&resp->host, peerbuf, sizeof(peerbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH,
+ DNS_LOGMODULE_DISPATCH, level,
+ "dispatch %p response %p %s: %s", disp, resp,
+ peerbuf, msgbuf);
+ } else {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH,
+ DNS_LOGMODULE_DISPATCH, level,
+ "dispatch %p req/resp %p: %s", disp, resp,
+ msgbuf);
+ }
+}
+
+/*
+ * Return a hash of the destination and message id.
+ */
+static uint32_t
+dns_hash(dns_qid_t *qid, const isc_sockaddr_t *dest, dns_messageid_t id,
+ in_port_t port) {
+ uint32_t ret;
+
+ ret = isc_sockaddr_hash(dest, true);
+ ret ^= ((uint32_t)id << 16) | port;
+ ret %= qid->qid_nbuckets;
+
+ INSIST(ret < qid->qid_nbuckets);
+
+ return (ret);
+}
+
+/*
+ * Find the first entry in 'qid'. Returns NULL if there are no entries.
+ */
+static dns_dispentry_t *
+linear_first(dns_qid_t *qid) {
+ dns_dispentry_t *ret;
+ unsigned int bucket;
+
+ bucket = 0;
+
+ while (bucket < qid->qid_nbuckets) {
+ ret = ISC_LIST_HEAD(qid->qid_table[bucket]);
+ if (ret != NULL) {
+ return (ret);
+ }
+ bucket++;
+ }
+
+ return (NULL);
+}
+
+/*
+ * Find the next entry after 'resp' in 'qid'. Return NULL if there are
+ * no more entries.
+ */
+static dns_dispentry_t *
+linear_next(dns_qid_t *qid, dns_dispentry_t *resp) {
+ dns_dispentry_t *ret;
+ unsigned int bucket;
+
+ ret = ISC_LIST_NEXT(resp, link);
+ if (ret != NULL) {
+ return (ret);
+ }
+
+ bucket = resp->bucket;
+ bucket++;
+ while (bucket < qid->qid_nbuckets) {
+ ret = ISC_LIST_HEAD(qid->qid_table[bucket]);
+ if (ret != NULL) {
+ return (ret);
+ }
+ bucket++;
+ }
+
+ return (NULL);
+}
+
+/*
+ * The dispatch must be locked.
+ */
+static bool
+destroy_disp_ok(dns_dispatch_t *disp) {
+ if (disp->refcount != 0) {
+ return (false);
+ }
+
+ if (disp->recv_pending != 0) {
+ return (false);
+ }
+
+ if (!ISC_LIST_EMPTY(disp->activesockets)) {
+ return (false);
+ }
+
+ if (disp->shutting_down == 0) {
+ return (false);
+ }
+
+ return (true);
+}
+
+/*
+ * Called when refcount reaches 0 (and safe to destroy).
+ *
+ * The dispatcher must be locked.
+ * The manager must not be locked.
+ */
+static void
+destroy_disp(isc_task_t *task, isc_event_t *event) {
+ dns_dispatch_t *disp;
+ dns_dispatchmgr_t *mgr;
+ bool killmgr;
+ dispsocket_t *dispsocket;
+ int i;
+
+ INSIST(event->ev_type == DNS_EVENT_DISPATCHCONTROL);
+
+ UNUSED(task);
+
+ disp = event->ev_arg;
+ mgr = disp->mgr;
+
+ LOCK(&mgr->lock);
+ ISC_LIST_UNLINK(mgr->list, disp, link);
+
+ dispatch_log(disp, LVL(90),
+ "shutting down; detaching from sock %p, task %p",
+ disp->socket, disp->task[0]); /* XXXX */
+
+ if (disp->sepool != NULL) {
+ isc_mem_destroy(&disp->sepool);
+ }
+
+ if (disp->socket != NULL) {
+ isc_socket_detach(&disp->socket);
+ }
+ while ((dispsocket = ISC_LIST_HEAD(disp->inactivesockets)) != NULL) {
+ ISC_LIST_UNLINK(disp->inactivesockets, dispsocket, link);
+ destroy_dispsocket(disp, &dispsocket);
+ }
+ for (i = 0; i < disp->ntasks; i++) {
+ isc_task_detach(&disp->task[i]);
+ }
+ isc_event_free(&event);
+
+ dispatch_free(&disp);
+
+ killmgr = destroy_mgr_ok(mgr);
+ UNLOCK(&mgr->lock);
+ if (killmgr) {
+ destroy_mgr(&mgr);
+ }
+}
+
+/*%
+ * Manipulate port table per dispatch: find an entry for a given port number,
+ * create a new entry, and decrement a given entry with possible clean-up.
+ */
+static dispportentry_t *
+port_search(dns_dispatch_t *disp, in_port_t port) {
+ dispportentry_t *portentry;
+
+ REQUIRE(disp->port_table != NULL);
+
+ portentry = ISC_LIST_HEAD(
+ disp->port_table[port % DNS_DISPATCH_PORTTABLESIZE]);
+ while (portentry != NULL) {
+ if (portentry->port == port) {
+ return (portentry);
+ }
+ portentry = ISC_LIST_NEXT(portentry, link);
+ }
+
+ return (NULL);
+}
+
+static dispportentry_t *
+new_portentry(dns_dispatch_t *disp, in_port_t port) {
+ dispportentry_t *portentry;
+ dns_qid_t *qid;
+
+ REQUIRE(disp->port_table != NULL);
+
+ portentry = isc_mem_get(disp->mgr->mctx, sizeof(*portentry));
+
+ portentry->port = port;
+ isc_refcount_init(&portentry->refs, 1);
+ ISC_LINK_INIT(portentry, link);
+ qid = DNS_QID(disp);
+ LOCK(&qid->lock);
+ ISC_LIST_APPEND(disp->port_table[port % DNS_DISPATCH_PORTTABLESIZE],
+ portentry, link);
+ UNLOCK(&qid->lock);
+
+ return (portentry);
+}
+
+/*%
+ * The caller must hold the qid->lock.
+ */
+static void
+deref_portentry(dns_dispatch_t *disp, dispportentry_t **portentryp) {
+ dispportentry_t *portentry = *portentryp;
+ *portentryp = NULL;
+
+ REQUIRE(disp->port_table != NULL);
+ REQUIRE(portentry != NULL);
+
+ if (isc_refcount_decrement(&portentry->refs) == 1) {
+ ISC_LIST_UNLINK(disp->port_table[portentry->port %
+ DNS_DISPATCH_PORTTABLESIZE],
+ portentry, link);
+ isc_mem_put(disp->mgr->mctx, portentry, sizeof(*portentry));
+ }
+}
+
+/*%
+ * Find a dispsocket for socket address 'dest', and port number 'port'.
+ * Return NULL if no such entry exists. Requires qid->lock to be held.
+ */
+static dispsocket_t *
+socket_search(dns_qid_t *qid, const isc_sockaddr_t *dest, in_port_t port,
+ unsigned int bucket) {
+ dispsocket_t *dispsock;
+
+ REQUIRE(VALID_QID(qid));
+ REQUIRE(bucket < qid->qid_nbuckets);
+
+ dispsock = ISC_LIST_HEAD(qid->sock_table[bucket]);
+
+ while (dispsock != NULL) {
+ if (dispsock->portentry != NULL &&
+ dispsock->portentry->port == port &&
+ isc_sockaddr_equal(dest, &dispsock->host))
+ {
+ return (dispsock);
+ }
+ dispsock = ISC_LIST_NEXT(dispsock, blink);
+ }
+
+ return (NULL);
+}
+
+/*%
+ * Make a new socket for a single dispatch with a random port number.
+ * The caller must hold the disp->lock
+ */
+static isc_result_t
+get_dispsocket(dns_dispatch_t *disp, const isc_sockaddr_t *dest,
+ isc_socketmgr_t *sockmgr, dispsocket_t **dispsockp,
+ in_port_t *portp) {
+ int i;
+ dns_dispatchmgr_t *mgr = disp->mgr;
+ isc_socket_t *sock = NULL;
+ isc_result_t result = ISC_R_FAILURE;
+ in_port_t port;
+ isc_sockaddr_t localaddr;
+ unsigned int bucket = 0;
+ dispsocket_t *dispsock;
+ unsigned int nports;
+ in_port_t *ports;
+ isc_socket_options_t bindoptions;
+ dispportentry_t *portentry = NULL;
+ dns_qid_t *qid;
+
+ if (isc_sockaddr_pf(&disp->local) == AF_INET) {
+ nports = disp->mgr->nv4ports;
+ ports = disp->mgr->v4ports;
+ } else {
+ nports = disp->mgr->nv6ports;
+ ports = disp->mgr->v6ports;
+ }
+ if (nports == 0) {
+ return (ISC_R_ADDRNOTAVAIL);
+ }
+
+ dispsock = ISC_LIST_HEAD(disp->inactivesockets);
+ if (dispsock != NULL) {
+ ISC_LIST_UNLINK(disp->inactivesockets, dispsock, link);
+ sock = dispsock->socket;
+ dispsock->socket = NULL;
+ } else {
+ dispsock = isc_mem_get(mgr->mctx, sizeof(*dispsock));
+
+ disp->nsockets++;
+ dispsock->socket = NULL;
+ dispsock->disp = disp;
+ dispsock->resp = NULL;
+ dispsock->portentry = NULL;
+ dispsock->task = NULL;
+ isc_task_attach(disp->task[isc_random_uniform(disp->ntasks)],
+ &dispsock->task);
+ ISC_LINK_INIT(dispsock, link);
+ ISC_LINK_INIT(dispsock, blink);
+ dispsock->magic = DISPSOCK_MAGIC;
+ }
+
+ /*
+ * Pick up a random UDP port and open a new socket with it. Avoid
+ * choosing ports that share the same destination because it will be
+ * very likely to fail in bind(2) or connect(2).
+ */
+ localaddr = disp->local;
+ qid = DNS_QID(disp);
+
+ for (i = 0; i < 64; i++) {
+ port = ports[isc_random_uniform(nports)];
+ isc_sockaddr_setport(&localaddr, port);
+
+ LOCK(&qid->lock);
+ bucket = dns_hash(qid, dest, 0, port);
+ if (socket_search(qid, dest, port, bucket) != NULL) {
+ UNLOCK(&qid->lock);
+ continue;
+ }
+ UNLOCK(&qid->lock);
+ bindoptions = 0;
+ portentry = port_search(disp, port);
+
+ if (portentry != NULL) {
+ bindoptions |= ISC_SOCKET_REUSEADDRESS;
+ }
+ result = open_socket(sockmgr, &localaddr, bindoptions, &sock,
+ NULL, false);
+ if (result == ISC_R_SUCCESS) {
+ if (portentry == NULL) {
+ portentry = new_portentry(disp, port);
+ if (portentry == NULL) {
+ result = ISC_R_NOMEMORY;
+ break;
+ }
+ } else {
+ isc_refcount_increment(&portentry->refs);
+ }
+ break;
+ } else if (result == ISC_R_NOPERM) {
+ char buf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_format(&localaddr, buf, sizeof(buf));
+ dispatch_log(disp, ISC_LOG_WARNING,
+ "open_socket(%s) -> %s: continuing", buf,
+ isc_result_totext(result));
+ } else if (result != ISC_R_ADDRINUSE) {
+ break;
+ }
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ dispsock->socket = sock;
+ dispsock->host = *dest;
+ dispsock->bucket = bucket;
+ LOCK(&qid->lock);
+ dispsock->portentry = portentry;
+ ISC_LIST_APPEND(qid->sock_table[bucket], dispsock, blink);
+ UNLOCK(&qid->lock);
+ *dispsockp = dispsock;
+ *portp = port;
+ } else {
+ /*
+ * We could keep it in the inactive list, but since this should
+ * be an exceptional case and might be resource shortage, we'd
+ * rather destroy it.
+ */
+ if (sock != NULL) {
+ isc_socket_detach(&sock);
+ }
+ destroy_dispsocket(disp, &dispsock);
+ }
+
+ return (result);
+}
+
+/*%
+ * Destroy a dedicated dispatch socket.
+ */
+static void
+destroy_dispsocket(dns_dispatch_t *disp, dispsocket_t **dispsockp) {
+ dispsocket_t *dispsock;
+ dns_qid_t *qid = DNS_QID(disp);
+
+ /*
+ * The dispatch must be locked.
+ */
+
+ REQUIRE(dispsockp != NULL && *dispsockp != NULL);
+ dispsock = *dispsockp;
+ *dispsockp = NULL;
+ REQUIRE(!ISC_LINK_LINKED(dispsock, link));
+
+ disp->nsockets--;
+ dispsock->magic = 0;
+ if (dispsock->portentry != NULL) {
+ /* socket_search() tests and dereferences portentry. */
+ LOCK(&qid->lock);
+ deref_portentry(disp, &dispsock->portentry);
+ UNLOCK(&qid->lock);
+ }
+ if (dispsock->socket != NULL) {
+ isc_socket_detach(&dispsock->socket);
+ }
+ if (ISC_LINK_LINKED(dispsock, blink)) {
+ LOCK(&qid->lock);
+ ISC_LIST_UNLINK(qid->sock_table[dispsock->bucket], dispsock,
+ blink);
+ UNLOCK(&qid->lock);
+ }
+ if (dispsock->task != NULL) {
+ isc_task_detach(&dispsock->task);
+ }
+ isc_mem_put(disp->mgr->mctx, dispsock, sizeof(*dispsock));
+}
+
+/*%
+ * Deactivate a dedicated dispatch socket. Move it to the inactive list for
+ * future reuse unless the total number of sockets are exceeding the maximum.
+ */
+static void
+deactivate_dispsocket(dns_dispatch_t *disp, dispsocket_t *dispsock) {
+ isc_result_t result;
+ dns_qid_t *qid = DNS_QID(disp);
+
+ /*
+ * The dispatch must be locked.
+ */
+ ISC_LIST_UNLINK(disp->activesockets, dispsock, link);
+ if (dispsock->resp != NULL) {
+ INSIST(dispsock->resp->dispsocket == dispsock);
+ dispsock->resp->dispsocket = NULL;
+ }
+
+ INSIST(dispsock->portentry != NULL);
+ /* socket_search() tests and dereferences portentry. */
+ LOCK(&qid->lock);
+ deref_portentry(disp, &dispsock->portentry);
+ UNLOCK(&qid->lock);
+
+ if (disp->nsockets > DNS_DISPATCH_POOLSOCKS) {
+ destroy_dispsocket(disp, &dispsock);
+ } else {
+ result = isc_socket_close(dispsock->socket);
+
+ LOCK(&qid->lock);
+ ISC_LIST_UNLINK(qid->sock_table[dispsock->bucket], dispsock,
+ blink);
+ UNLOCK(&qid->lock);
+
+ if (result == ISC_R_SUCCESS) {
+ ISC_LIST_APPEND(disp->inactivesockets, dispsock, link);
+ } else {
+ /*
+ * If the underlying system does not allow this
+ * optimization, destroy this temporary structure (and
+ * create a new one for a new transaction).
+ */
+ INSIST(result == ISC_R_NOTIMPLEMENTED);
+ destroy_dispsocket(disp, &dispsock);
+ }
+ }
+}
+
+/*
+ * Find an entry for query ID 'id', socket address 'dest', and port number
+ * 'port'.
+ * Return NULL if no such entry exists.
+ */
+static dns_dispentry_t *
+entry_search(dns_qid_t *qid, const isc_sockaddr_t *dest, dns_messageid_t id,
+ in_port_t port, unsigned int bucket) {
+ dns_dispentry_t *res;
+
+ REQUIRE(VALID_QID(qid));
+ REQUIRE(bucket < qid->qid_nbuckets);
+
+ res = ISC_LIST_HEAD(qid->qid_table[bucket]);
+
+ while (res != NULL) {
+ if (res->id == id && isc_sockaddr_equal(dest, &res->host) &&
+ res->port == port)
+ {
+ return (res);
+ }
+ res = ISC_LIST_NEXT(res, link);
+ }
+
+ return (NULL);
+}
+
+static void
+free_buffer(dns_dispatch_t *disp, void *buf, unsigned int len) {
+ unsigned int buffersize;
+ INSIST(buf != NULL && len != 0);
+
+ switch (disp->socktype) {
+ case isc_sockettype_tcp:
+ INSIST(disp->tcpbuffers > 0);
+ disp->tcpbuffers--;
+ isc_mem_put(disp->mgr->mctx, buf, len);
+ break;
+ case isc_sockettype_udp:
+ LOCK(&disp->mgr->buffer_lock);
+ INSIST(disp->mgr->buffers > 0);
+ INSIST(len == disp->mgr->buffersize);
+ disp->mgr->buffers--;
+ buffersize = disp->mgr->buffersize;
+ UNLOCK(&disp->mgr->buffer_lock);
+ isc_mem_put(disp->mgr->mctx, buf, buffersize);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+static void *
+allocate_udp_buffer(dns_dispatch_t *disp) {
+ unsigned int buffersize;
+
+ LOCK(&disp->mgr->buffer_lock);
+ if (disp->mgr->buffers >= disp->mgr->maxbuffers) {
+ UNLOCK(&disp->mgr->buffer_lock);
+ return (NULL);
+ }
+ buffersize = disp->mgr->buffersize;
+ disp->mgr->buffers++;
+ UNLOCK(&disp->mgr->buffer_lock);
+
+ return (isc_mem_get(disp->mgr->mctx, buffersize));
+}
+
+static void
+free_sevent(isc_event_t *ev) {
+ isc_mem_t *pool = ev->ev_destroy_arg;
+ isc_socketevent_t *sev = (isc_socketevent_t *)ev;
+ isc_mem_put(pool, sev, sizeof(*sev));
+}
+
+static isc_socketevent_t *
+allocate_sevent(dns_dispatch_t *disp, isc_socket_t *sock, isc_eventtype_t type,
+ isc_taskaction_t action, const void *arg) {
+ isc_socketevent_t *ev;
+ void *deconst_arg;
+
+ ev = isc_mem_get(disp->sepool, sizeof(*ev));
+ DE_CONST(arg, deconst_arg);
+ ISC_EVENT_INIT(ev, sizeof(*ev), 0, NULL, type, action, deconst_arg,
+ sock, free_sevent, disp->sepool);
+ ev->result = ISC_R_UNSET;
+ ISC_LINK_INIT(ev, ev_link);
+ ev->region.base = NULL;
+ ev->n = 0;
+ ev->offset = 0;
+ ev->attributes = 0;
+
+ return (ev);
+}
+
+static void
+free_devent(dns_dispatch_t *disp, dns_dispatchevent_t *ev) {
+ if (disp->failsafe_ev == ev) {
+ INSIST(disp->shutdown_out == 1);
+ disp->shutdown_out = 0;
+
+ return;
+ }
+
+ isc_refcount_decrement(&disp->mgr->irefs);
+ isc_mem_put(disp->mgr->mctx, ev, sizeof(*ev));
+}
+
+static dns_dispatchevent_t *
+allocate_devent(dns_dispatch_t *disp) {
+ dns_dispatchevent_t *ev;
+
+ ev = isc_mem_get(disp->mgr->mctx, sizeof(*ev));
+ isc_refcount_increment0(&disp->mgr->irefs);
+ ISC_EVENT_INIT(ev, sizeof(*ev), 0, NULL, 0, NULL, NULL, NULL, NULL,
+ NULL);
+
+ return (ev);
+}
+
+static void
+udp_exrecv(isc_task_t *task, isc_event_t *ev) {
+ dispsocket_t *dispsock = ev->ev_arg;
+
+ UNUSED(task);
+
+ REQUIRE(VALID_DISPSOCK(dispsock));
+ udp_recv(ev, dispsock->disp, dispsock);
+}
+
+static void
+udp_shrecv(isc_task_t *task, isc_event_t *ev) {
+ dns_dispatch_t *disp = ev->ev_arg;
+
+ UNUSED(task);
+
+ REQUIRE(VALID_DISPATCH(disp));
+ udp_recv(ev, disp, NULL);
+}
+
+/*
+ * General flow:
+ *
+ * If I/O result == CANCELED or error, free the buffer.
+ *
+ * If query, free the buffer, restart.
+ *
+ * If response:
+ * Allocate event, fill in details.
+ * If cannot allocate, free buffer, restart.
+ * find target. If not found, free buffer, restart.
+ * if event queue is not empty, queue. else, send.
+ * restart.
+ */
+static void
+udp_recv(isc_event_t *ev_in, dns_dispatch_t *disp, dispsocket_t *dispsock) {
+ isc_socketevent_t *ev = (isc_socketevent_t *)ev_in;
+ dns_messageid_t id;
+ isc_result_t dres;
+ isc_buffer_t source;
+ unsigned int flags;
+ dns_dispentry_t *resp = NULL;
+ dns_dispatchevent_t *rev;
+ unsigned int bucket;
+ bool killit;
+ bool queue_response;
+ dns_dispatchmgr_t *mgr;
+ dns_qid_t *qid;
+ isc_netaddr_t netaddr;
+ int match;
+ int result;
+ bool qidlocked = false;
+
+ LOCK(&disp->lock);
+
+ mgr = disp->mgr;
+ qid = mgr->qid;
+
+ LOCK(&disp->mgr->buffer_lock);
+ dispatch_log(disp, LVL(90),
+ "got packet: requests %d, buffers %d, recvs %d",
+ disp->requests, disp->mgr->buffers, disp->recv_pending);
+ UNLOCK(&disp->mgr->buffer_lock);
+
+ if (dispsock == NULL && ev->ev_type == ISC_SOCKEVENT_RECVDONE) {
+ /*
+ * Unless the receive event was imported from a listening
+ * interface, in which case the event type is
+ * DNS_EVENT_IMPORTRECVDONE, receive operation must be pending.
+ */
+ INSIST(disp->recv_pending != 0);
+ disp->recv_pending = 0;
+ }
+
+ if (dispsock != NULL &&
+ (ev->result == ISC_R_CANCELED || dispsock->resp == NULL))
+ {
+ /*
+ * dispsock->resp can be NULL if this transaction was canceled
+ * just after receiving a response. Since this socket is
+ * exclusively used and there should be at most one receive
+ * event the canceled event should have been no effect. So
+ * we can (and should) deactivate the socket right now.
+ */
+ deactivate_dispsocket(disp, dispsock);
+ dispsock = NULL;
+ }
+
+ if (disp->shutting_down) {
+ /*
+ * This dispatcher is shutting down.
+ */
+ free_buffer(disp, ev->region.base, ev->region.length);
+
+ isc_event_free(&ev_in);
+ ev = NULL;
+
+ killit = destroy_disp_ok(disp);
+ UNLOCK(&disp->lock);
+ if (killit) {
+ isc_task_send(disp->task[0], &disp->ctlevent);
+ }
+
+ return;
+ }
+
+ if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) {
+ if (dispsock != NULL) {
+ resp = dispsock->resp;
+ id = resp->id;
+ if (ev->result != ISC_R_SUCCESS) {
+ /*
+ * This is most likely a network error on a
+ * connected socket. It makes no sense to
+ * check the address or parse the packet, but it
+ * will help to return the error to the caller.
+ */
+ goto sendresponse;
+ }
+ } else {
+ free_buffer(disp, ev->region.base, ev->region.length);
+
+ isc_event_free(&ev_in);
+ UNLOCK(&disp->lock);
+ return;
+ }
+ } else if (ev->result != ISC_R_SUCCESS) {
+ free_buffer(disp, ev->region.base, ev->region.length);
+
+ if (ev->result != ISC_R_CANCELED) {
+ dispatch_log(disp, ISC_LOG_ERROR,
+ "odd socket result in udp_recv(): %s",
+ isc_result_totext(ev->result));
+ }
+
+ isc_event_free(&ev_in);
+ UNLOCK(&disp->lock);
+ return;
+ }
+
+ /*
+ * If this is from a blackholed address, drop it.
+ */
+ isc_netaddr_fromsockaddr(&netaddr, &ev->address);
+ if (disp->mgr->blackhole != NULL &&
+ dns_acl_match(&netaddr, NULL, disp->mgr->blackhole, NULL, &match,
+ NULL) == ISC_R_SUCCESS &&
+ match > 0)
+ {
+ if (isc_log_wouldlog(dns_lctx, LVL(10))) {
+ char netaddrstr[ISC_NETADDR_FORMATSIZE];
+ isc_netaddr_format(&netaddr, netaddrstr,
+ sizeof(netaddrstr));
+ dispatch_log(disp, LVL(10), "blackholed packet from %s",
+ netaddrstr);
+ }
+ free_buffer(disp, ev->region.base, ev->region.length);
+ goto restart;
+ }
+
+ /*
+ * Peek into the buffer to see what we can see.
+ */
+ isc_buffer_init(&source, ev->region.base, ev->region.length);
+ isc_buffer_add(&source, ev->n);
+ dres = dns_message_peekheader(&source, &id, &flags);
+ if (dres != ISC_R_SUCCESS) {
+ free_buffer(disp, ev->region.base, ev->region.length);
+ dispatch_log(disp, LVL(10), "got garbage packet");
+ goto restart;
+ }
+
+ dispatch_log(disp, LVL(92),
+ "got valid DNS message header, /QR %c, id %u",
+ (((flags & DNS_MESSAGEFLAG_QR) != 0) ? '1' : '0'), id);
+
+ /*
+ * Look at flags. If query, drop it. If response,
+ * look to see where it goes.
+ */
+ if ((flags & DNS_MESSAGEFLAG_QR) == 0) {
+ /* query */
+ free_buffer(disp, ev->region.base, ev->region.length);
+ goto restart;
+ }
+
+ /*
+ * Search for the corresponding response. If we are using an exclusive
+ * socket, we've already identified it and we can skip the search; but
+ * the ID and the address must match the expected ones.
+ */
+ if (resp == NULL) {
+ bucket = dns_hash(qid, &ev->address, id, disp->localport);
+ LOCK(&qid->lock);
+ qidlocked = true;
+ resp = entry_search(qid, &ev->address, id, disp->localport,
+ bucket);
+ dispatch_log(disp, LVL(90),
+ "search for response in bucket %d: %s", bucket,
+ (resp == NULL ? "not found" : "found"));
+
+ } else if (resp->id != id ||
+ !isc_sockaddr_equal(&ev->address, &resp->host))
+ {
+ dispatch_log(disp, LVL(90),
+ "response to an exclusive socket doesn't match");
+ inc_stats(mgr, dns_resstatscounter_mismatch);
+ free_buffer(disp, ev->region.base, ev->region.length);
+ goto unlock;
+ }
+
+ if (resp == NULL) {
+ inc_stats(mgr, dns_resstatscounter_mismatch);
+ free_buffer(disp, ev->region.base, ev->region.length);
+ goto unlock;
+ }
+
+ /*
+ * Now that we have the original dispatch the query was sent
+ * from check that the address and port the response was
+ * sent to make sense.
+ */
+ if (disp != resp->disp) {
+ isc_sockaddr_t a1;
+ isc_sockaddr_t a2;
+
+ /*
+ * Check that the socket types and ports match.
+ */
+ if (disp->socktype != resp->disp->socktype ||
+ isc_sockaddr_getport(&disp->local) !=
+ isc_sockaddr_getport(&resp->disp->local))
+ {
+ free_buffer(disp, ev->region.base, ev->region.length);
+ goto unlock;
+ }
+
+ /*
+ * If each dispatch is bound to a different address
+ * then fail.
+ *
+ * Note under Linux a packet can be sent out via IPv4 socket
+ * and the response be received via a IPv6 socket.
+ *
+ * Requests sent out via IPv6 should always come back in
+ * via IPv6.
+ */
+ if (isc_sockaddr_pf(&resp->disp->local) == PF_INET6 &&
+ isc_sockaddr_pf(&disp->local) != PF_INET6)
+ {
+ free_buffer(disp, ev->region.base, ev->region.length);
+ goto unlock;
+ }
+ isc_sockaddr_anyofpf(&a1, isc_sockaddr_pf(&resp->disp->local));
+ isc_sockaddr_anyofpf(&a2, isc_sockaddr_pf(&disp->local));
+ if (!isc_sockaddr_eqaddr(&disp->local, &resp->disp->local) &&
+ !isc_sockaddr_eqaddr(&a1, &resp->disp->local) &&
+ !isc_sockaddr_eqaddr(&a2, &disp->local))
+ {
+ free_buffer(disp, ev->region.base, ev->region.length);
+ goto unlock;
+ }
+ }
+
+sendresponse:
+ queue_response = resp->item_out;
+ rev = allocate_devent(resp->disp);
+ if (rev == NULL) {
+ free_buffer(disp, ev->region.base, ev->region.length);
+ goto unlock;
+ }
+
+ /*
+ * At this point, rev contains the event we want to fill in, and
+ * resp contains the information on the place to send it to.
+ * Send the event off.
+ */
+ isc_buffer_init(&rev->buffer, ev->region.base, ev->region.length);
+ isc_buffer_add(&rev->buffer, ev->n);
+ rev->result = ev->result;
+ rev->id = id;
+ rev->addr = ev->address;
+ rev->pktinfo = ev->pktinfo;
+ rev->attributes = ev->attributes;
+ if (queue_response) {
+ ISC_LIST_APPEND(resp->items, rev, ev_link);
+ } else {
+ ISC_EVENT_INIT(rev, sizeof(*rev), 0, NULL, DNS_EVENT_DISPATCH,
+ resp->action, resp->arg, resp, NULL, NULL);
+ request_log(disp, resp, LVL(90),
+ "[a] Sent event %p buffer %p len %d to task %p",
+ rev, rev->buffer.base, rev->buffer.length,
+ resp->task);
+ resp->item_out = true;
+ isc_task_send(resp->task, ISC_EVENT_PTR(&rev));
+ }
+unlock:
+ if (qidlocked) {
+ UNLOCK(&qid->lock);
+ }
+
+ /*
+ * Restart recv() to get the next packet.
+ */
+restart:
+ result = startrecv(disp, dispsock);
+ if (result != ISC_R_SUCCESS && dispsock != NULL) {
+ /*
+ * XXX: wired. There seems to be no recovery process other than
+ * deactivate this socket anyway (since we cannot start
+ * receiving, we won't be able to receive a cancel event
+ * from the user).
+ */
+ deactivate_dispsocket(disp, dispsock);
+ }
+ isc_event_free(&ev_in);
+ UNLOCK(&disp->lock);
+}
+
+/*
+ * General flow:
+ *
+ * If I/O result == CANCELED, EOF, or error, notify everyone as the
+ * various queues drain.
+ *
+ * If query, restart.
+ *
+ * If response:
+ * Allocate event, fill in details.
+ * If cannot allocate, restart.
+ * find target. If not found, restart.
+ * if event queue is not empty, queue. else, send.
+ * restart.
+ */
+static void
+tcp_recv(isc_task_t *task, isc_event_t *ev_in) {
+ dns_dispatch_t *disp = ev_in->ev_arg;
+ dns_tcpmsg_t *tcpmsg = &disp->tcpmsg;
+ dns_messageid_t id;
+ isc_result_t dres;
+ unsigned int flags;
+ dns_dispentry_t *resp;
+ dns_dispatchevent_t *rev;
+ unsigned int bucket;
+ bool killit;
+ bool queue_response;
+ dns_qid_t *qid;
+ int level;
+ char buf[ISC_SOCKADDR_FORMATSIZE];
+
+ UNUSED(task);
+
+ REQUIRE(VALID_DISPATCH(disp));
+
+ qid = disp->qid;
+
+ LOCK(&disp->lock);
+
+ dispatch_log(disp, LVL(90),
+ "got TCP packet: requests %d, buffers %d, recvs %d",
+ disp->requests, disp->tcpbuffers, disp->recv_pending);
+
+ INSIST(disp->recv_pending != 0);
+ disp->recv_pending = 0;
+
+ if (disp->refcount == 0) {
+ /*
+ * This dispatcher is shutting down. Force cancellation.
+ */
+ tcpmsg->result = ISC_R_CANCELED;
+ }
+
+ if (tcpmsg->result != ISC_R_SUCCESS) {
+ switch (tcpmsg->result) {
+ case ISC_R_CANCELED:
+ break;
+
+ case ISC_R_EOF:
+ dispatch_log(disp, LVL(90), "shutting down on EOF");
+ do_cancel(disp);
+ break;
+
+ case ISC_R_CONNECTIONRESET:
+ level = ISC_LOG_INFO;
+ goto logit;
+
+ default:
+ level = ISC_LOG_ERROR;
+ logit:
+ isc_sockaddr_format(&tcpmsg->address, buf, sizeof(buf));
+ dispatch_log(disp, level,
+ "shutting down due to TCP "
+ "receive error: %s: %s",
+ buf, isc_result_totext(tcpmsg->result));
+ do_cancel(disp);
+ break;
+ }
+
+ /*
+ * The event is statically allocated in the tcpmsg
+ * structure, and destroy_disp() frees the tcpmsg, so we must
+ * free the event *before* calling destroy_disp().
+ */
+ isc_event_free(&ev_in);
+
+ disp->shutting_down = 1;
+ disp->shutdown_why = tcpmsg->result;
+
+ /*
+ * If the recv() was canceled pass the word on.
+ */
+ killit = destroy_disp_ok(disp);
+ UNLOCK(&disp->lock);
+ if (killit) {
+ isc_task_send(disp->task[0], &disp->ctlevent);
+ }
+ return;
+ }
+
+ dispatch_log(disp, LVL(90), "result %d, length == %d, addr = %p",
+ tcpmsg->result, tcpmsg->buffer.length,
+ tcpmsg->buffer.base);
+
+ /*
+ * Peek into the buffer to see what we can see.
+ */
+ dres = dns_message_peekheader(&tcpmsg->buffer, &id, &flags);
+ if (dres != ISC_R_SUCCESS) {
+ dispatch_log(disp, LVL(10), "got garbage packet");
+ goto restart;
+ }
+
+ dispatch_log(disp, LVL(92),
+ "got valid DNS message header, /QR %c, id %u",
+ (((flags & DNS_MESSAGEFLAG_QR) != 0) ? '1' : '0'), id);
+
+ /*
+ * Allocate an event to send to the query or response client, and
+ * allocate a new buffer for our use.
+ */
+
+ /*
+ * Look at flags. If query, drop it. If response,
+ * look to see where it goes.
+ */
+ if ((flags & DNS_MESSAGEFLAG_QR) == 0) {
+ /*
+ * Query.
+ */
+ goto restart;
+ }
+
+ /*
+ * Response.
+ */
+ bucket = dns_hash(qid, &tcpmsg->address, id, disp->localport);
+ LOCK(&qid->lock);
+ resp = entry_search(qid, &tcpmsg->address, id, disp->localport, bucket);
+ dispatch_log(disp, LVL(90), "search for response in bucket %d: %s",
+ bucket, (resp == NULL ? "not found" : "found"));
+
+ if (resp == NULL) {
+ goto unlock;
+ }
+ queue_response = resp->item_out;
+ rev = allocate_devent(disp);
+ if (rev == NULL) {
+ goto unlock;
+ }
+
+ /*
+ * At this point, rev contains the event we want to fill in, and
+ * resp contains the information on the place to send it to.
+ * Send the event off.
+ */
+ dns_tcpmsg_keepbuffer(tcpmsg, &rev->buffer);
+ disp->tcpbuffers++;
+ rev->result = ISC_R_SUCCESS;
+ rev->id = id;
+ rev->addr = tcpmsg->address;
+ if (queue_response) {
+ ISC_LIST_APPEND(resp->items, rev, ev_link);
+ } else {
+ ISC_EVENT_INIT(rev, sizeof(*rev), 0, NULL, DNS_EVENT_DISPATCH,
+ resp->action, resp->arg, resp, NULL, NULL);
+ request_log(disp, resp, LVL(90),
+ "[b] Sent event %p buffer %p len %d to task %p",
+ rev, rev->buffer.base, rev->buffer.length,
+ resp->task);
+ resp->item_out = true;
+ isc_task_send(resp->task, ISC_EVENT_PTR(&rev));
+ }
+unlock:
+ UNLOCK(&qid->lock);
+
+ /*
+ * Restart recv() to get the next packet.
+ */
+restart:
+ (void)startrecv(disp, NULL);
+
+ isc_event_free(&ev_in);
+ UNLOCK(&disp->lock);
+}
+
+/*
+ * disp must be locked.
+ */
+static isc_result_t
+startrecv(dns_dispatch_t *disp, dispsocket_t *dispsock) {
+ isc_result_t res;
+ isc_region_t region;
+ isc_socket_t *sock;
+
+ if (disp->shutting_down == 1) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if ((disp->attributes & DNS_DISPATCHATTR_NOLISTEN) != 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (disp->recv_pending != 0 && dispsock == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0 &&
+ dispsock == NULL)
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (dispsock != NULL) {
+ sock = dispsock->socket;
+ } else {
+ sock = disp->socket;
+ }
+ INSIST(sock != NULL);
+
+ switch (disp->socktype) {
+ /*
+ * UDP reads are always maximal.
+ */
+ case isc_sockettype_udp:
+ region.length = disp->mgr->buffersize;
+ region.base = allocate_udp_buffer(disp);
+ if (region.base == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ if (dispsock != NULL) {
+ isc_task_t *dt = dispsock->task;
+ isc_socketevent_t *sev = allocate_sevent(
+ disp, sock, ISC_SOCKEVENT_RECVDONE, udp_exrecv,
+ dispsock);
+ if (sev == NULL) {
+ free_buffer(disp, region.base, region.length);
+ return (ISC_R_NOMEMORY);
+ }
+
+ res = isc_socket_recv2(sock, &region, 1, dt, sev, 0);
+ if (res != ISC_R_SUCCESS) {
+ free_buffer(disp, region.base, region.length);
+ return (res);
+ }
+ } else {
+ isc_task_t *dt = disp->task[0];
+ isc_socketevent_t *sev = allocate_sevent(
+ disp, sock, ISC_SOCKEVENT_RECVDONE, udp_shrecv,
+ disp);
+ if (sev == NULL) {
+ free_buffer(disp, region.base, region.length);
+ return (ISC_R_NOMEMORY);
+ }
+
+ res = isc_socket_recv2(sock, &region, 1, dt, sev, 0);
+ if (res != ISC_R_SUCCESS) {
+ free_buffer(disp, region.base, region.length);
+ disp->shutdown_why = res;
+ disp->shutting_down = 1;
+ do_cancel(disp);
+ return (ISC_R_SUCCESS); /* recover by cancel */
+ }
+ INSIST(disp->recv_pending == 0);
+ disp->recv_pending = 1;
+ }
+ break;
+
+ case isc_sockettype_tcp:
+ res = dns_tcpmsg_readmessage(&disp->tcpmsg, disp->task[0],
+ tcp_recv, disp);
+ if (res != ISC_R_SUCCESS) {
+ disp->shutdown_why = res;
+ disp->shutting_down = 1;
+ do_cancel(disp);
+ return (ISC_R_SUCCESS); /* recover by cancel */
+ }
+ INSIST(disp->recv_pending == 0);
+ disp->recv_pending = 1;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Mgr must be locked when calling this function.
+ */
+static bool
+destroy_mgr_ok(dns_dispatchmgr_t *mgr) {
+ mgr_log(mgr, LVL(90),
+ "destroy_mgr_ok: shuttingdown=%d, listnonempty=%d, ",
+ MGR_IS_SHUTTINGDOWN(mgr), !ISC_LIST_EMPTY(mgr->list));
+ if (!MGR_IS_SHUTTINGDOWN(mgr)) {
+ return (false);
+ }
+ if (!ISC_LIST_EMPTY(mgr->list)) {
+ return (false);
+ }
+ if (isc_refcount_current(&mgr->irefs) != 0) {
+ return (false);
+ }
+
+ return (true);
+}
+
+/*
+ * Mgr must be unlocked when calling this function.
+ */
+static void
+destroy_mgr(dns_dispatchmgr_t **mgrp) {
+ dns_dispatchmgr_t *mgr;
+
+ mgr = *mgrp;
+ *mgrp = NULL;
+
+ mgr->magic = 0;
+ isc_mutex_destroy(&mgr->lock);
+ mgr->state = 0;
+
+ if (mgr->qid != NULL) {
+ qid_destroy(mgr->mctx, &mgr->qid);
+ }
+
+ isc_mutex_destroy(&mgr->buffer_lock);
+
+ if (mgr->blackhole != NULL) {
+ dns_acl_detach(&mgr->blackhole);
+ }
+
+ if (mgr->stats != NULL) {
+ isc_stats_detach(&mgr->stats);
+ }
+
+ if (mgr->v4ports != NULL) {
+ isc_mem_put(mgr->mctx, mgr->v4ports,
+ mgr->nv4ports * sizeof(in_port_t));
+ }
+ if (mgr->v6ports != NULL) {
+ isc_mem_put(mgr->mctx, mgr->v6ports,
+ mgr->nv6ports * sizeof(in_port_t));
+ }
+ isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(dns_dispatchmgr_t));
+}
+
+static isc_result_t
+open_socket(isc_socketmgr_t *mgr, const isc_sockaddr_t *local,
+ unsigned int options, isc_socket_t **sockp,
+ isc_socket_t *dup_socket, bool duponly) {
+ isc_socket_t *sock;
+ isc_result_t result;
+
+ sock = *sockp;
+ if (sock != NULL) {
+ result = isc_socket_open(sock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ } else if (dup_socket != NULL &&
+ (!isc_socket_hasreuseport() || duponly))
+ {
+ result = isc_socket_dup(dup_socket, &sock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_socket_setname(sock, "dispatcher", NULL);
+ *sockp = sock;
+ return (ISC_R_SUCCESS);
+ } else {
+ result = isc_socket_create(mgr, isc_sockaddr_pf(local),
+ isc_sockettype_udp, &sock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ isc_socket_setname(sock, "dispatcher", NULL);
+
+#ifndef ISC_ALLOW_MAPPED
+ isc_socket_ipv6only(sock, true);
+#endif /* ifndef ISC_ALLOW_MAPPED */
+ result = isc_socket_bind(sock, local, options);
+ if (result != ISC_R_SUCCESS) {
+ if (*sockp == NULL) {
+ isc_socket_detach(&sock);
+ } else {
+ isc_socket_close(sock);
+ }
+ return (result);
+ }
+
+ *sockp = sock;
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Create a temporary port list to set the initial default set of dispatch
+ * ports: [1024, 65535]. This is almost meaningless as the application will
+ * normally set the ports explicitly, but is provided to fill some minor corner
+ * cases.
+ */
+static isc_result_t
+create_default_portset(isc_mem_t *mctx, isc_portset_t **portsetp) {
+ isc_result_t result;
+
+ result = isc_portset_create(mctx, portsetp);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_portset_addrange(*portsetp, 1024, 65535);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Publics.
+ */
+
+isc_result_t
+dns_dispatchmgr_create(isc_mem_t *mctx, dns_dispatchmgr_t **mgrp) {
+ dns_dispatchmgr_t *mgr;
+ isc_result_t result;
+ isc_portset_t *v4portset = NULL;
+ isc_portset_t *v6portset = NULL;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(mgrp != NULL && *mgrp == NULL);
+
+ mgr = isc_mem_get(mctx, sizeof(dns_dispatchmgr_t));
+ *mgr = (dns_dispatchmgr_t){ 0 };
+
+ isc_mem_attach(mctx, &mgr->mctx);
+
+ isc_mutex_init(&mgr->lock);
+ isc_mutex_init(&mgr->buffer_lock);
+
+ isc_refcount_init(&mgr->irefs, 0);
+
+ ISC_LIST_INIT(mgr->list);
+
+ mgr->magic = DNS_DISPATCHMGR_MAGIC;
+
+ result = create_default_portset(mctx, &v4portset);
+ if (result == ISC_R_SUCCESS) {
+ result = create_default_portset(mctx, &v6portset);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_dispatchmgr_setavailports(mgr, v4portset,
+ v6portset);
+ }
+ }
+ if (v4portset != NULL) {
+ isc_portset_destroy(mctx, &v4portset);
+ }
+ if (v6portset != NULL) {
+ isc_portset_destroy(mctx, &v6portset);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto kill_dpool;
+ }
+
+ *mgrp = mgr;
+ return (ISC_R_SUCCESS);
+
+kill_dpool:
+ isc_mutex_destroy(&mgr->buffer_lock);
+ isc_mutex_destroy(&mgr->lock);
+ isc_mem_putanddetach(&mctx, mgr, sizeof(dns_dispatchmgr_t));
+
+ return (result);
+}
+
+void
+dns_dispatchmgr_setblackhole(dns_dispatchmgr_t *mgr, dns_acl_t *blackhole) {
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ if (mgr->blackhole != NULL) {
+ dns_acl_detach(&mgr->blackhole);
+ }
+ dns_acl_attach(blackhole, &mgr->blackhole);
+}
+
+dns_acl_t *
+dns_dispatchmgr_getblackhole(dns_dispatchmgr_t *mgr) {
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ return (mgr->blackhole);
+}
+
+void
+dns_dispatchmgr_setblackportlist(dns_dispatchmgr_t *mgr,
+ dns_portlist_t *portlist) {
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ UNUSED(portlist);
+
+ /* This function is deprecated: use dns_dispatchmgr_setavailports(). */
+ return;
+}
+
+dns_portlist_t *
+dns_dispatchmgr_getblackportlist(dns_dispatchmgr_t *mgr) {
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ return (NULL); /* this function is deprecated */
+}
+
+isc_result_t
+dns_dispatchmgr_setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset,
+ isc_portset_t *v6portset) {
+ in_port_t *v4ports, *v6ports, p;
+ unsigned int nv4ports, nv6ports, i4, i6;
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+
+ nv4ports = isc_portset_nports(v4portset);
+ nv6ports = isc_portset_nports(v6portset);
+
+ v4ports = NULL;
+ if (nv4ports != 0) {
+ v4ports = isc_mem_get(mgr->mctx, sizeof(in_port_t) * nv4ports);
+ }
+ v6ports = NULL;
+ if (nv6ports != 0) {
+ v6ports = isc_mem_get(mgr->mctx, sizeof(in_port_t) * nv6ports);
+ }
+
+ p = 0;
+ i4 = 0;
+ i6 = 0;
+ do {
+ if (isc_portset_isset(v4portset, p)) {
+ INSIST(i4 < nv4ports);
+ v4ports[i4++] = p;
+ }
+ if (isc_portset_isset(v6portset, p)) {
+ INSIST(i6 < nv6ports);
+ v6ports[i6++] = p;
+ }
+ } while (p++ < 65535);
+ INSIST(i4 == nv4ports && i6 == nv6ports);
+
+ PORTBUFLOCK(mgr);
+ if (mgr->v4ports != NULL) {
+ isc_mem_put(mgr->mctx, mgr->v4ports,
+ mgr->nv4ports * sizeof(in_port_t));
+ }
+ mgr->v4ports = v4ports;
+ mgr->nv4ports = nv4ports;
+
+ if (mgr->v6ports != NULL) {
+ isc_mem_put(mgr->mctx, mgr->v6ports,
+ mgr->nv6ports * sizeof(in_port_t));
+ }
+ mgr->v6ports = v6ports;
+ mgr->nv6ports = nv6ports;
+ PORTBUFUNLOCK(mgr);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dns_dispatchmgr_setudp(dns_dispatchmgr_t *mgr, unsigned int buffersize,
+ unsigned int maxbuffers, unsigned int maxrequests,
+ unsigned int buckets, unsigned int increment) {
+ isc_result_t result;
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(buffersize >= 512 && buffersize < (64 * 1024));
+ REQUIRE(maxbuffers > 0);
+ REQUIRE(buckets < 2097169); /* next prime > 65536 * 32 */
+ REQUIRE(increment > buckets);
+ UNUSED(maxrequests);
+
+ /*
+ * Keep some number of items around. This should be a config
+ * option. For now, keep 8, but later keep at least two even
+ * if the caller wants less. This allows us to ensure certain
+ * things, like an event can be "freed" and the next allocation
+ * will always succeed.
+ *
+ * Note that if limits are placed on anything here, we use one
+ * event internally, so the actual limit should be "wanted + 1."
+ *
+ * XXXMLG
+ */
+
+ if (maxbuffers < 8) {
+ maxbuffers = 8;
+ }
+
+ LOCK(&mgr->buffer_lock);
+
+ if (maxbuffers > mgr->maxbuffers) {
+ mgr->maxbuffers = maxbuffers;
+ }
+
+ /* Create or adjust socket pool */
+ if (mgr->qid != NULL) {
+ UNLOCK(&mgr->buffer_lock);
+ return (ISC_R_SUCCESS);
+ }
+
+ result = qid_allocate(mgr, buckets, increment, &mgr->qid, true);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ mgr->buffersize = buffersize;
+ mgr->maxbuffers = maxbuffers;
+ UNLOCK(&mgr->buffer_lock);
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ UNLOCK(&mgr->buffer_lock);
+ return (result);
+}
+
+void
+dns_dispatchmgr_destroy(dns_dispatchmgr_t **mgrp) {
+ dns_dispatchmgr_t *mgr;
+ bool killit;
+
+ REQUIRE(mgrp != NULL);
+ REQUIRE(VALID_DISPATCHMGR(*mgrp));
+
+ mgr = *mgrp;
+ *mgrp = NULL;
+
+ LOCK(&mgr->lock);
+ mgr->state |= MGR_SHUTTINGDOWN;
+ killit = destroy_mgr_ok(mgr);
+ UNLOCK(&mgr->lock);
+
+ mgr_log(mgr, LVL(90), "destroy: killit=%d", killit);
+
+ if (killit) {
+ destroy_mgr(&mgr);
+ }
+}
+
+void
+dns_dispatchmgr_setstats(dns_dispatchmgr_t *mgr, isc_stats_t *stats) {
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(ISC_LIST_EMPTY(mgr->list));
+ REQUIRE(mgr->stats == NULL);
+
+ isc_stats_attach(stats, &mgr->stats);
+}
+
+static int
+port_cmp(const void *key, const void *ent) {
+ in_port_t p1 = *(const in_port_t *)key;
+ in_port_t p2 = *(const in_port_t *)ent;
+
+ if (p1 < p2) {
+ return (-1);
+ } else if (p1 == p2) {
+ return (0);
+ } else {
+ return (1);
+ }
+}
+
+static bool
+portavailable(dns_dispatchmgr_t *mgr, isc_socket_t *sock,
+ isc_sockaddr_t *sockaddrp) {
+ isc_sockaddr_t sockaddr;
+ isc_result_t result;
+ in_port_t *ports, port;
+ unsigned int nports;
+ bool available = false;
+
+ REQUIRE(sock != NULL || sockaddrp != NULL);
+
+ PORTBUFLOCK(mgr);
+ if (sock != NULL) {
+ sockaddrp = &sockaddr;
+ result = isc_socket_getsockname(sock, sockaddrp);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+ }
+
+ if (isc_sockaddr_pf(sockaddrp) == AF_INET) {
+ ports = mgr->v4ports;
+ nports = mgr->nv4ports;
+ } else {
+ ports = mgr->v6ports;
+ nports = mgr->nv6ports;
+ }
+ if (ports == NULL) {
+ goto unlock;
+ }
+
+ port = isc_sockaddr_getport(sockaddrp);
+ if (bsearch(&port, ports, nports, sizeof(in_port_t), port_cmp) != NULL)
+ {
+ available = true;
+ }
+
+unlock:
+ PORTBUFUNLOCK(mgr);
+ return (available);
+}
+
+#define ATTRMATCH(_a1, _a2, _mask) (((_a1) & (_mask)) == ((_a2) & (_mask)))
+
+static bool
+local_addr_match(dns_dispatch_t *disp, const isc_sockaddr_t *addr) {
+ isc_sockaddr_t sockaddr;
+ isc_result_t result;
+
+ REQUIRE(disp->socket != NULL);
+
+ if (addr == NULL) {
+ return (true);
+ }
+
+ /*
+ * Don't match wildcard ports unless the port is available in the
+ * current configuration.
+ */
+ if (isc_sockaddr_getport(addr) == 0 &&
+ isc_sockaddr_getport(&disp->local) == 0 &&
+ !portavailable(disp->mgr, disp->socket, NULL))
+ {
+ return (false);
+ }
+
+ /*
+ * Check if we match the binding <address,port>.
+ * Wildcard ports match/fail here.
+ */
+ if (isc_sockaddr_equal(&disp->local, addr)) {
+ return (true);
+ }
+ if (isc_sockaddr_getport(addr) == 0) {
+ return (false);
+ }
+
+ /*
+ * Check if we match a bound wildcard port <address,port>.
+ */
+ if (!isc_sockaddr_eqaddr(&disp->local, addr)) {
+ return (false);
+ }
+ result = isc_socket_getsockname(disp->socket, &sockaddr);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+
+ return (isc_sockaddr_equal(&sockaddr, addr));
+}
+
+/*
+ * Requires mgr be locked.
+ *
+ * No dispatcher can be locked by this thread when calling this function.
+ *
+ *
+ * NOTE:
+ * If a matching dispatcher is found, it is locked after this function
+ * returns, and must be unlocked by the caller.
+ */
+static isc_result_t
+dispatch_find(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *local,
+ unsigned int attributes, unsigned int mask,
+ dns_dispatch_t **dispp) {
+ dns_dispatch_t *disp;
+ isc_result_t result;
+
+ /*
+ * Make certain that we will not match a private or exclusive dispatch.
+ */
+ attributes &= ~(DNS_DISPATCHATTR_PRIVATE | DNS_DISPATCHATTR_EXCLUSIVE);
+ mask |= (DNS_DISPATCHATTR_PRIVATE | DNS_DISPATCHATTR_EXCLUSIVE);
+
+ disp = ISC_LIST_HEAD(mgr->list);
+ while (disp != NULL) {
+ LOCK(&disp->lock);
+ if ((disp->shutting_down == 0) &&
+ ATTRMATCH(disp->attributes, attributes, mask) &&
+ local_addr_match(disp, local))
+ {
+ break;
+ }
+ UNLOCK(&disp->lock);
+ disp = ISC_LIST_NEXT(disp, link);
+ }
+
+ if (disp == NULL) {
+ result = ISC_R_NOTFOUND;
+ goto out;
+ }
+
+ *dispp = disp;
+ result = ISC_R_SUCCESS;
+out:
+
+ return (result);
+}
+
+static isc_result_t
+qid_allocate(dns_dispatchmgr_t *mgr, unsigned int buckets,
+ unsigned int increment, dns_qid_t **qidp, bool needsocktable) {
+ dns_qid_t *qid;
+ unsigned int i;
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(buckets < 2097169); /* next prime > 65536 * 32 */
+ REQUIRE(increment > buckets);
+ REQUIRE(qidp != NULL && *qidp == NULL);
+
+ qid = isc_mem_get(mgr->mctx, sizeof(*qid));
+
+ qid->qid_table = isc_mem_get(mgr->mctx,
+ buckets * sizeof(dns_displist_t));
+
+ qid->sock_table = NULL;
+ if (needsocktable) {
+ qid->sock_table = isc_mem_get(
+ mgr->mctx, buckets * sizeof(dispsocketlist_t));
+ }
+
+ isc_mutex_init(&qid->lock);
+
+ for (i = 0; i < buckets; i++) {
+ ISC_LIST_INIT(qid->qid_table[i]);
+ if (qid->sock_table != NULL) {
+ ISC_LIST_INIT(qid->sock_table[i]);
+ }
+ }
+
+ qid->qid_nbuckets = buckets;
+ qid->qid_increment = increment;
+ qid->magic = QID_MAGIC;
+ *qidp = qid;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+qid_destroy(isc_mem_t *mctx, dns_qid_t **qidp) {
+ dns_qid_t *qid;
+
+ REQUIRE(qidp != NULL);
+ qid = *qidp;
+ *qidp = NULL;
+
+ REQUIRE(VALID_QID(qid));
+
+ qid->magic = 0;
+ isc_mem_put(mctx, qid->qid_table,
+ qid->qid_nbuckets * sizeof(dns_displist_t));
+ if (qid->sock_table != NULL) {
+ isc_mem_put(mctx, qid->sock_table,
+ qid->qid_nbuckets * sizeof(dispsocketlist_t));
+ }
+ isc_mutex_destroy(&qid->lock);
+ isc_mem_put(mctx, qid, sizeof(*qid));
+}
+
+/*
+ * Allocate and set important limits.
+ */
+static isc_result_t
+dispatch_allocate(dns_dispatchmgr_t *mgr, unsigned int maxrequests,
+ dns_dispatch_t **dispp) {
+ dns_dispatch_t *disp;
+ isc_result_t result;
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(dispp != NULL && *dispp == NULL);
+
+ /*
+ * Set up the dispatcher, mostly. Don't bother setting some of
+ * the options that are controlled by tcp vs. udp, etc.
+ */
+
+ disp = isc_mem_get(mgr->mctx, sizeof(*disp));
+ isc_refcount_increment0(&mgr->irefs);
+
+ disp->magic = 0;
+ disp->mgr = mgr;
+ disp->maxrequests = maxrequests;
+ disp->attributes = 0;
+ ISC_LINK_INIT(disp, link);
+ disp->refcount = 1;
+ disp->recv_pending = 0;
+ memset(&disp->local, 0, sizeof(disp->local));
+ memset(&disp->peer, 0, sizeof(disp->peer));
+ disp->localport = 0;
+ disp->shutting_down = 0;
+ disp->shutdown_out = 0;
+ disp->connected = 0;
+ disp->tcpmsg_valid = 0;
+ disp->shutdown_why = ISC_R_UNEXPECTED;
+ disp->requests = 0;
+ disp->tcpbuffers = 0;
+ disp->qid = NULL;
+ ISC_LIST_INIT(disp->activesockets);
+ ISC_LIST_INIT(disp->inactivesockets);
+ disp->nsockets = 0;
+ disp->port_table = NULL;
+ disp->dscp = -1;
+
+ isc_mutex_init(&disp->lock);
+
+ disp->failsafe_ev = allocate_devent(disp);
+ if (disp->failsafe_ev == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto kill_lock;
+ }
+
+ disp->magic = DISPATCH_MAGIC;
+
+ *dispp = disp;
+ return (ISC_R_SUCCESS);
+
+ /*
+ * error returns
+ */
+kill_lock:
+ isc_mutex_destroy(&disp->lock);
+ isc_refcount_decrement(&mgr->irefs);
+ isc_mem_put(mgr->mctx, disp, sizeof(*disp));
+
+ return (result);
+}
+
+/*
+ * MUST be unlocked, and not used by anything.
+ */
+static void
+dispatch_free(dns_dispatch_t **dispp) {
+ dns_dispatch_t *disp;
+ dns_dispatchmgr_t *mgr;
+
+ REQUIRE(VALID_DISPATCH(*dispp));
+ disp = *dispp;
+ *dispp = NULL;
+
+ mgr = disp->mgr;
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+
+ if (disp->tcpmsg_valid) {
+ dns_tcpmsg_invalidate(&disp->tcpmsg);
+ disp->tcpmsg_valid = 0;
+ }
+
+ INSIST(disp->tcpbuffers == 0);
+ INSIST(disp->requests == 0);
+ INSIST(disp->recv_pending == 0);
+ INSIST(ISC_LIST_EMPTY(disp->activesockets));
+ INSIST(ISC_LIST_EMPTY(disp->inactivesockets));
+
+ isc_refcount_decrement(&mgr->irefs);
+ isc_mem_put(mgr->mctx, disp->failsafe_ev, sizeof(*disp->failsafe_ev));
+ disp->failsafe_ev = NULL;
+
+ if (disp->qid != NULL) {
+ qid_destroy(mgr->mctx, &disp->qid);
+ }
+
+ if (disp->port_table != NULL) {
+ for (int i = 0; i < DNS_DISPATCH_PORTTABLESIZE; i++) {
+ INSIST(ISC_LIST_EMPTY(disp->port_table[i]));
+ }
+ isc_mem_put(mgr->mctx, disp->port_table,
+ sizeof(disp->port_table[0]) *
+ DNS_DISPATCH_PORTTABLESIZE);
+ }
+
+ disp->mgr = NULL;
+ isc_mutex_destroy(&disp->lock);
+ disp->magic = 0;
+ isc_refcount_decrement(&mgr->irefs);
+ isc_mem_put(mgr->mctx, disp, sizeof(*disp));
+}
+
+isc_result_t
+dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, isc_socket_t *sock,
+ isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr,
+ const isc_sockaddr_t *destaddr, unsigned int buffersize,
+ unsigned int maxbuffers, unsigned int maxrequests,
+ unsigned int buckets, unsigned int increment,
+ unsigned int attributes, dns_dispatch_t **dispp) {
+ isc_result_t result;
+ dns_dispatch_t *disp;
+
+ UNUSED(maxbuffers);
+ UNUSED(buffersize);
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(isc_socket_gettype(sock) == isc_sockettype_tcp);
+ REQUIRE((attributes & DNS_DISPATCHATTR_TCP) != 0);
+ REQUIRE((attributes & DNS_DISPATCHATTR_UDP) == 0);
+
+ if (destaddr == NULL) {
+ attributes |= DNS_DISPATCHATTR_PRIVATE; /* XXXMLG */
+ }
+
+ LOCK(&mgr->lock);
+
+ /*
+ * dispatch_allocate() checks mgr for us.
+ * qid_allocate() checks buckets and increment for us.
+ */
+ disp = NULL;
+ result = dispatch_allocate(mgr, maxrequests, &disp);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK(&mgr->lock);
+ return (result);
+ }
+
+ result = qid_allocate(mgr, buckets, increment, &disp->qid, false);
+ if (result != ISC_R_SUCCESS) {
+ goto deallocate_dispatch;
+ }
+
+ disp->socktype = isc_sockettype_tcp;
+ disp->socket = NULL;
+ isc_socket_attach(sock, &disp->socket);
+
+ disp->sepool = NULL;
+
+ disp->ntasks = 1;
+ disp->task[0] = NULL;
+ result = isc_task_create(taskmgr, 50, &disp->task[0]);
+ if (result != ISC_R_SUCCESS) {
+ goto kill_socket;
+ }
+
+ disp->ctlevent =
+ isc_event_allocate(mgr->mctx, disp, DNS_EVENT_DISPATCHCONTROL,
+ destroy_disp, disp, sizeof(isc_event_t));
+
+ isc_task_setname(disp->task[0], "tcpdispatch", disp);
+
+ dns_tcpmsg_init(mgr->mctx, disp->socket, &disp->tcpmsg);
+ disp->tcpmsg_valid = 1;
+
+ disp->attributes = attributes;
+
+ if (localaddr == NULL) {
+ if (destaddr != NULL) {
+ switch (isc_sockaddr_pf(destaddr)) {
+ case AF_INET:
+ isc_sockaddr_any(&disp->local);
+ break;
+ case AF_INET6:
+ isc_sockaddr_any6(&disp->local);
+ break;
+ }
+ }
+ } else {
+ disp->local = *localaddr;
+ }
+
+ if (destaddr != NULL) {
+ disp->peer = *destaddr;
+ }
+
+ /*
+ * Append it to the dispatcher list.
+ */
+ ISC_LIST_APPEND(mgr->list, disp, link);
+ UNLOCK(&mgr->lock);
+
+ mgr_log(mgr, LVL(90), "created TCP dispatcher %p", disp);
+ dispatch_log(disp, LVL(90), "created task %p", disp->task[0]);
+ *dispp = disp;
+
+ return (ISC_R_SUCCESS);
+
+kill_socket:
+ isc_socket_detach(&disp->socket);
+deallocate_dispatch:
+ dispatch_free(&disp);
+
+ UNLOCK(&mgr->lock);
+
+ return (result);
+}
+
+isc_result_t
+dns_dispatch_gettcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *destaddr,
+ const isc_sockaddr_t *localaddr, bool *connected,
+ dns_dispatch_t **dispp) {
+ dns_dispatch_t *disp;
+ isc_result_t result;
+ isc_sockaddr_t peeraddr;
+ isc_sockaddr_t sockname;
+ unsigned int attributes, mask;
+ bool match = false;
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(destaddr != NULL);
+ REQUIRE(dispp != NULL && *dispp == NULL);
+
+ /* First pass */
+ attributes = DNS_DISPATCHATTR_TCP | DNS_DISPATCHATTR_CONNECTED;
+ mask = DNS_DISPATCHATTR_TCP | DNS_DISPATCHATTR_PRIVATE |
+ DNS_DISPATCHATTR_EXCLUSIVE | DNS_DISPATCHATTR_CONNECTED;
+
+ LOCK(&mgr->lock);
+ disp = ISC_LIST_HEAD(mgr->list);
+ while (disp != NULL && !match) {
+ LOCK(&disp->lock);
+ if ((disp->shutting_down == 0) &&
+ ATTRMATCH(disp->attributes, attributes, mask) &&
+ (localaddr == NULL ||
+ isc_sockaddr_eqaddr(localaddr, &disp->local)))
+ {
+ result = isc_socket_getsockname(disp->socket,
+ &sockname);
+ if (result == ISC_R_SUCCESS) {
+ result = isc_socket_getpeername(disp->socket,
+ &peeraddr);
+ }
+ if (result == ISC_R_SUCCESS &&
+ isc_sockaddr_equal(destaddr, &peeraddr) &&
+ (localaddr == NULL ||
+ isc_sockaddr_eqaddr(localaddr, &sockname)))
+ {
+ /* attach */
+ disp->refcount++;
+ *dispp = disp;
+ match = true;
+ if (connected != NULL) {
+ *connected = true;
+ }
+ }
+ }
+ UNLOCK(&disp->lock);
+ disp = ISC_LIST_NEXT(disp, link);
+ }
+ if (match || connected == NULL) {
+ UNLOCK(&mgr->lock);
+ return (match ? ISC_R_SUCCESS : ISC_R_NOTFOUND);
+ }
+
+ /* Second pass, only if connected != NULL */
+ attributes = DNS_DISPATCHATTR_TCP;
+
+ disp = ISC_LIST_HEAD(mgr->list);
+ while (disp != NULL && !match) {
+ LOCK(&disp->lock);
+ if ((disp->shutting_down == 0) &&
+ ATTRMATCH(disp->attributes, attributes, mask) &&
+ (localaddr == NULL ||
+ isc_sockaddr_eqaddr(localaddr, &disp->local)) &&
+ isc_sockaddr_equal(destaddr, &disp->peer))
+ {
+ /* attach */
+ disp->refcount++;
+ *dispp = disp;
+ match = true;
+ }
+ UNLOCK(&disp->lock);
+ disp = ISC_LIST_NEXT(disp, link);
+ }
+ UNLOCK(&mgr->lock);
+ return (match ? ISC_R_SUCCESS : ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_dispatch_getudp_dup(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr,
+ isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr,
+ unsigned int buffersize, unsigned int maxbuffers,
+ unsigned int maxrequests, unsigned int buckets,
+ unsigned int increment, unsigned int attributes,
+ unsigned int mask, dns_dispatch_t **dispp,
+ dns_dispatch_t *dup_dispatch) {
+ isc_result_t result;
+ dns_dispatch_t *disp = NULL;
+
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+ REQUIRE(sockmgr != NULL);
+ REQUIRE(localaddr != NULL);
+ REQUIRE(taskmgr != NULL);
+ REQUIRE(buffersize >= 512 && buffersize < (64 * 1024));
+ REQUIRE(maxbuffers > 0);
+ REQUIRE(buckets < 2097169); /* next prime > 65536 * 32 */
+ REQUIRE(increment > buckets);
+ REQUIRE(dispp != NULL && *dispp == NULL);
+ REQUIRE((attributes & DNS_DISPATCHATTR_TCP) == 0);
+
+ result = dns_dispatchmgr_setudp(mgr, buffersize, maxbuffers,
+ maxrequests, buckets, increment);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ LOCK(&mgr->lock);
+
+ if ((attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) {
+ REQUIRE(isc_sockaddr_getport(localaddr) == 0);
+ goto createudp;
+ }
+
+ /*
+ * See if we have a dispatcher that matches.
+ */
+ if (dup_dispatch == NULL) {
+ result = dispatch_find(mgr, localaddr, attributes, mask, &disp);
+ if (result == ISC_R_SUCCESS) {
+ disp->refcount++;
+
+ if (disp->maxrequests < maxrequests) {
+ disp->maxrequests = maxrequests;
+ }
+
+ if ((disp->attributes & DNS_DISPATCHATTR_NOLISTEN) ==
+ 0 &&
+ (attributes & DNS_DISPATCHATTR_NOLISTEN) != 0)
+ {
+ disp->attributes |= DNS_DISPATCHATTR_NOLISTEN;
+ if (disp->recv_pending != 0) {
+ isc_socket_cancel(disp->socket,
+ disp->task[0],
+ ISC_SOCKCANCEL_RECV);
+ }
+ }
+
+ UNLOCK(&disp->lock);
+ UNLOCK(&mgr->lock);
+
+ *dispp = disp;
+
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+createudp:
+ /*
+ * Nope, create one.
+ */
+ result = dispatch_createudp(
+ mgr, sockmgr, taskmgr, localaddr, maxrequests, attributes,
+ &disp, dup_dispatch == NULL ? NULL : dup_dispatch->socket);
+
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK(&mgr->lock);
+ return (result);
+ }
+
+ UNLOCK(&mgr->lock);
+ *dispp = disp;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dispatch_getudp(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr,
+ isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr,
+ unsigned int buffersize, unsigned int maxbuffers,
+ unsigned int maxrequests, unsigned int buckets,
+ unsigned int increment, unsigned int attributes,
+ unsigned int mask, dns_dispatch_t **dispp) {
+ return (dns_dispatch_getudp_dup(mgr, sockmgr, taskmgr, localaddr,
+ buffersize, maxbuffers, maxrequests,
+ buckets, increment, attributes, mask,
+ dispp, NULL));
+}
+
+/*
+ * mgr should be locked.
+ */
+
+#ifndef DNS_DISPATCH_HELD
+#define DNS_DISPATCH_HELD 20U
+#endif /* ifndef DNS_DISPATCH_HELD */
+
+static isc_result_t
+get_udpsocket(dns_dispatchmgr_t *mgr, dns_dispatch_t *disp,
+ isc_socketmgr_t *sockmgr, const isc_sockaddr_t *localaddr,
+ isc_socket_t **sockp, isc_socket_t *dup_socket, bool duponly) {
+ unsigned int i, j;
+ isc_socket_t *held[DNS_DISPATCH_HELD];
+ isc_sockaddr_t localaddr_bound;
+ isc_socket_t *sock = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ bool anyport;
+
+ INSIST(sockp != NULL && *sockp == NULL);
+
+ localaddr_bound = *localaddr;
+ anyport = (isc_sockaddr_getport(localaddr) == 0);
+
+ if (anyport) {
+ unsigned int nports;
+ in_port_t *ports;
+
+ /*
+ * If no port is specified, we first try to pick up a random
+ * port by ourselves.
+ */
+ if (isc_sockaddr_pf(localaddr) == AF_INET) {
+ nports = disp->mgr->nv4ports;
+ ports = disp->mgr->v4ports;
+ } else {
+ nports = disp->mgr->nv6ports;
+ ports = disp->mgr->v6ports;
+ }
+ if (nports == 0) {
+ return (ISC_R_ADDRNOTAVAIL);
+ }
+
+ for (i = 0; i < 1024; i++) {
+ in_port_t prt;
+
+ prt = ports[isc_random_uniform(nports)];
+ isc_sockaddr_setport(&localaddr_bound, prt);
+ result = open_socket(sockmgr, &localaddr_bound, 0,
+ &sock, NULL, false);
+ /*
+ * Continue if the port chosen is already in use
+ * or the OS has reserved it.
+ */
+ if (result == ISC_R_NOPERM || result == ISC_R_ADDRINUSE)
+ {
+ continue;
+ }
+ disp->localport = prt;
+ *sockp = sock;
+ return (result);
+ }
+
+ /*
+ * If this fails 1024 times, we then ask the kernel for
+ * choosing one.
+ */
+ } else {
+ /* Allow to reuse address for non-random ports. */
+ result = open_socket(sockmgr, localaddr,
+ ISC_SOCKET_REUSEADDRESS, &sock, dup_socket,
+ duponly);
+
+ if (result == ISC_R_SUCCESS) {
+ *sockp = sock;
+ }
+
+ return (result);
+ }
+
+ memset(held, 0, sizeof(held));
+ i = 0;
+
+ for (j = 0; j < 0xffffU; j++) {
+ result = open_socket(sockmgr, localaddr, 0, &sock, NULL, false);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ } else if (portavailable(mgr, sock, NULL)) {
+ break;
+ }
+ if (held[i] != NULL) {
+ isc_socket_detach(&held[i]);
+ }
+ held[i++] = sock;
+ sock = NULL;
+ if (i == DNS_DISPATCH_HELD) {
+ i = 0;
+ }
+ }
+ if (j == 0xffffU) {
+ mgr_log(mgr, ISC_LOG_ERROR,
+ "avoid-v%s-udp-ports: unable to allocate "
+ "an available port",
+ isc_sockaddr_pf(localaddr) == AF_INET ? "4" : "6");
+ result = ISC_R_FAILURE;
+ goto end;
+ }
+ *sockp = sock;
+
+end:
+ for (i = 0; i < DNS_DISPATCH_HELD; i++) {
+ if (held[i] != NULL) {
+ isc_socket_detach(&held[i]);
+ }
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dispatch_createudp(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr,
+ isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr,
+ unsigned int maxrequests, unsigned int attributes,
+ dns_dispatch_t **dispp, isc_socket_t *dup_socket) {
+ isc_result_t result;
+ dns_dispatch_t *disp;
+ isc_socket_t *sock = NULL;
+ int i = 0;
+ bool duponly = ((attributes & DNS_DISPATCHATTR_CANREUSE) == 0);
+
+ /* This is an attribute needed only at creation time */
+ attributes &= ~DNS_DISPATCHATTR_CANREUSE;
+ /*
+ * dispatch_allocate() checks mgr for us.
+ */
+ disp = NULL;
+ result = dispatch_allocate(mgr, maxrequests, &disp);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ disp->socktype = isc_sockettype_udp;
+
+ if ((attributes & DNS_DISPATCHATTR_EXCLUSIVE) == 0) {
+ result = get_udpsocket(mgr, disp, sockmgr, localaddr, &sock,
+ dup_socket, duponly);
+ if (result != ISC_R_SUCCESS) {
+ goto deallocate_dispatch;
+ }
+
+ if (isc_log_wouldlog(dns_lctx, 90)) {
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+
+ isc_sockaddr_format(localaddr, addrbuf,
+ ISC_SOCKADDR_FORMATSIZE);
+ mgr_log(mgr, LVL(90),
+ "dns_dispatch_createudp: Created"
+ " UDP dispatch for %s with socket fd %d",
+ addrbuf, isc_socket_getfd(sock));
+ }
+ } else {
+ isc_sockaddr_t sa_any;
+
+ /*
+ * For dispatches using exclusive sockets with a specific
+ * source address, we only check if the specified address is
+ * available on the system. Query sockets will be created later
+ * on demand.
+ */
+ isc_sockaddr_anyofpf(&sa_any, isc_sockaddr_pf(localaddr));
+ if (!isc_sockaddr_eqaddr(&sa_any, localaddr)) {
+ result = open_socket(sockmgr, localaddr, 0, &sock, NULL,
+ false);
+ if (sock != NULL) {
+ isc_socket_detach(&sock);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto deallocate_dispatch;
+ }
+ }
+
+ disp->port_table = isc_mem_get(
+ mgr->mctx, sizeof(disp->port_table[0]) *
+ DNS_DISPATCH_PORTTABLESIZE);
+ for (i = 0; i < DNS_DISPATCH_PORTTABLESIZE; i++) {
+ ISC_LIST_INIT(disp->port_table[i]);
+ }
+ }
+ disp->socket = sock;
+ disp->local = *localaddr;
+
+ if ((attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) {
+ disp->ntasks = MAX_INTERNAL_TASKS;
+ } else {
+ disp->ntasks = 1;
+ }
+ for (i = 0; i < disp->ntasks; i++) {
+ disp->task[i] = NULL;
+ result = isc_task_create(taskmgr, 0, &disp->task[i]);
+ if (result != ISC_R_SUCCESS) {
+ while (--i >= 0) {
+ isc_task_shutdown(disp->task[i]);
+ isc_task_detach(&disp->task[i]);
+ }
+ goto kill_socket;
+ }
+ isc_task_setname(disp->task[i], "udpdispatch", disp);
+ }
+
+ disp->ctlevent =
+ isc_event_allocate(mgr->mctx, disp, DNS_EVENT_DISPATCHCONTROL,
+ destroy_disp, disp, sizeof(isc_event_t));
+
+ disp->sepool = NULL;
+ isc_mem_create(&disp->sepool);
+ isc_mem_setname(disp->sepool, "disp_sepool", NULL);
+
+ attributes &= ~DNS_DISPATCHATTR_TCP;
+ attributes |= DNS_DISPATCHATTR_UDP;
+ disp->attributes = attributes;
+
+ /*
+ * Append it to the dispatcher list.
+ */
+ ISC_LIST_APPEND(mgr->list, disp, link);
+
+ mgr_log(mgr, LVL(90), "created UDP dispatcher %p", disp);
+ dispatch_log(disp, LVL(90), "created task %p", disp->task[0]); /* XXX */
+ if (disp->socket != NULL) {
+ dispatch_log(disp, LVL(90), "created socket %p", disp->socket);
+ }
+
+ *dispp = disp;
+
+ return (result);
+
+ /*
+ * Error returns.
+ */
+kill_socket:
+ if (disp->socket != NULL) {
+ isc_socket_detach(&disp->socket);
+ }
+deallocate_dispatch:
+ dispatch_free(&disp);
+
+ return (result);
+}
+
+void
+dns_dispatch_attach(dns_dispatch_t *disp, dns_dispatch_t **dispp) {
+ REQUIRE(VALID_DISPATCH(disp));
+ REQUIRE(dispp != NULL && *dispp == NULL);
+
+ LOCK(&disp->lock);
+ disp->refcount++;
+ UNLOCK(&disp->lock);
+
+ *dispp = disp;
+}
+
+/*
+ * It is important to lock the manager while we are deleting the dispatch,
+ * since dns_dispatch_getudp will call dispatch_find, which returns to
+ * the caller a dispatch but does not attach to it until later. _getudp
+ * locks the manager, however, so locking it here will keep us from attaching
+ * to a dispatcher that is in the process of going away.
+ */
+void
+dns_dispatch_detach(dns_dispatch_t **dispp) {
+ dns_dispatch_t *disp;
+ dispsocket_t *dispsock;
+ bool killit;
+
+ REQUIRE(dispp != NULL && VALID_DISPATCH(*dispp));
+
+ disp = *dispp;
+ *dispp = NULL;
+
+ LOCK(&disp->lock);
+
+ INSIST(disp->refcount > 0);
+ disp->refcount--;
+ if (disp->refcount == 0) {
+ if (disp->recv_pending > 0) {
+ isc_socket_cancel(disp->socket, disp->task[0],
+ ISC_SOCKCANCEL_RECV);
+ }
+ for (dispsock = ISC_LIST_HEAD(disp->activesockets);
+ dispsock != NULL; dispsock = ISC_LIST_NEXT(dispsock, link))
+ {
+ isc_socket_cancel(dispsock->socket, dispsock->task,
+ ISC_SOCKCANCEL_RECV);
+ }
+ disp->shutting_down = 1;
+ }
+
+ dispatch_log(disp, LVL(90), "detach: refcount %d", disp->refcount);
+
+ killit = destroy_disp_ok(disp);
+ UNLOCK(&disp->lock);
+ if (killit) {
+ isc_task_send(disp->task[0], &disp->ctlevent);
+ }
+}
+
+isc_result_t
+dns_dispatch_addresponse(dns_dispatch_t *disp, unsigned int options,
+ const isc_sockaddr_t *dest, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_messageid_t *idp, dns_dispentry_t **resp,
+ isc_socketmgr_t *sockmgr) {
+ dns_dispentry_t *res;
+ unsigned int bucket;
+ in_port_t localport = 0;
+ dns_messageid_t id;
+ int i;
+ bool ok;
+ dns_qid_t *qid;
+ dispsocket_t *dispsocket = NULL;
+ isc_result_t result;
+
+ REQUIRE(VALID_DISPATCH(disp));
+ REQUIRE(task != NULL);
+ REQUIRE(dest != NULL);
+ REQUIRE(resp != NULL && *resp == NULL);
+ REQUIRE(idp != NULL);
+ if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) {
+ REQUIRE(sockmgr != NULL);
+ }
+
+ LOCK(&disp->lock);
+
+ if (disp->shutting_down == 1) {
+ UNLOCK(&disp->lock);
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ if (disp->requests >= disp->maxrequests) {
+ UNLOCK(&disp->lock);
+ return (ISC_R_QUOTA);
+ }
+
+ if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0 &&
+ disp->nsockets > DNS_DISPATCH_SOCKSQUOTA)
+ {
+ dispsocket_t *oldestsocket;
+ dns_dispentry_t *oldestresp;
+ dns_dispatchevent_t *rev;
+
+ /*
+ * Kill oldest outstanding query if the number of sockets
+ * exceeds the quota to keep the room for new queries.
+ */
+ oldestsocket = ISC_LIST_HEAD(disp->activesockets);
+ oldestresp = oldestsocket->resp;
+ if (oldestresp != NULL && !oldestresp->item_out) {
+ rev = allocate_devent(oldestresp->disp);
+ if (rev != NULL) {
+ rev->buffer.base = NULL;
+ rev->result = ISC_R_CANCELED;
+ rev->id = oldestresp->id;
+ ISC_EVENT_INIT(rev, sizeof(*rev), 0, NULL,
+ DNS_EVENT_DISPATCH,
+ oldestresp->action,
+ oldestresp->arg, oldestresp,
+ NULL, NULL);
+ oldestresp->item_out = true;
+ isc_task_send(oldestresp->task,
+ ISC_EVENT_PTR(&rev));
+ inc_stats(disp->mgr,
+ dns_resstatscounter_dispabort);
+ }
+ }
+
+ /*
+ * Move this entry to the tail so that it won't (easily) be
+ * examined before actually being canceled.
+ */
+ ISC_LIST_UNLINK(disp->activesockets, oldestsocket, link);
+ ISC_LIST_APPEND(disp->activesockets, oldestsocket, link);
+ }
+
+ qid = DNS_QID(disp);
+
+ if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) {
+ /*
+ * Get a separate UDP socket with a random port number.
+ */
+ result = get_dispsocket(disp, dest, sockmgr, &dispsocket,
+ &localport);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK(&disp->lock);
+ inc_stats(disp->mgr, dns_resstatscounter_dispsockfail);
+ return (result);
+ }
+ } else {
+ localport = disp->localport;
+ }
+
+ /*
+ * Try somewhat hard to find an unique ID unless FIXEDID is set
+ * in which case we use the id passed in via *idp.
+ */
+ LOCK(&qid->lock);
+ if ((options & DNS_DISPATCHOPT_FIXEDID) != 0) {
+ id = *idp;
+ } else {
+ id = (dns_messageid_t)isc_random16();
+ }
+ ok = false;
+ i = 0;
+ do {
+ bucket = dns_hash(qid, dest, id, localport);
+ if (entry_search(qid, dest, id, localport, bucket) == NULL) {
+ ok = true;
+ break;
+ }
+ if ((disp->attributes & DNS_DISPATCHATTR_FIXEDID) != 0) {
+ break;
+ }
+ id += qid->qid_increment;
+ id &= 0x0000ffff;
+ } while (i++ < 64);
+ UNLOCK(&qid->lock);
+
+ if (!ok) {
+ UNLOCK(&disp->lock);
+ return (ISC_R_NOMORE);
+ }
+
+ res = isc_mem_get(disp->mgr->mctx, sizeof(*res));
+ isc_refcount_increment0(&disp->mgr->irefs);
+
+ disp->refcount++;
+ disp->requests++;
+ res->task = NULL;
+ isc_task_attach(task, &res->task);
+ res->disp = disp;
+ res->id = id;
+ res->port = localport;
+ res->bucket = bucket;
+ res->host = *dest;
+ res->action = action;
+ res->arg = arg;
+ res->dispsocket = dispsocket;
+ if (dispsocket != NULL) {
+ dispsocket->resp = res;
+ }
+ res->item_out = false;
+ ISC_LIST_INIT(res->items);
+ ISC_LINK_INIT(res, link);
+ res->magic = RESPONSE_MAGIC;
+
+ LOCK(&qid->lock);
+ ISC_LIST_APPEND(qid->qid_table[bucket], res, link);
+ UNLOCK(&qid->lock);
+
+ inc_stats(disp->mgr, (qid == disp->mgr->qid)
+ ? dns_resstatscounter_disprequdp
+ : dns_resstatscounter_dispreqtcp);
+
+ request_log(disp, res, LVL(90), "attached to task %p", res->task);
+
+ if (((disp->attributes & DNS_DISPATCHATTR_UDP) != 0) ||
+ ((disp->attributes & DNS_DISPATCHATTR_CONNECTED) != 0))
+ {
+ result = startrecv(disp, dispsocket);
+ if (result != ISC_R_SUCCESS) {
+ LOCK(&qid->lock);
+ ISC_LIST_UNLINK(qid->qid_table[bucket], res, link);
+ UNLOCK(&qid->lock);
+
+ if (dispsocket != NULL) {
+ destroy_dispsocket(disp, &dispsocket);
+ }
+
+ disp->refcount--;
+ disp->requests--;
+
+ dec_stats(disp->mgr,
+ (qid == disp->mgr->qid)
+ ? dns_resstatscounter_disprequdp
+ : dns_resstatscounter_dispreqtcp);
+
+ UNLOCK(&disp->lock);
+ isc_task_detach(&res->task);
+ isc_refcount_decrement(&disp->mgr->irefs);
+ isc_mem_put(disp->mgr->mctx, res, sizeof(*res));
+ return (result);
+ }
+ }
+
+ if (dispsocket != NULL) {
+ ISC_LIST_APPEND(disp->activesockets, dispsocket, link);
+ }
+
+ UNLOCK(&disp->lock);
+
+ *idp = id;
+ *resp = res;
+
+ if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) {
+ INSIST(res->dispsocket != NULL);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_dispatch_starttcp(dns_dispatch_t *disp) {
+ REQUIRE(VALID_DISPATCH(disp));
+
+ dispatch_log(disp, LVL(90), "starttcp %p", disp->task[0]);
+
+ LOCK(&disp->lock);
+ if ((disp->attributes & DNS_DISPATCHATTR_CONNECTED) == 0) {
+ disp->attributes |= DNS_DISPATCHATTR_CONNECTED;
+ (void)startrecv(disp, NULL);
+ }
+ UNLOCK(&disp->lock);
+}
+
+isc_result_t
+dns_dispatch_getnext(dns_dispentry_t *resp, dns_dispatchevent_t **sockevent) {
+ dns_dispatch_t *disp;
+ dns_dispatchevent_t *ev;
+
+ REQUIRE(VALID_RESPONSE(resp));
+ REQUIRE(sockevent != NULL && *sockevent != NULL);
+
+ disp = resp->disp;
+ REQUIRE(VALID_DISPATCH(disp));
+
+ ev = *sockevent;
+ *sockevent = NULL;
+
+ LOCK(&disp->lock);
+
+ REQUIRE(resp->item_out);
+ resp->item_out = false;
+
+ if (ev->buffer.base != NULL) {
+ free_buffer(disp, ev->buffer.base, ev->buffer.length);
+ }
+ free_devent(disp, ev);
+
+ if (disp->shutting_down == 1) {
+ UNLOCK(&disp->lock);
+ return (ISC_R_SHUTTINGDOWN);
+ }
+ ev = ISC_LIST_HEAD(resp->items);
+ if (ev != NULL) {
+ ISC_LIST_UNLINK(resp->items, ev, ev_link);
+ ISC_EVENT_INIT(ev, sizeof(*ev), 0, NULL, DNS_EVENT_DISPATCH,
+ resp->action, resp->arg, resp, NULL, NULL);
+ request_log(disp, resp, LVL(90),
+ "[c] Sent event %p buffer %p len %d to task %p", ev,
+ ev->buffer.base, ev->buffer.length, resp->task);
+ resp->item_out = true;
+ isc_task_send(resp->task, ISC_EVENT_PTR(&ev));
+ }
+ UNLOCK(&disp->lock);
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_dispatch_removeresponse(dns_dispentry_t **resp,
+ dns_dispatchevent_t **sockevent) {
+ dns_dispatchmgr_t *mgr;
+ dns_dispatch_t *disp;
+ dns_dispentry_t *res;
+ dispsocket_t *dispsock;
+ dns_dispatchevent_t *ev;
+ unsigned int bucket;
+ bool killit;
+ unsigned int n;
+ isc_eventlist_t events;
+ dns_qid_t *qid;
+
+ REQUIRE(resp != NULL);
+ REQUIRE(VALID_RESPONSE(*resp));
+
+ res = *resp;
+ *resp = NULL;
+
+ disp = res->disp;
+ REQUIRE(VALID_DISPATCH(disp));
+ mgr = disp->mgr;
+ REQUIRE(VALID_DISPATCHMGR(mgr));
+
+ qid = DNS_QID(disp);
+
+ if (sockevent != NULL) {
+ REQUIRE(*sockevent != NULL);
+ ev = *sockevent;
+ *sockevent = NULL;
+ } else {
+ ev = NULL;
+ }
+
+ LOCK(&disp->lock);
+
+ INSIST(disp->requests > 0);
+ disp->requests--;
+ dec_stats(disp->mgr, (qid == disp->mgr->qid)
+ ? dns_resstatscounter_disprequdp
+ : dns_resstatscounter_dispreqtcp);
+ INSIST(disp->refcount > 0);
+ disp->refcount--;
+ if (disp->refcount == 0) {
+ if (disp->recv_pending > 0) {
+ isc_socket_cancel(disp->socket, disp->task[0],
+ ISC_SOCKCANCEL_RECV);
+ }
+ for (dispsock = ISC_LIST_HEAD(disp->activesockets);
+ dispsock != NULL; dispsock = ISC_LIST_NEXT(dispsock, link))
+ {
+ isc_socket_cancel(dispsock->socket, dispsock->task,
+ ISC_SOCKCANCEL_RECV);
+ }
+ disp->shutting_down = 1;
+ }
+
+ bucket = res->bucket;
+
+ LOCK(&qid->lock);
+ ISC_LIST_UNLINK(qid->qid_table[bucket], res, link);
+ UNLOCK(&qid->lock);
+
+ if (ev == NULL && res->item_out) {
+ /*
+ * We've posted our event, but the caller hasn't gotten it
+ * yet. Take it back.
+ */
+ ISC_LIST_INIT(events);
+ n = isc_task_unsend(res->task, res, DNS_EVENT_DISPATCH, NULL,
+ &events);
+ /*
+ * We had better have gotten it back.
+ */
+ INSIST(n == 1);
+ ev = (dns_dispatchevent_t *)ISC_LIST_HEAD(events);
+ }
+
+ if (ev != NULL) {
+ REQUIRE(res->item_out);
+ res->item_out = false;
+ if (ev->buffer.base != NULL) {
+ free_buffer(disp, ev->buffer.base, ev->buffer.length);
+ }
+ free_devent(disp, ev);
+ }
+
+ request_log(disp, res, LVL(90), "detaching from task %p", res->task);
+ isc_task_detach(&res->task);
+
+ if (res->dispsocket != NULL) {
+ isc_socket_cancel(res->dispsocket->socket,
+ res->dispsocket->task, ISC_SOCKCANCEL_RECV);
+ res->dispsocket->resp = NULL;
+ }
+
+ /*
+ * Free any buffered responses as well
+ */
+ ev = ISC_LIST_HEAD(res->items);
+ while (ev != NULL) {
+ ISC_LIST_UNLINK(res->items, ev, ev_link);
+ if (ev->buffer.base != NULL) {
+ free_buffer(disp, ev->buffer.base, ev->buffer.length);
+ }
+ free_devent(disp, ev);
+ ev = ISC_LIST_HEAD(res->items);
+ }
+ res->magic = 0;
+ isc_refcount_decrement(&disp->mgr->irefs);
+ isc_mem_put(disp->mgr->mctx, res, sizeof(*res));
+ if (disp->shutting_down == 1) {
+ do_cancel(disp);
+ } else {
+ (void)startrecv(disp, NULL);
+ }
+
+ killit = destroy_disp_ok(disp);
+ UNLOCK(&disp->lock);
+ if (killit) {
+ isc_task_send(disp->task[0], &disp->ctlevent);
+ }
+}
+
+/*
+ * disp must be locked.
+ */
+static void
+do_cancel(dns_dispatch_t *disp) {
+ dns_dispatchevent_t *ev;
+ dns_dispentry_t *resp;
+ dns_qid_t *qid;
+
+ if (disp->shutdown_out == 1) {
+ return;
+ }
+
+ qid = DNS_QID(disp);
+
+ /*
+ * Search for the first response handler without packets outstanding
+ * unless a specific handler is given.
+ */
+ LOCK(&qid->lock);
+ for (resp = linear_first(qid); resp != NULL && resp->item_out;
+ /* Empty. */)
+ {
+ resp = linear_next(qid, resp);
+ }
+
+ /*
+ * No one to send the cancel event to, so nothing to do.
+ */
+ if (resp == NULL) {
+ goto unlock;
+ }
+
+ /*
+ * Send the shutdown failsafe event to this resp.
+ */
+ ev = disp->failsafe_ev;
+ ISC_EVENT_INIT(ev, sizeof(*ev), 0, NULL, DNS_EVENT_DISPATCH,
+ resp->action, resp->arg, resp, NULL, NULL);
+ ev->result = disp->shutdown_why;
+ ev->buffer.base = NULL;
+ ev->buffer.length = 0;
+ disp->shutdown_out = 1;
+ request_log(disp, resp, LVL(10), "cancel: failsafe event %p -> task %p",
+ ev, resp->task);
+ resp->item_out = true;
+ isc_task_send(resp->task, ISC_EVENT_PTR(&ev));
+unlock:
+ UNLOCK(&qid->lock);
+}
+
+isc_socket_t *
+dns_dispatch_getsocket(dns_dispatch_t *disp) {
+ REQUIRE(VALID_DISPATCH(disp));
+
+ return (disp->socket);
+}
+
+isc_socket_t *
+dns_dispatch_getentrysocket(dns_dispentry_t *resp) {
+ REQUIRE(VALID_RESPONSE(resp));
+
+ if (resp->dispsocket != NULL) {
+ return (resp->dispsocket->socket);
+ } else {
+ return (NULL);
+ }
+}
+
+isc_result_t
+dns_dispatch_getlocaladdress(dns_dispatch_t *disp, isc_sockaddr_t *addrp) {
+ REQUIRE(VALID_DISPATCH(disp));
+ REQUIRE(addrp != NULL);
+
+ if (disp->socktype == isc_sockettype_udp) {
+ *addrp = disp->local;
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+void
+dns_dispatch_cancel(dns_dispatch_t *disp) {
+ REQUIRE(VALID_DISPATCH(disp));
+
+ LOCK(&disp->lock);
+
+ if (disp->shutting_down == 1) {
+ UNLOCK(&disp->lock);
+ return;
+ }
+
+ disp->shutdown_why = ISC_R_CANCELED;
+ disp->shutting_down = 1;
+ do_cancel(disp);
+
+ UNLOCK(&disp->lock);
+
+ return;
+}
+
+unsigned int
+dns_dispatch_getattributes(dns_dispatch_t *disp) {
+ REQUIRE(VALID_DISPATCH(disp));
+
+ /*
+ * We don't bother locking disp here; it's the caller's responsibility
+ * to use only non volatile flags.
+ */
+ return (disp->attributes);
+}
+
+void
+dns_dispatch_changeattributes(dns_dispatch_t *disp, unsigned int attributes,
+ unsigned int mask) {
+ REQUIRE(VALID_DISPATCH(disp));
+ /* Exclusive attribute can only be set on creation */
+ REQUIRE((attributes & DNS_DISPATCHATTR_EXCLUSIVE) == 0);
+ /* Also, a dispatch with randomport specified cannot start listening */
+ REQUIRE((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) == 0 ||
+ (attributes & DNS_DISPATCHATTR_NOLISTEN) == 0);
+
+ /* XXXMLG
+ * Should check for valid attributes here!
+ */
+
+ LOCK(&disp->lock);
+
+ if ((mask & DNS_DISPATCHATTR_NOLISTEN) != 0) {
+ if ((disp->attributes & DNS_DISPATCHATTR_NOLISTEN) != 0 &&
+ (attributes & DNS_DISPATCHATTR_NOLISTEN) == 0)
+ {
+ disp->attributes &= ~DNS_DISPATCHATTR_NOLISTEN;
+ (void)startrecv(disp, NULL);
+ } else if ((disp->attributes & DNS_DISPATCHATTR_NOLISTEN) ==
+ 0 &&
+ (attributes & DNS_DISPATCHATTR_NOLISTEN) != 0)
+ {
+ disp->attributes |= DNS_DISPATCHATTR_NOLISTEN;
+ if (disp->recv_pending != 0) {
+ isc_socket_cancel(disp->socket, disp->task[0],
+ ISC_SOCKCANCEL_RECV);
+ }
+ }
+ }
+
+ disp->attributes &= ~mask;
+ disp->attributes |= (attributes & mask);
+ UNLOCK(&disp->lock);
+}
+
+void
+dns_dispatch_importrecv(dns_dispatch_t *disp, isc_event_t *event) {
+ void *buf;
+ isc_socketevent_t *sevent, *newsevent;
+
+ REQUIRE(VALID_DISPATCH(disp));
+ REQUIRE(event != NULL);
+
+ if ((disp->attributes & DNS_DISPATCHATTR_NOLISTEN) == 0) {
+ return;
+ }
+
+ sevent = (isc_socketevent_t *)event;
+ INSIST(sevent->n <= disp->mgr->buffersize);
+
+ newsevent = (isc_socketevent_t *)isc_event_allocate(
+ disp->mgr->mctx, NULL, DNS_EVENT_IMPORTRECVDONE, udp_shrecv,
+ disp, sizeof(isc_socketevent_t));
+
+ buf = allocate_udp_buffer(disp);
+ if (buf == NULL) {
+ isc_event_free(ISC_EVENT_PTR(&newsevent));
+ return;
+ }
+ memmove(buf, sevent->region.base, sevent->n);
+ newsevent->region.base = buf;
+ newsevent->region.length = disp->mgr->buffersize;
+ newsevent->n = sevent->n;
+ newsevent->result = sevent->result;
+ newsevent->address = sevent->address;
+ newsevent->timestamp = sevent->timestamp;
+ newsevent->pktinfo = sevent->pktinfo;
+ newsevent->attributes = sevent->attributes;
+
+ isc_task_send(disp->task[0], ISC_EVENT_PTR(&newsevent));
+}
+
+dns_dispatch_t *
+dns_dispatchset_get(dns_dispatchset_t *dset) {
+ dns_dispatch_t *disp;
+
+ /* check that dispatch set is configured */
+ if (dset == NULL || dset->ndisp == 0) {
+ return (NULL);
+ }
+
+ LOCK(&dset->lock);
+ disp = dset->dispatches[dset->cur];
+ dset->cur++;
+ if (dset->cur == dset->ndisp) {
+ dset->cur = 0;
+ }
+ UNLOCK(&dset->lock);
+
+ return (disp);
+}
+
+isc_result_t
+dns_dispatchset_create(isc_mem_t *mctx, isc_socketmgr_t *sockmgr,
+ isc_taskmgr_t *taskmgr, dns_dispatch_t *source,
+ dns_dispatchset_t **dsetp, int n) {
+ isc_result_t result;
+ dns_dispatchset_t *dset;
+ dns_dispatchmgr_t *mgr;
+ int i, j;
+
+ REQUIRE(VALID_DISPATCH(source));
+ REQUIRE((source->attributes & DNS_DISPATCHATTR_UDP) != 0);
+ REQUIRE(dsetp != NULL && *dsetp == NULL);
+
+ mgr = source->mgr;
+
+ dset = isc_mem_get(mctx, sizeof(dns_dispatchset_t));
+ memset(dset, 0, sizeof(*dset));
+
+ isc_mutex_init(&dset->lock);
+
+ dset->dispatches = isc_mem_get(mctx, sizeof(dns_dispatch_t *) * n);
+
+ isc_mem_attach(mctx, &dset->mctx);
+ dset->ndisp = n;
+ dset->cur = 0;
+
+ dset->dispatches[0] = NULL;
+ dns_dispatch_attach(source, &dset->dispatches[0]);
+
+ LOCK(&mgr->lock);
+ for (i = 1; i < n; i++) {
+ dset->dispatches[i] = NULL;
+ result = dispatch_createudp(
+ mgr, sockmgr, taskmgr, &source->local,
+ source->maxrequests, source->attributes,
+ &dset->dispatches[i], source->socket);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+ }
+
+ UNLOCK(&mgr->lock);
+ *dsetp = dset;
+
+ return (ISC_R_SUCCESS);
+
+fail:
+ UNLOCK(&mgr->lock);
+
+ for (j = 0; j < i; j++) {
+ dns_dispatch_detach(&(dset->dispatches[j]));
+ }
+ isc_mem_put(mctx, dset->dispatches, sizeof(dns_dispatch_t *) * n);
+ if (dset->mctx == mctx) {
+ isc_mem_detach(&dset->mctx);
+ }
+
+ isc_mutex_destroy(&dset->lock);
+ isc_mem_put(mctx, dset, sizeof(dns_dispatchset_t));
+ return (result);
+}
+
+void
+dns_dispatchset_cancelall(dns_dispatchset_t *dset, isc_task_t *task) {
+ int i;
+
+ REQUIRE(dset != NULL);
+
+ for (i = 0; i < dset->ndisp; i++) {
+ isc_socket_t *sock;
+ sock = dns_dispatch_getsocket(dset->dispatches[i]);
+ isc_socket_cancel(sock, task, ISC_SOCKCANCEL_ALL);
+ }
+}
+
+void
+dns_dispatchset_destroy(dns_dispatchset_t **dsetp) {
+ dns_dispatchset_t *dset;
+ int i;
+
+ REQUIRE(dsetp != NULL && *dsetp != NULL);
+
+ dset = *dsetp;
+ *dsetp = NULL;
+ for (i = 0; i < dset->ndisp; i++) {
+ dns_dispatch_detach(&(dset->dispatches[i]));
+ }
+ isc_mem_put(dset->mctx, dset->dispatches,
+ sizeof(dns_dispatch_t *) * dset->ndisp);
+ isc_mutex_destroy(&dset->lock);
+ isc_mem_putanddetach(&dset->mctx, dset, sizeof(dns_dispatchset_t));
+}
+
+void
+dns_dispatch_setdscp(dns_dispatch_t *disp, isc_dscp_t dscp) {
+ REQUIRE(VALID_DISPATCH(disp));
+ disp->dscp = dscp;
+}
+
+isc_dscp_t
+dns_dispatch_getdscp(dns_dispatch_t *disp) {
+ REQUIRE(VALID_DISPATCH(disp));
+ return (disp->dscp);
+}
+
+#if 0
+void
+dns_dispatchmgr_dump(dns_dispatchmgr_t *mgr) {
+ dns_dispatch_t *disp;
+ char foo[1024];
+
+ disp = ISC_LIST_HEAD(mgr->list);
+ while (disp != NULL) {
+ isc_sockaddr_format(&disp->local, foo, sizeof(foo));
+ printf("\tdispatch %p, addr %s\n", disp, foo);
+ disp = ISC_LIST_NEXT(disp, link);
+ }
+}
+#endif /* if 0 */
diff --git a/lib/dns/dlz.c b/lib/dns/dlz.c
new file mode 100644
index 0000000..6d77f5b
--- /dev/null
+++ b/lib/dns/dlz.c
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
+ * conceived and contributed by Rob Butler.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+/***
+ *** Imports
+ ***/
+
+#include <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 master zone
+ *
+ * This function uses a callback setup in dns_dlzconfigure() to call
+ * into the server zone code to setup the remaining pieces of server
+ * specific functionality on the zone
+ */
+isc_result_t
+dns_dlz_writeablezone(dns_view_t *view, dns_dlzdb_t *dlzdb,
+ const char *zone_name) {
+ dns_zone_t *zone = NULL;
+ dns_zone_t *dupzone = NULL;
+ isc_result_t result;
+ isc_buffer_t buffer;
+ dns_fixedname_t fixorigin;
+ dns_name_t *origin;
+
+ REQUIRE(DNS_DLZ_VALID(dlzdb));
+
+ REQUIRE(dlzdb->configure_callback != NULL);
+
+ isc_buffer_constinit(&buffer, zone_name, strlen(zone_name));
+ isc_buffer_add(&buffer, strlen(zone_name));
+ dns_fixedname_init(&fixorigin);
+ result = dns_name_fromtext(dns_fixedname_name(&fixorigin), &buffer,
+ dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ origin = dns_fixedname_name(&fixorigin);
+
+ if (!dlzdb->search) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_WARNING,
+ "DLZ %s has 'search no;', but attempted to "
+ "register writeable zone %s.",
+ dlzdb->dlzname, zone_name);
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ /* See if the zone already exists */
+ result = dns_view_findzone(view, origin, &dupzone);
+ if (result == ISC_R_SUCCESS) {
+ dns_zone_detach(&dupzone);
+ result = ISC_R_EXISTS;
+ goto cleanup;
+ }
+ INSIST(dupzone == NULL);
+
+ /* Create it */
+ result = dns_zone_create(&zone, view->mctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_zone_setorigin(zone, origin);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ dns_zone_setview(zone, view);
+
+ dns_zone_setadded(zone, true);
+
+ if (dlzdb->ssutable == NULL) {
+ result = dns_ssutable_createdlz(dlzdb->mctx, &dlzdb->ssutable,
+ dlzdb);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ dns_zone_setssutable(zone, dlzdb->ssutable);
+
+ result = dlzdb->configure_callback(view, dlzdb, zone);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_view_addzone(view, zone);
+
+cleanup:
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+
+ return (result);
+}
+
+/*%
+ * Configure a DLZ driver. This is optional, and if supplied gives
+ * the backend an opportunity to configure parameters related to DLZ.
+ */
+isc_result_t
+dns_dlzconfigure(dns_view_t *view, dns_dlzdb_t *dlzdb,
+ dlzconfigure_callback_t callback) {
+ dns_dlzimplementation_t *impl;
+ isc_result_t result;
+
+ REQUIRE(DNS_DLZ_VALID(dlzdb));
+ REQUIRE(dlzdb->implementation != NULL);
+
+ impl = dlzdb->implementation;
+
+ if (impl->methods->configure == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ dlzdb->configure_callback = callback;
+
+ result = impl->methods->configure(impl->driverarg, dlzdb->dbdata, view,
+ dlzdb);
+ return (result);
+}
+
+bool
+dns_dlz_ssumatch(dns_dlzdb_t *dlzdatabase, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *tcpaddr,
+ dns_rdatatype_t type, const dst_key_t *key) {
+ dns_dlzimplementation_t *impl;
+ bool r;
+
+ REQUIRE(dlzdatabase != NULL);
+ REQUIRE(dlzdatabase->implementation != NULL);
+ REQUIRE(dlzdatabase->implementation->methods != NULL);
+ impl = dlzdatabase->implementation;
+
+ if (impl->methods->ssumatch == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
+ "No ssumatch method for DLZ database");
+ return (false);
+ }
+
+ r = impl->methods->ssumatch(signer, name, tcpaddr, type, key,
+ impl->driverarg, dlzdatabase->dbdata);
+ return (r);
+}
diff --git a/lib/dns/dns64.c b/lib/dns/dns64.c
new file mode 100644
index 0000000..03b3dca
--- /dev/null
+++ b/lib/dns/dns64.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <isc/list.h>
+#include <isc/mem.h>
+#include <isc/netaddr.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>
+#include <dns/result.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, const dns_aclenv_t *env,
+ unsigned int flags, unsigned char *a, unsigned char *aaaa) {
+ unsigned int nbytes, i;
+ isc_result_t result;
+ int match;
+
+ if ((dns64->flags & DNS_DNS64_RECURSIVE_ONLY) != 0 &&
+ (flags & DNS_DNS64_RECURSIVE) == 0)
+ {
+ return (DNS_R_DISALLOWED);
+ }
+
+ if ((dns64->flags & DNS_DNS64_BREAK_DNSSEC) == 0 &&
+ (flags & DNS_DNS64_DNSSEC) != 0)
+ {
+ return (DNS_R_DISALLOWED);
+ }
+
+ if (dns64->clients != NULL) {
+ result = dns_acl_match(reqaddr, reqsigner, dns64->clients, env,
+ &match, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (match <= 0) {
+ return (DNS_R_DISALLOWED);
+ }
+ }
+
+ if (dns64->mapped != NULL) {
+ struct in_addr ina;
+ isc_netaddr_t netaddr;
+
+ memmove(&ina.s_addr, a, 4);
+ isc_netaddr_fromin(&netaddr, &ina);
+ result = dns_acl_match(&netaddr, NULL, dns64->mapped, env,
+ &match, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (match <= 0) {
+ return (DNS_R_DISALLOWED);
+ }
+ }
+
+ nbytes = dns64->prefixlen / 8;
+ INSIST(nbytes <= 12);
+ /* Copy prefix. */
+ memmove(aaaa, dns64->bits, nbytes);
+ /* Bits 64-71 are zeros. rfc6052.txt */
+ if (nbytes == 8) {
+ aaaa[nbytes++] = 0;
+ }
+ /* Copy mapped address. */
+ for (i = 0; i < 4U; i++) {
+ aaaa[nbytes++] = a[i];
+ /* Bits 64-71 are zeros. rfc6052.txt */
+ if (nbytes == 8) {
+ aaaa[nbytes++] = 0;
+ }
+ }
+ /* Copy suffix. */
+ memmove(aaaa + nbytes, dns64->bits + nbytes, 16 - nbytes);
+ return (ISC_R_SUCCESS);
+}
+
+dns_dns64_t *
+dns_dns64_next(dns_dns64_t *dns64) {
+ dns64 = ISC_LIST_NEXT(dns64, link);
+ return (dns64);
+}
+
+void
+dns_dns64_append(dns_dns64list_t *list, dns_dns64_t *dns64) {
+ ISC_LIST_APPEND(*list, dns64, link);
+}
+
+void
+dns_dns64_unlink(dns_dns64list_t *list, dns_dns64_t *dns64) {
+ ISC_LIST_UNLINK(*list, dns64, link);
+}
+
+bool
+dns_dns64_aaaaok(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner, const dns_aclenv_t *env,
+ unsigned int flags, dns_rdataset_t *rdataset, bool *aaaaok,
+ size_t aaaaoklen) {
+ struct in6_addr in6;
+ isc_netaddr_t netaddr;
+ isc_result_t result;
+ int match;
+ bool answer = false;
+ bool found = false;
+ unsigned int i, ok;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->type == dns_rdatatype_aaaa);
+ REQUIRE(rdataset->rdclass == dns_rdataclass_in);
+ if (aaaaok != NULL) {
+ REQUIRE(aaaaoklen == dns_rdataset_count(rdataset));
+ }
+
+ for (; dns64 != NULL; dns64 = ISC_LIST_NEXT(dns64, link)) {
+ if ((dns64->flags & DNS_DNS64_RECURSIVE_ONLY) != 0 &&
+ (flags & DNS_DNS64_RECURSIVE) == 0)
+ {
+ continue;
+ }
+
+ if ((dns64->flags & DNS_DNS64_BREAK_DNSSEC) == 0 &&
+ (flags & DNS_DNS64_DNSSEC) != 0)
+ {
+ continue;
+ }
+ /*
+ * Work out if this dns64 structure applies to this client.
+ */
+ if (dns64->clients != NULL) {
+ result = dns_acl_match(reqaddr, reqsigner,
+ dns64->clients, env, &match,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (match <= 0) {
+ continue;
+ }
+ }
+
+ if (!found && aaaaok != NULL) {
+ for (i = 0; i < aaaaoklen; i++) {
+ aaaaok[i] = false;
+ }
+ }
+ found = true;
+
+ /*
+ * If we are not excluding any addresses then any AAAA
+ * will do.
+ */
+ if (dns64->excluded == NULL) {
+ answer = true;
+ if (aaaaok == NULL) {
+ goto done;
+ }
+ for (i = 0; i < aaaaoklen; i++) {
+ aaaaok[i] = true;
+ }
+ goto done;
+ }
+
+ i = 0;
+ ok = 0;
+ for (result = dns_rdataset_first(rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ if (aaaaok == NULL || !aaaaok[i]) {
+ dns_rdataset_current(rdataset, &rdata);
+ memmove(&in6.s6_addr, rdata.data, 16);
+ isc_netaddr_fromin6(&netaddr, &in6);
+
+ result = dns_acl_match(&netaddr, NULL,
+ dns64->excluded, env,
+ &match, NULL);
+ if (result == ISC_R_SUCCESS && match <= 0) {
+ answer = true;
+ if (aaaaok == NULL) {
+ goto done;
+ }
+ aaaaok[i] = true;
+ ok++;
+ }
+ } else {
+ ok++;
+ }
+ i++;
+ }
+ /*
+ * Are all addresses ok?
+ */
+ if (aaaaok != NULL && ok == aaaaoklen) {
+ goto done;
+ }
+ }
+
+done:
+ if (!found && aaaaok != NULL) {
+ for (i = 0; i < aaaaoklen; i++) {
+ aaaaok[i] = true;
+ }
+ }
+ return (found ? answer : true);
+}
diff --git a/lib/dns/dnsrps.c b/lib/dns/dnsrps.c
new file mode 100644
index 0000000..8c04337
--- /dev/null
+++ b/lib/dns/dnsrps.c
@@ -0,0 +1,1005 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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 <dns/dnsrps.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/result.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_copynf(name, foundname);
+ return (rpsdb_findrdataset(db, *nodep, NULL, type, 0, 0, rdataset,
+ sigrdataset));
+}
+
+static isc_result_t
+rpsdb_allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ rpsdb_t *rpsdb = (rpsdb_t *)db;
+ rpsdb_rdatasetiter_t *rpsdb_iter;
+
+ UNUSED(version);
+ UNUSED(now);
+
+ REQUIRE(VALID_RPSDB(rpsdb));
+ REQUIRE(node == &rpsdb->origin_node || node == &rpsdb->data_node);
+
+ rpsdb_iter = isc_mem_get(rpsdb->common.mctx, sizeof(*rpsdb_iter));
+
+ memset(rpsdb_iter, 0, sizeof(*rpsdb_iter));
+ rpsdb_iter->common.magic = DNS_RDATASETITER_MAGIC;
+ rpsdb_iter->common.methods = &rpsdb_rdatasetiter_methods;
+ rpsdb_iter->common.db = db;
+ rpsdb_iter->common.options = options;
+ rpsdb_attachnode(db, node, &rpsdb_iter->common.node);
+
+ *iteratorp = &rpsdb_iter->common;
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+rpsdb_issecure(dns_db_t *db) {
+ UNUSED(db);
+
+ return (false);
+}
+
+static isc_result_t
+rpsdb_getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ rpsdb_t *rpsdb = (rpsdb_t *)db;
+
+ REQUIRE(VALID_RPSDB(rpsdb));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ rpsdb_attachnode(db, &rpsdb->origin_node, nodep);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rpsdb_rdataset_disassociate(dns_rdataset_t *rdataset) {
+ dns_db_t *db;
+
+ /*
+ * Detach the last RR delivered.
+ */
+ if (RD_CUR_RR(rdataset) != NULL) {
+ free(RD_CUR_RR(rdataset));
+ RD_CUR_RR(rdataset) = NULL;
+ }
+
+ db = RD_DB(rdataset);
+ RD_DB(rdataset) = NULL;
+ dns_db_detach(&db);
+}
+
+static isc_result_t
+rpsdb_rdataset_next(dns_rdataset_t *rdataset) {
+ rpsdb_t *rpsdb;
+ uint16_t type;
+ dns_rdataclass_t class;
+ librpz_rr_t *rr;
+ librpz_emsg_t emsg;
+
+ rpsdb = RD_DB(rdataset);
+
+ /*
+ * Detach the previous RR.
+ */
+ if (RD_CUR_RR(rdataset) != NULL) {
+ free(RD_CUR_RR(rdataset));
+ RD_CUR_RR(rdataset) = NULL;
+ }
+
+ /*
+ * Get the next RR of the specified type.
+ * SOAs differ.
+ */
+ if (rdataset->type == dns_rdatatype_soa) {
+ if (RD_NEXT_RR(rdataset) == LIBRPZ_IDX_NULL) {
+ return (ISC_R_NOMORE);
+ }
+ RD_NEXT_RR(rdataset) = LIBRPZ_IDX_NULL;
+ if (!librpz->rsp_soa(&emsg, NULL, &rr, NULL, &rpsdb->result,
+ rpsdb->rsp))
+ {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ RD_CUR_RR(rdataset) = rr;
+ return (ISC_R_SUCCESS);
+ }
+
+ rpsdb->result.next_rr = RD_NEXT_RR(rdataset);
+ for (;;) {
+ if (!librpz->rsp_rr(&emsg, &type, &class, NULL, &rr,
+ &rpsdb->result, rpsdb->qname->ndata,
+ rpsdb->qname->length, rpsdb->rsp))
+ {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ if (rdataset->type == type && rdataset->rdclass == class) {
+ RD_CUR_RR(rdataset) = rr;
+ RD_NEXT_RR(rdataset) = rpsdb->result.next_rr;
+ return (ISC_R_SUCCESS);
+ }
+ if (type == dns_rdatatype_none) {
+ return (ISC_R_NOMORE);
+ }
+ free(rr);
+ }
+}
+
+static isc_result_t
+rpsdb_rdataset_first(dns_rdataset_t *rdataset) {
+ rpsdb_t *rpsdb;
+ librpz_emsg_t emsg;
+
+ rpsdb = RD_DB(rdataset);
+ REQUIRE(VALID_RPSDB(rpsdb));
+
+ if (RD_CUR_RR(rdataset) != NULL) {
+ free(RD_CUR_RR(rdataset));
+ RD_CUR_RR(rdataset) = NULL;
+ }
+
+ if (!librpz->rsp_result(&emsg, &rpsdb->result, true, rpsdb->rsp)) {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ if (rdataset->type == dns_rdatatype_soa) {
+ RD_NEXT_RR(rdataset) = LIBRPZ_IDX_BAD;
+ } else {
+ RD_NEXT_RR(rdataset) = rpsdb->result.next_rr;
+ }
+
+ return (rpsdb_rdataset_next(rdataset));
+}
+
+static void
+rpsdb_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ rpsdb_t *rpsdb;
+ librpz_rr_t *rr;
+ isc_region_t r;
+
+ rpsdb = RD_DB(rdataset);
+ REQUIRE(VALID_RPSDB(rpsdb));
+ rr = RD_CUR_RR(rdataset);
+ REQUIRE(rr != NULL);
+
+ r.length = ntohs(rr->rdlength);
+ r.base = rr->rdata;
+ dns_rdata_fromregion(rdata, ntohs(rr->class), ntohs(rr->type), &r);
+}
+
+static void
+rpsdb_rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ rpsdb_t *rpsdb;
+ dns_db_t *dbp;
+
+ INSIST(!ISC_LINK_LINKED(target, link));
+ *target = *source;
+ ISC_LINK_INIT(target, link);
+ rpsdb = RD_DB(source);
+ REQUIRE(VALID_RPSDB(rpsdb));
+ dbp = NULL;
+ dns_db_attach(&rpsdb->common, &dbp);
+ RD_DB(target) = dbp;
+ RD_CUR_RR(target) = NULL;
+ RD_NEXT_RR(target) = LIBRPZ_IDX_NULL;
+}
+
+static unsigned int
+rpsdb_rdataset_count(dns_rdataset_t *rdataset) {
+ rpsdb_t *rpsdb;
+
+ rpsdb = RD_DB(rdataset);
+ REQUIRE(VALID_RPSDB(rpsdb));
+
+ return (RD_COUNT(rdataset));
+}
+
+static void
+rpsdb_rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
+ rpsdb_t *rpsdb;
+ dns_rdatasetiter_t *iterator;
+ isc_mem_t *mctx;
+
+ iterator = *iteratorp;
+ *iteratorp = NULL;
+ rpsdb = (rpsdb_t *)iterator->db;
+ REQUIRE(VALID_RPSDB(rpsdb));
+
+ mctx = iterator->db->mctx;
+ dns_db_detachnode(iterator->db, &iterator->node);
+ isc_mem_put(mctx, iterator, sizeof(rpsdb_rdatasetiter_t));
+}
+
+static isc_result_t
+rpsdb_rdatasetiter_next(dns_rdatasetiter_t *iter) {
+ rpsdb_t *rpsdb;
+ rpsdb_rdatasetiter_t *rpsdb_iter;
+ dns_rdatatype_t next_type, type;
+ dns_rdataclass_t next_class, class;
+ uint32_t ttl;
+ librpz_emsg_t emsg;
+
+ rpsdb = (rpsdb_t *)iter->db;
+ REQUIRE(VALID_RPSDB(rpsdb));
+ rpsdb_iter = (rpsdb_rdatasetiter_t *)iter;
+
+ /*
+ * This function is only used after a policy has been chosen,
+ * and so without caring whether it is after recursion.
+ */
+ if (!librpz->rsp_result(&emsg, &rpsdb->result, true, rpsdb->rsp)) {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ /*
+ * Find the next class and type after the current class and type
+ * among the RRs in current result.
+ * As a side effect, count the number of those RRs.
+ */
+ rpsdb_iter->count = 0;
+ next_class = dns_rdataclass_reserved0;
+ next_type = dns_rdatatype_none;
+ for (;;) {
+ if (!librpz->rsp_rr(&emsg, &type, &class, &ttl, NULL,
+ &rpsdb->result, rpsdb->qname->ndata,
+ rpsdb->qname->length, rpsdb->rsp))
+ {
+ librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c);
+ return (DNS_R_SERVFAIL);
+ }
+ if (type == dns_rdatatype_none) {
+ if (next_type == dns_rdatatype_none) {
+ return (ISC_R_NOMORE);
+ }
+ rpsdb_iter->type = next_type;
+ rpsdb_iter->class = next_class;
+ return (ISC_R_SUCCESS);
+ }
+ /*
+ * Skip RRs with the current class and type or before.
+ */
+ if (rpsdb_iter->class > class ||
+ (rpsdb_iter->class = class && rpsdb_iter->type >= type))
+ {
+ continue;
+ }
+ if (next_type == dns_rdatatype_none || next_class > class ||
+ (next_class == class && next_type > type))
+ {
+ /*
+ * This is the first of a subsequent class and type.
+ */
+ next_type = type;
+ next_class = class;
+ rpsdb_iter->ttl = ttl;
+ rpsdb_iter->count = 1;
+ rpsdb_iter->next_rr = rpsdb->result.next_rr;
+ } else if (next_type == type && next_class == class) {
+ ++rpsdb_iter->count;
+ }
+ }
+}
+
+static isc_result_t
+rpsdb_rdatasetiter_first(dns_rdatasetiter_t *iterator) {
+ rpsdb_t *rpsdb;
+ rpsdb_rdatasetiter_t *rpsdb_iter;
+
+ rpsdb = (rpsdb_t *)iterator->db;
+ REQUIRE(VALID_RPSDB(rpsdb));
+ rpsdb_iter = (rpsdb_rdatasetiter_t *)iterator;
+
+ rpsdb_iter->type = dns_rdatatype_none;
+ rpsdb_iter->class = dns_rdataclass_reserved0;
+ return (rpsdb_rdatasetiter_next(iterator));
+}
+
+static void
+rpsdb_rdatasetiter_current(dns_rdatasetiter_t *iterator,
+ dns_rdataset_t *rdataset) {
+ rpsdb_t *rpsdb;
+ rpsdb_rdatasetiter_t *rpsdb_iter;
+
+ rpsdb = (rpsdb_t *)iterator->db;
+ REQUIRE(VALID_RPSDB(rpsdb));
+ rpsdb_iter = (rpsdb_rdatasetiter_t *)iterator;
+ REQUIRE(rpsdb_iter->type != dns_rdatatype_none);
+
+ rpsdb_bind_rdataset(rdataset, rpsdb_iter->count, rpsdb_iter->next_rr,
+ rpsdb_iter->type, rpsdb_iter->class,
+ rpsdb_iter->ttl, rpsdb);
+}
+
+static dns_dbmethods_t rpsdb_db_methods = {
+ rpsdb_attach,
+ rpsdb_detach,
+ NULL, /* beginload */
+ NULL, /* endload */
+ NULL, /* serialize */
+ NULL, /* dump */
+ NULL, /* currentversion */
+ NULL, /* newversion */
+ NULL, /* attachversion */
+ NULL, /* closeversion */
+ rpsdb_findnode,
+ rpsdb_finddb,
+ NULL, /* findzonecut*/
+ rpsdb_attachnode,
+ rpsdb_detachnode,
+ NULL, /* expirenode */
+ NULL, /* printnode */
+ NULL, /* createiterator */
+ rpsdb_findrdataset,
+ rpsdb_allrdatasets,
+ NULL, /* addrdataset */
+ NULL, /* subtractrdataset */
+ NULL, /* deleterdataset */
+ rpsdb_issecure,
+ NULL, /* nodecount */
+ NULL, /* ispersistent */
+ NULL, /* overmem */
+ NULL, /* settask */
+ rpsdb_getoriginnode,
+ NULL, /* transfernode */
+ NULL, /* getnsec3parameters */
+ NULL, /* findnsec3node */
+ NULL, /* setsigningtime */
+ NULL, /* getsigningtime */
+ NULL, /* resigned */
+ NULL, /* isdnssec */
+ NULL, /* getrrsetstats */
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ NULL, /* findnodeext */
+ NULL, /* findext */
+ NULL, /* setcachestats */
+ NULL, /* hashsize */
+ NULL, /* nodefullname */
+ NULL, /* getsize */
+ NULL, /* setservestalettl */
+ NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
+ NULL, /* setgluecachestats */
+ NULL /* adjusthashsize */
+};
+
+static dns_rdatasetmethods_t rpsdb_rdataset_methods = {
+ rpsdb_rdataset_disassociate,
+ rpsdb_rdataset_first,
+ rpsdb_rdataset_next,
+ rpsdb_rdataset_current,
+ rpsdb_rdataset_clone,
+ rpsdb_rdataset_count,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static dns_rdatasetitermethods_t rpsdb_rdatasetiter_methods = {
+ rpsdb_rdatasetiter_destroy, rpsdb_rdatasetiter_first,
+ rpsdb_rdatasetiter_next, rpsdb_rdatasetiter_current
+};
+
+#endif /* USE_DNSRPS */
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
new file mode 100644
index 0000000..17ee8bd
--- /dev/null
+++ b/lib/dns/dnssec.c
@@ -0,0 +1,2522 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/serial.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <pk11/site.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/result.h>
+#include <dns/stats.h>
+#include <dns/tsig.h> /* for DNS_TSIG_FUDGE */
+
+#include <dst/result.h>
+
+LIBDNS_EXTERNAL_DATA isc_stats_t *dns_dnssec_stats;
+
+#define is_response(msg) ((msg->flags & DNS_MESSAGEFLAG_QR) != 0)
+
+#define RETERR(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define TYPE_SIGN 0
+#define TYPE_VERIFY 1
+
+static isc_result_t
+digest_callback(void *arg, isc_region_t *data);
+
+static int
+rdata_compare_wrapper(const void *rdata1, const void *rdata2);
+
+static isc_result_t
+rdataset_to_sortedarray(dns_rdataset_t *set, isc_mem_t *mctx,
+ dns_rdata_t **rdata, int *nrdata);
+
+static isc_result_t
+digest_callback(void *arg, isc_region_t *data) {
+ dst_context_t *ctx = arg;
+
+ return (dst_context_adddata(ctx, data));
+}
+
+static void
+inc_stat(isc_statscounter_t counter) {
+ if (dns_dnssec_stats != NULL) {
+ isc_stats_increment(dns_dnssec_stats, counter);
+ }
+}
+
+/*
+ * Make qsort happy.
+ */
+static int
+rdata_compare_wrapper(const void *rdata1, const void *rdata2) {
+ return (dns_rdata_compare((const dns_rdata_t *)rdata1,
+ (const dns_rdata_t *)rdata2));
+}
+
+/*
+ * Sort the rdataset into an array.
+ */
+static isc_result_t
+rdataset_to_sortedarray(dns_rdataset_t *set, isc_mem_t *mctx,
+ dns_rdata_t **rdata, int *nrdata) {
+ isc_result_t ret;
+ int i = 0, n;
+ dns_rdata_t *data;
+ dns_rdataset_t rdataset;
+
+ n = dns_rdataset_count(set);
+
+ data = isc_mem_get(mctx, n * sizeof(dns_rdata_t));
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_clone(set, &rdataset);
+ ret = dns_rdataset_first(&rdataset);
+ if (ret != ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&rdataset);
+ isc_mem_put(mctx, data, n * sizeof(dns_rdata_t));
+ return (ret);
+ }
+
+ /*
+ * Put them in the array.
+ */
+ do {
+ dns_rdata_init(&data[i]);
+ dns_rdataset_current(&rdataset, &data[i++]);
+ } while (dns_rdataset_next(&rdataset) == ISC_R_SUCCESS);
+
+ /*
+ * Sort the array.
+ */
+ qsort(data, n, sizeof(dns_rdata_t), rdata_compare_wrapper);
+ *rdata = data;
+ *nrdata = n;
+ dns_rdataset_disassociate(&rdataset);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata,
+ isc_mem_t *mctx, dst_key_t **key) {
+ isc_buffer_t b;
+ isc_region_t r;
+
+ INSIST(name != NULL);
+ INSIST(rdata != NULL);
+ INSIST(mctx != NULL);
+ INSIST(key != NULL);
+ INSIST(*key == NULL);
+ REQUIRE(rdata->type == dns_rdatatype_key ||
+ rdata->type == dns_rdatatype_dnskey);
+
+ dns_rdata_toregion(rdata, &r);
+ isc_buffer_init(&b, r.base, r.length);
+ isc_buffer_add(&b, r.length);
+ return (dst_key_fromdns(name, rdata->rdclass, &b, mctx, key));
+}
+
+static isc_result_t
+digest_sig(dst_context_t *ctx, bool downcase, dns_rdata_t *sigrdata,
+ dns_rdata_rrsig_t *rrsig) {
+ isc_region_t r;
+ isc_result_t ret;
+ dns_fixedname_t fname;
+
+ dns_rdata_toregion(sigrdata, &r);
+ INSIST(r.length >= 19);
+
+ r.length = 18;
+ ret = dst_context_adddata(ctx, &r);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ if (downcase) {
+ dns_fixedname_init(&fname);
+
+ RUNTIME_CHECK(dns_name_downcase(&rrsig->signer,
+ dns_fixedname_name(&fname),
+ NULL) == ISC_R_SUCCESS);
+ dns_name_toregion(dns_fixedname_name(&fname), &r);
+ } else {
+ dns_name_toregion(&rrsig->signer, &r);
+ }
+
+ return (dst_context_adddata(ctx, &r));
+}
+
+isc_result_t
+dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
+ isc_stdtime_t *inception, isc_stdtime_t *expire,
+ isc_mem_t *mctx, isc_buffer_t *buffer, dns_rdata_t *sigrdata) {
+ dns_rdata_rrsig_t sig;
+ dns_rdata_t tmpsigrdata;
+ dns_rdata_t *rdatas;
+ int nrdatas, i;
+ isc_buffer_t sigbuf, envbuf;
+ isc_region_t r;
+ dst_context_t *ctx = NULL;
+ isc_result_t ret;
+ isc_buffer_t *databuf = NULL;
+ char data[256 + 8];
+ uint32_t flags;
+ unsigned int sigsize;
+ dns_fixedname_t fnewname;
+ dns_fixedname_t fsigner;
+
+ REQUIRE(name != NULL);
+ REQUIRE(dns_name_countlabels(name) <= 255);
+ REQUIRE(set != NULL);
+ REQUIRE(key != NULL);
+ REQUIRE(inception != NULL);
+ REQUIRE(expire != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(sigrdata != NULL);
+
+ if (*inception >= *expire) {
+ return (DNS_R_INVALIDTIME);
+ }
+
+ /*
+ * Is the key allowed to sign data?
+ */
+ flags = dst_key_flags(key);
+ if ((flags & DNS_KEYTYPE_NOAUTH) != 0) {
+ return (DNS_R_KEYUNAUTHORIZED);
+ }
+ if ((flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) {
+ return (DNS_R_KEYUNAUTHORIZED);
+ }
+
+ sig.mctx = mctx;
+ sig.common.rdclass = set->rdclass;
+ sig.common.rdtype = dns_rdatatype_rrsig;
+ ISC_LINK_INIT(&sig.common, link);
+
+ /*
+ * Downcase signer.
+ */
+ dns_name_init(&sig.signer, NULL);
+ dns_fixedname_init(&fsigner);
+ RUNTIME_CHECK(dns_name_downcase(dst_key_name(key),
+ dns_fixedname_name(&fsigner),
+ NULL) == ISC_R_SUCCESS);
+ dns_name_clone(dns_fixedname_name(&fsigner), &sig.signer);
+
+ sig.covered = set->type;
+ sig.algorithm = dst_key_alg(key);
+ sig.labels = dns_name_countlabels(name) - 1;
+ if (dns_name_iswildcard(name)) {
+ sig.labels--;
+ }
+ sig.originalttl = set->ttl;
+ sig.timesigned = *inception;
+ sig.timeexpire = *expire;
+ sig.keyid = dst_key_id(key);
+ ret = dst_key_sigsize(key, &sigsize);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ sig.siglen = sigsize;
+ /*
+ * The actual contents of sig.signature are not important yet, since
+ * they're not used in digest_sig().
+ */
+ sig.signature = isc_mem_get(mctx, sig.siglen);
+
+ isc_buffer_allocate(mctx, &databuf, sigsize + 256 + 18);
+
+ dns_rdata_init(&tmpsigrdata);
+ ret = dns_rdata_fromstruct(&tmpsigrdata, sig.common.rdclass,
+ sig.common.rdtype, &sig, databuf);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_databuf;
+ }
+
+ ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, true, 0,
+ &ctx);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_databuf;
+ }
+
+ /*
+ * Digest the SIG rdata.
+ */
+ ret = digest_sig(ctx, false, &tmpsigrdata, &sig);
+ if (ret != ISC_R_SUCCESS) {
+ goto cleanup_context;
+ }
+
+ dns_fixedname_init(&fnewname);
+ RUNTIME_CHECK(dns_name_downcase(name, dns_fixedname_name(&fnewname),
+ NULL) == ISC_R_SUCCESS);
+ dns_name_toregion(dns_fixedname_name(&fnewname), &r);
+
+ /*
+ * Create an envelope for each rdata: <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;
+
+ isc_stdtime_get(&now);
+ sig.timesigned = now - DNS_TSIG_FUDGE;
+ sig.timeexpire = now + DNS_TSIG_FUDGE;
+
+ sig.keyid = dst_key_id(key);
+
+ dns_name_init(&sig.signer, NULL);
+ dns_name_clone(dst_key_name(key), &sig.signer);
+
+ sig.siglen = 0;
+ sig.signature = NULL;
+
+ isc_buffer_init(&databuf, data, sizeof(data));
+
+ RETERR(dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, true, 0,
+ &ctx));
+
+ /*
+ * Digest the fields of the SIG - we can cheat and use
+ * dns_rdata_fromstruct. Since siglen is 0, the digested data
+ * is identical to dns format.
+ */
+ RETERR(dns_rdata_fromstruct(NULL, dns_rdataclass_any,
+ dns_rdatatype_sig /* SIG(0) */, &sig,
+ &databuf));
+ isc_buffer_usedregion(&databuf, &r);
+ RETERR(dst_context_adddata(ctx, &r));
+
+ /*
+ * If this is a response, digest the query.
+ */
+ if (is_response(msg)) {
+ RETERR(dst_context_adddata(ctx, &msg->query));
+ }
+
+ /*
+ * Digest the header.
+ */
+ isc_buffer_init(&headerbuf, header, sizeof(header));
+ dns_message_renderheader(msg, &headerbuf);
+ isc_buffer_usedregion(&headerbuf, &r);
+ RETERR(dst_context_adddata(ctx, &r));
+
+ /*
+ * Digest the remainder of the message.
+ */
+ isc_buffer_usedregion(msg->buffer, &r);
+ isc_region_consume(&r, DNS_MESSAGE_HEADERLEN);
+ RETERR(dst_context_adddata(ctx, &r));
+
+ RETERR(dst_key_sigsize(key, &sigsize));
+ sig.siglen = sigsize;
+ sig.signature = isc_mem_get(mctx, sig.siglen);
+
+ isc_buffer_init(&sigbuf, sig.signature, sig.siglen);
+ RETERR(dst_context_sign(ctx, &sigbuf));
+ dst_context_destroy(&ctx);
+
+ rdata = NULL;
+ RETERR(dns_message_gettemprdata(msg, &rdata));
+ isc_buffer_allocate(msg->mctx, &dynbuf, 1024);
+ RETERR(dns_rdata_fromstruct(rdata, dns_rdataclass_any,
+ dns_rdatatype_sig /* SIG(0) */, &sig,
+ dynbuf));
+
+ isc_mem_put(mctx, sig.signature, sig.siglen);
+
+ dns_message_takebuffer(msg, &dynbuf);
+
+ datalist = NULL;
+ RETERR(dns_message_gettemprdatalist(msg, &datalist));
+ datalist->rdclass = dns_rdataclass_any;
+ datalist->type = dns_rdatatype_sig; /* SIG(0) */
+ ISC_LIST_APPEND(datalist->rdata, rdata, link);
+ dataset = NULL;
+ RETERR(dns_message_gettemprdataset(msg, &dataset));
+ RUNTIME_CHECK(dns_rdatalist_tordataset(datalist, dataset) ==
+ ISC_R_SUCCESS);
+ msg->sig0 = dataset;
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (dynbuf != NULL) {
+ isc_buffer_free(&dynbuf);
+ }
+ if (sig.signature != NULL) {
+ isc_mem_put(mctx, sig.signature, sig.siglen);
+ }
+ if (ctx != NULL) {
+ dst_context_destroy(&ctx);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_dnssec_verifymessage(isc_buffer_t *source, dns_message_t *msg,
+ dst_key_t *key) {
+ dns_rdata_sig_t sig; /* SIG(0) */
+ unsigned char header[DNS_MESSAGE_HEADERLEN];
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_region_t r, source_r, sig_r, header_r;
+ isc_stdtime_t now;
+ dst_context_t *ctx = NULL;
+ isc_mem_t *mctx;
+ isc_result_t result;
+ uint16_t addcount, addcount_n;
+ bool signeedsfree = false;
+
+ REQUIRE(source != NULL);
+ REQUIRE(msg != NULL);
+ REQUIRE(key != NULL);
+
+ mctx = msg->mctx;
+
+ msg->verify_attempted = 1;
+ msg->verified_sig = 0;
+ msg->sig0status = dns_tsigerror_badsig;
+
+ if (is_response(msg)) {
+ if (msg->query.base == NULL) {
+ return (DNS_R_UNEXPECTEDTSIG);
+ }
+ }
+
+ isc_buffer_usedregion(source, &source_r);
+
+ RETERR(dns_rdataset_first(msg->sig0));
+ dns_rdataset_current(msg->sig0, &rdata);
+
+ RETERR(dns_rdata_tostruct(&rdata, &sig, NULL));
+ signeedsfree = true;
+
+ if (sig.labels != 0) {
+ result = DNS_R_SIGINVALID;
+ goto failure;
+ }
+
+ if (isc_serial_lt(sig.timeexpire, sig.timesigned)) {
+ result = DNS_R_SIGINVALID;
+ msg->sig0status = dns_tsigerror_badtime;
+ goto failure;
+ }
+
+ isc_stdtime_get(&now);
+ if (isc_serial_lt((uint32_t)now, sig.timesigned)) {
+ result = DNS_R_SIGFUTURE;
+ msg->sig0status = dns_tsigerror_badtime;
+ goto failure;
+ } else if (isc_serial_lt(sig.timeexpire, (uint32_t)now)) {
+ result = DNS_R_SIGEXPIRED;
+ msg->sig0status = dns_tsigerror_badtime;
+ goto failure;
+ }
+
+ if (!dns_name_equal(dst_key_name(key), &sig.signer)) {
+ result = DNS_R_SIGINVALID;
+ msg->sig0status = dns_tsigerror_badkey;
+ goto failure;
+ }
+
+ RETERR(dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, false, 0,
+ &ctx));
+
+ /*
+ * Digest the SIG(0) record, except for the signature.
+ */
+ dns_rdata_toregion(&rdata, &r);
+ r.length -= sig.siglen;
+ RETERR(dst_context_adddata(ctx, &r));
+
+ /*
+ * If this is a response, digest the query.
+ */
+ if (is_response(msg)) {
+ RETERR(dst_context_adddata(ctx, &msg->query));
+ }
+
+ /*
+ * Extract the header.
+ */
+ memmove(header, source_r.base, DNS_MESSAGE_HEADERLEN);
+
+ /*
+ * Decrement the additional field counter.
+ */
+ memmove(&addcount, &header[DNS_MESSAGE_HEADERLEN - 2], 2);
+ addcount_n = ntohs(addcount);
+ addcount = htons((uint16_t)(addcount_n - 1));
+ memmove(&header[DNS_MESSAGE_HEADERLEN - 2], &addcount, 2);
+
+ /*
+ * Digest the modified header.
+ */
+ header_r.base = (unsigned char *)header;
+ header_r.length = DNS_MESSAGE_HEADERLEN;
+ RETERR(dst_context_adddata(ctx, &header_r));
+
+ /*
+ * Digest all non-SIG(0) records.
+ */
+ r.base = source_r.base + DNS_MESSAGE_HEADERLEN;
+ r.length = msg->sigstart - DNS_MESSAGE_HEADERLEN;
+ RETERR(dst_context_adddata(ctx, &r));
+
+ sig_r.base = sig.signature;
+ sig_r.length = sig.siglen;
+ result = dst_context_verify(ctx, &sig_r);
+ if (result != ISC_R_SUCCESS) {
+ msg->sig0status = dns_tsigerror_badsig;
+ goto failure;
+ }
+
+ msg->verified_sig = 1;
+ msg->sig0status = dns_rcode_noerror;
+
+ dst_context_destroy(&ctx);
+ dns_rdata_freestruct(&sig);
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (signeedsfree) {
+ dns_rdata_freestruct(&sig);
+ }
+ if (ctx != NULL) {
+ dst_context_destroy(&ctx);
+ }
+
+ return (result);
+}
+
+/*%
+ * Does this key ('rdata') self sign the rrset ('rdataset')?
+ */
+bool
+dns_dnssec_selfsigns(dns_rdata_t *rdata, const dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ bool ignoretime, isc_mem_t *mctx) {
+ INSIST(rdataset->type == dns_rdatatype_key ||
+ rdataset->type == dns_rdatatype_dnskey);
+ if (rdataset->type == dns_rdatatype_key) {
+ INSIST(sigrdataset->type == dns_rdatatype_sig);
+ INSIST(sigrdataset->covers == dns_rdatatype_key);
+ } else {
+ INSIST(sigrdataset->type == dns_rdatatype_rrsig);
+ INSIST(sigrdataset->covers == dns_rdatatype_dnskey);
+ }
+
+ return (dns_dnssec_signs(rdata, name, rdataset, sigrdataset, ignoretime,
+ mctx));
+}
+
+bool
+dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ bool ignoretime, isc_mem_t *mctx) {
+ dst_key_t *dstkey = NULL;
+ dns_keytag_t keytag;
+ dns_rdata_dnskey_t key;
+ dns_rdata_rrsig_t sig;
+ dns_rdata_t sigrdata = DNS_RDATA_INIT;
+ isc_result_t result;
+
+ INSIST(sigrdataset->type == dns_rdatatype_rrsig);
+ if (sigrdataset->covers != rdataset->type) {
+ return (false);
+ }
+
+ result = dns_dnssec_keyfromrdata(name, rdata, mctx, &dstkey);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ result = dns_rdata_tostruct(rdata, &key, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ keytag = dst_key_id(dstkey);
+ for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(sigrdataset))
+ {
+ dns_rdata_reset(&sigrdata);
+ dns_rdataset_current(sigrdataset, &sigrdata);
+ result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (sig.algorithm == key.algorithm && sig.keyid == keytag) {
+ result = dns_dnssec_verify(name, rdataset, dstkey,
+ ignoretime, 0, mctx,
+ &sigrdata, NULL);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_free(&dstkey);
+ return (true);
+ }
+ }
+ }
+ dst_key_free(&dstkey);
+ return (false);
+}
+
+isc_result_t
+dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey,
+ dns_dnsseckey_t **dkp) {
+ isc_result_t result;
+ dns_dnsseckey_t *dk;
+ int major, minor;
+
+ REQUIRE(dkp != NULL && *dkp == NULL);
+ dk = isc_mem_get(mctx, sizeof(dns_dnsseckey_t));
+
+ dk->key = *dstkey;
+ *dstkey = NULL;
+ dk->force_publish = false;
+ dk->force_sign = false;
+ dk->hint_publish = false;
+ dk->hint_sign = false;
+ dk->hint_revoke = false;
+ dk->hint_remove = false;
+ dk->first_sign = false;
+ dk->is_active = false;
+ dk->purge = false;
+ dk->prepublish = 0;
+ dk->source = dns_keysource_unknown;
+ dk->index = 0;
+
+ /* KSK or ZSK? */
+ result = dst_key_getbool(dk->key, DST_BOOL_KSK, &dk->ksk);
+ if (result != ISC_R_SUCCESS) {
+ dk->ksk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) != 0);
+ }
+ result = dst_key_getbool(dk->key, DST_BOOL_ZSK, &dk->zsk);
+ if (result != ISC_R_SUCCESS) {
+ dk->zsk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) == 0);
+ }
+
+ /* Is this an old-style key? */
+ result = dst_key_getprivateformat(dk->key, &major, &minor);
+ INSIST(result == ISC_R_SUCCESS);
+
+ /* Smart signing started with key format 1.3 */
+ dk->legacy = (major == 1 && minor <= 2);
+
+ ISC_LINK_INIT(dk, link);
+ *dkp = dk;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp) {
+ dns_dnsseckey_t *dk;
+
+ REQUIRE(dkp != NULL && *dkp != NULL);
+ dk = *dkp;
+ *dkp = NULL;
+ if (dk->key != NULL) {
+ dst_key_free(&dk->key);
+ }
+ isc_mem_put(mctx, dk, sizeof(dns_dnsseckey_t));
+}
+
+void
+dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) {
+ isc_stdtime_t publish = 0, active = 0, revoke = 0, remove = 0;
+
+ REQUIRE(key != NULL && key->key != NULL);
+
+ key->hint_publish = dst_key_is_published(key->key, now, &publish);
+ key->hint_sign = dst_key_is_signing(key->key, DST_BOOL_ZSK, now,
+ &active);
+ key->hint_revoke = dst_key_is_revoked(key->key, now, &revoke);
+ key->hint_remove = dst_key_is_removed(key->key, now, &remove);
+
+ /*
+ * Activation date is set (maybe in the future), but publication date
+ * isn't. Most likely the user wants to publish now and activate later.
+ * Most likely because this is true for most rollovers, except for:
+ * 1. The unpopular ZSK Double-RRSIG method.
+ * 2. When introducing a new algorithm.
+ * These two cases are rare enough that we will set hint_publish
+ * anyway when hint_sign is set, because BIND 9 natively does not
+ * support the ZSK Double-RRSIG method, and when introducing a new
+ * algorithm, we strive to publish its signatures and DNSKEY records
+ * at the same time.
+ */
+ if (key->hint_sign && publish == 0) {
+ key->hint_publish = true;
+ }
+
+ /*
+ * If activation date is in the future, make note of how far off.
+ */
+ if (key->hint_publish && active > now) {
+ key->prepublish = active - now;
+ }
+
+ /*
+ * Metadata says revoke. If the key is published, we *have to* sign
+ * with it per RFC5011 -- even if it was not active before.
+ *
+ * If it hasn't already been done, we should also revoke it now.
+ */
+ if (key->hint_publish && key->hint_revoke) {
+ uint32_t flags;
+ key->hint_sign = true;
+ flags = dst_key_flags(key->key);
+ if ((flags & DNS_KEYFLAG_REVOKE) == 0) {
+ flags |= DNS_KEYFLAG_REVOKE;
+ dst_key_setflags(key->key, flags);
+ }
+ }
+
+ /*
+ * Metadata says delete, so don't publish this key or sign with it
+ * (note that signatures of a removed key may still be reused).
+ */
+ if (key->hint_remove) {
+ key->hint_publish = false;
+ key->hint_sign = false;
+ }
+}
+
+/*%
+ * Get a list of DNSSEC keys from the key repository.
+ */
+isc_result_t
+dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory,
+ isc_stdtime_t now, isc_mem_t *mctx,
+ dns_dnsseckeylist_t *keylist) {
+ isc_result_t result = ISC_R_SUCCESS;
+ bool dir_open = false;
+ dns_dnsseckeylist_t list;
+ isc_dir_t dir;
+ dns_dnsseckey_t *key = NULL;
+ dst_key_t *dstkey = NULL;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ isc_buffer_t b;
+ unsigned int len, i, alg;
+
+ REQUIRE(keylist != NULL);
+ ISC_LIST_INIT(list);
+ isc_dir_init(&dir);
+
+ isc_buffer_init(&b, namebuf, sizeof(namebuf) - 1);
+ RETERR(dns_name_tofilenametext(origin, false, &b));
+ len = isc_buffer_usedlength(&b);
+ namebuf[len] = '\0';
+
+ if (directory == NULL) {
+ directory = ".";
+ }
+ RETERR(isc_dir_open(&dir, directory));
+ dir_open = true;
+
+ while (isc_dir_read(&dir) == ISC_R_SUCCESS) {
+ if (dir.entry.name[0] != 'K' || dir.entry.length < len + 1 ||
+ dir.entry.name[len + 1] != '+' ||
+ strncasecmp(dir.entry.name + 1, namebuf, len) != 0)
+ {
+ continue;
+ }
+
+ alg = 0;
+ for (i = len + 1 + 1; i < dir.entry.length; i++) {
+ if (!isdigit((unsigned char)dir.entry.name[i])) {
+ break;
+ }
+ alg *= 10;
+ alg += dir.entry.name[i] - '0';
+ }
+
+ /*
+ * Did we not read exactly 3 digits?
+ * Did we overflow?
+ * Did we correctly terminate?
+ */
+ if (i != len + 1 + 1 + 3 || i >= dir.entry.length ||
+ dir.entry.name[i] != '+')
+ {
+ continue;
+ }
+
+ for (i++; i < dir.entry.length; i++) {
+ if (!isdigit((unsigned char)dir.entry.name[i])) {
+ break;
+ }
+ }
+
+ /*
+ * Did we not read exactly 5 more digits?
+ * Did we overflow?
+ * Did we correctly terminate?
+ */
+ if (i != len + 1 + 1 + 3 + 1 + 5 || i >= dir.entry.length ||
+ strcmp(dir.entry.name + i, ".private") != 0)
+ {
+ continue;
+ }
+
+ dstkey = NULL;
+ result = dst_key_fromnamedfile(
+ dir.entry.name, directory,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE,
+ mctx, &dstkey);
+
+ switch (alg) {
+ case DST_ALG_HMACMD5:
+ case DST_ALG_HMACSHA1:
+ case DST_ALG_HMACSHA224:
+ case DST_ALG_HMACSHA256:
+ case DST_ALG_HMACSHA384:
+ case DST_ALG_HMACSHA512:
+ if (result == DST_R_BADKEYTYPE) {
+ continue;
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "dns_dnssec_findmatchingkeys: "
+ "error reading key file %s: %s",
+ dir.entry.name,
+ isc_result_totext(result));
+ continue;
+ }
+
+ RETERR(dns_dnsseckey_create(mctx, &dstkey, &key));
+ key->source = dns_keysource_repository;
+ dns_dnssec_get_hints(key, now);
+
+ if (key->legacy) {
+ dns_dnsseckey_destroy(mctx, &key);
+ } else {
+ ISC_LIST_APPEND(list, key, link);
+ key = NULL;
+ }
+ }
+
+ if (!ISC_LIST_EMPTY(list)) {
+ result = ISC_R_SUCCESS;
+ ISC_LIST_APPENDLIST(*keylist, list, link);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+
+failure:
+ if (dir_open) {
+ isc_dir_close(&dir);
+ }
+ INSIST(key == NULL);
+ while ((key = ISC_LIST_HEAD(list)) != NULL) {
+ ISC_LIST_UNLINK(list, key, link);
+ INSIST(key->key != NULL);
+ dst_key_free(&key->key);
+ dns_dnsseckey_destroy(mctx, &key);
+ }
+ if (dstkey != NULL) {
+ dst_key_free(&dstkey);
+ }
+ return (result);
+}
+
+/*%
+ * Add 'newkey' to 'keylist' if it's not already there.
+ *
+ * If 'savekeys' is true, then we need to preserve all
+ * the keys in the keyset, regardless of whether they have
+ * metadata indicating they should be deactivated or removed.
+ */
+static isc_result_t
+addkey(dns_dnsseckeylist_t *keylist, dst_key_t **newkey, bool savekeys,
+ isc_mem_t *mctx) {
+ dns_dnsseckey_t *key;
+ isc_result_t result;
+
+ /* Skip duplicates */
+ for (key = ISC_LIST_HEAD(*keylist); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ if (dst_key_id(key->key) == dst_key_id(*newkey) &&
+ dst_key_alg(key->key) == dst_key_alg(*newkey) &&
+ dns_name_equal(dst_key_name(key->key),
+ dst_key_name(*newkey)))
+ {
+ break;
+ }
+ }
+
+ if (key != NULL) {
+ /*
+ * Found a match. If the old key was only public and the
+ * new key is private, replace the old one; otherwise
+ * leave it. But either way, mark the key as having
+ * been found in the zone.
+ */
+ if (dst_key_isprivate(key->key)) {
+ dst_key_free(newkey);
+ } else if (dst_key_isprivate(*newkey)) {
+ dst_key_free(&key->key);
+ key->key = *newkey;
+ }
+
+ key->source = dns_keysource_zoneapex;
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_dnsseckey_create(mctx, newkey, &key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (key->legacy || savekeys) {
+ key->force_publish = true;
+ key->force_sign = dst_key_isprivate(key->key);
+ }
+ key->source = dns_keysource_zoneapex;
+ ISC_LIST_APPEND(*keylist, key, link);
+ *newkey = NULL;
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Mark all keys which signed the DNSKEY/SOA RRsets as "active",
+ * for future reference.
+ */
+static isc_result_t
+mark_active_keys(dns_dnsseckeylist_t *keylist, dns_rdataset_t *rrsigs) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t sigs;
+ dns_dnsseckey_t *key;
+
+ REQUIRE(rrsigs != NULL && dns_rdataset_isassociated(rrsigs));
+
+ dns_rdataset_init(&sigs);
+ dns_rdataset_clone(rrsigs, &sigs);
+ for (key = ISC_LIST_HEAD(*keylist); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ uint16_t keyid, sigid;
+ dns_secalg_t keyalg, sigalg;
+ keyid = dst_key_id(key->key);
+ keyalg = dst_key_alg(key->key);
+
+ for (result = dns_rdataset_first(&sigs);
+ result == ISC_R_SUCCESS; result = dns_rdataset_next(&sigs))
+ {
+ dns_rdata_rrsig_t sig;
+
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&sigs, &rdata);
+ result = dns_rdata_tostruct(&rdata, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ sigalg = sig.algorithm;
+ sigid = sig.keyid;
+ if (keyid == sigid && keyalg == sigalg) {
+ key->is_active = true;
+ break;
+ }
+ }
+ }
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ if (dns_rdataset_isassociated(&sigs)) {
+ dns_rdataset_disassociate(&sigs);
+ }
+ return (result);
+}
+
+/*%
+ * Add the contents of a DNSKEY rdataset 'keyset' to 'keylist'.
+ */
+isc_result_t
+dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory,
+ isc_mem_t *mctx, dns_rdataset_t *keyset,
+ dns_rdataset_t *keysigs, dns_rdataset_t *soasigs,
+ bool savekeys, bool publickey,
+ dns_dnsseckeylist_t *keylist) {
+ dns_rdataset_t keys;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dst_key_t *dnskey = NULL, *pubkey = NULL, *privkey = NULL;
+ isc_result_t result;
+
+ REQUIRE(keyset != NULL && dns_rdataset_isassociated(keyset));
+
+ dns_rdataset_init(&keys);
+
+ dns_rdataset_clone(keyset, &keys);
+ for (result = dns_rdataset_first(&keys); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&keys))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&keys, &rdata);
+
+ REQUIRE(rdata.type == dns_rdatatype_key ||
+ rdata.type == dns_rdatatype_dnskey);
+ REQUIRE(rdata.length > 3);
+
+ /* Skip unsupported algorithms */
+ if (!dst_algorithm_supported(rdata.data[3])) {
+ goto skip;
+ }
+
+ RETERR(dns_dnssec_keyfromrdata(origin, &rdata, mctx, &dnskey));
+ dst_key_setttl(dnskey, keys.ttl);
+
+ if (!is_zone_key(dnskey) ||
+ (dst_key_flags(dnskey) & DNS_KEYTYPE_NOAUTH) != 0)
+ {
+ goto skip;
+ }
+
+ /* Corrupted .key file? */
+ if (!dns_name_equal(origin, dst_key_name(dnskey))) {
+ goto skip;
+ }
+
+ if (publickey) {
+ RETERR(addkey(keylist, &dnskey, savekeys, mctx));
+ goto skip;
+ }
+
+ /* Try to read the public key. */
+ result = dst_key_fromfile(
+ dst_key_name(dnskey), dst_key_id(dnskey),
+ dst_key_alg(dnskey), (DST_TYPE_PUBLIC | DST_TYPE_STATE),
+ directory, mctx, &pubkey);
+ if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) {
+ result = ISC_R_SUCCESS;
+ }
+ RETERR(result);
+
+ /* Now read the private key. */
+ result = dst_key_fromfile(
+ dst_key_name(dnskey), dst_key_id(dnskey),
+ dst_key_alg(dnskey),
+ (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE),
+ directory, mctx, &privkey);
+
+ /*
+ * If the key was revoked and the private file
+ * doesn't exist, maybe it was revoked internally
+ * by named. Try loading the unrevoked version.
+ */
+ if (result == ISC_R_FILENOTFOUND) {
+ uint32_t flags;
+ flags = dst_key_flags(dnskey);
+ if ((flags & DNS_KEYFLAG_REVOKE) != 0) {
+ dst_key_setflags(dnskey,
+ flags & ~DNS_KEYFLAG_REVOKE);
+ result = dst_key_fromfile(
+ dst_key_name(dnskey),
+ dst_key_id(dnskey), dst_key_alg(dnskey),
+ (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE |
+ DST_TYPE_STATE),
+ directory, mctx, &privkey);
+ if (result == ISC_R_SUCCESS &&
+ dst_key_pubcompare(dnskey, privkey, false))
+ {
+ dst_key_setflags(privkey, flags);
+ }
+ dst_key_setflags(dnskey, flags);
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ char filename[DNS_NAME_FORMATSIZE +
+ DNS_SECALG_FORMATSIZE +
+ sizeof("key file for //65535")];
+ isc_result_t result2;
+ isc_buffer_t buf;
+
+ isc_buffer_init(&buf, filename, NAME_MAX);
+ result2 = dst_key_getfilename(
+ dst_key_name(dnskey), dst_key_id(dnskey),
+ dst_key_alg(dnskey),
+ (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE |
+ DST_TYPE_STATE),
+ directory, mctx, &buf);
+ if (result2 != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char algbuf[DNS_SECALG_FORMATSIZE];
+
+ dns_name_format(dst_key_name(dnskey), namebuf,
+ sizeof(namebuf));
+ dns_secalg_format(dst_key_alg(dnskey), algbuf,
+ sizeof(algbuf));
+ snprintf(filename, sizeof(filename) - 1,
+ "key file for %s/%s/%d", namebuf,
+ algbuf, dst_key_id(dnskey));
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "dns_dnssec_keylistfromrdataset: error "
+ "reading %s: %s",
+ filename, isc_result_totext(result));
+ }
+
+ if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) {
+ if (pubkey != NULL) {
+ RETERR(addkey(keylist, &pubkey, savekeys,
+ mctx));
+ } else {
+ RETERR(addkey(keylist, &dnskey, savekeys,
+ mctx));
+ }
+ goto skip;
+ }
+ RETERR(result);
+
+ /* This should never happen. */
+ if ((dst_key_flags(privkey) & DNS_KEYTYPE_NOAUTH) != 0) {
+ goto skip;
+ }
+
+ /*
+ * Whatever the key's default TTL may have
+ * been, the rdataset TTL takes priority.
+ */
+ dst_key_setttl(privkey, dst_key_getttl(dnskey));
+
+ RETERR(addkey(keylist, &privkey, savekeys, mctx));
+ skip:
+ if (dnskey != NULL) {
+ dst_key_free(&dnskey);
+ }
+ if (pubkey != NULL) {
+ dst_key_free(&pubkey);
+ }
+ if (privkey != NULL) {
+ dst_key_free(&privkey);
+ }
+ }
+
+ if (result != ISC_R_NOMORE) {
+ RETERR(result);
+ }
+
+ if (keysigs != NULL && dns_rdataset_isassociated(keysigs)) {
+ RETERR(mark_active_keys(keylist, keysigs));
+ }
+
+ if (soasigs != NULL && dns_rdataset_isassociated(soasigs)) {
+ RETERR(mark_active_keys(keylist, soasigs));
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (dns_rdataset_isassociated(&keys)) {
+ dns_rdataset_disassociate(&keys);
+ }
+ if (dnskey != NULL) {
+ dst_key_free(&dnskey);
+ }
+ if (pubkey != NULL) {
+ dst_key_free(&pubkey);
+ }
+ if (privkey != NULL) {
+ dst_key_free(&privkey);
+ }
+ return (result);
+}
+
+static isc_result_t
+make_dnskey(dst_key_t *key, unsigned char *buf, int bufsize,
+ dns_rdata_t *target) {
+ isc_result_t result;
+ isc_buffer_t b;
+ isc_region_t r;
+
+ isc_buffer_init(&b, buf, bufsize);
+ result = dst_key_todns(key, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdata_reset(target);
+ isc_buffer_usedregion(&b, &r);
+ dns_rdata_fromregion(target, dst_key_class(key), dns_rdatatype_dnskey,
+ &r);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+addrdata(dns_rdata_t *rdata, dns_diff_t *diff, const dns_name_t *origin,
+ dns_ttl_t ttl, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_difftuple_t *tuple = NULL;
+
+ RETERR(dns_difftuple_create(mctx, DNS_DIFFOP_ADD, origin, ttl, rdata,
+ &tuple));
+ dns_diff_appendminimal(diff, &tuple);
+
+failure:
+ return (result);
+}
+
+static isc_result_t
+delrdata(dns_rdata_t *rdata, dns_diff_t *diff, const dns_name_t *origin,
+ dns_ttl_t ttl, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_difftuple_t *tuple = NULL;
+
+ RETERR(dns_difftuple_create(mctx, DNS_DIFFOP_DEL, origin, ttl, rdata,
+ &tuple));
+ dns_diff_appendminimal(diff, &tuple);
+
+failure:
+ return (result);
+}
+
+static isc_result_t
+publish_key(dns_diff_t *diff, dns_dnsseckey_t *key, const dns_name_t *origin,
+ dns_ttl_t ttl, isc_mem_t *mctx, void (*report)(const char *, ...)) {
+ isc_result_t result;
+ unsigned char buf[DST_KEY_MAXSIZE];
+ char keystr[DST_KEY_FORMATSIZE];
+ dns_rdata_t dnskey = DNS_RDATA_INIT;
+
+ dns_rdata_reset(&dnskey);
+ RETERR(make_dnskey(key->key, buf, sizeof(buf), &dnskey));
+ dst_key_format(key->key, keystr, sizeof(keystr));
+
+ report("Fetching %s (%s) from key %s.", keystr,
+ key->ksk ? (key->zsk ? "CSK" : "KSK") : "ZSK",
+ key->source == dns_keysource_user ? "file" : "repository");
+
+ if (key->prepublish && ttl > key->prepublish) {
+ isc_stdtime_t now;
+
+ report("Key %s: Delaying activation to match the DNSKEY TTL.",
+ keystr, ttl);
+
+ isc_stdtime_get(&now);
+ dst_key_settime(key->key, DST_TIME_ACTIVATE, now + ttl);
+ }
+
+ /* publish key */
+ result = addrdata(&dnskey, diff, origin, ttl, mctx);
+
+failure:
+ return (result);
+}
+
+static isc_result_t
+remove_key(dns_diff_t *diff, dns_dnsseckey_t *key, const dns_name_t *origin,
+ dns_ttl_t ttl, isc_mem_t *mctx, const char *reason,
+ void (*report)(const char *, ...)) {
+ isc_result_t result;
+ unsigned char buf[DST_KEY_MAXSIZE];
+ dns_rdata_t dnskey = DNS_RDATA_INIT;
+ char alg[80];
+
+ dns_secalg_format(dst_key_alg(key->key), alg, sizeof(alg));
+ report("Removing %s key %d/%s from DNSKEY RRset.", reason,
+ dst_key_id(key->key), alg);
+
+ RETERR(make_dnskey(key->key, buf, sizeof(buf), &dnskey));
+ result = delrdata(&dnskey, diff, origin, ttl, mctx);
+
+failure:
+ return (result);
+}
+
+static bool
+exists(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ isc_result_t result;
+ dns_rdataset_t trdataset;
+
+ dns_rdataset_init(&trdataset);
+ dns_rdataset_clone(rdataset, &trdataset);
+ for (result = dns_rdataset_first(&trdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&trdataset))
+ {
+ dns_rdata_t current = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&trdataset, &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_result_t result;
+ dns_dnsseckey_t *key, *key1, *key2, *next;
+ bool found_ttl = false;
+ dns_ttl_t ttl = hint_ttl;
+
+ /*
+ * First, look through the existing key list to find keys
+ * supplied from the command line which are not in the zone.
+ * Update the zone to include them.
+ *
+ * Also, if there are keys published in the zone already,
+ * use their TTL for all subsequent published keys.
+ */
+ for (key = ISC_LIST_HEAD(*keys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ if (key->source == dns_keysource_user &&
+ (key->hint_publish || key->force_publish))
+ {
+ RETERR(publish_key(diff, key, origin, ttl, mctx,
+ report));
+ }
+ if (key->source == dns_keysource_zoneapex) {
+ ttl = dst_key_getttl(key->key);
+ found_ttl = true;
+ }
+ }
+
+ /*
+ * If there were no existing keys, use the smallest nonzero
+ * TTL of the keys found in the repository.
+ */
+ if (!found_ttl && !ISC_LIST_EMPTY(*newkeys)) {
+ dns_ttl_t shortest = 0;
+
+ for (key = ISC_LIST_HEAD(*newkeys); key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ dns_ttl_t thisttl = dst_key_getttl(key->key);
+ if (thisttl != 0 &&
+ (shortest == 0 || thisttl < shortest))
+ {
+ shortest = thisttl;
+ }
+ }
+
+ if (shortest != 0) {
+ ttl = shortest;
+ }
+ }
+
+ /*
+ * Second, scan the list of newly found keys looking for matches
+ * with known keys, and update accordingly.
+ */
+ for (key1 = ISC_LIST_HEAD(*newkeys); key1 != NULL; key1 = next) {
+ bool key_revoked = false;
+ char keystr1[DST_KEY_FORMATSIZE];
+ char keystr2[DST_KEY_FORMATSIZE];
+
+ next = ISC_LIST_NEXT(key1, link);
+
+ for (key2 = ISC_LIST_HEAD(*keys); key2 != NULL;
+ key2 = ISC_LIST_NEXT(key2, link))
+ {
+ int f1 = dst_key_flags(key1->key);
+ int f2 = dst_key_flags(key2->key);
+ int nr1 = f1 & ~DNS_KEYFLAG_REVOKE;
+ int nr2 = f2 & ~DNS_KEYFLAG_REVOKE;
+ if (nr1 == nr2 &&
+ dst_key_alg(key1->key) == dst_key_alg(key2->key) &&
+ dst_key_pubcompare(key1->key, key2->key, true))
+ {
+ int r1, r2;
+ r1 = dst_key_flags(key1->key) &
+ DNS_KEYFLAG_REVOKE;
+ r2 = dst_key_flags(key2->key) &
+ DNS_KEYFLAG_REVOKE;
+ key_revoked = (r1 != r2);
+ break;
+ }
+ }
+
+ /* Printable version of key1 (the newly acquired key) */
+ dst_key_format(key1->key, keystr1, sizeof(keystr1));
+
+ /* No match found in keys; add the new key. */
+ if (key2 == NULL) {
+ ISC_LIST_UNLINK(*newkeys, key1, link);
+ ISC_LIST_APPEND(*keys, key1, link);
+
+ if (key1->source != dns_keysource_zoneapex &&
+ (key1->hint_publish || key1->force_publish))
+ {
+ RETERR(publish_key(diff, key1, origin, ttl,
+ mctx, report));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now published",
+ keystr1,
+ key1->ksk ? (key1->zsk ? "CSK" : "KSK")
+ : "ZSK");
+ if (key1->hint_sign || key1->force_sign) {
+ key1->first_sign = true;
+ isc_log_write(
+ dns_lctx,
+ DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now "
+ "active",
+ keystr1,
+ key1->ksk ? (key1->zsk ? "CSK"
+ : "KSK")
+ : "ZSK");
+ }
+ }
+
+ continue;
+ }
+
+ /* Printable version of key2 (the old key, if any) */
+ dst_key_format(key2->key, keystr2, sizeof(keystr2));
+
+ /* Copy key metadata. */
+ dst_key_copy_metadata(key2->key, key1->key);
+
+ /* Match found: remove or update it as needed */
+ if (key1->hint_remove) {
+ RETERR(remove_key(diff, key2, origin, ttl, mctx,
+ "expired", report));
+ ISC_LIST_UNLINK(*keys, key2, link);
+
+ if (removed != NULL) {
+ ISC_LIST_APPEND(*removed, key2, link);
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now deleted",
+ keystr2,
+ key2->ksk ? (key2->zsk ? "CSK" : "KSK")
+ : "ZSK");
+ } else {
+ dns_dnsseckey_destroy(mctx, &key2);
+ }
+ } else if (key_revoked &&
+ (dst_key_flags(key1->key) & DNS_KEYFLAG_REVOKE) != 0)
+ {
+ /*
+ * A previously valid key has been revoked.
+ * We need to remove the old version and pull
+ * in the new one.
+ */
+ RETERR(remove_key(diff, key2, origin, ttl, mctx,
+ "revoked", report));
+ ISC_LIST_UNLINK(*keys, key2, link);
+ if (removed != NULL) {
+ ISC_LIST_APPEND(*removed, key2, link);
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now revoked; "
+ "new ID is %05d",
+ keystr2,
+ key2->ksk ? (key2->zsk ? "CSK" : "KSK")
+ : "ZSK",
+ dst_key_id(key1->key));
+ } else {
+ dns_dnsseckey_destroy(mctx, &key2);
+ }
+
+ RETERR(publish_key(diff, key1, origin, ttl, mctx,
+ report));
+ ISC_LIST_UNLINK(*newkeys, key1, link);
+ ISC_LIST_APPEND(*keys, key1, link);
+
+ /*
+ * XXX: The revoke flag is only defined for trust
+ * anchors. Setting the flag on a non-KSK is legal,
+ * but not defined in any RFC. It seems reasonable
+ * to treat it the same as a KSK: keep it in the
+ * zone, sign the DNSKEY set with it, but not
+ * sign other records with it.
+ */
+ key1->ksk = true;
+ continue;
+ } else {
+ if (!key2->is_active &&
+ (key1->hint_sign || key1->force_sign))
+ {
+ key2->first_sign = true;
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now active", keystr1,
+ key1->ksk ? (key1->zsk ? "CSK" : "KSK")
+ : "ZSK");
+ } else if (key2->is_active && !key1->hint_sign &&
+ !key1->force_sign)
+ {
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "DNSKEY %s (%s) is now inactive",
+ keystr1,
+ key1->ksk ? (key1->zsk ? "CSK" : "KSK")
+ : "ZSK");
+ }
+
+ key2->hint_sign = key1->hint_sign;
+ key2->hint_publish = key1->hint_publish;
+ }
+ }
+
+ /* Free any leftover keys in newkeys */
+ while (!ISC_LIST_EMPTY(*newkeys)) {
+ key1 = ISC_LIST_HEAD(*newkeys);
+ ISC_LIST_UNLINK(*newkeys, key1, link);
+ dns_dnsseckey_destroy(mctx, &key1);
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ return (result);
+}
+
+isc_result_t
+dns_dnssec_matchdskey(dns_name_t *name, dns_rdata_t *dsrdata,
+ dns_rdataset_t *keyset, dns_rdata_t *keyrdata) {
+ isc_result_t result;
+ unsigned char buf[DNS_DS_BUFFERSIZE];
+ dns_keytag_t keytag;
+ dns_rdata_dnskey_t key;
+ dns_rdata_ds_t ds;
+ isc_region_t r;
+
+ result = dns_rdata_tostruct(dsrdata, &ds, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ for (result = dns_rdataset_first(keyset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(keyset))
+ {
+ dns_rdata_t newdsrdata = DNS_RDATA_INIT;
+
+ dns_rdata_reset(keyrdata);
+ dns_rdataset_current(keyset, keyrdata);
+
+ result = dns_rdata_tostruct(keyrdata, &key, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ dns_rdata_toregion(keyrdata, &r);
+ keytag = dst_region_computeid(&r);
+
+ if (ds.key_tag != keytag || ds.algorithm != key.algorithm) {
+ continue;
+ }
+
+ result = dns_ds_buildrdata(name, keyrdata, ds.digest_type, buf,
+ &newdsrdata);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ if (dns_rdata_compare(dsrdata, &newdsrdata) == 0) {
+ break;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
diff --git a/lib/dns/dnstap.c b/lib/dns/dnstap.c
new file mode 100644
index 0000000..97f0709
--- /dev/null
+++ b/lib/dns/dnstap.c
@@ -0,0 +1,1386 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (c) 2013-2014, Farsight Security, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*! \file */
+
+#ifndef HAVE_DNSTAP
+#error DNSTAP not configured.
+#endif /* HAVE_DNSTAP */
+
+#include <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/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/result.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;
+
+ISC_THREAD_LOCAL dt__ioq_t dt_ioq = { 0 };
+
+static atomic_uint_fast32_t global_generation;
+
+isc_result_t
+dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path,
+ struct fstrm_iothr_options **foptp, isc_task_t *reopen_task,
+ dns_dtenv_t **envp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ fstrm_res res;
+ struct fstrm_unix_writer_options *fuwopt = NULL;
+ struct fstrm_file_options *ffwopt = NULL;
+ struct fstrm_writer_options *fwopt = NULL;
+ struct fstrm_writer *fw = NULL;
+ dns_dtenv_t *env = NULL;
+
+ REQUIRE(path != NULL);
+ REQUIRE(envp != NULL && *envp == NULL);
+ REQUIRE(foptp != NULL && *foptp != NULL);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP,
+ ISC_LOG_INFO, "opening dnstap destination '%s'", path);
+
+ atomic_fetch_add_release(&global_generation, 1);
+
+ env = isc_mem_get(mctx, sizeof(dns_dtenv_t));
+
+ memset(env, 0, sizeof(dns_dtenv_t));
+ isc_mem_attach(mctx, &env->mctx);
+ env->reopen_task = reopen_task;
+ isc_mutex_init(&env->reopen_lock);
+ env->reopen_queued = false;
+ env->path = isc_mem_strdup(env->mctx, path);
+ isc_refcount_init(&env->refcount, 1);
+ CHECK(isc_stats_create(env->mctx, &env->stats, dns_dnstapcounter_max));
+
+ fwopt = fstrm_writer_options_init();
+ if (fwopt == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ res = fstrm_writer_options_add_content_type(
+ fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1);
+ if (res != fstrm_res_success) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ if (mode == dns_dtmode_file) {
+ ffwopt = fstrm_file_options_init();
+ if (ffwopt != NULL) {
+ fstrm_file_options_set_file_path(ffwopt, env->path);
+ fw = fstrm_file_writer_init(ffwopt, fwopt);
+ }
+ } else if (mode == dns_dtmode_unix) {
+ fuwopt = fstrm_unix_writer_options_init();
+ if (fuwopt != NULL) {
+ fstrm_unix_writer_options_set_socket_path(fuwopt,
+ env->path);
+ fw = fstrm_unix_writer_init(fuwopt, fwopt);
+ }
+ } else {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ if (fw == NULL) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ env->iothr = fstrm_iothr_init(*foptp, &fw);
+ if (env->iothr == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP,
+ DNS_LOGMODULE_DNSTAP, ISC_LOG_WARNING,
+ "unable to initialize dnstap I/O thread");
+ fstrm_writer_destroy(&fw);
+ CHECK(ISC_R_FAILURE);
+ }
+ env->mode = mode;
+ env->max_size = 0;
+ env->rolls = ISC_LOG_ROLLINFINITE;
+ env->fopt = *foptp;
+ *foptp = NULL;
+
+ env->magic = DTENV_MAGIC;
+ *envp = env;
+
+cleanup:
+ if (ffwopt != NULL) {
+ fstrm_file_options_destroy(&ffwopt);
+ }
+
+ if (fuwopt != NULL) {
+ fstrm_unix_writer_options_destroy(&fuwopt);
+ }
+
+ if (fwopt != NULL) {
+ fstrm_writer_options_destroy(&fwopt);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_mutex_destroy(&env->reopen_lock);
+ isc_mem_free(env->mctx, env->path);
+ if (env->stats != NULL) {
+ isc_stats_detach(&env->stats);
+ }
+ isc_mem_putanddetach(&env->mctx, env, sizeof(dns_dtenv_t));
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_dt_setupfile(dns_dtenv_t *env, uint64_t max_size, int rolls,
+ isc_log_rollsuffix_t suffix) {
+ REQUIRE(VALID_DTENV(env));
+
+ /*
+ * If we're using unix domain socket mode, then any
+ * change from the default values is invalid.
+ */
+ if (env->mode == dns_dtmode_unix) {
+ if (max_size == 0 && rolls == ISC_LOG_ROLLINFINITE &&
+ suffix == isc_log_rollsuffix_increment)
+ {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_INVALIDFILE);
+ }
+ }
+
+ env->max_size = max_size;
+ env->rolls = rolls;
+ env->suffix = suffix;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dt_reopen(dns_dtenv_t *env, int roll) {
+ isc_result_t result = ISC_R_SUCCESS;
+ fstrm_res res;
+ isc_logfile_t file;
+ struct fstrm_unix_writer_options *fuwopt = NULL;
+ struct fstrm_file_options *ffwopt = NULL;
+ struct fstrm_writer_options *fwopt = NULL;
+ struct fstrm_writer *fw = NULL;
+
+ REQUIRE(VALID_DTENV(env));
+
+ /*
+ * Run in task-exclusive mode.
+ */
+ result = isc_task_beginexclusive(env->reopen_task);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * Check that we can create a new fw object.
+ */
+ fwopt = fstrm_writer_options_init();
+ if (fwopt == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ res = fstrm_writer_options_add_content_type(
+ fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1);
+ if (res != fstrm_res_success) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ if (env->mode == dns_dtmode_file) {
+ ffwopt = fstrm_file_options_init();
+ if (ffwopt != NULL) {
+ fstrm_file_options_set_file_path(ffwopt, env->path);
+ fw = fstrm_file_writer_init(ffwopt, fwopt);
+ }
+ } else if (env->mode == dns_dtmode_unix) {
+ fuwopt = fstrm_unix_writer_options_init();
+ if (fuwopt != NULL) {
+ fstrm_unix_writer_options_set_socket_path(fuwopt,
+ env->path);
+ fw = fstrm_unix_writer_init(fuwopt, fwopt);
+ }
+ } else {
+ CHECK(ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (fw == NULL) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ /*
+ * We are committed here.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP,
+ ISC_LOG_INFO, "%s dnstap destination '%s'",
+ (roll < 0) ? "reopening" : "rolling", env->path);
+
+ atomic_fetch_add_release(&global_generation, 1);
+
+ if (env->iothr != NULL) {
+ fstrm_iothr_destroy(&env->iothr);
+ }
+
+ if (roll == 0) {
+ roll = env->rolls;
+ }
+
+ if (env->mode == dns_dtmode_file && roll != 0) {
+ /*
+ * Create a temporary isc_logfile_t structure so we can
+ * take advantage of the logfile rolling facility.
+ */
+ char *filename = isc_mem_strdup(env->mctx, env->path);
+ file.name = filename;
+ file.stream = NULL;
+ file.versions = roll;
+ file.maximum_size = 0;
+ file.maximum_reached = false;
+ file.suffix = env->suffix;
+ result = isc_logfile_roll(&file);
+ isc_mem_free(env->mctx, filename);
+ CHECK(result);
+ }
+
+ env->iothr = fstrm_iothr_init(env->fopt, &fw);
+ if (env->iothr == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP,
+ DNS_LOGMODULE_DNSTAP, ISC_LOG_WARNING,
+ "unable to initialize dnstap I/O thread");
+ CHECK(ISC_R_FAILURE);
+ }
+
+cleanup:
+ if (fw != NULL) {
+ fstrm_writer_destroy(&fw);
+ }
+
+ if (fuwopt != NULL) {
+ fstrm_unix_writer_options_destroy(&fuwopt);
+ }
+
+ if (ffwopt != NULL) {
+ fstrm_file_options_destroy(&ffwopt);
+ }
+
+ if (fwopt != NULL) {
+ fstrm_writer_options_destroy(&fwopt);
+ }
+
+ isc_task_endexclusive(env->reopen_task);
+
+ return (result);
+}
+
+static isc_result_t
+toregion(dns_dtenv_t *env, isc_region_t *r, const char *str) {
+ unsigned char *p = NULL;
+
+ REQUIRE(r != NULL);
+
+ if (str != NULL) {
+ p = (unsigned char *)isc_mem_strdup(env->mctx, str);
+ }
+
+ if (r->base != NULL) {
+ isc_mem_free(env->mctx, r->base);
+ r->length = 0;
+ }
+
+ if (p != NULL) {
+ r->base = p;
+ r->length = strlen((char *)p);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_dt_setidentity(dns_dtenv_t *env, const char *identity) {
+ REQUIRE(VALID_DTENV(env));
+
+ return (toregion(env, &env->identity, identity));
+}
+
+isc_result_t
+dns_dt_setversion(dns_dtenv_t *env, const char *version) {
+ REQUIRE(VALID_DTENV(env));
+
+ return (toregion(env, &env->version, version));
+}
+
+static void
+set_dt_ioq(unsigned int generation, struct fstrm_iothr_queue *ioq) {
+ dt_ioq.generation = generation;
+ dt_ioq.ioq = ioq;
+}
+
+static struct fstrm_iothr_queue *
+dt_queue(dns_dtenv_t *env) {
+ REQUIRE(VALID_DTENV(env));
+
+ unsigned int generation;
+
+ if (env->iothr == NULL) {
+ return (NULL);
+ }
+
+ generation = atomic_load_acquire(&global_generation);
+ if (dt_ioq.ioq != NULL && dt_ioq.generation != generation) {
+ set_dt_ioq(0, NULL);
+ }
+ if (dt_ioq.ioq == NULL) {
+ struct fstrm_iothr_queue *ioq =
+ fstrm_iothr_get_input_queue(env->iothr);
+ set_dt_ioq(generation, ioq);
+ }
+
+ return (dt_ioq.ioq);
+}
+
+void
+dns_dt_attach(dns_dtenv_t *source, dns_dtenv_t **destp) {
+ REQUIRE(VALID_DTENV(source));
+ REQUIRE(destp != NULL && *destp == NULL);
+
+ isc_refcount_increment(&source->refcount);
+ *destp = source;
+}
+
+isc_result_t
+dns_dt_getstats(dns_dtenv_t *env, isc_stats_t **statsp) {
+ REQUIRE(VALID_DTENV(env));
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ if (env->stats == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ isc_stats_attach(env->stats, statsp);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+destroy(dns_dtenv_t *env) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP,
+ ISC_LOG_INFO, "closing dnstap");
+ env->magic = 0;
+
+ atomic_fetch_add(&global_generation, 1);
+
+ if (env->iothr != NULL) {
+ fstrm_iothr_destroy(&env->iothr);
+ }
+ if (env->fopt != NULL) {
+ fstrm_iothr_options_destroy(&env->fopt);
+ }
+
+ if (env->identity.base != NULL) {
+ isc_mem_free(env->mctx, env->identity.base);
+ env->identity.length = 0;
+ }
+ if (env->version.base != NULL) {
+ isc_mem_free(env->mctx, env->version.base);
+ env->version.length = 0;
+ }
+ if (env->path != NULL) {
+ isc_mem_free(env->mctx, env->path);
+ }
+ if (env->stats != NULL) {
+ isc_stats_detach(&env->stats);
+ }
+
+ isc_mem_putanddetach(&env->mctx, env, sizeof(*env));
+}
+
+void
+dns_dt_detach(dns_dtenv_t **envp) {
+ REQUIRE(envp != NULL && VALID_DTENV(*envp));
+ dns_dtenv_t *env = *envp;
+ *envp = NULL;
+
+ if (isc_refcount_decrement(&env->refcount) == 1) {
+ isc_refcount_destroy(&env->refcount);
+ destroy(env);
+ }
+}
+
+static isc_result_t
+pack_dt(const Dnstap__Dnstap *d, void **buf, size_t *sz) {
+ ProtobufCBufferSimple sbuf;
+
+ REQUIRE(d != NULL);
+ REQUIRE(sz != NULL);
+
+ memset(&sbuf, 0, sizeof(sbuf));
+ sbuf.base.append = protobuf_c_buffer_simple_append;
+ sbuf.len = 0;
+ sbuf.alloced = DNSTAP_INITIAL_BUF_SIZE;
+
+ /* Need to use malloc() here because protobuf uses free() */
+ sbuf.data = malloc(sbuf.alloced);
+ if (sbuf.data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ sbuf.must_free_data = 1;
+
+ *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *)&sbuf);
+ if (sbuf.data == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ *buf = sbuf.data;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+send_dt(dns_dtenv_t *env, void *buf, size_t len) {
+ struct fstrm_iothr_queue *ioq;
+ fstrm_res res;
+
+ REQUIRE(env != NULL);
+
+ if (buf == NULL) {
+ return;
+ }
+
+ ioq = dt_queue(env);
+ if (ioq == NULL) {
+ free(buf);
+ return;
+ }
+
+ res = fstrm_iothr_submit(env->iothr, ioq, buf, len, fstrm_free_wrapper,
+ NULL);
+ if (res != fstrm_res_success) {
+ if (env->stats != NULL) {
+ isc_stats_increment(env->stats, dns_dnstapcounter_drop);
+ }
+ free(buf);
+ } else {
+ if (env->stats != NULL) {
+ isc_stats_increment(env->stats,
+ dns_dnstapcounter_success);
+ }
+ }
+}
+
+static void
+init_msg(dns_dtenv_t *env, dns_dtmsg_t *dm, Dnstap__Message__Type mtype) {
+ memset(dm, 0, sizeof(*dm));
+ dm->d.base.descriptor = &dnstap__dnstap__descriptor;
+ dm->m.base.descriptor = &dnstap__message__descriptor;
+ dm->d.type = DNSTAP__DNSTAP__TYPE__MESSAGE;
+ dm->d.message = &dm->m;
+ dm->m.type = mtype;
+
+ if (env->identity.length != 0) {
+ dm->d.identity.data = env->identity.base;
+ dm->d.identity.len = env->identity.length;
+ dm->d.has_identity = true;
+ }
+
+ if (env->version.length != 0) {
+ dm->d.version.data = env->version.base;
+ dm->d.version.len = env->version.length;
+ dm->d.has_version = true;
+ }
+}
+
+static Dnstap__Message__Type
+dnstap_type(dns_dtmsgtype_t msgtype) {
+ switch (msgtype) {
+ case DNS_DTTYPE_SQ:
+ return (DNSTAP__MESSAGE__TYPE__STUB_QUERY);
+ case DNS_DTTYPE_SR:
+ return (DNSTAP__MESSAGE__TYPE__STUB_RESPONSE);
+ case DNS_DTTYPE_CQ:
+ return (DNSTAP__MESSAGE__TYPE__CLIENT_QUERY);
+ case DNS_DTTYPE_CR:
+ return (DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE);
+ case DNS_DTTYPE_AQ:
+ return (DNSTAP__MESSAGE__TYPE__AUTH_QUERY);
+ case DNS_DTTYPE_AR:
+ return (DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE);
+ case DNS_DTTYPE_RQ:
+ return (DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY);
+ case DNS_DTTYPE_RR:
+ return (DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE);
+ case DNS_DTTYPE_FQ:
+ return (DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY);
+ case DNS_DTTYPE_FR:
+ return (DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE);
+ case DNS_DTTYPE_TQ:
+ return (DNSTAP__MESSAGE__TYPE__TOOL_QUERY);
+ case DNS_DTTYPE_TR:
+ return (DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE);
+ case DNS_DTTYPE_UQ:
+ return (DNSTAP__MESSAGE__TYPE__UPDATE_QUERY);
+ case DNS_DTTYPE_UR:
+ return (DNSTAP__MESSAGE__TYPE__UPDATE_RESPONSE);
+ default:
+ UNREACHABLE();
+ }
+}
+
+static void
+cpbuf(isc_buffer_t *buf, ProtobufCBinaryData *p, protobuf_c_boolean *has) {
+ p->data = isc_buffer_base(buf);
+ p->len = isc_buffer_usedlength(buf);
+ *has = 1;
+}
+
+static void
+setaddr(dns_dtmsg_t *dm, isc_sockaddr_t *sa, bool tcp,
+ ProtobufCBinaryData *addr, protobuf_c_boolean *has_addr, uint32_t *port,
+ protobuf_c_boolean *has_port) {
+ int family = isc_sockaddr_pf(sa);
+
+ if (family != AF_INET6 && family != AF_INET) {
+ return;
+ }
+
+ if (family == AF_INET6) {
+ dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET6;
+ addr->data = sa->type.sin6.sin6_addr.s6_addr;
+ addr->len = 16;
+ *port = ntohs(sa->type.sin6.sin6_port);
+ } else {
+ dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET;
+ addr->data = (uint8_t *)&sa->type.sin.sin_addr.s_addr;
+ addr->len = 4;
+ *port = ntohs(sa->type.sin.sin_port);
+ }
+
+ if (tcp) {
+ dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__TCP;
+ } else {
+ dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__UDP;
+ }
+
+ dm->m.has_socket_protocol = 1;
+ dm->m.has_socket_family = 1;
+ *has_addr = 1;
+ *has_port = 1;
+}
+
+/*%
+ * Invoke dns_dt_reopen() and re-allow dnstap output file rolling. This
+ * function is run in the context of the task stored in the 'reopen_task' field
+ * of the dnstap environment structure.
+ */
+static void
+perform_reopen(isc_task_t *task, isc_event_t *event) {
+ dns_dtenv_t *env;
+
+ REQUIRE(event != NULL);
+ REQUIRE(event->ev_type == DNS_EVENT_FREESTORAGE);
+
+ env = (dns_dtenv_t *)event->ev_arg;
+
+ REQUIRE(VALID_DTENV(env));
+ REQUIRE(task == env->reopen_task);
+
+ /*
+ * Roll output file in the context of env->reopen_task.
+ */
+ dns_dt_reopen(env, env->rolls);
+
+ /*
+ * Clean up.
+ */
+ isc_event_free(&event);
+ isc_task_detach(&task);
+
+ /*
+ * Re-allow output file rolling.
+ */
+ LOCK(&env->reopen_lock);
+ env->reopen_queued = false;
+ UNLOCK(&env->reopen_lock);
+}
+
+/*%
+ * Check whether a dnstap output file roll is due and if so, initiate it (the
+ * actual roll happens asynchronously).
+ */
+static void
+check_file_size_and_maybe_reopen(dns_dtenv_t *env) {
+ isc_task_t *reopen_task = NULL;
+ isc_event_t *event;
+ struct stat statbuf;
+
+ /*
+ * If the task from which the output file should be reopened was not
+ * specified, abort.
+ */
+ if (env->reopen_task == NULL) {
+ return;
+ }
+
+ /*
+ * If an output file roll is not currently queued, check the current
+ * size of the output file to see whether a roll is needed. Return if
+ * it is not.
+ */
+ LOCK(&env->reopen_lock);
+ if (env->reopen_queued || stat(env->path, &statbuf) < 0 ||
+ statbuf.st_size <= env->max_size)
+ {
+ goto unlock_and_return;
+ }
+
+ /*
+ * We need to roll the output file, but it needs to be done in the
+ * context of env->reopen_task. Allocate and send an event to achieve
+ * that, then disallow output file rolling until the roll we queue is
+ * completed.
+ */
+ event = isc_event_allocate(env->mctx, NULL, DNS_EVENT_FREESTORAGE,
+ perform_reopen, env, sizeof(*event));
+ isc_task_attach(env->reopen_task, &reopen_task);
+ isc_task_send(reopen_task, &event);
+ env->reopen_queued = true;
+
+unlock_and_return:
+ UNLOCK(&env->reopen_lock);
+}
+
+void
+dns_dt_send(dns_view_t *view, dns_dtmsgtype_t msgtype, isc_sockaddr_t *qaddr,
+ isc_sockaddr_t *raddr, bool tcp, isc_region_t *zone,
+ isc_time_t *qtime, isc_time_t *rtime, isc_buffer_t *buf) {
+ isc_time_t now, *t;
+ dns_dtmsg_t dm;
+
+ REQUIRE(DNS_VIEW_VALID(view));
+
+ if ((msgtype & view->dttypes) == 0) {
+ return;
+ }
+
+ if (view->dtenv == NULL) {
+ return;
+ }
+
+ REQUIRE(VALID_DTENV(view->dtenv));
+
+ if (view->dtenv->max_size != 0) {
+ check_file_size_and_maybe_reopen(view->dtenv);
+ }
+
+ TIME_NOW(&now);
+ t = &now;
+
+ init_msg(view->dtenv, &dm, dnstap_type(msgtype));
+
+ /* Query/response times */
+ switch (msgtype) {
+ case DNS_DTTYPE_AR:
+ case DNS_DTTYPE_CR:
+ case DNS_DTTYPE_RR:
+ case DNS_DTTYPE_FR:
+ case DNS_DTTYPE_SR:
+ case DNS_DTTYPE_TR:
+ case DNS_DTTYPE_UR:
+ if (rtime != NULL) {
+ t = rtime;
+ }
+
+ dm.m.response_time_sec = isc_time_seconds(t);
+ dm.m.has_response_time_sec = 1;
+ dm.m.response_time_nsec = isc_time_nanoseconds(t);
+ dm.m.has_response_time_nsec = 1;
+
+ /*
+ * Types RR and FR can fall through and get the query
+ * time set as well. Any other response type, break.
+ */
+ if (msgtype != DNS_DTTYPE_RR && msgtype != DNS_DTTYPE_FR) {
+ break;
+ }
+
+ FALLTHROUGH;
+ case DNS_DTTYPE_AQ:
+ case DNS_DTTYPE_CQ:
+ case DNS_DTTYPE_FQ:
+ case DNS_DTTYPE_RQ:
+ case DNS_DTTYPE_SQ:
+ case DNS_DTTYPE_TQ:
+ case DNS_DTTYPE_UQ:
+ if (qtime != NULL) {
+ t = qtime;
+ }
+
+ dm.m.query_time_sec = isc_time_seconds(t);
+ dm.m.has_query_time_sec = 1;
+ dm.m.query_time_nsec = isc_time_nanoseconds(t);
+ dm.m.has_query_time_nsec = 1;
+ break;
+ default:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP,
+ DNS_LOGMODULE_DNSTAP, ISC_LOG_ERROR,
+ "invalid dnstap message type %d", msgtype);
+ return;
+ }
+
+ /* Query and response messages */
+ if ((msgtype & DNS_DTTYPE_QUERY) != 0) {
+ cpbuf(buf, &dm.m.query_message, &dm.m.has_query_message);
+ } else if ((msgtype & DNS_DTTYPE_RESPONSE) != 0) {
+ cpbuf(buf, &dm.m.response_message, &dm.m.has_response_message);
+ }
+
+ /* Zone/bailiwick */
+ switch (msgtype) {
+ case DNS_DTTYPE_AR:
+ case DNS_DTTYPE_RQ:
+ case DNS_DTTYPE_RR:
+ case DNS_DTTYPE_FQ:
+ case DNS_DTTYPE_FR:
+ if (zone != NULL && zone->base != NULL && zone->length != 0) {
+ dm.m.query_zone.data = zone->base;
+ dm.m.query_zone.len = zone->length;
+ dm.m.has_query_zone = 1;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (qaddr != NULL) {
+ setaddr(&dm, qaddr, tcp, &dm.m.query_address,
+ &dm.m.has_query_address, &dm.m.query_port,
+ &dm.m.has_query_port);
+ }
+ if (raddr != NULL) {
+ setaddr(&dm, raddr, tcp, &dm.m.response_address,
+ &dm.m.has_response_address, &dm.m.response_port,
+ &dm.m.has_response_port);
+ }
+
+ if (pack_dt(&dm.d, &dm.buf, &dm.len) == ISC_R_SUCCESS) {
+ send_dt(view->dtenv, dm.buf, dm.len);
+ }
+}
+
+static isc_result_t
+putstr(isc_buffer_t **b, const char *str) {
+ isc_result_t result;
+
+ result = isc_buffer_reserve(b, strlen(str));
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOSPACE);
+ }
+
+ isc_buffer_putstr(*b, str);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+putaddr(isc_buffer_t **b, isc_region_t *ip) {
+ char buf[64];
+
+ if (ip->length == 4) {
+ if (!inet_ntop(AF_INET, ip->base, buf, sizeof(buf))) {
+ return (ISC_R_FAILURE);
+ }
+ } else if (ip->length == 16) {
+ if (!inet_ntop(AF_INET6, ip->base, buf, sizeof(buf))) {
+ return (ISC_R_FAILURE);
+ }
+ } else {
+ return (ISC_R_BADADDRESSFORM);
+ }
+
+ return (putstr(b, buf));
+}
+
+static bool
+dnstap_file(struct fstrm_reader *r) {
+ fstrm_res res;
+ const struct fstrm_control *control = NULL;
+ const uint8_t *rtype = NULL;
+ size_t dlen = strlen(DNSTAP_CONTENT_TYPE), rlen = 0;
+ size_t n = 0;
+
+ res = fstrm_reader_get_control(r, FSTRM_CONTROL_START, &control);
+ if (res != fstrm_res_success) {
+ return (false);
+ }
+
+ res = fstrm_control_get_num_field_content_type(control, &n);
+ if (res != fstrm_res_success) {
+ return (false);
+ }
+ if (n > 0) {
+ res = fstrm_control_get_field_content_type(control, 0, &rtype,
+ &rlen);
+ if (res != fstrm_res_success) {
+ return (false);
+ }
+
+ if (rlen != dlen) {
+ return (false);
+ }
+
+ if (memcmp(DNSTAP_CONTENT_TYPE, rtype, dlen) == 0) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+isc_result_t
+dns_dt_open(const char *filename, dns_dtmode_t mode, isc_mem_t *mctx,
+ dns_dthandle_t **handlep) {
+ isc_result_t result;
+ struct fstrm_file_options *fopt = NULL;
+ fstrm_res res;
+ dns_dthandle_t *handle;
+
+ REQUIRE(handlep != NULL && *handlep == NULL);
+
+ handle = isc_mem_get(mctx, sizeof(*handle));
+
+ handle->mode = mode;
+ handle->mctx = NULL;
+
+ switch (mode) {
+ case dns_dtmode_file:
+ fopt = fstrm_file_options_init();
+ if (fopt == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ fstrm_file_options_set_file_path(fopt, filename);
+
+ handle->reader = fstrm_file_reader_init(fopt, NULL);
+ if (handle->reader == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ res = fstrm_reader_open(handle->reader);
+ if (res != fstrm_res_success) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ if (!dnstap_file(handle->reader)) {
+ CHECK(DNS_R_BADDNSTAP);
+ }
+ break;
+ case dns_dtmode_unix:
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ default:
+ UNREACHABLE();
+ }
+
+ isc_mem_attach(mctx, &handle->mctx);
+ result = ISC_R_SUCCESS;
+ *handlep = handle;
+ handle = NULL;
+
+cleanup:
+ if (result != ISC_R_SUCCESS && handle->reader != NULL) {
+ fstrm_reader_destroy(&handle->reader);
+ handle->reader = NULL;
+ }
+ if (fopt != NULL) {
+ fstrm_file_options_destroy(&fopt);
+ }
+ if (handle != NULL) {
+ isc_mem_put(mctx, handle, sizeof(*handle));
+ }
+ return (result);
+}
+
+isc_result_t
+dns_dt_getframe(dns_dthandle_t *handle, uint8_t **bufp, size_t *sizep) {
+ const uint8_t *data;
+ fstrm_res res;
+
+ REQUIRE(handle != NULL);
+ REQUIRE(bufp != NULL);
+ REQUIRE(sizep != NULL);
+
+ data = (const uint8_t *)*bufp;
+
+ res = fstrm_reader_read(handle->reader, &data, sizep);
+ switch (res) {
+ case fstrm_res_success:
+ if (data == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ DE_CONST(data, *bufp);
+ return (ISC_R_SUCCESS);
+ case fstrm_res_stop:
+ return (ISC_R_NOMORE);
+ default:
+ return (ISC_R_FAILURE);
+ }
+}
+
+void
+dns_dt_close(dns_dthandle_t **handlep) {
+ dns_dthandle_t *handle;
+
+ REQUIRE(handlep != NULL && *handlep != NULL);
+
+ handle = *handlep;
+ *handlep = NULL;
+
+ if (handle->reader != NULL) {
+ fstrm_reader_destroy(&handle->reader);
+ handle->reader = NULL;
+ }
+ isc_mem_putanddetach(&handle->mctx, handle, sizeof(*handle));
+}
+
+isc_result_t
+dns_dt_parse(isc_mem_t *mctx, isc_region_t *src, dns_dtdata_t **destp) {
+ isc_result_t result;
+ Dnstap__Dnstap *frame;
+ Dnstap__Message *m;
+ dns_dtdata_t *d = NULL;
+ isc_buffer_t b;
+
+ REQUIRE(src != NULL);
+ REQUIRE(destp != NULL && *destp == NULL);
+
+ d = isc_mem_get(mctx, sizeof(*d));
+
+ memset(d, 0, sizeof(*d));
+ isc_mem_attach(mctx, &d->mctx);
+
+ d->frame = dnstap__dnstap__unpack(NULL, src->length, src->base);
+ if (d->frame == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ frame = (Dnstap__Dnstap *)d->frame;
+
+ if (frame->type != DNSTAP__DNSTAP__TYPE__MESSAGE) {
+ CHECK(DNS_R_BADDNSTAP);
+ }
+
+ m = frame->message;
+
+ /* Message type */
+ switch (m->type) {
+ case DNSTAP__MESSAGE__TYPE__AUTH_QUERY:
+ d->type = DNS_DTTYPE_AQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE:
+ d->type = DNS_DTTYPE_AR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__CLIENT_QUERY:
+ d->type = DNS_DTTYPE_CQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE:
+ d->type = DNS_DTTYPE_CR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY:
+ d->type = DNS_DTTYPE_FQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE:
+ d->type = DNS_DTTYPE_FR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY:
+ d->type = DNS_DTTYPE_RQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE:
+ d->type = DNS_DTTYPE_RR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__STUB_QUERY:
+ d->type = DNS_DTTYPE_SQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__STUB_RESPONSE:
+ d->type = DNS_DTTYPE_SR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__TOOL_QUERY:
+ d->type = DNS_DTTYPE_TQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE:
+ d->type = DNS_DTTYPE_TR;
+ break;
+ case DNSTAP__MESSAGE__TYPE__UPDATE_QUERY:
+ d->type = DNS_DTTYPE_UQ;
+ break;
+ case DNSTAP__MESSAGE__TYPE__UPDATE_RESPONSE:
+ d->type = DNS_DTTYPE_UR;
+ break;
+ default:
+ CHECK(DNS_R_BADDNSTAP);
+ }
+
+ /* Query? */
+ if ((d->type & DNS_DTTYPE_QUERY) != 0) {
+ d->query = true;
+ } else {
+ d->query = false;
+ }
+
+ /* Parse DNS message */
+ if (d->query && m->has_query_message) {
+ d->msgdata.base = m->query_message.data;
+ d->msgdata.length = m->query_message.len;
+ } else if (!d->query && m->has_response_message) {
+ d->msgdata.base = m->response_message.data;
+ d->msgdata.length = m->response_message.len;
+ }
+
+ isc_buffer_init(&b, d->msgdata.base, d->msgdata.length);
+ isc_buffer_add(&b, d->msgdata.length);
+ dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &d->msg);
+ result = dns_message_parse(d->msg, &b, 0);
+ if (result != ISC_R_SUCCESS) {
+ if (result != DNS_R_RECOVERABLE) {
+ dns_message_detach(&d->msg);
+ }
+ result = ISC_R_SUCCESS;
+ }
+
+ /* Timestamp */
+ if (d->query) {
+ if (m->has_query_time_sec && m->has_query_time_nsec) {
+ isc_time_set(&d->qtime, m->query_time_sec,
+ m->query_time_nsec);
+ }
+ } else {
+ if (m->has_response_time_sec && m->has_response_time_nsec) {
+ isc_time_set(&d->rtime, m->response_time_sec,
+ m->response_time_nsec);
+ }
+ }
+
+ /* Peer address */
+ if (m->has_query_address) {
+ d->qaddr.base = m->query_address.data;
+ d->qaddr.length = m->query_address.len;
+ }
+ if (m->has_query_port) {
+ d->qport = m->query_port;
+ }
+
+ if (m->has_response_address) {
+ d->raddr.base = m->response_address.data;
+ d->raddr.length = m->response_address.len;
+ }
+ if (m->has_response_port) {
+ d->rport = m->response_port;
+ }
+
+ /* Socket protocol */
+ if (m->has_socket_protocol) {
+ const ProtobufCEnumValue *type =
+ protobuf_c_enum_descriptor_get_value(
+ &dnstap__socket_protocol__descriptor,
+ m->socket_protocol);
+ if (type != NULL && type->value == DNSTAP__SOCKET_PROTOCOL__TCP)
+ {
+ d->tcp = true;
+ } else {
+ d->tcp = false;
+ }
+ }
+
+ /* Query tuple */
+ if (d->msg != NULL) {
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset;
+
+ CHECK(dns_message_firstname(d->msg, DNS_SECTION_QUESTION));
+ dns_message_currentname(d->msg, DNS_SECTION_QUESTION, &name);
+ rdataset = ISC_LIST_HEAD(name->list);
+
+ dns_name_format(name, d->namebuf, sizeof(d->namebuf));
+ dns_rdatatype_format(rdataset->type, d->typebuf,
+ sizeof(d->typebuf));
+ dns_rdataclass_format(rdataset->rdclass, d->classbuf,
+ sizeof(d->classbuf));
+ }
+
+ *destp = d;
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ dns_dtdata_free(&d);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_dt_datatotext(dns_dtdata_t *d, isc_buffer_t **dest) {
+ isc_result_t result;
+ char buf[100];
+
+ REQUIRE(d != NULL);
+ REQUIRE(dest != NULL && *dest != NULL);
+
+ memset(buf, 0, sizeof(buf));
+
+ /* Timestamp */
+ if (d->query && !isc_time_isepoch(&d->qtime)) {
+ isc_time_formattimestamp(&d->qtime, buf, sizeof(buf));
+ } else if (!d->query && !isc_time_isepoch(&d->rtime)) {
+ isc_time_formattimestamp(&d->rtime, buf, sizeof(buf));
+ }
+
+ if (buf[0] == '\0') {
+ CHECK(putstr(dest, "???\?-?\?-?? ??:??:??.??? "));
+ } else {
+ CHECK(putstr(dest, buf));
+ CHECK(putstr(dest, " "));
+ }
+
+ /* Type mnemonic */
+ switch (d->type) {
+ case DNS_DTTYPE_AQ:
+ CHECK(putstr(dest, "AQ "));
+ break;
+ case DNS_DTTYPE_AR:
+ CHECK(putstr(dest, "AR "));
+ break;
+ case DNS_DTTYPE_CQ:
+ CHECK(putstr(dest, "CQ "));
+ break;
+ case DNS_DTTYPE_CR:
+ CHECK(putstr(dest, "CR "));
+ break;
+ case DNS_DTTYPE_FQ:
+ CHECK(putstr(dest, "FQ "));
+ break;
+ case DNS_DTTYPE_FR:
+ CHECK(putstr(dest, "FR "));
+ break;
+ case DNS_DTTYPE_RQ:
+ CHECK(putstr(dest, "RQ "));
+ break;
+ case DNS_DTTYPE_RR:
+ CHECK(putstr(dest, "RR "));
+ break;
+ case DNS_DTTYPE_SQ:
+ CHECK(putstr(dest, "SQ "));
+ break;
+ case DNS_DTTYPE_SR:
+ CHECK(putstr(dest, "SR "));
+ break;
+ case DNS_DTTYPE_TQ:
+ CHECK(putstr(dest, "TQ "));
+ break;
+ case DNS_DTTYPE_TR:
+ CHECK(putstr(dest, "TR "));
+ break;
+ case DNS_DTTYPE_UQ:
+ CHECK(putstr(dest, "UQ "));
+ break;
+ case DNS_DTTYPE_UR:
+ CHECK(putstr(dest, "UR "));
+ break;
+ default:
+ return (DNS_R_BADDNSTAP);
+ }
+
+ /* Query and response addresses */
+ if (d->qaddr.length != 0) {
+ CHECK(putaddr(dest, &d->qaddr));
+ snprintf(buf, sizeof(buf), ":%u", d->qport);
+ CHECK(putstr(dest, buf));
+ } else {
+ CHECK(putstr(dest, "?"));
+ }
+ if ((d->type & DNS_DTTYPE_QUERY) != 0) {
+ CHECK(putstr(dest, " -> "));
+ } else {
+ CHECK(putstr(dest, " <- "));
+ }
+ if (d->raddr.length != 0) {
+ CHECK(putaddr(dest, &d->raddr));
+ snprintf(buf, sizeof(buf), ":%u", d->rport);
+ CHECK(putstr(dest, buf));
+ } else {
+ CHECK(putstr(dest, "?"));
+ }
+
+ CHECK(putstr(dest, " "));
+
+ /* Protocol */
+ if (d->tcp) {
+ CHECK(putstr(dest, "TCP "));
+ } else {
+ CHECK(putstr(dest, "UDP "));
+ }
+
+ /* Message size */
+ if (d->msgdata.base != NULL) {
+ snprintf(buf, sizeof(buf), "%zub ", (size_t)d->msgdata.length);
+ CHECK(putstr(dest, buf));
+ } else {
+ CHECK(putstr(dest, "0b "));
+ }
+
+ /* Query tuple */
+ if (d->namebuf[0] == '\0') {
+ CHECK(putstr(dest, "?/"));
+ } else {
+ CHECK(putstr(dest, d->namebuf));
+ CHECK(putstr(dest, "/"));
+ }
+
+ if (d->classbuf[0] == '\0') {
+ CHECK(putstr(dest, "?/"));
+ } else {
+ CHECK(putstr(dest, d->classbuf));
+ CHECK(putstr(dest, "/"));
+ }
+
+ if (d->typebuf[0] == '\0') {
+ CHECK(putstr(dest, "?"));
+ } else {
+ CHECK(putstr(dest, d->typebuf));
+ }
+
+ CHECK(isc_buffer_reserve(dest, 1));
+ isc_buffer_putuint8(*dest, 0);
+
+cleanup:
+ return (result);
+}
+
+void
+dns_dtdata_free(dns_dtdata_t **dp) {
+ dns_dtdata_t *d;
+
+ REQUIRE(dp != NULL && *dp != NULL);
+
+ d = *dp;
+ *dp = NULL;
+
+ if (d->msg != NULL) {
+ dns_message_detach(&d->msg);
+ }
+ if (d->frame != NULL) {
+ dnstap__dnstap__free_unpacked(d->frame, NULL);
+ }
+
+ isc_mem_putanddetach(&d->mctx, d, sizeof(*d));
+}
diff --git a/lib/dns/dnstap.proto b/lib/dns/dnstap.proto
new file mode 100644
index 0000000..9d0ac41
--- /dev/null
+++ b/lib/dns/dnstap.proto
@@ -0,0 +1,289 @@
+// Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+//
+// SPDX-License-Identifier: MPL-2.0
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, you can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// See the COPYRIGHT file distributed with this work for additional
+// information regarding copyright ownership.
+
+// dnstap: flexible, structured event replication format for DNS software
+//
+// This file contains the protobuf schemas for the "dnstap" structured event
+// replication format for DNS software.
+
+// Written in 2013-2014 by Farsight Security, Inc.
+//
+// To the extent possible under law, the author(s) have dedicated all
+// copyright and related and neighboring rights to this file to the public
+// domain worldwide. This file is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication along
+// with this file. If not, see:
+//
+// <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..feb73a7
--- /dev/null
+++ b/lib/dns/ds.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <string.h>
+
+#include <isc/buffer.h>
+#include <isc/md.h>
+#include <isc/region.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 <dns/result.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..d49beb2
--- /dev/null
+++ b/lib/dns/dst_api.c
@@ -0,0 +1,2797 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <isc/buffer.h>
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/fsaccess.h>
+#include <isc/lex.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/platform.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>
+
+#include <pk11/site.h>
+
+#define DST_KEY_INTERNAL
+
+#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/result.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);
+
+ dst_result_register();
+
+ memset(dst_t_func, 0, sizeof(dst_t_func));
+ RETERR(dst__hmacmd5_init(&dst_t_func[DST_ALG_HMACMD5]));
+ RETERR(dst__hmacsha1_init(&dst_t_func[DST_ALG_HMACSHA1]));
+ RETERR(dst__hmacsha224_init(&dst_t_func[DST_ALG_HMACSHA224]));
+ RETERR(dst__hmacsha256_init(&dst_t_func[DST_ALG_HMACSHA256]));
+ RETERR(dst__hmacsha384_init(&dst_t_func[DST_ALG_HMACSHA384]));
+ RETERR(dst__hmacsha512_init(&dst_t_func[DST_ALG_HMACSHA512]));
+ RETERR(dst__openssl_init(engine));
+ RETERR(dst__openssldh_init(&dst_t_func[DST_ALG_DH]));
+#if USE_OPENSSL
+ RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA1],
+ DST_ALG_RSASHA1));
+ RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_NSEC3RSASHA1],
+ DST_ALG_NSEC3RSASHA1));
+ RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA256],
+ DST_ALG_RSASHA256));
+ RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA512],
+ DST_ALG_RSASHA512));
+ RETERR(dst__opensslecdsa_init(&dst_t_func[DST_ALG_ECDSA256]));
+ RETERR(dst__opensslecdsa_init(&dst_t_func[DST_ALG_ECDSA384]));
+#ifdef HAVE_OPENSSL_ED25519
+ RETERR(dst__openssleddsa_init(&dst_t_func[DST_ALG_ED25519]));
+#endif /* ifdef HAVE_OPENSSL_ED25519 */
+#ifdef HAVE_OPENSSL_ED448
+ RETERR(dst__openssleddsa_init(&dst_t_func[DST_ALG_ED448]));
+#endif /* ifdef HAVE_OPENSSL_ED448 */
+#endif /* USE_OPENSSL */
+
+#if USE_PKCS11
+ RETERR(dst__pkcs11_init(mctx, engine));
+ RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA1]));
+ RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_NSEC3RSASHA1]));
+ RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA256]));
+ RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA512]));
+ RETERR(dst__pkcs11ecdsa_init(&dst_t_func[DST_ALG_ECDSA256]));
+ RETERR(dst__pkcs11ecdsa_init(&dst_t_func[DST_ALG_ECDSA384]));
+ RETERR(dst__pkcs11eddsa_init(&dst_t_func[DST_ALG_ED25519]));
+ RETERR(dst__pkcs11eddsa_init(&dst_t_func[DST_ALG_ED448]));
+#endif /* USE_PKCS11 */
+#ifdef GSSAPI
+ RETERR(dst__gssapi_init(&dst_t_func[DST_ALG_GSSAPI]));
+#endif /* ifdef GSSAPI */
+
+ dst_initialized = true;
+ return (ISC_R_SUCCESS);
+
+out:
+ /* avoid immediate crash! */
+ dst_initialized = true;
+ dst_lib_destroy();
+ return (result);
+}
+
+void
+dst_lib_destroy(void) {
+ int i;
+ RUNTIME_CHECK(dst_initialized);
+ dst_initialized = false;
+
+ for (i = 0; i < DST_MAX_ALGS; i++) {
+ if (dst_t_func[i] != NULL && dst_t_func[i]->cleanup != NULL) {
+ dst_t_func[i]->cleanup();
+ }
+ }
+ dst__openssl_destroy();
+#if USE_PKCS11
+ (void)dst__pkcs11_destroy();
+#endif /* USE_PKCS11 */
+}
+
+bool
+dst_algorithm_supported(unsigned int alg) {
+ REQUIRE(dst_initialized);
+
+ if (alg >= DST_MAX_ALGS || dst_t_func[alg] == NULL) {
+ return (false);
+ }
+ return (true);
+}
+
+bool
+dst_ds_digest_supported(unsigned int digest_type) {
+ return (digest_type == DNS_DSDIGEST_SHA1 ||
+ digest_type == DNS_DSDIGEST_SHA256 ||
+ digest_type == DNS_DSDIGEST_SHA384);
+}
+
+isc_result_t
+dst_context_create(dst_key_t *key, isc_mem_t *mctx, isc_logcategory_t *category,
+ bool useforsigning, int maxbits, dst_context_t **dctxp) {
+ dst_context_t *dctx;
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(mctx != NULL);
+ REQUIRE(dctxp != NULL && *dctxp == NULL);
+
+ if (key->func->createctx == NULL && key->func->createctx2 == NULL) {
+ return (DST_R_UNSUPPORTEDALG);
+ }
+ if (key->keydata.generic == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ dctx = isc_mem_get(mctx, sizeof(dst_context_t));
+ memset(dctx, 0, sizeof(*dctx));
+ dst_key_attach(key, &dctx->key);
+ isc_mem_attach(mctx, &dctx->mctx);
+ dctx->category = category;
+ if (useforsigning) {
+ dctx->use = DO_SIGN;
+ } else {
+ dctx->use = DO_VERIFY;
+ }
+ if (key->func->createctx2 != NULL) {
+ result = key->func->createctx2(key, maxbits, dctx);
+ } else {
+ result = key->func->createctx(key, dctx);
+ }
+ if (result != ISC_R_SUCCESS) {
+ if (dctx->key != NULL) {
+ dst_key_free(&dctx->key);
+ }
+ isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(dst_context_t));
+ return (result);
+ }
+ dctx->magic = CTX_MAGIC;
+ *dctxp = dctx;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_context_destroy(dst_context_t **dctxp) {
+ dst_context_t *dctx;
+
+ REQUIRE(dctxp != NULL && VALID_CTX(*dctxp));
+
+ dctx = *dctxp;
+ *dctxp = NULL;
+ INSIST(dctx->key->func->destroyctx != NULL);
+ dctx->key->func->destroyctx(dctx);
+ if (dctx->key != NULL) {
+ dst_key_free(&dctx->key);
+ }
+ dctx->magic = 0;
+ isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(dst_context_t));
+}
+
+isc_result_t
+dst_context_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ REQUIRE(VALID_CTX(dctx));
+ REQUIRE(data != NULL);
+ INSIST(dctx->key->func->adddata != NULL);
+
+ return (dctx->key->func->adddata(dctx, data));
+}
+
+isc_result_t
+dst_context_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ dst_key_t *key;
+
+ REQUIRE(VALID_CTX(dctx));
+ REQUIRE(sig != NULL);
+
+ key = dctx->key;
+ CHECKALG(key->key_alg);
+ if (key->keydata.generic == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->func->sign == NULL) {
+ return (DST_R_NOTPRIVATEKEY);
+ }
+ if (key->func->isprivate == NULL || !key->func->isprivate(key)) {
+ return (DST_R_NOTPRIVATEKEY);
+ }
+
+ return (key->func->sign(dctx, sig));
+}
+
+isc_result_t
+dst_context_verify(dst_context_t *dctx, isc_region_t *sig) {
+ REQUIRE(VALID_CTX(dctx));
+ REQUIRE(sig != NULL);
+
+ CHECKALG(dctx->key->key_alg);
+ if (dctx->key->keydata.generic == NULL) {
+ return (DST_R_NULLKEY);
+ }
+ if (dctx->key->func->verify == NULL) {
+ return (DST_R_NOTPUBLICKEY);
+ }
+
+ return (dctx->key->func->verify(dctx, sig));
+}
+
+isc_result_t
+dst_context_verify2(dst_context_t *dctx, unsigned int maxbits,
+ isc_region_t *sig) {
+ REQUIRE(VALID_CTX(dctx));
+ REQUIRE(sig != NULL);
+
+ CHECKALG(dctx->key->key_alg);
+ if (dctx->key->keydata.generic == NULL) {
+ return (DST_R_NULLKEY);
+ }
+ if (dctx->key->func->verify == NULL && dctx->key->func->verify2 == NULL)
+ {
+ return (DST_R_NOTPUBLICKEY);
+ }
+
+ return (dctx->key->func->verify2 != NULL
+ ? dctx->key->func->verify2(dctx, maxbits, sig)
+ : dctx->key->func->verify(dctx, sig));
+}
+
+isc_result_t
+dst_key_computesecret(const dst_key_t *pub, const dst_key_t *priv,
+ isc_buffer_t *secret) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(pub) && VALID_KEY(priv));
+ REQUIRE(secret != NULL);
+
+ CHECKALG(pub->key_alg);
+ CHECKALG(priv->key_alg);
+
+ if (pub->keydata.generic == NULL || priv->keydata.generic == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (pub->key_alg != priv->key_alg || pub->func->computesecret == NULL ||
+ priv->func->computesecret == NULL)
+ {
+ return (DST_R_KEYCANNOTCOMPUTESECRET);
+ }
+
+ if (!dst_key_isprivate(priv)) {
+ return (DST_R_NOTPRIVATEKEY);
+ }
+
+ return (pub->func->computesecret(pub, priv, secret));
+}
+
+isc_result_t
+dst_key_tofile(const dst_key_t *key, int type, const char *directory) {
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE((type &
+ (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE)) != 0);
+
+ CHECKALG(key->key_alg);
+
+ if (key->func->tofile == NULL) {
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ if ((type & DST_TYPE_PUBLIC) != 0) {
+ ret = write_public_key(key, type, directory);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ }
+
+ if ((type & DST_TYPE_STATE) != 0) {
+ ret = write_key_state(key, type, directory);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ }
+
+ if (((type & DST_TYPE_PRIVATE) != 0) &&
+ (key->key_flags & DNS_KEYFLAG_TYPEMASK) != DNS_KEYTYPE_NOKEY)
+ {
+ return (key->func->tofile(key, directory));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_setexternal(dst_key_t *key, bool value) {
+ REQUIRE(VALID_KEY(key));
+
+ key->external = value;
+}
+
+bool
+dst_key_isexternal(dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+
+ return (key->external);
+}
+
+void
+dst_key_setmodified(dst_key_t *key, bool value) {
+ REQUIRE(VALID_KEY(key));
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = value;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+bool
+dst_key_ismodified(const dst_key_t *key) {
+ bool modified;
+ dst_key_t *k;
+
+ REQUIRE(VALID_KEY(key));
+
+ DE_CONST(key, k);
+
+ isc_mutex_lock(&k->mdlock);
+ modified = key->modified;
+ isc_mutex_unlock(&k->mdlock);
+
+ return (modified);
+}
+
+isc_result_t
+dst_key_getfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg,
+ int type, const char *directory, isc_mem_t *mctx,
+ isc_buffer_t *buf) {
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE((type &
+ (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE)) != 0);
+ REQUIRE(mctx != NULL);
+ REQUIRE(buf != NULL);
+
+ CHECKALG(alg);
+
+ result = buildfilename(name, id, alg, type, directory, buf);
+ if (result == ISC_R_SUCCESS) {
+ if (isc_buffer_availablelength(buf) > 0) {
+ isc_buffer_putuint8(buf, 0);
+ } else {
+ result = ISC_R_NOSPACE;
+ }
+ }
+
+ return (result);
+}
+
+isc_result_t
+dst_key_fromfile(dns_name_t *name, dns_keytag_t id, unsigned int alg, int type,
+ const char *directory, isc_mem_t *mctx, dst_key_t **keyp) {
+ isc_result_t result;
+ char filename[NAME_MAX];
+ isc_buffer_t buf;
+ dst_key_t *key;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0);
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ CHECKALG(alg);
+
+ key = NULL;
+
+ isc_buffer_init(&buf, filename, NAME_MAX);
+ result = dst_key_getfilename(name, id, alg, type, NULL, mctx, &buf);
+ if (result != ISC_R_SUCCESS) {
+ goto out;
+ }
+
+ result = dst_key_fromnamedfile(filename, directory, type, mctx, &key);
+ if (result != ISC_R_SUCCESS) {
+ goto out;
+ }
+
+ result = computeid(key);
+ if (result != ISC_R_SUCCESS) {
+ goto out;
+ }
+
+ if (!dns_name_equal(name, key->key_name) || id != key->key_id ||
+ alg != key->key_alg)
+ {
+ result = DST_R_INVALIDPRIVATEKEY;
+ goto out;
+ }
+
+ *keyp = key;
+ result = ISC_R_SUCCESS;
+
+out:
+ if ((key != NULL) && (result != ISC_R_SUCCESS)) {
+ dst_key_free(&key);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dst_key_fromnamedfile(const char *filename, const char *dirname, int type,
+ isc_mem_t *mctx, dst_key_t **keyp) {
+ isc_result_t result;
+ dst_key_t *pubkey = NULL, *key = NULL;
+ char *newfilename = NULL, *statefilename = NULL;
+ int newfilenamelen = 0, statefilenamelen = 0;
+ isc_lex_t *lex = NULL;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(filename != NULL);
+ REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0);
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ /* If an absolute path is specified, don't use the key directory */
+#ifndef WIN32
+ if (filename[0] == '/') {
+ dirname = NULL;
+ }
+#else /* WIN32 */
+ if (filename[0] == '/' || filename[0] == '\\') {
+ dirname = NULL;
+ }
+#endif /* ifndef WIN32 */
+
+ newfilenamelen = strlen(filename) + 5;
+ if (dirname != NULL) {
+ newfilenamelen += strlen(dirname) + 1;
+ }
+ newfilename = isc_mem_get(mctx, newfilenamelen);
+ result = addsuffix(newfilename, newfilenamelen, dirname, filename,
+ ".key");
+ INSIST(result == ISC_R_SUCCESS);
+
+ RETERR(dst_key_read_public(newfilename, type, mctx, &pubkey));
+ isc_mem_put(mctx, newfilename, newfilenamelen);
+
+ /*
+ * Read the state file, if requested by type.
+ */
+ if ((type & DST_TYPE_STATE) != 0) {
+ statefilenamelen = strlen(filename) + 7;
+ if (dirname != NULL) {
+ statefilenamelen += strlen(dirname) + 1;
+ }
+ statefilename = isc_mem_get(mctx, statefilenamelen);
+ result = addsuffix(statefilename, statefilenamelen, dirname,
+ filename, ".state");
+ INSIST(result == ISC_R_SUCCESS);
+ }
+
+ pubkey->kasp = false;
+ if ((type & DST_TYPE_STATE) != 0) {
+ result = dst_key_read_state(statefilename, mctx, &pubkey);
+ if (result == ISC_R_SUCCESS) {
+ pubkey->kasp = true;
+ } else if (result == ISC_R_FILENOTFOUND) {
+ /* Having no state is valid. */
+ result = ISC_R_SUCCESS;
+ }
+ RETERR(result);
+ }
+
+ if ((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) == DST_TYPE_PUBLIC ||
+ (pubkey->key_flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY)
+ {
+ RETERR(computeid(pubkey));
+ pubkey->modified = false;
+ *keyp = pubkey;
+ pubkey = NULL;
+ goto out;
+ }
+
+ RETERR(algorithm_status(pubkey->key_alg));
+
+ key = get_key_struct(pubkey->key_name, pubkey->key_alg,
+ pubkey->key_flags, pubkey->key_proto,
+ pubkey->key_size, pubkey->key_class,
+ pubkey->key_ttl, mctx);
+ if (key == NULL) {
+ RETERR(ISC_R_NOMEMORY);
+ }
+
+ if (key->func->parse == NULL) {
+ RETERR(DST_R_UNSUPPORTEDALG);
+ }
+
+ newfilenamelen = strlen(filename) + 9;
+ if (dirname != NULL) {
+ newfilenamelen += strlen(dirname) + 1;
+ }
+ newfilename = isc_mem_get(mctx, newfilenamelen);
+ result = addsuffix(newfilename, newfilenamelen, dirname, filename,
+ ".private");
+ INSIST(result == ISC_R_SUCCESS);
+
+ RETERR(isc_lex_create(mctx, 1500, &lex));
+ RETERR(isc_lex_openfile(lex, newfilename));
+ isc_mem_put(mctx, newfilename, newfilenamelen);
+
+ RETERR(key->func->parse(key, lex, pubkey));
+ isc_lex_destroy(&lex);
+
+ key->kasp = false;
+ if ((type & DST_TYPE_STATE) != 0) {
+ result = dst_key_read_state(statefilename, mctx, &key);
+ if (result == ISC_R_SUCCESS) {
+ key->kasp = true;
+ } else if (result == ISC_R_FILENOTFOUND) {
+ /* Having no state is valid. */
+ result = ISC_R_SUCCESS;
+ }
+ RETERR(result);
+ }
+
+ RETERR(computeid(key));
+
+ if (pubkey->key_id != key->key_id) {
+ RETERR(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ key->modified = false;
+ *keyp = key;
+ key = NULL;
+
+out:
+ if (pubkey != NULL) {
+ dst_key_free(&pubkey);
+ }
+ if (newfilename != NULL) {
+ isc_mem_put(mctx, newfilename, newfilenamelen);
+ }
+ if (statefilename != NULL) {
+ isc_mem_put(mctx, statefilename, statefilenamelen);
+ }
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+ if (key != NULL) {
+ dst_key_free(&key);
+ }
+ return (result);
+}
+
+isc_result_t
+dst_key_todns(const dst_key_t *key, isc_buffer_t *target) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(target != NULL);
+
+ CHECKALG(key->key_alg);
+
+ if (key->func->todns == NULL) {
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ if (isc_buffer_availablelength(target) < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint16(target, (uint16_t)(key->key_flags & 0xffff));
+ isc_buffer_putuint8(target, (uint8_t)key->key_proto);
+ isc_buffer_putuint8(target, (uint8_t)key->key_alg);
+
+ if ((key->key_flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ if (isc_buffer_availablelength(target) < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint16(
+ target, (uint16_t)((key->key_flags >> 16) & 0xffff));
+ }
+
+ if (key->keydata.generic == NULL) { /*%< NULL KEY */
+ return (ISC_R_SUCCESS);
+ }
+
+ return (key->func->todns(key, target));
+}
+
+isc_result_t
+dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
+ uint8_t alg, proto;
+ uint32_t flags, extflags;
+ dst_key_t *key = NULL;
+ dns_keytag_t id, rid;
+ isc_region_t r;
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+
+ isc_buffer_remainingregion(source, &r);
+
+ if (isc_buffer_remaininglength(source) < 4) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ flags = isc_buffer_getuint16(source);
+ proto = isc_buffer_getuint8(source);
+ alg = isc_buffer_getuint8(source);
+
+ id = dst_region_computeid(&r);
+ rid = dst_region_computerid(&r);
+
+ if ((flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ if (isc_buffer_remaininglength(source) < 2) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ extflags = isc_buffer_getuint16(source);
+ flags |= (extflags << 16);
+ }
+
+ result = frombuffer(name, alg, flags, proto, rdclass, source, mctx,
+ &key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ key->key_id = id;
+ key->key_rid = rid;
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
+ dst_key_t *key = NULL;
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+
+ result = frombuffer(name, alg, flags, protocol, rdclass, source, mctx,
+ &key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = computeid(key);
+ if (result != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (result);
+ }
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_tobuffer(const dst_key_t *key, isc_buffer_t *target) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(target != NULL);
+
+ CHECKALG(key->key_alg);
+
+ if (key->func->todns == NULL) {
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ return (key->func->todns(key, target));
+}
+
+isc_result_t
+dst_key_privatefrombuffer(dst_key_t *key, isc_buffer_t *buffer) {
+ isc_lex_t *lex = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(!dst_key_isprivate(key));
+ REQUIRE(buffer != NULL);
+
+ if (key->func->parse == NULL) {
+ RETERR(DST_R_UNSUPPORTEDALG);
+ }
+
+ RETERR(isc_lex_create(key->mctx, 1500, &lex));
+ RETERR(isc_lex_openbuffer(lex, buffer));
+ RETERR(key->func->parse(key, lex, NULL));
+out:
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+ return (result);
+}
+
+dns_gss_ctx_id_t
+dst_key_getgssctx(const dst_key_t *key) {
+ REQUIRE(key != NULL);
+
+ return (key->keydata.gssctx);
+}
+
+isc_result_t
+dst_key_fromgssapi(const dns_name_t *name, dns_gss_ctx_id_t gssctx,
+ isc_mem_t *mctx, dst_key_t **keyp, isc_region_t *intoken) {
+ dst_key_t *key;
+ isc_result_t result;
+
+ REQUIRE(gssctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ key = get_key_struct(name, DST_ALG_GSSAPI, 0, DNS_KEYPROTO_DNSSEC, 0,
+ dns_rdataclass_in, 0, mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (intoken != NULL) {
+ /*
+ * Keep the token for use by external ssu rules. They may need
+ * to examine the PAC in the kerberos ticket.
+ */
+ isc_buffer_allocate(key->mctx, &key->key_tkeytoken,
+ intoken->length);
+ RETERR(isc_buffer_copyregion(key->key_tkeytoken, intoken));
+ }
+
+ key->keydata.gssctx = gssctx;
+ *keyp = key;
+ result = ISC_R_SUCCESS;
+out:
+ if (result != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ }
+ return (result);
+}
+
+isc_result_t
+dst_key_buildinternal(const dns_name_t *name, unsigned int alg,
+ unsigned int bits, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ void *data, isc_mem_t *mctx, dst_key_t **keyp) {
+ dst_key_t *key;
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+ REQUIRE(data != NULL);
+
+ CHECKALG(alg);
+
+ key = get_key_struct(name, alg, flags, protocol, bits, rdclass, 0,
+ mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ key->keydata.generic = data;
+
+ result = computeid(key);
+ if (result != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (result);
+ }
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_fromlabel(const dns_name_t *name, int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ const char *engine, const char *label, const char *pin,
+ isc_mem_t *mctx, dst_key_t **keyp) {
+ dst_key_t *key;
+ isc_result_t result;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+ REQUIRE(label != NULL);
+
+ CHECKALG(alg);
+
+ key = get_key_struct(name, alg, flags, protocol, 0, rdclass, 0, mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (key->func->fromlabel == NULL) {
+ dst_key_free(&key);
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ result = key->func->fromlabel(key, engine, label, pin);
+ if (result != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (result);
+ }
+
+ result = computeid(key);
+ if (result != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (result);
+ }
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_generate(const dns_name_t *name, unsigned int alg, unsigned int bits,
+ unsigned int param, unsigned int flags, unsigned int protocol,
+ dns_rdataclass_t rdclass, isc_mem_t *mctx, dst_key_t **keyp,
+ void (*callback)(int)) {
+ dst_key_t *key;
+ isc_result_t ret;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ CHECKALG(alg);
+
+ key = get_key_struct(name, alg, flags, protocol, bits, rdclass, 0,
+ mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (bits == 0) { /*%< NULL KEY */
+ key->key_flags |= DNS_KEYTYPE_NOKEY;
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+ }
+
+ if (key->func->generate == NULL) {
+ dst_key_free(&key);
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ ret = key->func->generate(key, param, callback);
+ if (ret != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (ret);
+ }
+
+ ret = computeid(key);
+ if (ret != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (ret);
+ }
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_getbool(const dst_key_t *key, int type, bool *valuep) {
+ dst_key_t *k;
+
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(valuep != NULL);
+ REQUIRE(type <= DST_MAX_BOOLEAN);
+
+ DE_CONST(key, k);
+
+ isc_mutex_lock(&k->mdlock);
+ if (!key->boolset[type]) {
+ isc_mutex_unlock(&k->mdlock);
+ return (ISC_R_NOTFOUND);
+ }
+ *valuep = key->bools[type];
+ isc_mutex_unlock(&k->mdlock);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_setbool(dst_key_t *key, int type, bool value) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_BOOLEAN);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || !key->boolset[type] ||
+ key->bools[type] != value;
+ key->bools[type] = value;
+ key->boolset[type] = true;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+void
+dst_key_unsetbool(dst_key_t *key, int type) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_BOOLEAN);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || key->boolset[type];
+ key->boolset[type] = false;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+isc_result_t
+dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep) {
+ dst_key_t *k;
+
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(valuep != NULL);
+ REQUIRE(type <= DST_MAX_NUMERIC);
+
+ DE_CONST(key, k);
+
+ isc_mutex_lock(&k->mdlock);
+ if (!key->numset[type]) {
+ isc_mutex_unlock(&k->mdlock);
+ return (ISC_R_NOTFOUND);
+ }
+ *valuep = key->nums[type];
+ isc_mutex_unlock(&k->mdlock);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_setnum(dst_key_t *key, int type, uint32_t value) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_NUMERIC);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || !key->numset[type] ||
+ key->nums[type] != value;
+ key->nums[type] = value;
+ key->numset[type] = true;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+void
+dst_key_unsetnum(dst_key_t *key, int type) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_NUMERIC);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || key->numset[type];
+ key->numset[type] = false;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+isc_result_t
+dst_key_gettime(const dst_key_t *key, int type, isc_stdtime_t *timep) {
+ dst_key_t *k;
+
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(timep != NULL);
+ REQUIRE(type <= DST_MAX_TIMES);
+
+ DE_CONST(key, k);
+
+ isc_mutex_lock(&k->mdlock);
+ if (!key->timeset[type]) {
+ isc_mutex_unlock(&k->mdlock);
+ return (ISC_R_NOTFOUND);
+ }
+ *timep = key->times[type];
+ isc_mutex_unlock(&k->mdlock);
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_settime(dst_key_t *key, int type, isc_stdtime_t when) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_TIMES);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || !key->timeset[type] ||
+ key->times[type] != when;
+ key->times[type] = when;
+ key->timeset[type] = true;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+void
+dst_key_unsettime(dst_key_t *key, int type) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_TIMES);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || key->timeset[type];
+ key->timeset[type] = false;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+isc_result_t
+dst_key_getstate(const dst_key_t *key, int type, dst_key_state_t *statep) {
+ dst_key_t *k;
+
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(statep != NULL);
+ REQUIRE(type <= DST_MAX_KEYSTATES);
+
+ DE_CONST(key, k);
+
+ isc_mutex_lock(&k->mdlock);
+ if (!key->keystateset[type]) {
+ isc_mutex_unlock(&k->mdlock);
+ return (ISC_R_NOTFOUND);
+ }
+ *statep = key->keystates[type];
+ isc_mutex_unlock(&k->mdlock);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_KEYSTATES);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || !key->keystateset[type] ||
+ key->keystates[type] != state;
+ key->keystates[type] = state;
+ key->keystateset[type] = true;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+void
+dst_key_unsetstate(dst_key_t *key, int type) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type <= DST_MAX_KEYSTATES);
+
+ isc_mutex_lock(&key->mdlock);
+ key->modified = key->modified || key->keystateset[type];
+ key->keystateset[type] = false;
+ isc_mutex_unlock(&key->mdlock);
+}
+
+isc_result_t
+dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(majorp != NULL);
+ REQUIRE(minorp != NULL);
+ *majorp = key->fmt_major;
+ *minorp = key->fmt_minor;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dst_key_setprivateformat(dst_key_t *key, int major, int minor) {
+ REQUIRE(VALID_KEY(key));
+ key->fmt_major = major;
+ key->fmt_minor = minor;
+}
+
+static bool
+comparekeys(const dst_key_t *key1, const dst_key_t *key2,
+ bool match_revoked_key,
+ bool (*compare)(const dst_key_t *key1, const dst_key_t *key2)) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key1));
+ REQUIRE(VALID_KEY(key2));
+
+ if (key1 == key2) {
+ return (true);
+ }
+
+ if (key1->key_alg != key2->key_alg) {
+ return (false);
+ }
+
+ if (key1->key_id != key2->key_id) {
+ if (!match_revoked_key) {
+ return (false);
+ }
+ if ((key1->key_flags & DNS_KEYFLAG_REVOKE) ==
+ (key2->key_flags & DNS_KEYFLAG_REVOKE))
+ {
+ return (false);
+ }
+ if (key1->key_id != key2->key_rid &&
+ key1->key_rid != key2->key_id)
+ {
+ return (false);
+ }
+ }
+
+ if (compare != NULL) {
+ return (compare(key1, key2));
+ } else {
+ return (false);
+ }
+}
+
+/*
+ * Compares only the public portion of two keys, by converting them
+ * both to wire format and comparing the results.
+ */
+static bool
+pub_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ isc_result_t result;
+ unsigned char buf1[DST_KEY_MAXSIZE], buf2[DST_KEY_MAXSIZE];
+ isc_buffer_t b1, b2;
+ isc_region_t r1, r2;
+
+ isc_buffer_init(&b1, buf1, sizeof(buf1));
+ result = dst_key_todns(key1, &b1);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ /* Zero out flags. */
+ buf1[0] = buf1[1] = 0;
+ if ((key1->key_flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ isc_buffer_subtract(&b1, 2);
+ }
+
+ isc_buffer_init(&b2, buf2, sizeof(buf2));
+ result = dst_key_todns(key2, &b2);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ /* Zero out flags. */
+ buf2[0] = buf2[1] = 0;
+ if ((key2->key_flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ isc_buffer_subtract(&b2, 2);
+ }
+
+ isc_buffer_usedregion(&b1, &r1);
+ /* Remove extended flags. */
+ if ((key1->key_flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ memmove(&buf1[4], &buf1[6], r1.length - 6);
+ r1.length -= 2;
+ }
+
+ isc_buffer_usedregion(&b2, &r2);
+ /* Remove extended flags. */
+ if ((key2->key_flags & DNS_KEYFLAG_EXTENDED) != 0) {
+ memmove(&buf2[4], &buf2[6], r2.length - 6);
+ r2.length -= 2;
+ }
+ return (isc_region_compare(&r1, &r2) == 0);
+}
+
+bool
+dst_key_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ return (comparekeys(key1, key2, false, key1->func->compare));
+}
+
+bool
+dst_key_pubcompare(const dst_key_t *key1, const dst_key_t *key2,
+ bool match_revoked_key) {
+ return (comparekeys(key1, key2, match_revoked_key, pub_compare));
+}
+
+bool
+dst_key_paramcompare(const dst_key_t *key1, const dst_key_t *key2) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key1));
+ REQUIRE(VALID_KEY(key2));
+
+ if (key1 == key2) {
+ return (true);
+ }
+ if (key1->key_alg == key2->key_alg &&
+ key1->func->paramcompare != NULL &&
+ key1->func->paramcompare(key1, key2))
+ {
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+void
+dst_key_attach(dst_key_t *source, dst_key_t **target) {
+ REQUIRE(dst_initialized);
+ REQUIRE(target != NULL && *target == NULL);
+ REQUIRE(VALID_KEY(source));
+
+ isc_refcount_increment(&source->refs);
+ *target = source;
+}
+
+void
+dst_key_free(dst_key_t **keyp) {
+ REQUIRE(dst_initialized);
+ REQUIRE(keyp != NULL && VALID_KEY(*keyp));
+ dst_key_t *key = *keyp;
+ *keyp = NULL;
+
+ if (isc_refcount_decrement(&key->refs) == 1) {
+ isc_refcount_destroy(&key->refs);
+ isc_mem_t *mctx = key->mctx;
+ if (key->keydata.generic != NULL) {
+ INSIST(key->func->destroy != NULL);
+ key->func->destroy(key);
+ }
+ if (key->engine != NULL) {
+ isc_mem_free(mctx, key->engine);
+ }
+ if (key->label != NULL) {
+ isc_mem_free(mctx, key->label);
+ }
+ dns_name_free(key->key_name, mctx);
+ isc_mem_put(mctx, key->key_name, sizeof(dns_name_t));
+ if (key->key_tkeytoken) {
+ isc_buffer_free(&key->key_tkeytoken);
+ }
+ isc_mutex_destroy(&key->mdlock);
+ isc_safe_memwipe(key, sizeof(*key));
+ isc_mem_putanddetach(&mctx, key, sizeof(*key));
+ }
+}
+
+bool
+dst_key_isprivate(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ INSIST(key->func->isprivate != NULL);
+ return (key->func->isprivate(key));
+}
+
+isc_result_t
+dst_key_buildfilename(const dst_key_t *key, int type, const char *directory,
+ isc_buffer_t *out) {
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(type == DST_TYPE_PRIVATE || type == DST_TYPE_PUBLIC ||
+ type == DST_TYPE_STATE || type == 0);
+
+ return (buildfilename(key->key_name, key->key_id, key->key_alg, type,
+ directory, out));
+}
+
+isc_result_t
+dst_key_sigsize(const dst_key_t *key, unsigned int *n) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(n != NULL);
+
+ /* XXXVIX this switch statement is too sparse to gen a jump table. */
+ switch (key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ case DST_ALG_RSASHA256:
+ case DST_ALG_RSASHA512:
+ *n = (key->key_size + 7) / 8;
+ break;
+ case DST_ALG_ECDSA256:
+ *n = DNS_SIG_ECDSA256SIZE;
+ break;
+ case DST_ALG_ECDSA384:
+ *n = DNS_SIG_ECDSA384SIZE;
+ break;
+ case DST_ALG_ED25519:
+ *n = DNS_SIG_ED25519SIZE;
+ break;
+ case DST_ALG_ED448:
+ *n = DNS_SIG_ED448SIZE;
+ break;
+ case DST_ALG_HMACMD5:
+ *n = isc_md_type_get_size(ISC_MD_MD5);
+ break;
+ case DST_ALG_HMACSHA1:
+ *n = isc_md_type_get_size(ISC_MD_SHA1);
+ break;
+ case DST_ALG_HMACSHA224:
+ *n = isc_md_type_get_size(ISC_MD_SHA224);
+ break;
+ case DST_ALG_HMACSHA256:
+ *n = isc_md_type_get_size(ISC_MD_SHA256);
+ break;
+ case DST_ALG_HMACSHA384:
+ *n = isc_md_type_get_size(ISC_MD_SHA384);
+ break;
+ case DST_ALG_HMACSHA512:
+ *n = isc_md_type_get_size(ISC_MD_SHA512);
+ break;
+ case DST_ALG_GSSAPI:
+ *n = 128; /*%< XXX */
+ break;
+ case DST_ALG_DH:
+ default:
+ return (DST_R_UNSUPPORTEDALG);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dst_key_secretsize(const dst_key_t *key, unsigned int *n) {
+ REQUIRE(dst_initialized);
+ REQUIRE(VALID_KEY(key));
+ REQUIRE(n != NULL);
+
+ if (key->key_alg == DST_ALG_DH) {
+ *n = (key->key_size + 7) / 8;
+ return (ISC_R_SUCCESS);
+ }
+ return (DST_R_UNSUPPORTEDALG);
+}
+
+/*%
+ * Set the flags on a key, then recompute the key ID
+ */
+isc_result_t
+dst_key_setflags(dst_key_t *key, uint32_t flags) {
+ REQUIRE(VALID_KEY(key));
+ key->key_flags = flags;
+ return (computeid(key));
+}
+
+void
+dst_key_format(const dst_key_t *key, char *cp, unsigned int size) {
+ char namestr[DNS_NAME_FORMATSIZE];
+ char algstr[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(dst_key_name(key), namestr, sizeof(namestr));
+ dns_secalg_format((dns_secalg_t)dst_key_alg(key), algstr,
+ sizeof(algstr));
+ snprintf(cp, size, "%s/%s/%d", namestr, algstr, dst_key_id(key));
+}
+
+isc_result_t
+dst_key_dump(dst_key_t *key, isc_mem_t *mctx, char **buffer, int *length) {
+ REQUIRE(buffer != NULL && *buffer == NULL);
+ REQUIRE(length != NULL && *length == 0);
+ REQUIRE(VALID_KEY(key));
+
+ if (key->func->dump == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return (key->func->dump(key, mctx, buffer, length));
+}
+
+isc_result_t
+dst_key_restore(dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_mem_t *mctx, const char *keystr, dst_key_t **keyp) {
+ isc_result_t result;
+ dst_key_t *key;
+
+ REQUIRE(dst_initialized);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ if (alg >= DST_MAX_ALGS || dst_t_func[alg] == NULL) {
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ if (dst_t_func[alg]->restore == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ key = get_key_struct(name, alg, flags, protocol, 0, rdclass, 0, mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ result = (dst_t_func[alg]->restore)(key, keystr);
+ if (result == ISC_R_SUCCESS) {
+ *keyp = key;
+ } else {
+ dst_key_free(&key);
+ }
+
+ return (result);
+}
+
+/***
+ *** Static methods
+ ***/
+
+/*%
+ * Allocates a key structure and fills in some of the fields.
+ */
+static dst_key_t *
+get_key_struct(const dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, unsigned int bits,
+ dns_rdataclass_t rdclass, dns_ttl_t ttl, isc_mem_t *mctx) {
+ dst_key_t *key;
+ int i;
+
+ key = isc_mem_get(mctx, sizeof(dst_key_t));
+
+ memset(key, 0, sizeof(dst_key_t));
+
+ key->key_name = isc_mem_get(mctx, sizeof(dns_name_t));
+
+ dns_name_init(key->key_name, NULL);
+ dns_name_dup(name, mctx, key->key_name);
+
+ isc_refcount_init(&key->refs, 1);
+ isc_mem_attach(mctx, &key->mctx);
+ key->key_alg = alg;
+ key->key_flags = flags;
+ key->key_proto = protocol;
+ key->keydata.generic = NULL;
+ key->key_size = bits;
+ key->key_class = rdclass;
+ key->key_ttl = ttl;
+ key->func = dst_t_func[alg];
+ key->fmt_major = 0;
+ key->fmt_minor = 0;
+ for (i = 0; i < (DST_MAX_TIMES + 1); i++) {
+ key->times[i] = 0;
+ key->timeset[i] = false;
+ }
+ isc_mutex_init(&key->mdlock);
+ key->inactive = false;
+ key->magic = KEY_MAGIC;
+ return (key);
+}
+
+bool
+dst_key_inactive(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+
+ return (key->inactive);
+}
+
+void
+dst_key_setinactive(dst_key_t *key, bool inactive) {
+ REQUIRE(VALID_KEY(key));
+
+ key->inactive = inactive;
+}
+
+/*%
+ * Reads a public key from disk.
+ */
+isc_result_t
+dst_key_read_public(const char *filename, int type, isc_mem_t *mctx,
+ dst_key_t **keyp) {
+ u_char rdatabuf[DST_KEY_MAXSIZE];
+ isc_buffer_t b;
+ dns_fixedname_t name;
+ isc_lex_t *lex = NULL;
+ isc_token_t token;
+ isc_result_t ret;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned int opt = ISC_LEXOPT_DNSMULTILINE;
+ dns_rdataclass_t rdclass = dns_rdataclass_in;
+ isc_lexspecials_t specials;
+ uint32_t ttl = 0;
+ isc_result_t result;
+ dns_rdatatype_t keytype;
+
+ /*
+ * Open the file and read its formatted contents
+ * File format:
+ * domain.name [ttl] [class] [KEY|DNSKEY] <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;
+ char filename[NAME_MAX];
+ isc_result_t ret;
+ isc_fsaccess_t access;
+
+ REQUIRE(VALID_KEY(key));
+
+ /*
+ * Make the filename.
+ */
+ isc_buffer_init(&fileb, filename, sizeof(filename));
+ ret = dst_key_buildfilename(key, DST_TYPE_STATE, directory, &fileb);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ /*
+ * Create public key file.
+ */
+ if ((fp = fopen(filename, "w")) == NULL) {
+ return (DST_R_WRITEERROR);
+ }
+
+ if (issymmetric(key)) {
+ access = 0;
+ isc_fsaccess_add(ISC_FSACCESS_OWNER,
+ ISC_FSACCESS_READ | ISC_FSACCESS_WRITE,
+ &access);
+ (void)isc_fsaccess_set(filename, access);
+ }
+
+ /* Write key state */
+ if ((type & DST_TYPE_KEY) == 0) {
+ fprintf(fp, "; This is the state of key %d, for ", key->key_id);
+ ret = dns_name_print(key->key_name, fp);
+ if (ret != ISC_R_SUCCESS) {
+ fclose(fp);
+ return (ret);
+ }
+ fputc('\n', fp);
+
+ fprintf(fp, "Algorithm: %u\n", key->key_alg);
+ fprintf(fp, "Length: %u\n", key->key_size);
+
+ printnum(key, DST_NUM_LIFETIME, "Lifetime", fp);
+ printnum(key, DST_NUM_PREDECESSOR, "Predecessor", fp);
+ printnum(key, DST_NUM_SUCCESSOR, "Successor", fp);
+
+ printbool(key, DST_BOOL_KSK, "KSK", fp);
+ printbool(key, DST_BOOL_ZSK, "ZSK", fp);
+
+ printtime(key, DST_TIME_CREATED, "Generated", fp);
+ printtime(key, DST_TIME_PUBLISH, "Published", fp);
+ printtime(key, DST_TIME_ACTIVATE, "Active", fp);
+ printtime(key, DST_TIME_INACTIVE, "Retired", fp);
+ printtime(key, DST_TIME_REVOKE, "Revoked", fp);
+ printtime(key, DST_TIME_DELETE, "Removed", fp);
+ printtime(key, DST_TIME_DSPUBLISH, "DSPublish", fp);
+ printtime(key, DST_TIME_DSDELETE, "DSRemoved", fp);
+ printtime(key, DST_TIME_SYNCPUBLISH, "PublishCDS", fp);
+ printtime(key, DST_TIME_SYNCDELETE, "DeleteCDS", fp);
+
+ printnum(key, DST_NUM_DSPUBCOUNT, "DSPubCount", fp);
+ printnum(key, DST_NUM_DSDELCOUNT, "DSDelCount", fp);
+
+ printtime(key, DST_TIME_DNSKEY, "DNSKEYChange", fp);
+ printtime(key, DST_TIME_ZRRSIG, "ZRRSIGChange", fp);
+ printtime(key, DST_TIME_KRRSIG, "KRRSIGChange", fp);
+ printtime(key, DST_TIME_DS, "DSChange", fp);
+
+ printstate(key, DST_KEY_DNSKEY, "DNSKEYState", fp);
+ printstate(key, DST_KEY_ZRRSIG, "ZRRSIGState", fp);
+ printstate(key, DST_KEY_KRRSIG, "KRRSIGState", fp);
+ printstate(key, DST_KEY_DS, "DSState", fp);
+ printstate(key, DST_KEY_GOAL, "GoalState", fp);
+ }
+
+ fflush(fp);
+ if (ferror(fp)) {
+ ret = DST_R_WRITEERROR;
+ }
+ fclose(fp);
+
+ return (ret);
+}
+
+/*%
+ * Writes a public key to disk in DNS format.
+ */
+static isc_result_t
+write_public_key(const dst_key_t *key, int type, const char *directory) {
+ FILE *fp;
+ isc_buffer_t keyb, textb, fileb, classb;
+ isc_region_t r;
+ char filename[NAME_MAX];
+ unsigned char key_array[DST_KEY_MAXSIZE];
+ char text_array[DST_KEY_MAXTEXTSIZE];
+ char class_array[10];
+ isc_result_t ret;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_fsaccess_t access;
+
+ REQUIRE(VALID_KEY(key));
+
+ isc_buffer_init(&keyb, key_array, sizeof(key_array));
+ isc_buffer_init(&textb, text_array, sizeof(text_array));
+ isc_buffer_init(&classb, class_array, sizeof(class_array));
+
+ ret = dst_key_todns(key, &keyb);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ isc_buffer_usedregion(&keyb, &r);
+ dns_rdata_fromregion(&rdata, key->key_class, dns_rdatatype_dnskey, &r);
+
+ ret = dns_rdata_totext(&rdata, (dns_name_t *)NULL, &textb);
+ if (ret != ISC_R_SUCCESS) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+
+ ret = dns_rdataclass_totext(key->key_class, &classb);
+ if (ret != ISC_R_SUCCESS) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+
+ /*
+ * Make the filename.
+ */
+ isc_buffer_init(&fileb, filename, sizeof(filename));
+ ret = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, &fileb);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ /*
+ * Create public key file.
+ */
+ if ((fp = fopen(filename, "w")) == NULL) {
+ return (DST_R_WRITEERROR);
+ }
+
+ if (issymmetric(key)) {
+ access = 0;
+ isc_fsaccess_add(ISC_FSACCESS_OWNER,
+ ISC_FSACCESS_READ | ISC_FSACCESS_WRITE,
+ &access);
+ (void)isc_fsaccess_set(filename, access);
+ }
+
+ /* Write key information in comments */
+ if ((type & DST_TYPE_KEY) == 0) {
+ fprintf(fp, "; This is a %s%s-signing key, keyid %d, for ",
+ (key->key_flags & DNS_KEYFLAG_REVOKE) != 0 ? "revoked "
+ : "",
+ (key->key_flags & DNS_KEYFLAG_KSK) != 0 ? "key"
+ : "zone",
+ key->key_id);
+ ret = dns_name_print(key->key_name, fp);
+ if (ret != ISC_R_SUCCESS) {
+ fclose(fp);
+ return (ret);
+ }
+ fputc('\n', fp);
+
+ printtime(key, DST_TIME_CREATED, "; Created", fp);
+ printtime(key, DST_TIME_PUBLISH, "; Publish", fp);
+ printtime(key, DST_TIME_ACTIVATE, "; Activate", fp);
+ printtime(key, DST_TIME_REVOKE, "; Revoke", fp);
+ printtime(key, DST_TIME_INACTIVE, "; Inactive", fp);
+ printtime(key, DST_TIME_DELETE, "; Delete", fp);
+ printtime(key, DST_TIME_SYNCPUBLISH, "; SyncPublish", fp);
+ printtime(key, DST_TIME_SYNCDELETE, "; SyncDelete", fp);
+ }
+
+ /* Now print the actual key */
+ ret = dns_name_print(key->key_name, fp);
+ fprintf(fp, " ");
+
+ if (key->key_ttl != 0) {
+ fprintf(fp, "%u ", key->key_ttl);
+ }
+
+ isc_buffer_usedregion(&classb, &r);
+ if ((unsigned)fwrite(r.base, 1, r.length, fp) != r.length) {
+ ret = DST_R_WRITEERROR;
+ }
+
+ if ((type & DST_TYPE_KEY) != 0) {
+ fprintf(fp, " KEY ");
+ } else {
+ fprintf(fp, " DNSKEY ");
+ }
+
+ isc_buffer_usedregion(&textb, &r);
+ if ((unsigned)fwrite(r.base, 1, r.length, fp) != r.length) {
+ ret = DST_R_WRITEERROR;
+ }
+
+ fputc('\n', fp);
+ fflush(fp);
+ if (ferror(fp)) {
+ ret = DST_R_WRITEERROR;
+ }
+ fclose(fp);
+
+ return (ret);
+}
+
+static isc_result_t
+buildfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg,
+ unsigned int type, const char *directory, isc_buffer_t *out) {
+ const char *suffix = "";
+ isc_result_t result;
+
+ REQUIRE(out != NULL);
+ if ((type & DST_TYPE_PRIVATE) != 0) {
+ suffix = ".private";
+ } else if ((type & DST_TYPE_PUBLIC) != 0) {
+ suffix = ".key";
+ } else if ((type & DST_TYPE_STATE) != 0) {
+ suffix = ".state";
+ }
+
+ if (directory != NULL) {
+ if (isc_buffer_availablelength(out) < strlen(directory)) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putstr(out, directory);
+ if (strlen(directory) > 0U &&
+ directory[strlen(directory) - 1] != '/')
+ {
+ isc_buffer_putstr(out, "/");
+ }
+ }
+ if (isc_buffer_availablelength(out) < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putstr(out, "K");
+ result = dns_name_tofilenametext(name, false, out);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ return (isc_buffer_printf(out, "+%03d+%05d%s", alg, id, suffix));
+}
+
+static isc_result_t
+computeid(dst_key_t *key) {
+ isc_buffer_t dnsbuf;
+ unsigned char dns_array[DST_KEY_MAXSIZE];
+ isc_region_t r;
+ isc_result_t ret;
+
+ isc_buffer_init(&dnsbuf, dns_array, sizeof(dns_array));
+ ret = dst_key_todns(key, &dnsbuf);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ isc_buffer_usedregion(&dnsbuf, &r);
+ key->key_id = dst_region_computeid(&r);
+ key->key_rid = dst_region_computerid(&r);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
+ dst_key_t *key;
+ isc_result_t ret;
+
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(source != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ key = get_key_struct(name, alg, flags, protocol, 0, rdclass, 0, mctx);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (isc_buffer_remaininglength(source) > 0) {
+ ret = algorithm_status(alg);
+ if (ret != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (ret);
+ }
+ if (key->func->fromdns == NULL) {
+ dst_key_free(&key);
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ ret = key->func->fromdns(key, source);
+ if (ret != ISC_R_SUCCESS) {
+ dst_key_free(&key);
+ return (ret);
+ }
+ }
+
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+algorithm_status(unsigned int alg) {
+ REQUIRE(dst_initialized);
+
+ if (dst_algorithm_supported(alg)) {
+ return (ISC_R_SUCCESS);
+ }
+ return (DST_R_UNSUPPORTEDALG);
+}
+
+static isc_result_t
+addsuffix(char *filename, int len, const char *odirname, const char *ofilename,
+ const char *suffix) {
+ int olen = strlen(ofilename);
+ int n;
+
+ if (olen > 1 && ofilename[olen - 1] == '.') {
+ olen -= 1;
+ } else if (olen > 8 && strcmp(ofilename + olen - 8, ".private") == 0) {
+ olen -= 8;
+ } else if (olen > 4 && strcmp(ofilename + olen - 4, ".key") == 0) {
+ olen -= 4;
+ }
+
+ if (odirname == NULL) {
+ n = snprintf(filename, len, "%.*s%s", olen, ofilename, suffix);
+ } else {
+ n = snprintf(filename, len, "%s/%.*s%s", odirname, olen,
+ ofilename, suffix);
+ }
+ if (n < 0) {
+ return (ISC_R_FAILURE);
+ }
+ if (n >= len) {
+ return (ISC_R_NOSPACE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_buffer_t *
+dst_key_tkeytoken(const dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+ return (key->key_tkeytoken);
+}
+
+/*
+ * A key is considered unused if it does not have any timing metadata set
+ * other than "Created".
+ *
+ */
+bool
+dst_key_is_unused(dst_key_t *key) {
+ isc_stdtime_t val;
+ dst_key_state_t st;
+ int state_type;
+ bool state_type_set;
+
+ REQUIRE(VALID_KEY(key));
+
+ /*
+ * None of the key timing metadata, except Created, may be set. Key
+ * state times may be set only if their respective state is HIDDEN.
+ */
+ for (int i = 0; i < DST_MAX_TIMES + 1; i++) {
+ state_type_set = false;
+
+ switch (i) {
+ case DST_TIME_CREATED:
+ break;
+ case DST_TIME_DNSKEY:
+ state_type = DST_KEY_DNSKEY;
+ state_type_set = true;
+ break;
+ case DST_TIME_ZRRSIG:
+ state_type = DST_KEY_ZRRSIG;
+ state_type_set = true;
+ break;
+ case DST_TIME_KRRSIG:
+ state_type = DST_KEY_KRRSIG;
+ state_type_set = true;
+ break;
+ case DST_TIME_DS:
+ state_type = DST_KEY_DS;
+ state_type_set = true;
+ break;
+ default:
+ break;
+ }
+
+ /* Created is fine. */
+ if (i == DST_TIME_CREATED) {
+ continue;
+ }
+ /* No such timing metadata found, that is fine too. */
+ if (dst_key_gettime(key, i, &val) == ISC_R_NOTFOUND) {
+ continue;
+ }
+ /*
+ * Found timing metadata and it is not related to key states.
+ * This key is used.
+ */
+ if (!state_type_set) {
+ return (false);
+ }
+ /*
+ * If the state is not HIDDEN, the key is in use.
+ * If the state is not set, this is odd and we default to NA.
+ */
+ if (dst_key_getstate(key, state_type, &st) != ISC_R_SUCCESS) {
+ st = DST_KEY_STATE_NA;
+ }
+ if (st != DST_KEY_STATE_HIDDEN) {
+ return (false);
+ }
+ }
+ /* This key is unused. */
+ return (true);
+}
+
+isc_result_t
+dst_key_role(dst_key_t *key, bool *ksk, bool *zsk) {
+ bool k = false, z = false;
+ isc_result_t result, ret = ISC_R_SUCCESS;
+
+ if (ksk != NULL) {
+ result = dst_key_getbool(key, DST_BOOL_KSK, &k);
+ if (result == ISC_R_SUCCESS) {
+ *ksk = k;
+ } else {
+ *ksk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) != 0);
+ ret = result;
+ }
+ }
+
+ if (zsk != NULL) {
+ result = dst_key_getbool(key, DST_BOOL_ZSK, &z);
+ if (result == ISC_R_SUCCESS) {
+ *zsk = z;
+ } else {
+ *zsk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0);
+ ret = result;
+ }
+ }
+ return (ret);
+}
+
+/* Hints on key whether it can be published and/or used for signing. */
+
+bool
+dst_key_is_published(dst_key_t *key, isc_stdtime_t now,
+ isc_stdtime_t *publish) {
+ dst_key_state_t state;
+ isc_result_t result;
+ isc_stdtime_t when;
+ bool state_ok = true, time_ok = false;
+
+ REQUIRE(VALID_KEY(key));
+
+ result = dst_key_gettime(key, DST_TIME_PUBLISH, &when);
+ if (result == ISC_R_SUCCESS) {
+ *publish = when;
+ time_ok = (when <= now);
+ }
+
+ /* Check key states:
+ * If the DNSKEY state is RUMOURED or OMNIPRESENT, it means it
+ * should be published.
+ */
+ result = dst_key_getstate(key, DST_KEY_DNSKEY, &state);
+ if (result == ISC_R_SUCCESS) {
+ state_ok = ((state == DST_KEY_STATE_RUMOURED) ||
+ (state == DST_KEY_STATE_OMNIPRESENT));
+ /*
+ * Key states trump timing metadata.
+ * Ignore inactive time.
+ */
+ time_ok = true;
+ }
+
+ return (state_ok && time_ok);
+}
+
+bool
+dst_key_is_active(dst_key_t *key, isc_stdtime_t now) {
+ dst_key_state_t state;
+ isc_result_t result;
+ isc_stdtime_t when = 0;
+ bool ksk = false, zsk = false, inactive = false;
+ bool ds_ok = true, zrrsig_ok = true, time_ok = false;
+
+ REQUIRE(VALID_KEY(key));
+
+ result = dst_key_gettime(key, DST_TIME_INACTIVE, &when);
+ if (result == ISC_R_SUCCESS) {
+ inactive = (when <= now);
+ }
+
+ result = dst_key_gettime(key, DST_TIME_ACTIVATE, &when);
+ if (result == ISC_R_SUCCESS) {
+ time_ok = (when <= now);
+ }
+
+ (void)dst_key_role(key, &ksk, &zsk);
+
+ /* Check key states:
+ * KSK: If the DS is RUMOURED or OMNIPRESENT the key is considered
+ * active.
+ */
+ if (ksk) {
+ result = dst_key_getstate(key, DST_KEY_DS, &state);
+ if (result == ISC_R_SUCCESS) {
+ ds_ok = ((state == DST_KEY_STATE_RUMOURED) ||
+ (state == DST_KEY_STATE_OMNIPRESENT));
+ /*
+ * Key states trump timing metadata.
+ * Ignore inactive time.
+ */
+ time_ok = true;
+ inactive = false;
+ }
+ }
+ /*
+ * ZSK: If the ZRRSIG state is RUMOURED or OMNIPRESENT, it means the
+ * key is active.
+ */
+ if (zsk) {
+ result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state);
+ if (result == ISC_R_SUCCESS) {
+ zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) ||
+ (state == DST_KEY_STATE_OMNIPRESENT));
+ /*
+ * Key states trump timing metadata.
+ * Ignore inactive time.
+ */
+ time_ok = true;
+ inactive = false;
+ }
+ }
+ return (ds_ok && zrrsig_ok && time_ok && !inactive);
+}
+
+bool
+dst_key_is_signing(dst_key_t *key, int role, isc_stdtime_t now,
+ isc_stdtime_t *active) {
+ dst_key_state_t state;
+ isc_result_t result;
+ isc_stdtime_t when = 0;
+ bool ksk = false, zsk = false, inactive = false;
+ bool krrsig_ok = true, zrrsig_ok = true, time_ok = false;
+
+ REQUIRE(VALID_KEY(key));
+
+ result = dst_key_gettime(key, DST_TIME_INACTIVE, &when);
+ if (result == ISC_R_SUCCESS) {
+ inactive = (when <= now);
+ }
+
+ result = dst_key_gettime(key, DST_TIME_ACTIVATE, &when);
+ if (result == ISC_R_SUCCESS) {
+ *active = when;
+ time_ok = (when <= now);
+ }
+
+ (void)dst_key_role(key, &ksk, &zsk);
+
+ /* Check key states:
+ * If the RRSIG state is RUMOURED or OMNIPRESENT, it means the key
+ * is active.
+ */
+ if (ksk && role == DST_BOOL_KSK) {
+ result = dst_key_getstate(key, DST_KEY_KRRSIG, &state);
+ if (result == ISC_R_SUCCESS) {
+ krrsig_ok = ((state == DST_KEY_STATE_RUMOURED) ||
+ (state == DST_KEY_STATE_OMNIPRESENT));
+ /*
+ * Key states trump timing metadata.
+ * Ignore inactive time.
+ */
+ time_ok = true;
+ inactive = false;
+ }
+ } else if (zsk && role == DST_BOOL_ZSK) {
+ result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state);
+ if (result == ISC_R_SUCCESS) {
+ zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) ||
+ (state == DST_KEY_STATE_OMNIPRESENT));
+ /*
+ * Key states trump timing metadata.
+ * Ignore inactive time.
+ */
+ time_ok = true;
+ inactive = false;
+ }
+ }
+ return (krrsig_ok && zrrsig_ok && time_ok && !inactive);
+}
+
+bool
+dst_key_is_revoked(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *revoke) {
+ isc_result_t result;
+ isc_stdtime_t when = 0;
+ bool time_ok = false;
+
+ REQUIRE(VALID_KEY(key));
+
+ result = dst_key_gettime(key, DST_TIME_REVOKE, &when);
+ if (result == ISC_R_SUCCESS) {
+ *revoke = when;
+ time_ok = (when <= now);
+ }
+
+ return (time_ok);
+}
+
+bool
+dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove) {
+ dst_key_state_t state;
+ isc_result_t result;
+ isc_stdtime_t when = 0;
+ bool state_ok = true, time_ok = false;
+
+ REQUIRE(VALID_KEY(key));
+
+ if (dst_key_is_unused(key)) {
+ /* This key was never used. */
+ return (false);
+ }
+
+ result = dst_key_gettime(key, DST_TIME_DELETE, &when);
+ if (result == ISC_R_SUCCESS) {
+ *remove = when;
+ time_ok = (when <= now);
+ }
+
+ /* Check key states:
+ * If the DNSKEY state is UNRETENTIVE or HIDDEN, it means the key
+ * should not be published.
+ */
+ result = dst_key_getstate(key, DST_KEY_DNSKEY, &state);
+ if (result == ISC_R_SUCCESS) {
+ state_ok = ((state == DST_KEY_STATE_UNRETENTIVE) ||
+ (state == DST_KEY_STATE_HIDDEN));
+ /*
+ * Key states trump timing metadata.
+ * Ignore delete time.
+ */
+ time_ok = true;
+ }
+
+ return (state_ok && time_ok);
+}
+
+dst_key_state_t
+dst_key_goal(dst_key_t *key) {
+ dst_key_state_t state;
+ isc_result_t result;
+
+ REQUIRE(VALID_KEY(key));
+
+ result = dst_key_getstate(key, DST_KEY_GOAL, &state);
+ if (result == ISC_R_SUCCESS) {
+ return (state);
+ }
+ return (DST_KEY_STATE_HIDDEN);
+}
+
+bool
+dst_key_haskasp(dst_key_t *key) {
+ REQUIRE(VALID_KEY(key));
+
+ return (key->kasp);
+}
+
+void
+dst_key_copy_metadata(dst_key_t *to, dst_key_t *from) {
+ dst_key_state_t state;
+ isc_stdtime_t when;
+ uint32_t num;
+ bool yesno;
+ isc_result_t result;
+
+ REQUIRE(VALID_KEY(to));
+ REQUIRE(VALID_KEY(from));
+
+ for (int i = 0; i < DST_MAX_TIMES + 1; i++) {
+ result = dst_key_gettime(from, i, &when);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_settime(to, i, when);
+ } else {
+ dst_key_unsettime(to, i);
+ }
+ }
+
+ for (int i = 0; i < DST_MAX_NUMERIC + 1; i++) {
+ result = dst_key_getnum(from, i, &num);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_setnum(to, i, num);
+ } else {
+ dst_key_unsetnum(to, i);
+ }
+ }
+
+ for (int i = 0; i < DST_MAX_BOOLEAN + 1; i++) {
+ result = dst_key_getbool(from, i, &yesno);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_setbool(to, i, yesno);
+ } else {
+ dst_key_unsetbool(to, i);
+ }
+ }
+
+ for (int i = 0; i < DST_MAX_KEYSTATES + 1; i++) {
+ result = dst_key_getstate(from, i, &state);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_setstate(to, i, state);
+ } else {
+ dst_key_unsetstate(to, i);
+ }
+ }
+
+ dst_key_setmodified(to, dst_key_ismodified(from));
+}
diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h
new file mode 100644
index 0000000..4933cfd
--- /dev/null
+++ b/lib/dns/dst_internal.h
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Portions Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.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>
+
+#if USE_PKCS11
+#include <pk11/pk11.h>
+#include <pk11/site.h>
+#endif /* USE_PKCS11 */
+
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/rsa.h>
+
+#include <dns/time.h>
+
+#include <dst/dst.h>
+
+#ifdef GSSAPI
+#ifdef WIN32
+/*
+ * MSVC does not like macros in #include lines.
+ */
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+#else /* ifdef WIN32 */
+#include ISC_PLATFORM_GSSAPIHEADER
+#ifdef ISC_PLATFORM_GSSAPI_KRB5_HEADER
+#include ISC_PLATFORM_GSSAPI_KRB5_HEADER
+#endif /* ifdef ISC_PLATFORM_GSSAPI_KRB5_HEADER */
+#endif /* ifdef WIN32 */
+#endif /* ifdef GSSAPI */
+
+ISC_LANG_BEGINDECLS
+
+#define KEY_MAGIC ISC_MAGIC('D', 'S', 'T', 'K')
+#define CTX_MAGIC ISC_MAGIC('D', 'S', 'T', 'C')
+
+#define VALID_KEY(x) ISC_MAGIC_VALID(x, KEY_MAGIC)
+#define VALID_CTX(x) ISC_MAGIC_VALID(x, CTX_MAGIC)
+
+/***
+ *** Types
+ ***/
+
+typedef struct dst_func dst_func_t;
+
+typedef struct dst_hmac_key dst_hmac_key_t;
+
+/*%
+ * Indicate whether a DST context will be used for signing
+ * or for verification
+ */
+typedef enum { DO_SIGN, DO_VERIFY } dst_use_t;
+
+/*% DST Key Structure */
+struct dst_key {
+ unsigned int magic;
+ isc_refcount_t refs;
+ isc_mutex_t mdlock; /*%< lock for read/write metadata */
+ dns_name_t *key_name; /*%< name of the key */
+ unsigned int key_size; /*%< size of the key in bits */
+ unsigned int key_proto; /*%< protocols this key is used for
+ * */
+ unsigned int key_alg; /*%< algorithm of the key */
+ uint32_t key_flags; /*%< flags of the public key */
+ uint16_t key_id; /*%< identifier of the key */
+ uint16_t key_rid; /*%< identifier of the key when
+ * revoked */
+ uint16_t key_bits; /*%< hmac digest bits */
+ dns_rdataclass_t key_class; /*%< class of the key record */
+ dns_ttl_t key_ttl; /*%< default/initial dnskey ttl */
+ isc_mem_t *mctx; /*%< memory context */
+ char *engine; /*%< engine name (HSM) */
+ char *label; /*%< engine label (HSM) */
+ union {
+ void *generic;
+ dns_gss_ctx_id_t gssctx;
+ DH *dh;
+#if USE_OPENSSL
+ EVP_PKEY *pkey;
+#endif /* if USE_OPENSSL */
+#if USE_PKCS11
+ pk11_object_t *pkey;
+#endif /* if USE_PKCS11 */
+ dst_hmac_key_t *hmac_key;
+ } keydata; /*%< pointer to key in crypto pkg fmt */
+
+ isc_stdtime_t times[DST_MAX_TIMES + 1]; /*%< timing metadata */
+ bool timeset[DST_MAX_TIMES + 1]; /*%< data set? */
+
+ uint32_t nums[DST_MAX_NUMERIC + 1]; /*%< numeric metadata
+ * */
+ bool numset[DST_MAX_NUMERIC + 1]; /*%< data set? */
+
+ bool bools[DST_MAX_BOOLEAN + 1]; /*%< boolean metadata
+ * */
+ bool boolset[DST_MAX_BOOLEAN + 1]; /*%< data set? */
+
+ dst_key_state_t keystates[DST_MAX_KEYSTATES + 1]; /*%< key states
+ * */
+ bool keystateset[DST_MAX_KEYSTATES + 1]; /*%< data
+ * set? */
+
+ bool kasp; /*%< key has kasp state */
+ bool inactive; /*%< private key not present as it is
+ * inactive */
+ bool external; /*%< external key */
+ bool modified; /*%< set to true if key file metadata has changed */
+
+ int fmt_major; /*%< private key format, major version
+ * */
+ int fmt_minor; /*%< private key format, minor version
+ * */
+
+ dst_func_t *func; /*%< crypto package specific functions */
+ isc_buffer_t *key_tkeytoken; /*%< TKEY token data */
+};
+
+struct dst_context {
+ unsigned int magic;
+ dst_use_t use;
+ dst_key_t *key;
+ isc_mem_t *mctx;
+ isc_logcategory_t *category;
+ union {
+ void *generic;
+ dst_gssapi_signverifyctx_t *gssctx;
+ isc_hmac_t *hmac_ctx;
+ EVP_MD_CTX *evp_md_ctx;
+#if USE_PKCS11
+ pk11_context_t *pk11_ctx;
+#endif /* if USE_PKCS11 */
+ } ctxdata;
+};
+
+struct dst_func {
+ /*
+ * Context functions
+ */
+ isc_result_t (*createctx)(dst_key_t *key, dst_context_t *dctx);
+ isc_result_t (*createctx2)(dst_key_t *key, int maxbits,
+ dst_context_t *dctx);
+ void (*destroyctx)(dst_context_t *dctx);
+ isc_result_t (*adddata)(dst_context_t *dctx, const isc_region_t *data);
+
+ /*
+ * Key operations
+ */
+ isc_result_t (*sign)(dst_context_t *dctx, isc_buffer_t *sig);
+ isc_result_t (*verify)(dst_context_t *dctx, const isc_region_t *sig);
+ isc_result_t (*verify2)(dst_context_t *dctx, int maxbits,
+ const isc_region_t *sig);
+ isc_result_t (*computesecret)(const dst_key_t *pub,
+ const dst_key_t *priv,
+ isc_buffer_t *secret);
+ bool (*compare)(const dst_key_t *key1, const dst_key_t *key2);
+ bool (*paramcompare)(const dst_key_t *key1, const dst_key_t *key2);
+ isc_result_t (*generate)(dst_key_t *key, int parms,
+ void (*callback)(int));
+ bool (*isprivate)(const dst_key_t *key);
+ void (*destroy)(dst_key_t *key);
+
+ /* conversion functions */
+ isc_result_t (*todns)(const dst_key_t *key, isc_buffer_t *data);
+ isc_result_t (*fromdns)(dst_key_t *key, isc_buffer_t *data);
+ isc_result_t (*tofile)(const dst_key_t *key, const char *directory);
+ isc_result_t (*parse)(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub);
+
+ /* cleanup */
+ void (*cleanup)(void);
+
+ isc_result_t (*fromlabel)(dst_key_t *key, const char *engine,
+ const char *label, const char *pin);
+ isc_result_t (*dump)(dst_key_t *key, isc_mem_t *mctx, char **buffer,
+ int *length);
+ isc_result_t (*restore)(dst_key_t *key, const char *keystr);
+};
+
+/*%
+ * Initializers
+ */
+isc_result_t
+dst__openssl_init(const char *engine);
+#define dst__pkcs11_init pk11_initialize
+
+isc_result_t
+dst__hmacmd5_init(struct dst_func **funcp);
+isc_result_t
+dst__hmacsha1_init(struct dst_func **funcp);
+isc_result_t
+dst__hmacsha224_init(struct dst_func **funcp);
+isc_result_t
+dst__hmacsha256_init(struct dst_func **funcp);
+isc_result_t
+dst__hmacsha384_init(struct dst_func **funcp);
+isc_result_t
+dst__hmacsha512_init(struct dst_func **funcp);
+isc_result_t
+dst__openssldh_init(struct dst_func **funcp);
+#if USE_OPENSSL
+isc_result_t
+dst__opensslrsa_init(struct dst_func **funcp, unsigned char algorithm);
+isc_result_t
+dst__opensslecdsa_init(struct dst_func **funcp);
+#if HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448
+isc_result_t
+dst__openssleddsa_init(struct dst_func **funcp);
+#endif /* HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 */
+#endif /* USE_OPENSSL */
+#if USE_PKCS11
+isc_result_t
+dst__pkcs11rsa_init(struct dst_func **funcp);
+isc_result_t
+dst__pkcs11dsa_init(struct dst_func **funcp);
+isc_result_t
+dst__pkcs11ecdsa_init(struct dst_func **funcp);
+isc_result_t
+dst__pkcs11eddsa_init(struct dst_func **funcp);
+#endif /* USE_PKCS11 */
+#ifdef GSSAPI
+isc_result_t
+dst__gssapi_init(struct dst_func **funcp);
+#endif /* GSSAPI */
+
+/*%
+ * Destructors
+ */
+void
+dst__openssl_destroy(void);
+#define dst__pkcs11_destroy pk11_finalize
+
+/*%
+ * Memory allocators using the DST memory pool.
+ */
+void *
+dst__mem_alloc(size_t size);
+void
+dst__mem_free(void *ptr);
+void *
+dst__mem_realloc(void *ptr, size_t size);
+
+ISC_LANG_ENDDECLS
+
+/*! \file */
diff --git a/lib/dns/dst_openssl.h b/lib/dns/dst_openssl.h
new file mode 100644
index 0000000..5727848
--- /dev/null
+++ b/lib/dns/dst_openssl.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DST_OPENSSL_H
+#define DST_OPENSSL_H 1
+
+#include <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)
+ENGINE *
+dst__openssl_getengine(const char *engine);
+#else /* if !defined(OPENSSL_NO_ENGINE) */
+#define dst__openssl_getengine(x) NULL
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DST_OPENSSL_H */
+/*! \file */
diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c
new file mode 100644
index 0000000..b7bbee9
--- /dev/null
+++ b/lib/dns/dst_parse.c
@@ -0,0 +1,804 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "dst_parse.h"
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/base64.h>
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/fsaccess.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 <pk11/site.h>
+
+#include <dns/log.h>
+#include <dns/time.h>
+
+#include "dst/result.h"
+#include "dst_internal.h"
+
+#define DST_AS_STR(t) ((t).value.as_textregion.base)
+
+#define PRIVATE_KEY_STR "Private-key-format:"
+#define ALGORITHM_STR "Algorithm:"
+
+#define TIMING_NTAGS (DST_MAX_TIMES + 1)
+static const char *timetags[TIMING_NTAGS] = {
+ "Created:", "Publish:", "Activate:", "Revoke:",
+ "Inactive:", "Delete:", "DSPublish:", "SyncPublish:",
+ "SyncDelete:", NULL, NULL, NULL,
+ NULL
+};
+
+#define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1)
+static const char *numerictags[NUMERIC_NTAGS] = {
+ "Predecessor:", "Successor:", "MaxTTL:", "RollPeriod:", NULL, NULL, NULL
+};
+
+struct parse_map {
+ const int value;
+ const char *tag;
+};
+
+static struct parse_map map[] = { { TAG_RSA_MODULUS, "Modulus:" },
+ { TAG_RSA_PUBLICEXPONENT, "PublicExponent:" },
+ { TAG_RSA_PRIVATEEXPONENT, "PrivateExponent"
+ ":" },
+ { TAG_RSA_PRIME1, "Prime1:" },
+ { TAG_RSA_PRIME2, "Prime2:" },
+ { TAG_RSA_EXPONENT1, "Exponent1:" },
+ { TAG_RSA_EXPONENT2, "Exponent2:" },
+ { TAG_RSA_COEFFICIENT, "Coefficient:" },
+ { TAG_RSA_ENGINE, "Engine:" },
+ { TAG_RSA_LABEL, "Label:" },
+
+ { TAG_DH_PRIME, "Prime(p):" },
+ { TAG_DH_GENERATOR, "Generator(g):" },
+ { TAG_DH_PRIVATE, "Private_value(x):" },
+ { TAG_DH_PUBLIC, "Public_value(y):" },
+
+ { TAG_ECDSA_PRIVATEKEY, "PrivateKey:" },
+ { TAG_ECDSA_ENGINE, "Engine:" },
+ { TAG_ECDSA_LABEL, "Label:" },
+
+ { TAG_EDDSA_PRIVATEKEY, "PrivateKey:" },
+ { TAG_EDDSA_ENGINE, "Engine:" },
+ { TAG_EDDSA_LABEL, "Label:" },
+
+ { TAG_HMACMD5_KEY, "Key:" },
+ { TAG_HMACMD5_BITS, "Bits:" },
+
+ { TAG_HMACSHA1_KEY, "Key:" },
+ { TAG_HMACSHA1_BITS, "Bits:" },
+
+ { TAG_HMACSHA224_KEY, "Key:" },
+ { TAG_HMACSHA224_BITS, "Bits:" },
+
+ { TAG_HMACSHA256_KEY, "Key:" },
+ { TAG_HMACSHA256_BITS, "Bits:" },
+
+ { TAG_HMACSHA384_KEY, "Key:" },
+ { TAG_HMACSHA384_BITS, "Bits:" },
+
+ { TAG_HMACSHA512_KEY, "Key:" },
+ { TAG_HMACSHA512_BITS, "Bits:" },
+
+ { 0, NULL } };
+
+static int
+find_value(const char *s, const unsigned int alg) {
+ int i;
+
+ for (i = 0; map[i].tag != NULL; i++) {
+ if (strcasecmp(s, map[i].tag) == 0 &&
+ (TAG_ALG(map[i].value) == alg))
+ {
+ return (map[i].value);
+ }
+ }
+ return (-1);
+}
+
+static const char *
+find_tag(const int value) {
+ int i;
+
+ for (i = 0;; i++) {
+ if (map[i].tag == NULL) {
+ return (NULL);
+ } else if (value == map[i].value) {
+ return (map[i].tag);
+ }
+ }
+}
+
+static int
+find_metadata(const char *s, const char *tags[], int ntags) {
+ int i;
+
+ for (i = 0; i < ntags; i++) {
+ if (tags[i] != NULL && strcasecmp(s, tags[i]) == 0) {
+ return (i);
+ }
+ }
+
+ return (-1);
+}
+
+static int
+find_timedata(const char *s) {
+ return (find_metadata(s, timetags, TIMING_NTAGS));
+}
+
+static int
+find_numericdata(const char *s) {
+ return (find_metadata(s, numerictags, NUMERIC_NTAGS));
+}
+
+static int
+check_rsa(const dst_private_t *priv, bool external) {
+ int i, j;
+ bool have[RSA_NTAGS];
+ bool ok;
+ unsigned int mask;
+
+ if (external) {
+ return ((priv->nelements == 0) ? 0 : -1);
+ }
+
+ for (i = 0; i < RSA_NTAGS; i++) {
+ have[i] = false;
+ }
+
+ for (j = 0; j < priv->nelements; j++) {
+ for (i = 0; i < RSA_NTAGS; i++) {
+ if (priv->elements[j].tag == TAG(DST_ALG_RSA, i)) {
+ break;
+ }
+ }
+ if (i == RSA_NTAGS) {
+ return (-1);
+ }
+ have[i] = true;
+ }
+
+ mask = (1ULL << TAG_SHIFT) - 1;
+
+ if (have[TAG_RSA_ENGINE & mask]) {
+ ok = have[TAG_RSA_MODULUS & mask] &&
+ have[TAG_RSA_PUBLICEXPONENT & mask] &&
+ have[TAG_RSA_LABEL & mask];
+ } else {
+ ok = have[TAG_RSA_MODULUS & mask] &&
+ have[TAG_RSA_PUBLICEXPONENT & mask] &&
+ have[TAG_RSA_PRIVATEEXPONENT & mask] &&
+ have[TAG_RSA_PRIME1 & mask] &&
+ have[TAG_RSA_PRIME2 & mask] &&
+ have[TAG_RSA_EXPONENT1 & mask] &&
+ have[TAG_RSA_EXPONENT2 & mask] &&
+ have[TAG_RSA_COEFFICIENT & mask];
+ }
+ return (ok ? 0 : -1);
+}
+
+static int
+check_dh(const dst_private_t *priv) {
+ int i, j;
+ if (priv->nelements != DH_NTAGS) {
+ return (-1);
+ }
+ for (i = 0; i < DH_NTAGS; i++) {
+ for (j = 0; j < priv->nelements; j++) {
+ if (priv->elements[j].tag == TAG(DST_ALG_DH, i)) {
+ break;
+ }
+ }
+ if (j == priv->nelements) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+static int
+check_ecdsa(const dst_private_t *priv, bool external) {
+ int i, j;
+ bool have[ECDSA_NTAGS];
+ bool ok;
+ unsigned int mask;
+
+ if (external) {
+ return ((priv->nelements == 0) ? 0 : -1);
+ }
+
+ for (i = 0; i < ECDSA_NTAGS; i++) {
+ have[i] = false;
+ }
+ for (j = 0; j < priv->nelements; j++) {
+ for (i = 0; i < ECDSA_NTAGS; i++) {
+ if (priv->elements[j].tag == TAG(DST_ALG_ECDSA256, i)) {
+ break;
+ }
+ }
+ if (i == ECDSA_NTAGS) {
+ return (-1);
+ }
+ have[i] = true;
+ }
+
+ mask = (1ULL << TAG_SHIFT) - 1;
+
+ if (have[TAG_ECDSA_ENGINE & mask]) {
+ ok = have[TAG_ECDSA_LABEL & mask];
+ } else {
+ ok = have[TAG_ECDSA_PRIVATEKEY & mask];
+ }
+ return (ok ? 0 : -1);
+}
+
+static int
+check_eddsa(const dst_private_t *priv, bool external) {
+ int i, j;
+ bool have[EDDSA_NTAGS];
+ bool ok;
+ unsigned int mask;
+
+ if (external) {
+ return ((priv->nelements == 0) ? 0 : -1);
+ }
+
+ for (i = 0; i < EDDSA_NTAGS; i++) {
+ have[i] = false;
+ }
+ for (j = 0; j < priv->nelements; j++) {
+ for (i = 0; i < EDDSA_NTAGS; i++) {
+ if (priv->elements[j].tag == TAG(DST_ALG_ED25519, i)) {
+ break;
+ }
+ }
+ if (i == EDDSA_NTAGS) {
+ return (-1);
+ }
+ have[i] = true;
+ }
+
+ mask = (1ULL << TAG_SHIFT) - 1;
+
+ if (have[TAG_EDDSA_ENGINE & mask]) {
+ ok = have[TAG_EDDSA_LABEL & mask];
+ } else {
+ ok = have[TAG_EDDSA_PRIVATEKEY & mask];
+ }
+ return (ok ? 0 : -1);
+}
+
+static int
+check_hmac_md5(const dst_private_t *priv, bool old) {
+ int i, j;
+
+ if (priv->nelements != HMACMD5_NTAGS) {
+ /*
+ * If this is a good old format and we are accepting
+ * the old format return success.
+ */
+ if (old && priv->nelements == OLD_HMACMD5_NTAGS &&
+ priv->elements[0].tag == TAG_HMACMD5_KEY)
+ {
+ return (0);
+ }
+ return (-1);
+ }
+ /*
+ * We must be new format at this point.
+ */
+ for (i = 0; i < HMACMD5_NTAGS; i++) {
+ for (j = 0; j < priv->nelements; j++) {
+ if (priv->elements[j].tag == TAG(DST_ALG_HMACMD5, i)) {
+ break;
+ }
+ }
+ if (j == priv->nelements) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+static int
+check_hmac_sha(const dst_private_t *priv, unsigned int ntags,
+ unsigned int alg) {
+ unsigned int i, j;
+ if (priv->nelements != ntags) {
+ return (-1);
+ }
+ for (i = 0; i < ntags; i++) {
+ for (j = 0; j < priv->nelements; j++) {
+ if (priv->elements[j].tag == TAG(alg, i)) {
+ break;
+ }
+ }
+ if (j == priv->nelements) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+static int
+check_data(const dst_private_t *priv, const unsigned int alg, bool old,
+ bool external) {
+ /* XXXVIX this switch statement is too sparse to gen a jump table. */
+ switch (alg) {
+ case DST_ALG_RSA:
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ case DST_ALG_RSASHA256:
+ case DST_ALG_RSASHA512:
+ return (check_rsa(priv, external));
+ case DST_ALG_DH:
+ return (check_dh(priv));
+ case DST_ALG_ECDSA256:
+ case DST_ALG_ECDSA384:
+ return (check_ecdsa(priv, external));
+ case DST_ALG_ED25519:
+ case DST_ALG_ED448:
+ return (check_eddsa(priv, external));
+ case DST_ALG_HMACMD5:
+ return (check_hmac_md5(priv, old));
+ case DST_ALG_HMACSHA1:
+ return (check_hmac_sha(priv, HMACSHA1_NTAGS, alg));
+ case DST_ALG_HMACSHA224:
+ return (check_hmac_sha(priv, HMACSHA224_NTAGS, alg));
+ case DST_ALG_HMACSHA256:
+ return (check_hmac_sha(priv, HMACSHA256_NTAGS, alg));
+ case DST_ALG_HMACSHA384:
+ return (check_hmac_sha(priv, HMACSHA384_NTAGS, alg));
+ case DST_ALG_HMACSHA512:
+ return (check_hmac_sha(priv, HMACSHA512_NTAGS, alg));
+ default:
+ return (DST_R_UNSUPPORTEDALG);
+ }
+}
+
+void
+dst__privstruct_free(dst_private_t *priv, isc_mem_t *mctx) {
+ int i;
+
+ if (priv == NULL) {
+ return;
+ }
+ for (i = 0; i < priv->nelements; i++) {
+ if (priv->elements[i].data == NULL) {
+ continue;
+ }
+ memset(priv->elements[i].data, 0, MAXFIELDSIZE);
+ isc_mem_put(mctx, priv->elements[i].data, MAXFIELDSIZE);
+ }
+ priv->nelements = 0;
+}
+
+isc_result_t
+dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex,
+ isc_mem_t *mctx, dst_private_t *priv) {
+ int n = 0, major, minor, check;
+ isc_buffer_t b;
+ isc_token_t token;
+ unsigned char *data = NULL;
+ unsigned int opt = ISC_LEXOPT_EOL;
+ isc_stdtime_t when;
+ isc_result_t ret;
+ bool external = false;
+
+ REQUIRE(priv != NULL);
+
+ priv->nelements = 0;
+ memset(priv->elements, 0, sizeof(priv->elements));
+
+#define NEXTTOKEN(lex, opt, token) \
+ do { \
+ ret = isc_lex_gettoken(lex, opt, token); \
+ if (ret != ISC_R_SUCCESS) \
+ goto fail; \
+ } while (0)
+
+#define READLINE(lex, opt, token) \
+ do { \
+ ret = isc_lex_gettoken(lex, opt, token); \
+ if (ret == ISC_R_EOF) \
+ break; \
+ else if (ret != ISC_R_SUCCESS) \
+ goto fail; \
+ } while ((*token).type != isc_tokentype_eol)
+
+ /*
+ * Read the description line.
+ */
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string ||
+ strcmp(DST_AS_STR(token), PRIVATE_KEY_STR) != 0)
+ {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string || (DST_AS_STR(token))[0] != 'v')
+ {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+ if (sscanf(DST_AS_STR(token), "v%d.%d", &major, &minor) != 2) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ if (major > DST_MAJOR_VERSION) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ /*
+ * Store the private key format version number
+ */
+ dst_key_setprivateformat(key, major, minor);
+
+ READLINE(lex, opt, &token);
+
+ /*
+ * Read the algorithm line.
+ */
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string ||
+ strcmp(DST_AS_STR(token), ALGORITHM_STR) != 0)
+ {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
+ if (token.type != isc_tokentype_number ||
+ token.value.as_ulong != (unsigned long)dst_key_alg(key))
+ {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ READLINE(lex, opt, &token);
+
+ /*
+ * Read the key data.
+ */
+ for (n = 0; n < MAXFIELDS; n++) {
+ int tag;
+ isc_region_t r;
+ do {
+ ret = isc_lex_gettoken(lex, opt, &token);
+ if (ret == ISC_R_EOF) {
+ goto done;
+ }
+ if (ret != ISC_R_SUCCESS) {
+ goto fail;
+ }
+ } while (token.type == isc_tokentype_eol);
+
+ if (token.type != isc_tokentype_string) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ if (strcmp(DST_AS_STR(token), "External:") == 0) {
+ external = true;
+ goto next;
+ }
+
+ /* Numeric metadata */
+ tag = find_numericdata(DST_AS_STR(token));
+ if (tag >= 0) {
+ INSIST(tag < NUMERIC_NTAGS);
+
+ NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
+ if (token.type != isc_tokentype_number) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ dst_key_setnum(key, tag, token.value.as_ulong);
+ goto next;
+ }
+
+ /* Timing metadata */
+ tag = find_timedata(DST_AS_STR(token));
+ if (tag >= 0) {
+ INSIST(tag < TIMING_NTAGS);
+
+ NEXTTOKEN(lex, opt, &token);
+ if (token.type != isc_tokentype_string) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ ret = dns_time32_fromtext(DST_AS_STR(token), &when);
+ if (ret != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ dst_key_settime(key, tag, when);
+
+ goto next;
+ }
+
+ /* Key data */
+ tag = find_value(DST_AS_STR(token), alg);
+ if (tag < 0 && minor > DST_MINOR_VERSION) {
+ goto next;
+ } else if (tag < 0) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ priv->elements[n].tag = tag;
+
+ data = isc_mem_get(mctx, MAXFIELDSIZE);
+
+ isc_buffer_init(&b, data, MAXFIELDSIZE);
+ ret = isc_base64_tobuffer(lex, &b, -1);
+ if (ret != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ isc_buffer_usedregion(&b, &r);
+ priv->elements[n].length = r.length;
+ priv->elements[n].data = r.base;
+ priv->nelements++;
+
+ next:
+ READLINE(lex, opt, &token);
+ data = NULL;
+ }
+
+done:
+ if (external && priv->nelements != 0) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ }
+
+ check = check_data(priv, alg, true, external);
+ if (check < 0) {
+ ret = DST_R_INVALIDPRIVATEKEY;
+ goto fail;
+ } else if (check != ISC_R_SUCCESS) {
+ ret = check;
+ goto fail;
+ }
+
+ key->external = external;
+
+ return (ISC_R_SUCCESS);
+
+fail:
+ dst__privstruct_free(priv, mctx);
+ if (data != NULL) {
+ isc_mem_put(mctx, data, MAXFIELDSIZE);
+ }
+
+ return (ret);
+}
+
+isc_result_t
+dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv,
+ const char *directory) {
+ FILE *fp;
+ isc_result_t result;
+ char filename[NAME_MAX];
+ char buffer[MAXFIELDSIZE * 2];
+ isc_fsaccess_t access;
+ isc_stdtime_t when;
+ uint32_t value;
+ isc_buffer_t b;
+ isc_region_t r;
+ int major, minor;
+ mode_t mode;
+ int i, ret;
+
+ REQUIRE(priv != NULL);
+
+ ret = check_data(priv, dst_key_alg(key), false, key->external);
+ if (ret < 0) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ } else if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ isc_buffer_init(&b, filename, sizeof(filename));
+ result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = isc_file_mode(filename, &mode);
+ if (result == ISC_R_SUCCESS && mode != 0600) {
+ /* File exists; warn that we are changing its permissions */
+ int level;
+
+#ifdef _WIN32
+ /* Windows security model is pretty different,
+ * e.g., there is no umask... */
+ level = ISC_LOG_NOTICE;
+#else /* ifdef _WIN32 */
+ level = ISC_LOG_WARNING;
+#endif /* ifdef _WIN32 */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_DNSSEC, level,
+ "Permissions on the file %s "
+ "have changed from 0%o to 0600 as "
+ "a result of this operation.",
+ filename, (unsigned int)mode);
+ }
+
+ if ((fp = fopen(filename, "w")) == NULL) {
+ return (DST_R_WRITEERROR);
+ }
+
+ access = 0;
+ isc_fsaccess_add(ISC_FSACCESS_OWNER,
+ ISC_FSACCESS_READ | ISC_FSACCESS_WRITE, &access);
+ (void)isc_fsaccess_set(filename, access);
+
+ dst_key_getprivateformat(key, &major, &minor);
+ if (major == 0 && minor == 0) {
+ major = DST_MAJOR_VERSION;
+ minor = DST_MINOR_VERSION;
+ }
+
+ /* XXXDCL return value should be checked for full filesystem */
+ fprintf(fp, "%s v%d.%d\n", PRIVATE_KEY_STR, major, minor);
+
+ fprintf(fp, "%s %u ", ALGORITHM_STR, dst_key_alg(key));
+
+ /* XXXVIX this switch statement is too sparse to gen a jump table. */
+ switch (dst_key_alg(key)) {
+ case DST_ALG_DH:
+ fprintf(fp, "(DH)\n");
+ break;
+ case DST_ALG_RSASHA1:
+ fprintf(fp, "(RSASHA1)\n");
+ break;
+ case DST_ALG_NSEC3RSASHA1:
+ fprintf(fp, "(NSEC3RSASHA1)\n");
+ break;
+ case DST_ALG_RSASHA256:
+ fprintf(fp, "(RSASHA256)\n");
+ break;
+ case DST_ALG_RSASHA512:
+ fprintf(fp, "(RSASHA512)\n");
+ break;
+ case DST_ALG_ECDSA256:
+ fprintf(fp, "(ECDSAP256SHA256)\n");
+ break;
+ case DST_ALG_ECDSA384:
+ fprintf(fp, "(ECDSAP384SHA384)\n");
+ break;
+ case DST_ALG_ED25519:
+ fprintf(fp, "(ED25519)\n");
+ break;
+ case DST_ALG_ED448:
+ fprintf(fp, "(ED448)\n");
+ break;
+ case DST_ALG_HMACMD5:
+ fprintf(fp, "(HMAC_MD5)\n");
+ break;
+ case DST_ALG_HMACSHA1:
+ fprintf(fp, "(HMAC_SHA1)\n");
+ break;
+ case DST_ALG_HMACSHA224:
+ fprintf(fp, "(HMAC_SHA224)\n");
+ break;
+ case DST_ALG_HMACSHA256:
+ fprintf(fp, "(HMAC_SHA256)\n");
+ break;
+ case DST_ALG_HMACSHA384:
+ fprintf(fp, "(HMAC_SHA384)\n");
+ break;
+ case DST_ALG_HMACSHA512:
+ fprintf(fp, "(HMAC_SHA512)\n");
+ break;
+ default:
+ fprintf(fp, "(?)\n");
+ break;
+ }
+
+ for (i = 0; i < priv->nelements; i++) {
+ const char *s;
+
+ s = find_tag(priv->elements[i].tag);
+
+ r.base = priv->elements[i].data;
+ r.length = priv->elements[i].length;
+ isc_buffer_init(&b, buffer, sizeof(buffer));
+ result = isc_base64_totext(&r, sizeof(buffer), "", &b);
+ if (result != ISC_R_SUCCESS) {
+ fclose(fp);
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+ isc_buffer_usedregion(&b, &r);
+
+ fprintf(fp, "%s %.*s\n", s, (int)r.length, r.base);
+ }
+
+ if (key->external) {
+ fprintf(fp, "External:\n");
+ }
+
+ /* Add the metadata tags */
+ if (major > 1 || (major == 1 && minor >= 3)) {
+ for (i = 0; i < NUMERIC_NTAGS; i++) {
+ result = dst_key_getnum(key, i, &value);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (numerictags[i] != NULL) {
+ fprintf(fp, "%s %u\n", numerictags[i], value);
+ }
+ }
+ for (i = 0; i < TIMING_NTAGS; i++) {
+ result = dst_key_gettime(key, i, &when);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ isc_buffer_init(&b, buffer, sizeof(buffer));
+ result = dns_time32_totext(when, &b);
+ if (result != ISC_R_SUCCESS) {
+ fclose(fp);
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+
+ isc_buffer_usedregion(&b, &r);
+
+ if (timetags[i] != NULL) {
+ fprintf(fp, "%s %.*s\n", timetags[i],
+ (int)r.length, r.base);
+ }
+ }
+ }
+
+ fflush(fp);
+ result = ferror(fp) ? DST_R_WRITEERROR : ISC_R_SUCCESS;
+ fclose(fp);
+ return (result);
+}
+
+/*! \file */
diff --git a/lib/dns/dst_parse.h b/lib/dns/dst_parse.h
new file mode 100644
index 0000000..347a729
--- /dev/null
+++ b/lib/dns/dst_parse.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+#ifndef DST_DST_PARSE_H
+#define DST_DST_PARSE_H 1
+
+#include <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
+
+#endif /* DST_DST_PARSE_H */
diff --git a/lib/dns/dst_pkcs11.h b/lib/dns/dst_pkcs11.h
new file mode 100644
index 0000000..4589022
--- /dev/null
+++ b/lib/dns/dst_pkcs11.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DST_PKCS11_H
+#define DST_PKCS11_H 1
+
+#include <isc/lang.h>
+#include <isc/log.h>
+#include <isc/result.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dst__pkcs11_toresult(const char *funcname, const char *file, int line,
+ isc_result_t fallback, CK_RV rv);
+
+#define PK11_CALL(func, args, fallback) \
+ ((void)(((rv = (func)args) == CKR_OK) || \
+ ((ret = dst__pkcs11_toresult(#func, __FILE__, __LINE__, \
+ fallback, rv)), \
+ 0)))
+
+#define PK11_RET(func, args, fallback) \
+ ((void)(((rv = (func)args) == CKR_OK) || \
+ ((ret = dst__pkcs11_toresult(#func, __FILE__, __LINE__, \
+ fallback, rv)), \
+ 0))); \
+ if (rv != CKR_OK) \
+ goto err;
+
+ISC_LANG_ENDDECLS
+
+#endif /* DST_PKCS11_H */
diff --git a/lib/dns/dst_result.c b/lib/dns/dst_result.c
new file mode 100644
index 0000000..6c3acae
--- /dev/null
+++ b/lib/dns/dst_result.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <isc/once.h>
+#include <isc/util.h>
+
+#include <dst/result.h>
+
+static const char *text[DST_R_NRESULTS] = {
+ "algorithm is unsupported", /*%< 0 */
+ "crypto failure", /*%< 1 */
+ "built with no crypto support", /*%< 2 */
+ "illegal operation for a null key", /*%< 3 */
+ "public key is invalid", /*%< 4 */
+ "private key is invalid", /*%< 5 */
+ "external key", /*%< 6 */
+ "error occurred writing key to disk", /*%< 7 */
+ "invalid algorithm specific parameter", /*%< 8 */
+ "UNUSED9", /*%< 9 */
+ "UNUSED10", /*%< 10 */
+ "sign failure", /*%< 11 */
+ "UNUSED12", /*%< 12 */
+ "UNUSED13", /*%< 13 */
+ "verify failure", /*%< 14 */
+ "not a public key", /*%< 15 */
+ "not a private key", /*%< 16 */
+ "not a key that can compute a secret", /*%< 17 */
+ "failure computing a shared secret", /*%< 18 */
+ "no randomness available", /*%< 19 */
+ "bad key type", /*%< 20 */
+ "no engine", /*%< 21 */
+ "illegal operation for an external key", /*%< 22 */
+};
+
+static const char *ids[DST_R_NRESULTS] = {
+ "DST_R_UNSUPPORTEDALG",
+ "DST_R_CRYPTOFAILURE",
+ "DST_R_NOCRYPTO",
+ "DST_R_NULLKEY",
+ "DST_R_INVALIDPUBLICKEY",
+ "DST_R_INVALIDPRIVATEKEY",
+ "UNUSED",
+ "DST_R_WRITEERROR",
+ "DST_R_INVALIDPARAM",
+ "UNUSED",
+ "UNUSED",
+ "DST_R_SIGNFAILURE",
+ "UNUSED",
+ "UNUSED",
+ "DST_R_VERIFYFAILURE",
+ "DST_R_NOTPUBLICKEY",
+ "DST_R_NOTPRIVATEKEY",
+ "DST_R_KEYCANNOTCOMPUTESECRET",
+ "DST_R_COMPUTESECRETFAILURE",
+ "DST_R_NORANDOMNESS",
+ "DST_R_BADKEYTYPE",
+ "DST_R_NOENGINE",
+ "DST_R_EXTERNALKEY",
+};
+
+#define DST_RESULT_RESULTSET 2
+
+static isc_once_t once = ISC_ONCE_INIT;
+
+static void
+initialize_action(void) {
+ isc_result_t result;
+
+ result = isc_result_register(ISC_RESULTCLASS_DST, DST_R_NRESULTS, text,
+ DST_RESULT_RESULTSET);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_result_register() failed: %u", result);
+ }
+ result = isc_result_registerids(ISC_RESULTCLASS_DST, DST_R_NRESULTS,
+ ids, DST_RESULT_RESULTSET);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_result_registerids() failed: %u", result);
+ }
+}
+
+static void
+initialize(void) {
+ RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS);
+}
+
+const char *
+dst_result_totext(isc_result_t result) {
+ initialize();
+
+ return (isc_result_totext(result));
+}
+
+void
+dst_result_register(void) {
+ initialize();
+}
+
+/*! \file */
diff --git a/lib/dns/dyndb.c b/lib/dns/dyndb.c
new file mode 100644
index 0000000..1caf90b
--- /dev/null
+++ b/lib/dns/dyndb.c
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#elif _WIN32
+#include <windows.h>
+#endif /* if HAVE_DLFCN_H */
+
+#include <string.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;
+ void *handle;
+ dns_dyndb_register_t *register_func;
+ dns_dyndb_destroy_t *destroy_func;
+ char *name;
+ void *inst;
+ LINK(dyndb_implementation_t) link;
+};
+
+/*
+ * List of dyndb implementations. Locked by dyndb_lock.
+ *
+ * These are stored here so they can be cleaned up on shutdown.
+ * (The order in which they are stored is not important.)
+ */
+static LIST(dyndb_implementation_t) dyndb_implementations;
+
+/* Locks dyndb_implementations. */
+static isc_mutex_t dyndb_lock;
+static isc_once_t once = ISC_ONCE_INIT;
+
+static void
+dyndb_initialize(void) {
+ isc_mutex_init(&dyndb_lock);
+ INIT_LIST(dyndb_implementations);
+}
+
+static dyndb_implementation_t *
+impfind(const char *name) {
+ dyndb_implementation_t *imp;
+
+ for (imp = ISC_LIST_HEAD(dyndb_implementations); imp != NULL;
+ imp = ISC_LIST_NEXT(imp, link))
+ {
+ if (strcasecmp(name, imp->name) == 0) {
+ return (imp);
+ }
+ }
+ return (NULL);
+}
+
+#if HAVE_DLFCN_H && HAVE_DLOPEN
+static isc_result_t
+load_symbol(void *handle, const char *filename, const char *symbol_name,
+ void **symbolp) {
+ const char *errmsg;
+ void *symbol;
+
+ REQUIRE(handle != NULL);
+ REQUIRE(symbolp != NULL && *symbolp == NULL);
+
+ symbol = dlsym(handle, symbol_name);
+ if (symbol == NULL) {
+ errmsg = dlerror();
+ if (errmsg == NULL) {
+ errmsg = "returned function pointer is NULL";
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
+ "failed to lookup symbol %s in "
+ "dyndb module '%s': %s",
+ symbol_name, filename, errmsg);
+ return (ISC_R_FAILURE);
+ }
+ dlerror();
+
+ *symbolp = symbol;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+load_library(isc_mem_t *mctx, const char *filename, const char *instname,
+ dyndb_implementation_t **impp) {
+ isc_result_t result;
+ void *handle = NULL;
+ dyndb_implementation_t *imp = NULL;
+ dns_dyndb_register_t *register_func = NULL;
+ dns_dyndb_destroy_t *destroy_func = NULL;
+ dns_dyndb_version_t *version_func = NULL;
+ int version;
+
+ REQUIRE(impp != NULL && *impp == NULL);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB,
+ ISC_LOG_INFO, "loading DynDB instance '%s' driver '%s'",
+ instname, filename);
+
+ handle = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
+ if (handle == NULL) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ /* Clear dlerror */
+ dlerror();
+
+ CHECK(load_symbol(handle, filename, "dyndb_version",
+ (void **)&version_func));
+
+ version = version_func(NULL);
+ if (version < (DNS_DYNDB_VERSION - DNS_DYNDB_AGE) ||
+ version > DNS_DYNDB_VERSION)
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
+ "driver API version mismatch: %d/%d", version,
+ DNS_DYNDB_VERSION);
+ CHECK(ISC_R_FAILURE);
+ }
+
+ CHECK(load_symbol(handle, filename, "dyndb_init",
+ (void **)&register_func));
+ CHECK(load_symbol(handle, filename, "dyndb_destroy",
+ (void **)&destroy_func));
+
+ imp = isc_mem_get(mctx, sizeof(dyndb_implementation_t));
+
+ imp->mctx = NULL;
+ isc_mem_attach(mctx, &imp->mctx);
+ imp->handle = handle;
+ imp->register_func = register_func;
+ imp->destroy_func = destroy_func;
+ imp->name = isc_mem_strdup(mctx, instname);
+
+ imp->inst = NULL;
+ INIT_LINK(imp, link);
+
+ *impp = imp;
+ imp = NULL;
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
+ "failed to dynamically load instance '%s' "
+ "driver '%s': %s (%s)",
+ instname, filename, dlerror(),
+ isc_result_totext(result));
+ }
+ if (imp != NULL) {
+ isc_mem_putanddetach(&imp->mctx, imp,
+ sizeof(dyndb_implementation_t));
+ }
+ if (result != ISC_R_SUCCESS && handle != NULL) {
+ dlclose(handle);
+ }
+
+ return (result);
+}
+
+static void
+unload_library(dyndb_implementation_t **impp) {
+ dyndb_implementation_t *imp;
+
+ REQUIRE(impp != NULL && *impp != NULL);
+
+ imp = *impp;
+ *impp = NULL;
+
+ isc_mem_free(imp->mctx, imp->name);
+ isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t));
+}
+#elif _WIN32
+static isc_result_t
+load_symbol(HMODULE handle, const char *filename, const char *symbol_name,
+ void **symbolp) {
+ void *symbol;
+
+ REQUIRE(handle != NULL);
+ REQUIRE(symbolp != NULL && *symbolp == NULL);
+
+ symbol = GetProcAddress(handle, symbol_name);
+ if (symbol == NULL) {
+ int errstatus = GetLastError();
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
+ "failed to lookup symbol %s in "
+ "dyndb module '%s': %d",
+ symbol_name, filename, errstatus);
+ return (ISC_R_FAILURE);
+ }
+
+ *symbolp = symbol;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+load_library(isc_mem_t *mctx, const char *filename, const char *instname,
+ dyndb_implementation_t **impp) {
+ isc_result_t result;
+ HMODULE handle;
+ dyndb_implementation_t *imp = NULL;
+ dns_dyndb_register_t *register_func = NULL;
+ dns_dyndb_destroy_t *destroy_func = NULL;
+ dns_dyndb_version_t *version_func = NULL;
+ int version;
+
+ REQUIRE(impp != NULL && *impp == NULL);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB,
+ ISC_LOG_INFO, "loading DynDB instance '%s' driver '%s'",
+ instname, filename);
+
+ handle = LoadLibraryA(filename);
+ if (handle == NULL) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ CHECK(load_symbol(handle, filename, "dyndb_version",
+ (void **)&version_func));
+
+ version = version_func(NULL);
+ if (version < (DNS_DYNDB_VERSION - DNS_DYNDB_AGE) ||
+ version > DNS_DYNDB_VERSION)
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
+ "driver API version mismatch: %d/%d", version,
+ DNS_DYNDB_VERSION);
+ CHECK(ISC_R_FAILURE);
+ }
+
+ CHECK(load_symbol(handle, filename, "dyndb_init",
+ (void **)&register_func));
+ CHECK(load_symbol(handle, filename, "dyndb_destroy",
+ (void **)&destroy_func));
+
+ imp = isc_mem_get(mctx, sizeof(dyndb_implementation_t));
+
+ imp->mctx = NULL;
+ isc_mem_attach(mctx, &imp->mctx);
+ imp->handle = handle;
+ imp->register_func = register_func;
+ imp->destroy_func = destroy_func;
+ imp->name = isc_mem_strdup(mctx, instname);
+
+ imp->inst = NULL;
+ INIT_LINK(imp, link);
+
+ *impp = imp;
+ imp = NULL;
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
+ "failed to dynamically load instance '%s' "
+ "driver '%s': %d (%s)",
+ instname, filename, GetLastError(),
+ isc_result_totext(result));
+ }
+ if (imp != NULL) {
+ isc_mem_putanddetach(&imp->mctx, imp,
+ sizeof(dyndb_implementation_t));
+ }
+ if (result != ISC_R_SUCCESS && handle != NULL) {
+ FreeLibrary(handle);
+ }
+
+ return (result);
+}
+
+static void
+unload_library(dyndb_implementation_t **impp) {
+ dyndb_implementation_t *imp;
+
+ REQUIRE(impp != NULL && *impp != NULL);
+
+ imp = *impp;
+ *impp = NULL;
+
+ isc_mem_free(imp->mctx, imp->name);
+ isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t));
+}
+#else /* HAVE_DLFCN_H || _WIN32 */
+static isc_result_t
+load_library(isc_mem_t *mctx, const char *filename, const char *instname,
+ dyndb_implementation_t **impp) {
+ UNUSED(mctx);
+ UNUSED(filename);
+ UNUSED(instname);
+ UNUSED(impp);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB,
+ ISC_LOG_ERROR,
+ "dynamic database support is not implemented");
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static void
+unload_library(dyndb_implementation_t **impp) {
+ UNUSED(impp);
+}
+#endif /* HAVE_DLFCN_H */
+
+isc_result_t
+dns_dyndb_load(const char *libname, const char *name, const char *parameters,
+ const char *file, unsigned long line, isc_mem_t *mctx,
+ const dns_dyndbctx_t *dctx) {
+ isc_result_t result;
+ dyndb_implementation_t *implementation = NULL;
+
+ REQUIRE(DNS_DYNDBCTX_VALID(dctx));
+ REQUIRE(name != NULL);
+
+ RUNTIME_CHECK(isc_once_do(&once, dyndb_initialize) == ISC_R_SUCCESS);
+
+ LOCK(&dyndb_lock);
+
+ /* duplicate instance names are not allowed */
+ if (impfind(name) != NULL) {
+ CHECK(ISC_R_EXISTS);
+ }
+
+ CHECK(load_library(mctx, libname, name, &implementation));
+ CHECK(implementation->register_func(mctx, name, parameters, file, line,
+ dctx, &implementation->inst));
+
+ APPEND(dyndb_implementations, implementation, link);
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ if (implementation != NULL) {
+ unload_library(&implementation);
+ }
+ }
+
+ UNLOCK(&dyndb_lock);
+ return (result);
+}
+
+void
+dns_dyndb_cleanup(bool exiting) {
+ dyndb_implementation_t *elem;
+ dyndb_implementation_t *prev;
+
+ RUNTIME_CHECK(isc_once_do(&once, dyndb_initialize) == ISC_R_SUCCESS);
+
+ LOCK(&dyndb_lock);
+ elem = TAIL(dyndb_implementations);
+ while (elem != NULL) {
+ prev = PREV(elem, link);
+ UNLINK(dyndb_implementations, elem, link);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DYNDB, ISC_LOG_INFO,
+ "unloading DynDB instance '%s'", elem->name);
+ elem->destroy_func(&elem->inst);
+ ENSURE(elem->inst == NULL);
+ unload_library(&elem);
+ elem = prev;
+ }
+ UNLOCK(&dyndb_lock);
+
+ if (exiting) {
+ isc_mutex_destroy(&dyndb_lock);
+ }
+}
+
+isc_result_t
+dns_dyndb_createctx(isc_mem_t *mctx, const void *hashinit, isc_log_t *lctx,
+ dns_view_t *view, dns_zonemgr_t *zmgr, isc_task_t *task,
+ isc_timermgr_t *tmgr, dns_dyndbctx_t **dctxp) {
+ dns_dyndbctx_t *dctx;
+
+ REQUIRE(dctxp != NULL && *dctxp == NULL);
+
+ dctx = isc_mem_get(mctx, sizeof(*dctx));
+
+ memset(dctx, 0, sizeof(*dctx));
+ if (view != NULL) {
+ dns_view_attach(view, &dctx->view);
+ }
+ if (zmgr != NULL) {
+ dns_zonemgr_attach(zmgr, &dctx->zmgr);
+ }
+ if (task != NULL) {
+ isc_task_attach(task, &dctx->task);
+ }
+ dctx->timermgr = tmgr;
+ dctx->hashinit = hashinit;
+ dctx->lctx = lctx;
+ dctx->memdebug = &isc_mem_debugging;
+
+ isc_mem_attach(mctx, &dctx->mctx);
+ dctx->magic = DNS_DYNDBCTX_MAGIC;
+
+ *dctxp = dctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_dyndb_destroyctx(dns_dyndbctx_t **dctxp) {
+ dns_dyndbctx_t *dctx;
+
+ REQUIRE(dctxp != NULL && DNS_DYNDBCTX_VALID(*dctxp));
+
+ dctx = *dctxp;
+ *dctxp = NULL;
+
+ dctx->magic = 0;
+
+ if (dctx->view != NULL) {
+ dns_view_detach(&dctx->view);
+ }
+ if (dctx->zmgr != NULL) {
+ dns_zonemgr_detach(&dctx->zmgr);
+ }
+ if (dctx->task != NULL) {
+ isc_task_detach(&dctx->task);
+ }
+ dctx->timermgr = NULL;
+ dctx->lctx = NULL;
+
+ isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(*dctx));
+}
diff --git a/lib/dns/ecdb.c b/lib/dns/ecdb.c
new file mode 100644
index 0000000..abbb8d9
--- /dev/null
+++ b/lib/dns/ecdb.c
@@ -0,0 +1,797 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/ecdb.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdataslab.h>
+
+#define ECDB_MAGIC ISC_MAGIC('E', 'C', 'D', 'B')
+#define VALID_ECDB(db) ((db) != NULL && (db)->common.impmagic == ECDB_MAGIC)
+
+#define ECDBNODE_MAGIC ISC_MAGIC('E', 'C', 'D', 'N')
+#define VALID_ECDBNODE(ecdbn) ISC_MAGIC_VALID(ecdbn, ECDBNODE_MAGIC)
+
+/*%
+ * The 'ephemeral' cache DB (ecdb) implementation. An ecdb just provides
+ * temporary storage for ongoing name resolution with the common DB interfaces.
+ * It actually doesn't cache anything. The implementation expects any stored
+ * data is released within a short period, and does not care about the
+ * scalability in terms of the number of nodes.
+ */
+
+typedef struct dns_ecdb {
+ /* Unlocked */
+ dns_db_t common;
+ isc_mutex_t lock;
+
+ /* Protected by atomics */
+ isc_refcount_t references;
+
+ /* Locked */
+ ISC_LIST(struct dns_ecdbnode) nodes;
+} dns_ecdb_t;
+
+typedef struct dns_ecdbnode {
+ /* Unlocked */
+ unsigned int magic;
+ isc_mutex_t lock;
+ dns_ecdb_t *ecdb;
+ dns_name_t name;
+ ISC_LINK(struct dns_ecdbnode) link;
+
+ /* Locked */
+ ISC_LIST(struct rdatasetheader) rdatasets;
+
+ /* Protected by atomics */
+ isc_refcount_t references;
+} dns_ecdbnode_t;
+
+typedef struct rdatasetheader {
+ dns_rdatatype_t type;
+ dns_ttl_t ttl;
+ dns_trust_t trust;
+ dns_rdatatype_t covers;
+ unsigned int attributes;
+
+ ISC_LINK(struct rdatasetheader) link;
+} rdatasetheader_t;
+
+/* Copied from rbtdb.c */
+#define RDATASET_ATTR_NXDOMAIN 0x0010
+#define RDATASET_ATTR_NEGATIVE 0x0100
+#define NXDOMAIN(header) (((header)->attributes & RDATASET_ATTR_NXDOMAIN) != 0)
+#define NEGATIVE(header) (((header)->attributes & RDATASET_ATTR_NEGATIVE) != 0)
+
+static isc_result_t
+dns_ecdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
+ dns_rdataclass_t rdclass, unsigned int argc, char *argv[],
+ void *driverarg, dns_db_t **dbp);
+
+static void
+rdataset_disassociate(dns_rdataset_t *rdataset);
+static isc_result_t
+rdataset_first(dns_rdataset_t *rdataset);
+static isc_result_t
+rdataset_next(dns_rdataset_t *rdataset);
+static void
+rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata);
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target);
+static unsigned int
+rdataset_count(dns_rdataset_t *rdataset);
+static void
+rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust);
+
+static dns_rdatasetmethods_t rdataset_methods = {
+ rdataset_disassociate,
+ rdataset_first,
+ rdataset_next,
+ rdataset_current,
+ rdataset_clone,
+ rdataset_count,
+ NULL, /* addnoqname */
+ NULL, /* getnoqname */
+ NULL, /* addclosest */
+ NULL, /* getclosest */
+ rdataset_settrust, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL, /* setownercase */
+ NULL, /* getownercase */
+ NULL /* addglue */
+};
+
+typedef struct ecdb_rdatasetiter {
+ dns_rdatasetiter_t common;
+ rdatasetheader_t *current;
+} ecdb_rdatasetiter_t;
+
+static void
+rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp);
+static isc_result_t
+rdatasetiter_first(dns_rdatasetiter_t *iterator);
+static isc_result_t
+rdatasetiter_next(dns_rdatasetiter_t *iterator);
+static void
+rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset);
+
+static dns_rdatasetitermethods_t rdatasetiter_methods = {
+ rdatasetiter_destroy, rdatasetiter_first, rdatasetiter_next,
+ rdatasetiter_current
+};
+
+isc_result_t
+dns_ecdb_register(isc_mem_t *mctx, dns_dbimplementation_t **dbimp) {
+ REQUIRE(mctx != NULL);
+ REQUIRE(dbimp != NULL && *dbimp == NULL);
+
+ return (dns_db_register("ecdb", dns_ecdb_create, NULL, mctx, dbimp));
+}
+
+void
+dns_ecdb_unregister(dns_dbimplementation_t **dbimp) {
+ REQUIRE(dbimp != NULL && *dbimp != NULL);
+
+ dns_db_unregister(dbimp);
+}
+
+/*%
+ * DB routines
+ */
+
+static void
+attach(dns_db_t *source, dns_db_t **targetp) {
+ dns_ecdb_t *ecdb = (dns_ecdb_t *)source;
+
+ REQUIRE(VALID_ECDB(ecdb));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&ecdb->references);
+
+ *targetp = source;
+}
+
+static void
+destroy_ecdb(dns_ecdb_t *ecdb) {
+ if (isc_refcount_decrement(&ecdb->references) == 1) {
+ isc_refcount_destroy(&ecdb->references);
+
+ INSIST(ISC_LIST_EMPTY(ecdb->nodes));
+
+ if (dns_name_dynamic(&ecdb->common.origin)) {
+ dns_name_free(&ecdb->common.origin, ecdb->common.mctx);
+ }
+
+ isc_mutex_destroy(&ecdb->lock);
+
+ ecdb->common.impmagic = 0;
+ ecdb->common.magic = 0;
+
+ isc_mem_putanddetach(&ecdb->common.mctx, ecdb, sizeof(*ecdb));
+ }
+}
+
+static void
+detach(dns_db_t **dbp) {
+ dns_ecdb_t *ecdb;
+
+ REQUIRE(dbp != NULL);
+ ecdb = (dns_ecdb_t *)*dbp;
+ REQUIRE(VALID_ECDB(ecdb));
+
+ *dbp = NULL;
+
+ destroy_ecdb(ecdb);
+}
+
+static void
+attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) {
+ dns_ecdb_t *ecdb = (dns_ecdb_t *)db;
+ dns_ecdbnode_t *node = (dns_ecdbnode_t *)source;
+
+ REQUIRE(VALID_ECDB(ecdb));
+ REQUIRE(VALID_ECDBNODE(node));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&node->references);
+ isc_refcount_increment(&node->references);
+
+ *targetp = node;
+}
+
+static void
+destroynode(dns_ecdbnode_t *node) {
+ isc_mem_t *mctx;
+ dns_ecdb_t *ecdb = node->ecdb;
+ rdatasetheader_t *header;
+
+ mctx = ecdb->common.mctx;
+
+ LOCK(&ecdb->lock);
+ ISC_LIST_UNLINK(ecdb->nodes, node, link);
+ UNLOCK(&ecdb->lock);
+
+ dns_name_free(&node->name, mctx);
+
+ while ((header = ISC_LIST_HEAD(node->rdatasets)) != NULL) {
+ unsigned int headersize;
+
+ ISC_LIST_UNLINK(node->rdatasets, header, link);
+ headersize = dns_rdataslab_size((unsigned char *)header,
+ sizeof(*header));
+ isc_mem_put(mctx, header, headersize);
+ }
+
+ isc_mutex_destroy(&node->lock);
+ isc_refcount_destroy(&node->references);
+
+ node->magic = 0;
+ isc_mem_put(mctx, node, sizeof(*node));
+
+ destroy_ecdb(ecdb);
+}
+
+static void
+detachnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ dns_ecdb_t *ecdb = (dns_ecdb_t *)db;
+ dns_ecdbnode_t *node;
+
+ REQUIRE(VALID_ECDB(ecdb));
+ REQUIRE(nodep != NULL);
+ node = (dns_ecdbnode_t *)*nodep;
+ REQUIRE(VALID_ECDBNODE(node));
+ *nodep = NULL;
+
+ if (isc_refcount_decrement(&node->references) == 1) {
+ destroynode(node);
+ }
+}
+
+static isc_result_t
+find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ dns_ecdb_t *ecdb = (dns_ecdb_t *)db;
+
+ REQUIRE(VALID_ECDB(ecdb));
+
+ UNUSED(name);
+ UNUSED(version);
+ UNUSED(type);
+ UNUSED(options);
+ UNUSED(now);
+ UNUSED(nodep);
+ UNUSED(foundname);
+ UNUSED(rdataset);
+ UNUSED(sigrdataset);
+
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_name_t *dcname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ dns_ecdb_t *ecdb = (dns_ecdb_t *)db;
+
+ REQUIRE(VALID_ECDB(ecdb));
+
+ UNUSED(name);
+ UNUSED(options);
+ UNUSED(now);
+ UNUSED(nodep);
+ UNUSED(foundname);
+ UNUSED(dcname);
+ UNUSED(rdataset);
+ UNUSED(sigrdataset);
+
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+findnode(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep) {
+ dns_ecdb_t *ecdb = (dns_ecdb_t *)db;
+ isc_mem_t *mctx;
+ dns_ecdbnode_t *node;
+
+ REQUIRE(VALID_ECDB(ecdb));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ UNUSED(name);
+
+ if (create != true) {
+ /* an 'ephemeral' node is never reused. */
+ return (ISC_R_NOTFOUND);
+ }
+
+ mctx = ecdb->common.mctx;
+ node = isc_mem_get(mctx, sizeof(*node));
+
+ isc_mutex_init(&node->lock);
+
+ dns_name_init(&node->name, NULL);
+ dns_name_dup(name, mctx, &node->name);
+
+ isc_refcount_init(&node->references, 1);
+ ISC_LIST_INIT(node->rdatasets);
+
+ ISC_LINK_INIT(node, link);
+
+ isc_refcount_increment(&ecdb->references);
+ node->ecdb = ecdb;
+
+ LOCK(&ecdb->lock);
+ ISC_LIST_APPEND(ecdb->nodes, node, link);
+ UNLOCK(&ecdb->lock);
+
+ node->magic = ECDBNODE_MAGIC;
+
+ *nodep = node;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+bind_rdataset(dns_ecdb_t *ecdb, dns_ecdbnode_t *node, rdatasetheader_t *header,
+ dns_rdataset_t *rdataset) {
+ unsigned char *raw;
+
+ /*
+ * Caller must be holding the node lock.
+ */
+
+ REQUIRE(!dns_rdataset_isassociated(rdataset));
+
+ rdataset->methods = &rdataset_methods;
+ rdataset->rdclass = ecdb->common.rdclass;
+ rdataset->type = header->type;
+ rdataset->covers = header->covers;
+ rdataset->ttl = header->ttl;
+ rdataset->trust = header->trust;
+ if (NXDOMAIN(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_NXDOMAIN;
+ }
+ if (NEGATIVE(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_NEGATIVE;
+ }
+
+ rdataset->private1 = ecdb;
+ rdataset->private2 = node;
+ raw = (unsigned char *)header + sizeof(*header);
+ rdataset->private3 = raw;
+ rdataset->count = 0;
+
+ /*
+ * Reset iterator state.
+ */
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+
+ isc_refcount_increment(&node->references);
+}
+
+static isc_result_t
+addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *addedrdataset) {
+ dns_ecdb_t *ecdb = (dns_ecdb_t *)db;
+ isc_region_t r;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_mem_t *mctx;
+ dns_ecdbnode_t *ecdbnode = (dns_ecdbnode_t *)node;
+ rdatasetheader_t *header;
+
+ REQUIRE(VALID_ECDB(ecdb));
+ REQUIRE(VALID_ECDBNODE(ecdbnode));
+
+ UNUSED(version);
+ UNUSED(now);
+ UNUSED(options);
+
+ mctx = ecdb->common.mctx;
+
+ LOCK(&ecdbnode->lock);
+
+ /*
+ * Sanity check: this implementation does not allow overriding an
+ * existing rdataset of the same type.
+ */
+ for (header = ISC_LIST_HEAD(ecdbnode->rdatasets); header != NULL;
+ header = ISC_LIST_NEXT(header, link))
+ {
+ INSIST(header->type != rdataset->type ||
+ header->covers != rdataset->covers);
+ }
+
+ result = dns_rdataslab_fromrdataset(rdataset, mctx, &r,
+ sizeof(rdatasetheader_t));
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+
+ header = (rdatasetheader_t *)r.base;
+ header->type = rdataset->type;
+ header->ttl = rdataset->ttl;
+ header->trust = rdataset->trust;
+ header->covers = rdataset->covers;
+ header->attributes = 0;
+ if ((rdataset->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) {
+ header->attributes |= RDATASET_ATTR_NXDOMAIN;
+ }
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ header->attributes |= RDATASET_ATTR_NEGATIVE;
+ }
+ ISC_LINK_INIT(header, link);
+ ISC_LIST_APPEND(ecdbnode->rdatasets, header, link);
+
+ if (addedrdataset == NULL) {
+ goto unlock;
+ }
+
+ bind_rdataset(ecdb, ecdbnode, header, addedrdataset);
+
+unlock:
+ UNLOCK(&ecdbnode->lock);
+
+ return (result);
+}
+
+static isc_result_t
+deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(version);
+ UNUSED(type);
+ UNUSED(covers);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+createiterator(dns_db_t *db, unsigned int options,
+ dns_dbiterator_t **iteratorp) {
+ UNUSED(db);
+ UNUSED(options);
+ UNUSED(iteratorp);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ dns_ecdb_t *ecdb = (dns_ecdb_t *)db;
+ dns_ecdbnode_t *ecdbnode = (dns_ecdbnode_t *)node;
+ isc_mem_t *mctx;
+ ecdb_rdatasetiter_t *iterator;
+
+ REQUIRE(VALID_ECDB(ecdb));
+ REQUIRE(VALID_ECDBNODE(ecdbnode));
+
+ mctx = ecdb->common.mctx;
+
+ iterator = isc_mem_get(mctx, sizeof(ecdb_rdatasetiter_t));
+
+ iterator->common.magic = DNS_RDATASETITER_MAGIC;
+ iterator->common.methods = &rdatasetiter_methods;
+ iterator->common.db = db;
+ iterator->common.node = NULL;
+ attachnode(db, node, &iterator->common.node);
+ iterator->common.version = version;
+ iterator->common.options = options;
+ iterator->common.now = now;
+
+ *iteratorp = (dns_rdatasetiter_t *)iterator;
+
+ return (ISC_R_SUCCESS);
+}
+
+static dns_dbmethods_t ecdb_methods = {
+ attach,
+ detach,
+ NULL, /* beginload */
+ NULL, /* endload */
+ NULL, /* serialize */
+ NULL, /* dump */
+ NULL, /* currentversion */
+ NULL, /* newversion */
+ NULL, /* attachversion */
+ NULL, /* closeversion */
+ findnode,
+ find,
+ findzonecut,
+ attachnode,
+ detachnode,
+ NULL, /* expirenode */
+ NULL, /* printnode */
+ createiterator, /* createiterator */
+ NULL, /* findrdataset */
+ allrdatasets,
+ addrdataset,
+ NULL, /* subtractrdataset */
+ deleterdataset,
+ NULL, /* issecure */
+ NULL, /* nodecount */
+ NULL, /* ispersistent */
+ NULL, /* overmem */
+ NULL, /* settask */
+ NULL, /* getoriginnode */
+ NULL, /* transfernode */
+ NULL, /* getnsec3parameters */
+ NULL, /* findnsec3node */
+ NULL, /* setsigningtime */
+ NULL, /* getsigningtime */
+ NULL, /* resigned */
+ NULL, /* isdnssec */
+ NULL, /* getrrsetstats */
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ NULL, /* findnodeext */
+ NULL, /* findext */
+ NULL, /* setcachestats */
+ NULL, /* hashsize */
+ NULL, /* nodefullname */
+ NULL, /* getsize */
+ NULL, /* setservestalettl */
+ NULL, /* getservestalettl */
+ NULL /* setgluecachestats */
+};
+
+static isc_result_t
+dns_ecdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
+ dns_rdataclass_t rdclass, unsigned int argc, char *argv[],
+ void *driverarg, dns_db_t **dbp) {
+ dns_ecdb_t *ecdb;
+ isc_result_t result;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(origin == dns_rootname);
+ REQUIRE(type == dns_dbtype_cache);
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ UNUSED(argc);
+ UNUSED(argv);
+ UNUSED(driverarg);
+
+ ecdb = isc_mem_get(mctx, sizeof(*ecdb));
+
+ ecdb->common.attributes = DNS_DBATTR_CACHE;
+ ecdb->common.rdclass = rdclass;
+ ecdb->common.methods = &ecdb_methods;
+ dns_name_init(&ecdb->common.origin, NULL);
+ result = dns_name_dupwithoffsets(origin, mctx, &ecdb->common.origin);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(mctx, ecdb, sizeof(*ecdb));
+ return (result);
+ }
+
+ isc_mutex_init(&ecdb->lock);
+
+ isc_refcount_init(&ecdb->references, 1);
+ ISC_LIST_INIT(ecdb->nodes);
+
+ ecdb->common.mctx = NULL;
+ isc_mem_attach(mctx, &ecdb->common.mctx);
+ ecdb->common.impmagic = ECDB_MAGIC;
+ ecdb->common.magic = DNS_DB_MAGIC;
+
+ *dbp = (dns_db_t *)ecdb;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Rdataset Methods
+ */
+
+static void
+rdataset_disassociate(dns_rdataset_t *rdataset) {
+ dns_db_t *db = rdataset->private1;
+ dns_dbnode_t *node = rdataset->private2;
+
+ dns_db_detachnode(db, &node);
+}
+
+static isc_result_t
+rdataset_first(dns_rdataset_t *rdataset) {
+ unsigned char *raw = rdataset->private3;
+ unsigned int count;
+
+ count = raw[0] * 256 + raw[1];
+ if (count == 0) {
+ rdataset->private5 = NULL;
+ return (ISC_R_NOMORE);
+ }
+#if DNS_RDATASET_FIXED
+ raw += 2 + (4 * count);
+#else /* if DNS_RDATASET_FIXED */
+ raw += 2;
+#endif /* if DNS_RDATASET_FIXED */
+ /*
+ * The privateuint4 field is the number of rdata beyond the cursor
+ * position, so we decrement the total count by one before storing
+ * it.
+ */
+ count--;
+ rdataset->privateuint4 = count;
+ rdataset->private5 = raw;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdataset_next(dns_rdataset_t *rdataset) {
+ unsigned int count;
+ unsigned int length;
+ unsigned char *raw;
+
+ count = rdataset->privateuint4;
+ if (count == 0) {
+ return (ISC_R_NOMORE);
+ }
+ count--;
+ rdataset->privateuint4 = count;
+ raw = rdataset->private5;
+ length = raw[0] * 256 + raw[1];
+#if DNS_RDATASET_FIXED
+ raw += length + 4;
+#else /* if DNS_RDATASET_FIXED */
+ raw += length + 2;
+#endif /* if DNS_RDATASET_FIXED */
+ rdataset->private5 = raw;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ unsigned char *raw = rdataset->private5;
+ isc_region_t r;
+ unsigned int length;
+ unsigned int flags = 0;
+
+ REQUIRE(raw != NULL);
+
+ length = raw[0] * 256 + raw[1];
+#if DNS_RDATASET_FIXED
+ raw += 4;
+#else /* if DNS_RDATASET_FIXED */
+ raw += 2;
+#endif /* if DNS_RDATASET_FIXED */
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ if ((*raw & DNS_RDATASLAB_OFFLINE) != 0) {
+ flags |= DNS_RDATA_OFFLINE;
+ }
+ length--;
+ raw++;
+ }
+ r.length = length;
+ r.base = raw;
+ dns_rdata_fromregion(rdata, rdataset->rdclass, rdataset->type, &r);
+ rdata->flags |= flags;
+}
+
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ dns_db_t *db = source->private1;
+ dns_dbnode_t *node = source->private2;
+ dns_dbnode_t *cloned_node = NULL;
+
+ attachnode(db, node, &cloned_node);
+ *target = *source;
+
+ /*
+ * Reset iterator state.
+ */
+ target->privateuint4 = 0;
+ target->private5 = NULL;
+}
+
+static unsigned int
+rdataset_count(dns_rdataset_t *rdataset) {
+ unsigned char *raw = rdataset->private3;
+ unsigned int count;
+
+ count = raw[0] * 256 + raw[1];
+
+ return (count);
+}
+
+static void
+rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) {
+ rdatasetheader_t *header = rdataset->private3;
+
+ header--;
+ header->trust = rdataset->trust = trust;
+}
+
+/*
+ * Rdataset Iterator Methods
+ */
+
+static void
+rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
+ isc_mem_t *mctx;
+ union {
+ dns_rdatasetiter_t *rdatasetiterator;
+ ecdb_rdatasetiter_t *ecdbiterator;
+ } u;
+
+ REQUIRE(iteratorp != NULL);
+ REQUIRE(DNS_RDATASETITER_VALID(*iteratorp));
+
+ u.rdatasetiterator = *iteratorp;
+ *iteratorp = NULL;
+
+ mctx = u.ecdbiterator->common.db->mctx;
+ u.ecdbiterator->common.magic = 0;
+
+ dns_db_detachnode(u.ecdbiterator->common.db,
+ &u.ecdbiterator->common.node);
+ isc_mem_put(mctx, u.ecdbiterator, sizeof(ecdb_rdatasetiter_t));
+}
+
+static isc_result_t
+rdatasetiter_first(dns_rdatasetiter_t *iterator) {
+ REQUIRE(DNS_RDATASETITER_VALID(iterator));
+
+ ecdb_rdatasetiter_t *ecdbiterator = (ecdb_rdatasetiter_t *)iterator;
+ dns_ecdbnode_t *ecdbnode = (dns_ecdbnode_t *)iterator->node;
+
+ if (ISC_LIST_EMPTY(ecdbnode->rdatasets)) {
+ return (ISC_R_NOMORE);
+ }
+ ecdbiterator->current = ISC_LIST_HEAD(ecdbnode->rdatasets);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdatasetiter_next(dns_rdatasetiter_t *iterator) {
+ REQUIRE(DNS_RDATASETITER_VALID(iterator));
+
+ ecdb_rdatasetiter_t *ecdbiterator = (ecdb_rdatasetiter_t *)iterator;
+
+ ecdbiterator->current = ISC_LIST_NEXT(ecdbiterator->current, link);
+ if (ecdbiterator->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static void
+rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset) {
+ ecdb_rdatasetiter_t *ecdbiterator = (ecdb_rdatasetiter_t *)iterator;
+ dns_ecdb_t *ecdb;
+
+ ecdb = (dns_ecdb_t *)iterator->db;
+ REQUIRE(VALID_ECDB(ecdb));
+
+ bind_rdataset(ecdb, iterator->node, ecdbiterator->current, rdataset);
+}
diff --git a/lib/dns/ecs.c b/lib/dns/ecs.c
new file mode 100644
index 0000000..676c740
--- /dev/null
+++ b/lib/dns/ecs.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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..e59f23c
--- /dev/null
+++ b/lib/dns/forward.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/rwlock.h>
+#include <isc/util.h>
+
+#include <dns/forward.h>
+#include <dns/rbt.h>
+#include <dns/result.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;
+ fwd->dscp = -1;
+ ISC_LINK_INIT(fwd, link);
+ ISC_LIST_APPEND(forwarders->fwdrs, fwd, link);
+ }
+ forwarders->fwdpolicy = fwdpolicy;
+
+ RWLOCK(&fwdtable->rwlock, isc_rwlocktype_write);
+ result = dns_rbt_addname(fwdtable->table, name, forwarders);
+ RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_write);
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ while (!ISC_LIST_EMPTY(forwarders->fwdrs)) {
+ fwd = ISC_LIST_HEAD(forwarders->fwdrs);
+ ISC_LIST_UNLINK(forwarders->fwdrs, fwd, link);
+ isc_mem_put(fwdtable->mctx, fwd, sizeof(*fwd));
+ }
+ isc_mem_put(fwdtable->mctx, forwarders, sizeof(*forwarders));
+ return (result);
+}
+
+isc_result_t
+dns_fwdtable_delete(dns_fwdtable_t *fwdtable, const dns_name_t *name) {
+ isc_result_t result;
+
+ REQUIRE(VALID_FWDTABLE(fwdtable));
+
+ RWLOCK(&fwdtable->rwlock, isc_rwlocktype_write);
+ result = dns_rbt_deletename(fwdtable->table, name, false);
+ RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_write);
+
+ if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_fwdtable_find(dns_fwdtable_t *fwdtable, const dns_name_t *name,
+ dns_name_t *foundname, dns_forwarders_t **forwardersp) {
+ isc_result_t result;
+
+ REQUIRE(VALID_FWDTABLE(fwdtable));
+
+ RWLOCK(&fwdtable->rwlock, isc_rwlocktype_read);
+
+ result = dns_rbt_findname(fwdtable->table, name, 0, foundname,
+ (void **)forwardersp);
+ if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_SUCCESS;
+ }
+
+ RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+void
+dns_fwdtable_destroy(dns_fwdtable_t **fwdtablep) {
+ dns_fwdtable_t *fwdtable;
+
+ REQUIRE(fwdtablep != NULL && VALID_FWDTABLE(*fwdtablep));
+
+ fwdtable = *fwdtablep;
+ *fwdtablep = NULL;
+
+ dns_rbt_destroy(&fwdtable->table);
+ isc_rwlock_destroy(&fwdtable->rwlock);
+ fwdtable->magic = 0;
+
+ isc_mem_putanddetach(&fwdtable->mctx, fwdtable, sizeof(*fwdtable));
+}
+
+/***
+ *** Private
+ ***/
+
+static void
+auto_detach(void *data, void *arg) {
+ dns_forwarders_t *forwarders = data;
+ dns_fwdtable_t *fwdtable = arg;
+ dns_forwarder_t *fwd;
+
+ UNUSED(arg);
+
+ while (!ISC_LIST_EMPTY(forwarders->fwdrs)) {
+ fwd = ISC_LIST_HEAD(forwarders->fwdrs);
+ ISC_LIST_UNLINK(forwarders->fwdrs, fwd, link);
+ isc_mem_put(fwdtable->mctx, fwd, sizeof(*fwd));
+ }
+ isc_mem_put(fwdtable->mctx, forwarders, sizeof(*forwarders));
+}
diff --git a/lib/dns/gen-unix.h b/lib/dns/gen-unix.h
new file mode 100644
index 0000000..5974635
--- /dev/null
+++ b/lib/dns/gen-unix.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file
+ * \brief
+ * This file is responsible for defining two operations that are not
+ * directly portable between Unix-like systems and Windows NT, option
+ * parsing and directory scanning. It is here because it was decided
+ * that the "gen" build utility was not to depend on libisc.a, so
+ * the functions declared in isc/commandline.h and isc/dir.h could not
+ * be used.
+ *
+ * The commandline stuff is really just a wrapper around getopt().
+ * The dir stuff was shrunk to fit the needs of gen.c.
+ */
+
+#ifndef DNS_GEN_UNIX_H
+#define DNS_GEN_UNIX_H 1
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h> /* Required on some systems for dirent.h. */
+#include <unistd.h> /* XXXDCL Required for ?. */
+
+#include <isc/lang.h>
+
+#ifdef NEED_OPTARG
+extern char *optarg;
+#endif /* ifdef NEED_OPTARG */
+
+#define isc_commandline_parse getopt
+#define isc_commandline_argument optarg
+
+typedef struct {
+ DIR *handle;
+ char *filename;
+} isc_dir_t;
+
+ISC_LANG_BEGINDECLS
+
+static bool
+start_directory(const char *path, isc_dir_t *dir) {
+ dir->handle = opendir(path);
+
+ if (dir->handle != NULL) {
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+static bool
+next_file(isc_dir_t *dir) {
+ struct dirent *dirent;
+
+ dir->filename = NULL;
+
+ if (dir->handle != NULL) {
+ errno = 0;
+ dirent = readdir(dir->handle);
+ if (dirent != NULL) {
+ dir->filename = dirent->d_name;
+ } else {
+ if (errno != 0) {
+ exit(1);
+ }
+ }
+ }
+
+ if (dir->filename != NULL) {
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+static void
+end_directory(isc_dir_t *dir) {
+ if (dir->handle != NULL) {
+ (void)closedir(dir->handle);
+ }
+
+ dir->handle = NULL;
+}
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_GEN_UNIX_H */
diff --git a/lib/dns/gen-win32.h b/lib/dns/gen-win32.h
new file mode 100644
index 0000000..1718295
--- /dev/null
+++ b/lib/dns/gen-win32.h
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * \note This file was adapted from the NetBSD project's source tree, RCS ID:
+ * NetBSD: getopt.c,v 1.15 1999/09/20 04:39:37 lukem Exp
+ *
+ * The primary change has been to rename items to the ISC namespace
+ * and format in the ISC coding style.
+ *
+ * This file is responsible for defining two operations that are not
+ * directly portable between Unix-like systems and Windows NT, option
+ * parsing and directory scanning. It is here because it was decided
+ * that the "gen" build utility was not to depend on libisc.a, so
+ * the functions declared in isc/commandline.h and isc/dir.h could not
+ * be used.
+ *
+ * The commandline stuff is pretty much a straight copy from the initial
+ * isc/commandline.c. The dir stuff was shrunk to fit the needs of gen.c.
+ */
+
+#ifndef DNS_GEN_WIN32_H
+#define DNS_GEN_WIN32_H 1
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <windows.h>
+
+#include <isc/lang.h>
+
+int isc_commandline_index = 1; /* Index into parent argv vector. */
+int isc_commandline_option; /* Character checked for validity. */
+
+char *isc_commandline_argument; /* Argument associated with option. */
+char *isc_commandline_progname; /* For printing error messages. */
+
+bool isc_commandline_errprint = true; /* Print error messages. */
+bool isc_commandline_reset = true; /* Reset processing. */
+
+#define BADOPT '?'
+#define BADARG ':'
+#define ENDOPT ""
+
+ISC_LANG_BEGINDECLS
+
+/*
+ * getopt --
+ * Parse argc/argv argument vector.
+ */
+int
+isc_commandline_parse(int argc, char *const *argv, const char *options) {
+ static char *place = ENDOPT;
+ char *option; /* Index into *options of option. */
+
+ /*
+ * Update scanning pointer, either because a reset was requested or
+ * the previous argv was finished.
+ */
+ if (isc_commandline_reset || *place == '\0') {
+ isc_commandline_reset = false;
+
+ if (isc_commandline_progname == NULL) {
+ isc_commandline_progname = argv[0];
+ }
+
+ if (isc_commandline_index >= argc ||
+ *(place = argv[isc_commandline_index]) != '-')
+ {
+ /*
+ * Index out of range or points to non-option.
+ */
+ place = ENDOPT;
+ return (-1);
+ }
+
+ if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
+ /*
+ * Found '--' to signal end of options. Advance
+ * index to next argv, the first non-option.
+ */
+ isc_commandline_index++;
+ place = ENDOPT;
+ return (-1);
+ }
+ }
+
+ isc_commandline_option = *place++;
+ option = strchr(options, isc_commandline_option);
+
+ /*
+ * Ensure valid option has been passed as specified by options string.
+ * '-:' is never a valid command line option because it could not
+ * distinguish ':' from the argument specifier in the options string.
+ */
+ if (isc_commandline_option == ':' || option == NULL) {
+ if (*place == '\0') {
+ isc_commandline_index++;
+ }
+
+ if (isc_commandline_errprint && *options != ':') {
+ fprintf(stderr, "%s: illegal option -- %c\n",
+ isc_commandline_progname,
+ isc_commandline_option);
+ }
+
+ return (BADOPT);
+ }
+
+ if (*++option != ':') {
+ /*
+ * Option does not take an argument.
+ */
+ isc_commandline_argument = NULL;
+
+ /*
+ * Skip to next argv if at the end of the current argv.
+ */
+ if (*place == '\0') {
+ ++isc_commandline_index;
+ }
+ } else {
+ /*
+ * Option needs an argument.
+ */
+ if (*place != '\0') {
+ /*
+ * Option is in this argv, -D1 style.
+ */
+ isc_commandline_argument = place;
+ } else if (argc > ++isc_commandline_index) {
+ /*
+ * Option is next argv, -D 1 style.
+ */
+ isc_commandline_argument = argv[isc_commandline_index];
+ } else {
+ /*
+ * Argument needed, but no more argv.
+ */
+ place = ENDOPT;
+
+ /*
+ * Silent failure with "missing argument" return
+ * when ':' starts options string, per historical spec.
+ */
+ if (*options == ':') {
+ return (BADARG);
+ }
+
+ if (isc_commandline_errprint) {
+ fprintf(stderr,
+ "%s: option requires an argument -- "
+ "%c\n",
+ isc_commandline_progname,
+ isc_commandline_option);
+ }
+
+ return (BADOPT);
+ }
+
+ place = ENDOPT;
+
+ /*
+ * Point to argv that follows argument.
+ */
+ isc_commandline_index++;
+ }
+
+ return (isc_commandline_option);
+}
+
+typedef struct {
+ HANDLE handle;
+ WIN32_FIND_DATA find_data;
+ bool first_file;
+ char *filename;
+} isc_dir_t;
+
+bool
+start_directory(const char *path, isc_dir_t *dir) {
+ char pattern[_MAX_PATH], *p;
+
+ /*
+ * Need space for slash-splat and final NUL.
+ */
+ if (strlen(path) + 3 > sizeof(pattern)) {
+ return (false);
+ }
+
+ strcpy(pattern, path);
+
+ /*
+ * Append slash (if needed) and splat.
+ */
+ p = pattern + strlen(pattern);
+ if (p != pattern && p[-1] != '\\' && p[-1] != ':') {
+ *p++ = '\\';
+ }
+ *p++ = '*';
+ *p++ = '\0';
+
+ dir->first_file = true;
+
+ dir->handle = FindFirstFile(pattern, &dir->find_data);
+
+ if (dir->handle == INVALID_HANDLE_VALUE) {
+ dir->filename = NULL;
+ return (false);
+ } else {
+ dir->filename = dir->find_data.cFileName;
+ return (true);
+ }
+}
+
+bool
+next_file(isc_dir_t *dir) {
+ if (dir->first_file) {
+ dir->first_file = false;
+ } else if (dir->handle != INVALID_HANDLE_VALUE) {
+ if (FindNextFile(dir->handle, &dir->find_data) == TRUE) {
+ dir->filename = dir->find_data.cFileName;
+ } else {
+ dir->filename = NULL;
+ }
+ } else {
+ dir->filename = NULL;
+ }
+
+ if (dir->filename != NULL) {
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+void
+end_directory(isc_dir_t *dir) {
+ if (dir->handle != INVALID_HANDLE_VALUE) {
+ FindClose(dir->handle);
+ }
+}
+
+inline struct tm *
+gmtime_r(const time_t *clock, struct tm *result) {
+ errno_t ret = gmtime_s(result, clock);
+ if (ret != 0) {
+ errno = ret;
+ return (NULL);
+ }
+ return (result);
+}
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_GEN_WIN32_H */
diff --git a/lib/dns/gen.c b/lib/dns/gen.c
new file mode 100644
index 0000000..6d40858
--- /dev/null
+++ b/lib/dns/gen.c
@@ -0,0 +1,1028 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#ifdef WIN32
+/*
+ * Silence compiler warnings about using strcpy and friends.
+ */
+#define _CRT_SECURE_NO_DEPRECATE 1
+/*
+ * We use snprintf which was defined late in Windows even it is in C99.
+ */
+#if _MSC_VER < 1900
+#define snprintf _snprintf
+#endif /* if _MSC_VER < 1900 */
+#endif /* ifdef WIN32 */
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+
+#ifndef PATH_MAX
+#define PATH_MAX 1024
+#endif /* ifndef PATH_MAX */
+
+#ifdef WIN32
+#include "gen-win32.h"
+#else /* ifdef WIN32 */
+#include "gen-unix.h"
+#endif /* ifdef WIN32 */
+
+#ifndef ULLONG_MAX
+#define ULLONG_MAX (~0ULL)
+#endif /* ifndef ULLONG_MAX */
+
+#define INSIST(cond) \
+ if (!(cond)) { \
+ fprintf(stderr, "%s:%d: INSIST(%s)\n", __FILE__, __LINE__, \
+ #cond); \
+ abort(); \
+ }
+
+#define FROMTEXTARGS "rdclass, type, lexer, origin, options, target, callbacks"
+#define FROMTEXTCLASS "rdclass"
+#define FROMTEXTTYPE "type"
+#define FROMTEXTDEF "result = DNS_R_UNKNOWN"
+
+#define TOTEXTARGS "rdata, tctx, target"
+#define TOTEXTCLASS "rdata->rdclass"
+#define TOTEXTTYPE "rdata->type"
+#define TOTEXTDEF "use_default = true"
+
+#define FROMWIREARGS "rdclass, type, source, dctx, options, target"
+#define FROMWIRECLASS "rdclass"
+#define FROMWIRETYPE "type"
+#define FROMWIREDEF "use_default = true"
+
+#define TOWIREARGS "rdata, cctx, target"
+#define TOWIRECLASS "rdata->rdclass"
+#define TOWIRETYPE "rdata->type"
+#define TOWIREDEF "use_default = true"
+
+#define FROMSTRUCTARGS "rdclass, type, source, target"
+#define FROMSTRUCTCLASS "rdclass"
+#define FROMSTRUCTTYPE "type"
+#define FROMSTRUCTDEF "use_default = true"
+
+#define TOSTRUCTARGS "rdata, target, mctx"
+#define TOSTRUCTCLASS "rdata->rdclass"
+#define TOSTRUCTTYPE "rdata->type"
+#define TOSTRUCTDEF "use_default = true"
+
+#define FREESTRUCTARGS "source"
+#define FREESTRUCTCLASS "common->rdclass"
+#define FREESTRUCTTYPE "common->rdtype"
+#define FREESTRUCTDEF NULL
+
+#define COMPAREARGS "rdata1, rdata2"
+#define COMPARECLASS "rdata1->rdclass"
+#define COMPARETYPE "rdata1->type"
+#define COMPAREDEF "use_default = true"
+
+#define ADDITIONALDATAARGS "rdata, add, arg"
+#define ADDITIONALDATACLASS "rdata->rdclass"
+#define ADDITIONALDATATYPE "rdata->type"
+#define ADDITIONALDATADEF "use_default = true"
+
+#define DIGESTARGS "rdata, digest, arg"
+#define DIGESTCLASS "rdata->rdclass"
+#define DIGESTTYPE "rdata->type"
+#define DIGESTDEF "use_default = true"
+
+#define CHECKOWNERARGS "name, rdclass, type, wildcard"
+#define CHECKOWNERCLASS "rdclass"
+#define CHECKOWNERTYPE "type"
+#define CHECKOWNERDEF "result = true"
+
+#define CHECKNAMESARGS "rdata, owner, bad"
+#define CHECKNAMESCLASS "rdata->rdclass"
+#define CHECKNAMESTYPE "rdata->type"
+#define CHECKNAMESDEF "result = true"
+
+static const char copyright[] = "/*\n"
+ " * Copyright (C) 1998%s Internet Systems "
+ "Consortium, Inc. (\"ISC\")\n"
+ " *\n"
+ " * This Source Code Form is subject to the "
+ "terms of the Mozilla Public\n"
+ " * License, v. 2.0. If a copy of the MPL was "
+ "not distributed with this\n"
+ " * file, you can obtain one at "
+ "https://mozilla.org/MPL/2.0/.\n"
+ " */\n"
+ "\n"
+ "/***************\n"
+ " ***************\n"
+ " *************** THIS FILE IS AUTOMATICALLY "
+ "GENERATED BY gen.c.\n"
+ " *************** DO NOT EDIT!\n"
+ " ***************\n"
+ " ***************/\n"
+ "\n"
+ "/*! \\file */\n"
+ "\n";
+
+#define STR_EXPAND(tok) #tok
+#define STR(tok) STR_EXPAND(tok)
+
+#define TYPENAMES 256
+#define TYPECLASSLEN 20 /* DNS mnemonic size. Must be less than 100. */
+#define TYPECLASSBUF (TYPECLASSLEN + 1)
+#define TYPECLASSFMT "%" STR(TYPECLASSLEN) "[-0-9a-z]_%d"
+#define ATTRIBUTESIZE 256
+
+static struct cc {
+ struct cc *next;
+ int rdclass;
+ char classbuf[TYPECLASSBUF];
+} *classes;
+
+static struct tt {
+ struct tt *next;
+ uint16_t rdclass;
+ uint16_t type;
+ char classbuf[TYPECLASSBUF];
+ char typebuf[TYPECLASSBUF];
+ char dirbuf[PATH_MAX - 30];
+} *types;
+
+static struct ttnam {
+ char typebuf[TYPECLASSBUF];
+ char macroname[TYPECLASSBUF];
+ char attr[ATTRIBUTESIZE];
+ unsigned int sorted;
+ uint16_t type;
+} typenames[TYPENAMES];
+
+static int maxtype = -1;
+
+static char *
+upper(char *);
+static char *
+funname(const char *, char *);
+static void
+doswitch(const char *, const char *, const char *, const char *, const char *,
+ const char *);
+static void
+add(int, const char *, int, const char *, const char *);
+static void
+sd(int, const char *, const char *, char);
+static void
+insert_into_typenames(int, const char *, const char *);
+
+/*%
+ * If you use more than 10 of these in, say, a printf(), you'll have problems.
+ */
+static char *
+upper(char *s) {
+ static int buf_to_use = 0;
+ static char buf[10][256];
+ char *b;
+ int c;
+
+ buf_to_use++;
+ if (buf_to_use > 9) {
+ buf_to_use = 0;
+ }
+
+ b = buf[buf_to_use];
+ memset(b, 0, 256);
+
+ while ((c = (*s++) & 0xff)) {
+ *b++ = islower(c) ? toupper(c) : c;
+ }
+ *b = '\0';
+ return (buf[buf_to_use]);
+}
+
+static char *
+funname(const char *s, char *buf) {
+ char *b = buf;
+ char c;
+
+ INSIST(strlen(s) < TYPECLASSBUF);
+ while ((c = *s++)) {
+ *b++ = (c == '-') ? '_' : c;
+ }
+ *b = '\0';
+ return (buf);
+}
+
+static void
+doswitch(const char *name, const char *function, const char *args,
+ const char *tsw, const char *csw, const char *res) {
+ struct tt *tt;
+ int first = 1;
+ int lasttype = 0;
+ int subswitch = 0;
+ char buf1[TYPECLASSBUF], buf2[TYPECLASSBUF];
+ const char *result = " result =";
+
+ if (res == NULL) {
+ result = "";
+ }
+
+ for (tt = types; tt != NULL; tt = tt->next) {
+ if (first) {
+ fprintf(stdout, "\n#define %s \\\n", name);
+ fprintf(stdout, "\tswitch (%s) { \\\n" /*}*/, tsw);
+ first = 0;
+ }
+ if (tt->type != lasttype && subswitch) {
+ if (res == NULL) {
+ fprintf(stdout, "\t\tdefault: break; \\\n");
+ } else {
+ fprintf(stdout, "\t\tdefault: %s; break; \\\n",
+ res);
+ }
+ fputs(/*{*/ "\t\t} \\\n", stdout);
+ fputs("\t\tbreak; \\\n", stdout);
+ subswitch = 0;
+ }
+ if (tt->rdclass && tt->type != lasttype) {
+ fprintf(stdout, "\tcase %d: switch (%s) { \\\n" /*}*/,
+ tt->type, csw);
+ subswitch = 1;
+ }
+ if (tt->rdclass == 0) {
+ fprintf(stdout, "\tcase %d:%s %s_%s(%s); break;",
+ tt->type, result, function,
+ funname(tt->typebuf, buf1), args);
+ } else {
+ fprintf(stdout, "\t\tcase %d:%s %s_%s_%s(%s); break;",
+ tt->rdclass, result, function,
+ funname(tt->classbuf, buf1),
+ funname(tt->typebuf, buf2), args);
+ }
+ fputs(" \\\n", stdout);
+ lasttype = tt->type;
+ }
+ if (subswitch) {
+ if (res == NULL) {
+ fprintf(stdout, "\t\tdefault: break; \\\n");
+ } else {
+ fprintf(stdout, "\t\tdefault: %s; break; \\\n", res);
+ }
+ fputs(/*{*/ "\t\t} \\\n", stdout);
+ fputs("\t\tbreak; \\\n", stdout);
+ }
+ if (first) {
+ if (res == NULL) {
+ fprintf(stdout, "\n#define %s\n", name);
+ } else {
+ fprintf(stdout, "\n#define %s %s;\n", name, res);
+ }
+ } else {
+ if (res == NULL) {
+ fprintf(stdout, "\tdefault: break; \\\n");
+ } else {
+ fprintf(stdout, "\tdefault: %s; break; \\\n", res);
+ }
+ fputs(/*{*/ "\t}\n", stdout);
+ }
+}
+
+static struct ttnam *
+find_typename(int type) {
+ int i;
+
+ for (i = 0; i < TYPENAMES; i++) {
+ if (typenames[i].typebuf[0] != 0 && typenames[i].type == type) {
+ return (&typenames[i]);
+ }
+ }
+ return (NULL);
+}
+
+static void
+insert_into_typenames(int type, const char *typebuf, const char *attr) {
+ struct ttnam *ttn = NULL;
+ size_t c;
+ int i, n;
+ char tmp[256];
+
+ INSIST(strlen(typebuf) < TYPECLASSBUF);
+ for (i = 0; i < TYPENAMES; i++) {
+ if (typenames[i].typebuf[0] != 0 && typenames[i].type == type &&
+ strcmp(typebuf, typenames[i].typebuf) != 0)
+ {
+ fprintf(stderr,
+ "Error: type %d has two names: %s, %s\n", type,
+ typenames[i].typebuf, typebuf);
+ exit(1);
+ }
+ if (typenames[i].typebuf[0] == 0 && ttn == NULL) {
+ ttn = &typenames[i];
+ }
+ }
+ if (ttn == NULL) {
+ fprintf(stderr, "Error: typenames array too small\n");
+ exit(1);
+ }
+
+ /* XXXMUKS: This is redundant due to the INSIST above. */
+ if (strlen(typebuf) > sizeof(ttn->typebuf) - 1) {
+ fprintf(stderr, "Error: type name %s is too long\n", typebuf);
+ exit(1);
+ }
+
+ strncpy(ttn->typebuf, typebuf, sizeof(ttn->typebuf));
+ ttn->typebuf[sizeof(ttn->typebuf) - 1] = '\0';
+
+ strncpy(ttn->macroname, ttn->typebuf, sizeof(ttn->macroname));
+ ttn->macroname[sizeof(ttn->macroname) - 1] = '\0';
+
+ ttn->type = type;
+ c = strlen(ttn->macroname);
+ while (c > 0) {
+ if (ttn->macroname[c - 1] == '-') {
+ ttn->macroname[c - 1] = '_';
+ }
+ c--;
+ }
+
+ if (attr == NULL) {
+ n = snprintf(tmp, sizeof(tmp), "RRTYPE_%s_ATTRIBUTES",
+ upper(ttn->macroname));
+ INSIST(n > 0 && (unsigned)n < sizeof(tmp));
+ attr = tmp;
+ }
+
+ if (ttn->attr[0] != 0 && strcmp(attr, ttn->attr) != 0) {
+ fprintf(stderr,
+ "Error: type %d has different attributes: "
+ "%s, %s\n",
+ type, ttn->attr, attr);
+ exit(1);
+ }
+
+ if (strlen(attr) > sizeof(ttn->attr) - 1) {
+ fprintf(stderr, "Error: attr (%s) [name %s] is too long\n",
+ attr, typebuf);
+ exit(1);
+ }
+
+ strncpy(ttn->attr, attr, sizeof(ttn->attr));
+ ttn->attr[sizeof(ttn->attr) - 1] = '\0';
+
+ ttn->sorted = 0;
+ if (maxtype < type) {
+ maxtype = type;
+ }
+}
+
+static void
+add(int rdclass, const char *classbuf, int type, const char *typebuf,
+ const char *dirbuf) {
+ struct tt *newtt = (struct tt *)malloc(sizeof(*newtt));
+ struct tt *tt, *oldtt;
+ struct cc *newcc;
+ struct cc *cc, *oldcc;
+
+ INSIST(strlen(typebuf) < TYPECLASSBUF);
+ INSIST(strlen(classbuf) < TYPECLASSBUF);
+ INSIST(strlen(dirbuf) < PATH_MAX);
+
+ insert_into_typenames(type, typebuf, NULL);
+
+ if (newtt == NULL) {
+ fprintf(stderr, "malloc() failed\n");
+ exit(1);
+ }
+
+ newtt->next = NULL;
+ newtt->rdclass = rdclass;
+ newtt->type = type;
+
+ strncpy(newtt->classbuf, classbuf, sizeof(newtt->classbuf));
+ newtt->classbuf[sizeof(newtt->classbuf) - 1] = '\0';
+
+ strncpy(newtt->typebuf, typebuf, sizeof(newtt->typebuf));
+ newtt->typebuf[sizeof(newtt->typebuf) - 1] = '\0';
+
+ if (strncmp(dirbuf, "./", 2) == 0) {
+ dirbuf += 2;
+ }
+ strncpy(newtt->dirbuf, dirbuf, sizeof(newtt->dirbuf));
+ newtt->dirbuf[sizeof(newtt->dirbuf) - 1] = '\0';
+
+ tt = types;
+ oldtt = NULL;
+
+ while ((tt != NULL) && (tt->type < type)) {
+ oldtt = tt;
+ tt = tt->next;
+ }
+
+ while ((tt != NULL) && (tt->type == type) && (tt->rdclass < rdclass)) {
+ if (strcmp(tt->typebuf, typebuf) != 0) {
+ exit(1);
+ }
+ oldtt = tt;
+ tt = tt->next;
+ }
+
+ if ((tt != NULL) && (tt->type == type) && (tt->rdclass == rdclass)) {
+ exit(1);
+ }
+
+ newtt->next = tt;
+ if (oldtt != NULL) {
+ oldtt->next = newtt;
+ } else {
+ types = newtt;
+ }
+
+ /*
+ * Do a class switch for this type.
+ */
+ if (rdclass == 0) {
+ return;
+ }
+
+ newcc = (struct cc *)malloc(sizeof(*newcc));
+ if (newcc == NULL) {
+ fprintf(stderr, "malloc() failed\n");
+ exit(1);
+ }
+ newcc->rdclass = rdclass;
+ strncpy(newcc->classbuf, classbuf, sizeof(newcc->classbuf));
+ newcc->classbuf[sizeof(newcc->classbuf) - 1] = '\0';
+ cc = classes;
+ oldcc = NULL;
+
+ while ((cc != NULL) && (cc->rdclass < rdclass)) {
+ oldcc = cc;
+ cc = cc->next;
+ }
+
+ if ((cc != NULL) && cc->rdclass == rdclass) {
+ free((char *)newcc);
+ return;
+ }
+
+ newcc->next = cc;
+ if (oldcc != NULL) {
+ oldcc->next = newcc;
+ } else {
+ classes = newcc;
+ }
+}
+
+static void
+sd(int rdclass, const char *classbuf, const char *dirbuf, char filetype) {
+ char buf[TYPECLASSLEN + sizeof("_65535.h")];
+ char typebuf[TYPECLASSBUF];
+ int type, n;
+ isc_dir_t dir;
+
+ if (!start_directory(dirbuf, &dir)) {
+ return;
+ }
+
+ while (next_file(&dir)) {
+ if (sscanf(dir.filename, TYPECLASSFMT, typebuf, &type) != 2) {
+ continue;
+ }
+ if ((type > 65535) || (type < 0)) {
+ continue;
+ }
+
+ n = snprintf(buf, sizeof(buf), "%s_%d.%c", typebuf, type,
+ filetype);
+ INSIST(n > 0 && (unsigned)n < sizeof(buf));
+ if (strcmp(buf, dir.filename) != 0) {
+ continue;
+ }
+ add(rdclass, classbuf, type, typebuf, dirbuf);
+ }
+
+ end_directory(&dir);
+}
+
+static unsigned int
+HASH(char *string) {
+ size_t n;
+ unsigned char a, b;
+
+ n = strlen(string);
+ if (n == 0) {
+ fprintf(stderr, "n == 0?\n");
+ exit(1);
+ }
+ a = tolower((unsigned char)string[0]);
+ b = tolower((unsigned char)string[n - 1]);
+
+ return (((a + n) * b) % 256);
+}
+
+int
+main(int argc, char **argv) {
+ char buf[PATH_MAX];
+ char srcdir[PATH_MAX];
+ int rdclass;
+ char classbuf[TYPECLASSBUF];
+ struct tt *tt;
+ struct cc *cc;
+ struct ttnam *ttn, *ttn2;
+ unsigned int hash;
+ time_t now;
+ char year[11];
+ int lasttype;
+ int code = 1;
+ int class_enum = 0;
+ int type_enum = 0;
+ int structs = 0;
+ int depend = 0;
+ int c, i, j, n;
+ char buf1[TYPECLASSBUF];
+ char filetype = 'c';
+ FILE *fd;
+ char *prefix = NULL;
+ char *suffix = NULL;
+ char *file = NULL;
+ char *source_date_epoch;
+ unsigned long long epoch;
+ char *endptr;
+ isc_dir_t dir;
+
+ for (i = 0; i < TYPENAMES; i++) {
+ memset(&typenames[i], 0, sizeof(typenames[i]));
+ }
+
+ srcdir[0] = '\0';
+ while ((c = isc_commandline_parse(argc, argv, "cdits:F:P:S:")) != -1) {
+ switch (c) {
+ case 'c':
+ code = 0;
+ depend = 0;
+ type_enum = 0;
+ class_enum = 1;
+ filetype = 'c';
+ structs = 0;
+ break;
+ case 'd':
+ code = 0;
+ depend = 1;
+ class_enum = 0;
+ type_enum = 0;
+ structs = 0;
+ filetype = 'h';
+ break;
+ case 't':
+ code = 0;
+ depend = 0;
+ class_enum = 0;
+ type_enum = 1;
+ filetype = 'c';
+ structs = 0;
+ break;
+ case 'i':
+ code = 0;
+ depend = 0;
+ class_enum = 0;
+ type_enum = 0;
+ structs = 1;
+ filetype = 'h';
+ break;
+ case 's':
+ if (strlen(isc_commandline_argument) >
+ PATH_MAX - 2 * TYPECLASSLEN -
+ sizeof("/rdata/_65535_65535"))
+ {
+ fprintf(stderr, "\"%s\" too long\n",
+ isc_commandline_argument);
+ exit(1);
+ }
+ n = snprintf(srcdir, sizeof(srcdir), "%s/",
+ isc_commandline_argument);
+ INSIST(n > 0 && (unsigned)n < sizeof(srcdir));
+ break;
+ case 'F':
+ file = isc_commandline_argument;
+ break;
+ case 'P':
+ prefix = isc_commandline_argument;
+ break;
+ case 'S':
+ suffix = isc_commandline_argument;
+ break;
+ case '?':
+ exit(1);
+ }
+ }
+
+ n = snprintf(buf, sizeof(buf), "%srdata", srcdir);
+ INSIST(n > 0 && (unsigned)n < sizeof(srcdir));
+
+ if (!start_directory(buf, &dir)) {
+ exit(1);
+ }
+
+ while (next_file(&dir)) {
+ if (sscanf(dir.filename, TYPECLASSFMT, classbuf, &rdclass) != 2)
+ {
+ continue;
+ }
+ if ((rdclass > 65535) || (rdclass < 0)) {
+ continue;
+ }
+
+ n = snprintf(buf, sizeof(buf), "%srdata/%s_%d", srcdir,
+ classbuf, rdclass);
+ INSIST(n > 0 && (unsigned)n < sizeof(buf));
+ if (strcmp(buf + 6 + strlen(srcdir), dir.filename) != 0) {
+ continue;
+ }
+ sd(rdclass, classbuf, buf, filetype);
+ }
+ end_directory(&dir);
+ n = snprintf(buf, sizeof(buf), "%srdata/generic", srcdir);
+ INSIST(n > 0 && (unsigned)n < sizeof(srcdir));
+ sd(0, "", buf, filetype);
+
+ source_date_epoch = getenv("SOURCE_DATE_EPOCH");
+ if (source_date_epoch) {
+ errno = 0;
+ epoch = strtoull(source_date_epoch, &endptr, 10);
+ if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0)) ||
+ (errno != 0 && epoch == 0))
+ {
+ fprintf(stderr,
+ "Environment variable "
+ "$SOURCE_DATE_EPOCH: strtoull: %s\n",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (endptr == source_date_epoch) {
+ fprintf(stderr,
+ "Environment variable "
+ "$SOURCE_DATE_EPOCH: "
+ "No digits were found: %s\n",
+ endptr);
+ exit(EXIT_FAILURE);
+ }
+ if (*endptr != '\0') {
+ fprintf(stderr,
+ "Environment variable "
+ "$SOURCE_DATE_EPOCH: Trailing garbage: %s\n",
+ endptr);
+ exit(EXIT_FAILURE);
+ }
+ if (epoch > ULONG_MAX) {
+ fprintf(stderr,
+ "Environment variable "
+ "$SOURCE_DATE_EPOCH: value must be "
+ "smaller than or equal to: %lu but "
+ "was found to be: %llu \n",
+ ULONG_MAX, epoch);
+ exit(EXIT_FAILURE);
+ }
+ now = epoch;
+ } else {
+ time(&now);
+ }
+
+ if (now != -1) {
+ struct tm t, *tm = gmtime_r(&now, &t);
+
+ if (tm != NULL && tm->tm_year > 104) {
+ n = snprintf(year, sizeof(year), "-%d",
+ tm->tm_year + 1900);
+ INSIST(n > 0 && (unsigned)n < sizeof(year));
+ } else {
+ snprintf(year, sizeof(year), "-2016");
+ }
+ } else {
+ snprintf(year, sizeof(year), "-2016");
+ }
+
+ if (!depend) {
+ fprintf(stdout, copyright, year);
+ }
+
+ if (code) {
+ fputs("#ifndef DNS_CODE_H\n", stdout);
+ fputs("#define DNS_CODE_H 1\n\n", stdout);
+
+ fputs("#include <stdbool.h>\n", stdout);
+ fputs("#include <isc/result.h>\n\n", stdout);
+ fputs("#include <dns/name.h>\n\n", stdout);
+
+ for (tt = types; tt != NULL; tt = tt->next) {
+ fprintf(stdout, "#include \"%s/%s_%d.c\"\n", tt->dirbuf,
+ tt->typebuf, tt->type);
+ }
+
+ fputs("\n\n", stdout);
+
+ doswitch("FROMTEXTSWITCH", "fromtext", FROMTEXTARGS,
+ FROMTEXTTYPE, FROMTEXTCLASS, FROMTEXTDEF);
+ doswitch("TOTEXTSWITCH", "totext", TOTEXTARGS, TOTEXTTYPE,
+ TOTEXTCLASS, TOTEXTDEF);
+ doswitch("FROMWIRESWITCH", "fromwire", FROMWIREARGS,
+ FROMWIRETYPE, FROMWIRECLASS, FROMWIREDEF);
+ doswitch("TOWIRESWITCH", "towire", TOWIREARGS, TOWIRETYPE,
+ TOWIRECLASS, TOWIREDEF);
+ doswitch("COMPARESWITCH", "compare", COMPAREARGS, COMPARETYPE,
+ COMPARECLASS, COMPAREDEF);
+ doswitch("CASECOMPARESWITCH", "casecompare", COMPAREARGS,
+ COMPARETYPE, COMPARECLASS, COMPAREDEF);
+ doswitch("FROMSTRUCTSWITCH", "fromstruct", FROMSTRUCTARGS,
+ FROMSTRUCTTYPE, FROMSTRUCTCLASS, FROMSTRUCTDEF);
+ doswitch("TOSTRUCTSWITCH", "tostruct", TOSTRUCTARGS,
+ TOSTRUCTTYPE, TOSTRUCTCLASS, TOSTRUCTDEF);
+ doswitch("FREESTRUCTSWITCH", "freestruct", FREESTRUCTARGS,
+ FREESTRUCTTYPE, FREESTRUCTCLASS, FREESTRUCTDEF);
+ doswitch("ADDITIONALDATASWITCH", "additionaldata",
+ ADDITIONALDATAARGS, ADDITIONALDATATYPE,
+ ADDITIONALDATACLASS, ADDITIONALDATADEF);
+ doswitch("DIGESTSWITCH", "digest", DIGESTARGS, DIGESTTYPE,
+ DIGESTCLASS, DIGESTDEF);
+ doswitch("CHECKOWNERSWITCH", "checkowner", CHECKOWNERARGS,
+ CHECKOWNERTYPE, CHECKOWNERCLASS, CHECKOWNERDEF);
+ doswitch("CHECKNAMESSWITCH", "checknames", CHECKNAMESARGS,
+ CHECKNAMESTYPE, CHECKNAMESCLASS, CHECKNAMESDEF);
+
+ /*
+ * From here down, we are processing the rdata names and
+ * attributes.
+ */
+
+#define PRINT_COMMA(x) (x == maxtype ? "" : ",")
+
+#define METANOTQUESTION \
+ "DNS_RDATATYPEATTR_META | " \
+ "DNS_RDATATYPEATTR_NOTQUESTION"
+#define METAQUESTIONONLY \
+ "DNS_RDATATYPEATTR_META | " \
+ "DNS_RDATATYPEATTR_QUESTIONONLY"
+#define RESERVEDNAME "0"
+#define RESERVED "DNS_RDATATYPEATTR_RESERVED"
+
+ /*
+ * Add in reserved/special types. This will let us
+ * sort them without special cases.
+ */
+ insert_into_typenames(100, "uinfo", RESERVEDNAME);
+ insert_into_typenames(101, "uid", RESERVEDNAME);
+ insert_into_typenames(102, "gid", RESERVEDNAME);
+ insert_into_typenames(103, "unspec", RESERVEDNAME);
+ insert_into_typenames(251, "ixfr", METAQUESTIONONLY);
+ insert_into_typenames(252, "axfr", METAQUESTIONONLY);
+ insert_into_typenames(253, "mailb", METAQUESTIONONLY);
+ insert_into_typenames(254, "maila", METAQUESTIONONLY);
+ insert_into_typenames(255, "any", METAQUESTIONONLY);
+
+ /*
+ * Spit out a quick and dirty hash function. Here,
+ * we walk through the list of type names, and calculate
+ * a hash. This isn't perfect, but it will generate "pretty
+ * good" estimates. Lowercase the characters before
+ * computing in all cases.
+ *
+ * Here, walk the list from top to bottom, calculating
+ * the hash (mod 256) for each name.
+ */
+ fprintf(stdout, "#define RDATATYPE_COMPARE(_s, _d, _tn, _n, "
+ "_tp) \\\n");
+ fprintf(stdout, "\tdo { \\\n");
+ fprintf(stdout, "\t\tif (sizeof(_s) - 1 == _n && \\\n"
+ "\t\t strncasecmp(_s,(_tn),"
+ "(sizeof(_s) - 1)) == 0) { \\\n");
+ fprintf(stdout, "\t\t\tif ((dns_rdatatype_attributes(_d) & "
+ "DNS_RDATATYPEATTR_RESERVED) != 0) \\\n");
+ fprintf(stdout, "\t\t\t\treturn (ISC_R_NOTIMPLEMENTED); \\\n");
+ fprintf(stdout, "\t\t\t*(_tp) = _d; \\\n");
+ fprintf(stdout, "\t\t\treturn (ISC_R_SUCCESS); \\\n");
+ fprintf(stdout, "\t\t} \\\n");
+ fprintf(stdout, "\t} while (0)\n\n");
+
+ fprintf(stdout, "#define RDATATYPE_FROMTEXT_SW(_hash,"
+ "_typename,_length,_typep) \\\n");
+ fprintf(stdout, "\tswitch (_hash) { \\\n");
+ for (i = 0; i <= maxtype; i++) {
+ ttn = find_typename(i);
+ if (ttn == NULL) {
+ continue;
+ }
+
+ /*
+ * Skip entries we already processed.
+ */
+ if (ttn->sorted != 0) {
+ continue;
+ }
+
+ hash = HASH(ttn->typebuf);
+ fprintf(stdout, "\t\tcase %u: \\\n", hash);
+
+ /*
+ * Find all other entries that happen to match
+ * this hash.
+ */
+ for (j = 0; j <= maxtype; j++) {
+ ttn2 = find_typename(j);
+ if (ttn2 == NULL) {
+ continue;
+ }
+ if (hash == HASH(ttn2->typebuf)) {
+ fprintf(stdout,
+ "\t\t\t"
+ "RDATATYPE_COMPARE"
+ "(\"%s\", %d, _typename, "
+ " _length, _typep); \\\n",
+ ttn2->typebuf, ttn2->type);
+ ttn2->sorted = 1;
+ }
+ }
+ fprintf(stdout, "\t\t\tbreak; \\\n");
+ }
+ fprintf(stdout, "\t}\n");
+
+ fprintf(stdout, "#define RDATATYPE_ATTRIBUTE_SW \\\n");
+ fprintf(stdout, "\tswitch (type) { \\\n");
+ for (i = 0; i <= maxtype; i++) {
+ ttn = find_typename(i);
+ if (ttn == NULL) {
+ continue;
+ }
+ fprintf(stdout, "\tcase %d: return (%s); \\\n", i,
+ upper(ttn->attr));
+ }
+ fprintf(stdout, "\t}\n");
+
+ fprintf(stdout, "#define RDATATYPE_TOTEXT_SW \\\n");
+ fprintf(stdout, "\tswitch (type) { \\\n");
+ for (i = 0; i <= maxtype; i++) {
+ ttn = find_typename(i);
+ if (ttn == NULL) {
+ continue;
+ }
+ /*
+ * Remove KEYDATA (65533) from the type to memonic
+ * translation as it is internal use only. This
+ * stops the tools from displaying KEYDATA instead
+ * of TYPE65533.
+ */
+ if (i == 65533U) {
+ continue;
+ }
+ fprintf(stdout,
+ "\tcase %d: return "
+ "(str_totext(\"%s\", target)); \\\n",
+ i, upper(ttn->typebuf));
+ }
+ fprintf(stdout, "\t}\n");
+
+ fputs("#endif /* DNS_CODE_H */\n", stdout);
+ } else if (type_enum) {
+ char *s;
+
+ fprintf(stdout, "#ifndef DNS_ENUMTYPE_H\n");
+ fprintf(stdout, "#define DNS_ENUMTYPE_H 1\n\n");
+
+ fprintf(stdout, "enum {\n");
+ fprintf(stdout, "\tdns_rdatatype_none = 0,\n");
+
+ lasttype = 0;
+ for (tt = types; tt != NULL; tt = tt->next) {
+ if (tt->type != lasttype) {
+ fprintf(stdout, "\tdns_rdatatype_%s = %d,\n",
+ funname(tt->typebuf, buf1),
+ lasttype = tt->type);
+ }
+ }
+
+ fprintf(stdout, "\tdns_rdatatype_ixfr = 251,\n");
+ fprintf(stdout, "\tdns_rdatatype_axfr = 252,\n");
+ fprintf(stdout, "\tdns_rdatatype_mailb = 253,\n");
+ fprintf(stdout, "\tdns_rdatatype_maila = 254,\n");
+ fprintf(stdout, "\tdns_rdatatype_any = 255\n");
+
+ fprintf(stdout, "};\n\n");
+
+ fprintf(stdout, "#define dns_rdatatype_none\t"
+ "((dns_rdatatype_t)dns_rdatatype_none)\n");
+
+ for (tt = types; tt != NULL; tt = tt->next) {
+ if (tt->type != lasttype) {
+ s = funname(tt->typebuf, buf1);
+ fprintf(stdout,
+ "#define dns_rdatatype_%s\t%s"
+ "((dns_rdatatype_t)dns_rdatatype_%s)"
+ "\n",
+ s, strlen(s) < 2U ? "\t" : "", s);
+ lasttype = tt->type;
+ }
+ }
+
+ fprintf(stdout, "#define dns_rdatatype_ixfr\t"
+ "((dns_rdatatype_t)dns_rdatatype_ixfr)\n");
+ fprintf(stdout, "#define dns_rdatatype_axfr\t"
+ "((dns_rdatatype_t)dns_rdatatype_axfr)\n");
+ fprintf(stdout, "#define dns_rdatatype_mailb\t"
+ "((dns_rdatatype_t)dns_rdatatype_mailb)\n");
+ fprintf(stdout, "#define dns_rdatatype_maila\t"
+ "((dns_rdatatype_t)dns_rdatatype_maila)\n");
+ fprintf(stdout, "#define dns_rdatatype_any\t"
+ "((dns_rdatatype_t)dns_rdatatype_any)\n");
+
+ fprintf(stdout, "\n#endif /* DNS_ENUMTYPE_H */\n");
+ } else if (class_enum) {
+ char *s;
+ int classnum;
+
+ fprintf(stdout, "#ifndef DNS_ENUMCLASS_H\n");
+ fprintf(stdout, "#define DNS_ENUMCLASS_H 1\n\n");
+
+ fprintf(stdout, "enum {\n");
+
+ fprintf(stdout, "\tdns_rdataclass_reserved0 = 0,\n");
+ fprintf(stdout, "#define dns_rdataclass_reserved0 \\\n\t\t\t\t"
+ "((dns_rdataclass_t)dns_rdataclass_reserved0)"
+ "\n");
+
+#define PRINTCLASS(name, num) \
+ do { \
+ s = funname(name, buf1); \
+ classnum = num; \
+ fprintf(stdout, "\tdns_rdataclass_%s = %d%s\n", s, classnum, \
+ classnum != 255 ? "," : ""); \
+ fprintf(stdout, \
+ "#define dns_rdataclass_%s\t" \
+ "((dns_rdataclass_t)dns_rdataclass_%s)\n", \
+ s, s); \
+ } while (0)
+
+ for (cc = classes; cc != NULL; cc = cc->next) {
+ if (cc->rdclass == 3) {
+ PRINTCLASS("chaos", 3);
+ } else if (cc->rdclass == 255) {
+ PRINTCLASS("none", 254);
+ }
+ PRINTCLASS(cc->classbuf, cc->rdclass);
+ }
+
+#undef PRINTCLASS
+
+ fprintf(stdout, "};\n\n");
+ fprintf(stdout, "#endif /* DNS_ENUMCLASS_H */\n");
+ } else if (structs) {
+ if (prefix != NULL) {
+ if ((fd = fopen(prefix, "r")) != NULL) {
+ while (fgets(buf, sizeof(buf), fd) != NULL) {
+ fputs(buf, stdout);
+ }
+ fclose(fd);
+ }
+ }
+ for (tt = types; tt != NULL; tt = tt->next) {
+ snprintf(buf, sizeof(buf), "%s/%s_%d.h", tt->dirbuf,
+ tt->typebuf, tt->type);
+ if ((fd = fopen(buf, "r")) != NULL) {
+ while (fgets(buf, sizeof(buf), fd) != NULL) {
+ fputs(buf, stdout);
+ }
+ fclose(fd);
+ }
+ }
+ if (suffix != NULL) {
+ if ((fd = fopen(suffix, "r")) != NULL) {
+ while (fgets(buf, sizeof(buf), fd) != NULL) {
+ fputs(buf, stdout);
+ }
+ fclose(fd);
+ }
+ }
+ } else if (depend) {
+ for (tt = types; tt != NULL; tt = tt->next) {
+ fprintf(stdout, "%s:\t%s/%s_%d.h\n", file, tt->dirbuf,
+ tt->typebuf, tt->type);
+ }
+ }
+
+ if (ferror(stdout) != 0) {
+ exit(1);
+ }
+
+ return (0);
+}
diff --git a/lib/dns/geoip2.c b/lib/dns/geoip2.c
new file mode 100644
index 0000000..94a3ed8
--- /dev/null
+++ b/lib/dns/geoip2.c
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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 <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>
+#ifndef WIN32
+#include <netinet/in.h>
+#else /* ifndef WIN32 */
+#ifndef _WINSOCKAPI_
+#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */
+#endif /* ifndef _WINSOCKAPI_ */
+#include <winsock2.h>
+#endif /* WIN32 */
+#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;
+
+ISC_THREAD_LOCAL geoip_state_t geoip_state = { 0 };
+
+static void
+set_state(const MMDB_s *db, const isc_netaddr_t *addr,
+ MMDB_lookup_result_s mmresult, MMDB_entry_s entry) {
+ geoip_state.db = db;
+ geoip_state.addr = *addr;
+ geoip_state.mmresult = mmresult;
+ geoip_state.entry = entry;
+}
+
+static geoip_state_t *
+get_entry_for(MMDB_s *const db, const isc_netaddr_t *addr) {
+ isc_sockaddr_t sa;
+ MMDB_lookup_result_s match;
+ int err;
+
+ if (db == geoip_state.db && isc_netaddr_equal(addr, &geoip_state.addr))
+ {
+ return (&geoip_state);
+ }
+
+ isc_sockaddr_fromnetaddr(&sa, addr, 0);
+ match = MMDB_lookup_sockaddr(db, &sa.type.sa, &err);
+ if (err != MMDB_SUCCESS || !match.found_entry) {
+ return (NULL);
+ }
+
+ set_state(db, addr, match, match.entry);
+
+ return (&geoip_state);
+}
+
+static dns_geoip_subtype_t
+fix_subtype(const dns_geoip_databases_t *geoip, dns_geoip_subtype_t subtype) {
+ dns_geoip_subtype_t ret = subtype;
+
+ switch (subtype) {
+ case dns_geoip_countrycode:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_countrycode;
+ } else if (geoip->country != NULL) {
+ ret = dns_geoip_country_code;
+ }
+ break;
+ case dns_geoip_countryname:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_countryname;
+ } else if (geoip->country != NULL) {
+ ret = dns_geoip_country_name;
+ }
+ break;
+ case dns_geoip_continentcode:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_continentcode;
+ } else if (geoip->country != NULL) {
+ ret = dns_geoip_country_continentcode;
+ }
+ break;
+ case dns_geoip_continent:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_continent;
+ } else if (geoip->country != NULL) {
+ ret = dns_geoip_country_continent;
+ }
+ break;
+ case dns_geoip_region:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_region;
+ }
+ break;
+ case dns_geoip_regionname:
+ if (geoip->city != NULL) {
+ ret = dns_geoip_city_regionname;
+ }
+ default:
+ break;
+ }
+
+ return (ret);
+}
+
+static MMDB_s *
+geoip2_database(const dns_geoip_databases_t *geoip,
+ dns_geoip_subtype_t subtype) {
+ switch (subtype) {
+ case dns_geoip_country_code:
+ case dns_geoip_country_name:
+ case dns_geoip_country_continentcode:
+ case dns_geoip_country_continent:
+ return (geoip->country);
+
+ case dns_geoip_city_countrycode:
+ case dns_geoip_city_countryname:
+ case dns_geoip_city_continentcode:
+ case dns_geoip_city_continent:
+ case dns_geoip_city_region:
+ case dns_geoip_city_regionname:
+ case dns_geoip_city_name:
+ case dns_geoip_city_postalcode:
+ case dns_geoip_city_timezonecode:
+ case dns_geoip_city_metrocode:
+ case dns_geoip_city_areacode:
+ return (geoip->city);
+
+ case dns_geoip_isp_name:
+ return (geoip->isp);
+
+ case dns_geoip_as_asnum:
+ case dns_geoip_org_name:
+ return (geoip->as);
+
+ case dns_geoip_domain_name:
+ return (geoip->domain);
+
+ default:
+ /*
+ * All other subtypes are unavailable in GeoIP2.
+ */
+ return (NULL);
+ }
+}
+
+static bool
+match_string(MMDB_entry_data_s *value, const char *str) {
+ REQUIRE(str != NULL);
+
+ if (value == NULL || !value->has_data ||
+ value->type != MMDB_DATA_TYPE_UTF8_STRING ||
+ value->utf8_string == NULL)
+ {
+ return (false);
+ }
+
+ return (strncasecmp(value->utf8_string, str, value->data_size) == 0);
+}
+
+static bool
+match_int(MMDB_entry_data_s *value, const uint32_t ui32) {
+ if (value == NULL || !value->has_data ||
+ (value->type != MMDB_DATA_TYPE_UINT32 &&
+ value->type != MMDB_DATA_TYPE_UINT16))
+ {
+ return (false);
+ }
+
+ return (value->uint32 == ui32);
+}
+
+bool
+dns_geoip_match(const isc_netaddr_t *reqaddr,
+ const dns_geoip_databases_t *geoip,
+ const dns_geoip_elem_t *elt) {
+ MMDB_s *db = NULL;
+ MMDB_entry_data_s value;
+ geoip_state_t *state = NULL;
+ dns_geoip_subtype_t subtype;
+ const char *s = NULL;
+ int ret;
+
+ REQUIRE(reqaddr != NULL);
+ REQUIRE(elt != NULL);
+ REQUIRE(geoip != NULL);
+
+ subtype = fix_subtype(geoip, elt->subtype);
+ db = geoip2_database(geoip, subtype);
+ if (db == NULL) {
+ return (false);
+ }
+
+ state = get_entry_for(db, reqaddr);
+ if (state == NULL) {
+ return (false);
+ }
+
+ switch (subtype) {
+ case dns_geoip_country_code:
+ case dns_geoip_city_countrycode:
+ ret = MMDB_get_value(&state->entry, &value, "country",
+ "iso_code", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_country_name:
+ case dns_geoip_city_countryname:
+ ret = MMDB_get_value(&state->entry, &value, "country", "names",
+ "en", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_country_continentcode:
+ case dns_geoip_city_continentcode:
+ ret = MMDB_get_value(&state->entry, &value, "continent", "code",
+ (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_country_continent:
+ case dns_geoip_city_continent:
+ ret = MMDB_get_value(&state->entry, &value, "continent",
+ "names", "en", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_region:
+ case dns_geoip_city_region:
+ ret = MMDB_get_value(&state->entry, &value, "subdivisions", "0",
+ "iso_code", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_regionname:
+ case dns_geoip_city_regionname:
+ ret = MMDB_get_value(&state->entry, &value, "subdivisions", "0",
+ "names", "en", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_city_name:
+ ret = MMDB_get_value(&state->entry, &value, "city", "names",
+ "en", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_city_postalcode:
+ ret = MMDB_get_value(&state->entry, &value, "postal", "code",
+ (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_city_timezonecode:
+ ret = MMDB_get_value(&state->entry, &value, "location",
+ "time_zone", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_city_metrocode:
+ ret = MMDB_get_value(&state->entry, &value, "location",
+ "metro_code", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_isp_name:
+ ret = MMDB_get_value(&state->entry, &value, "isp", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_as_asnum:
+ INSIST(elt->as_string != NULL);
+
+ ret = MMDB_get_value(&state->entry, &value,
+ "autonomous_system_number", (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ int i;
+ s = elt->as_string;
+ if (strncasecmp(s, "AS", 2) == 0) {
+ s += 2;
+ }
+ i = strtol(s, NULL, 10);
+ return (match_int(&value, i));
+ }
+ break;
+
+ case dns_geoip_org_name:
+ ret = MMDB_get_value(&state->entry, &value,
+ "autonomous_system_organization",
+ (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ case dns_geoip_domain_name:
+ ret = MMDB_get_value(&state->entry, &value, "domain",
+ (char *)0);
+ if (ret == MMDB_SUCCESS) {
+ return (match_string(&value, elt->as_string));
+ }
+ break;
+
+ default:
+ /*
+ * For any other subtype, we assume the database was
+ * unavailable and return false.
+ */
+ return (false);
+ }
+
+ /*
+ * No database matched: return false.
+ */
+ return (false);
+}
diff --git a/lib/dns/gssapi_link.c b/lib/dns/gssapi_link.c
new file mode 100644
index 0000000..966cc2c
--- /dev/null
+++ b/lib/dns/gssapi_link.c
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifdef GSSAPI
+
+#include <stdbool.h>
+
+#include <isc/base64.h>
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dst/gssapi.h>
+#include <dst/result.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);
+}
+
+#else /* ifdef GSSAPI */
+int gssapi_link_unneeded = 1;
+#endif /* ifdef GSSAPI */
+
+/*! \file */
diff --git a/lib/dns/gssapictx.c b/lib/dns/gssapictx.c
new file mode 100644
index 0000000..f2a64e8
--- /dev/null
+++ b/lib/dns/gssapictx.c
@@ -0,0 +1,957 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.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/platform.h>
+#include <isc/print.h>
+#include <isc/random.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/result.h>
+#include <dns/types.h>
+
+#include <dst/gssapi.h>
+#include <dst/result.h>
+
+#include "dst_internal.h"
+
+/*
+ * Solaris8 apparently needs an explicit OID set, and Solaris10 needs
+ * one for anything but Kerberos. Supplying an explicit OID set
+ * doesn't appear to hurt anything in other implementations, so we
+ * always use one. If we're not using our own SPNEGO implementation,
+ * we include SPNEGO's OID.
+ */
+#ifdef GSSAPI
+#ifdef WIN32
+#include <krb5/krb5.h>
+#else /* ifdef WIN32 */
+#include ISC_PLATFORM_KRB5HEADER
+#endif /* ifdef WIN32 */
+
+#ifndef GSS_KRB5_MECHANISM
+static unsigned char krb5_mech_oid_bytes[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x12, 0x01, 0x02, 0x02 };
+static gss_OID_desc __gss_krb5_mechanism_oid_desc = {
+ sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes
+};
+#define GSS_KRB5_MECHANISM (&__gss_krb5_mechanism_oid_desc)
+#endif /* ifndef GSS_KRB5_MECHANISM */
+
+#ifndef GSS_SPNEGO_MECHANISM
+static unsigned char spnego_mech_oid_bytes[] = { 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x02 };
+static gss_OID_desc __gss_spnego_mechanism_oid_desc = {
+ sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes
+};
+#define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc)
+#endif /* ifndef GSS_SPNEGO_MECHANISM */
+
+#define REGION_TO_GBUFFER(r, gb) \
+ do { \
+ (gb).length = (r).length; \
+ (gb).value = (r).base; \
+ } while (0)
+
+#define GBUFFER_TO_REGION(gb, r) \
+ do { \
+ (r).length = (unsigned int)(gb).length; \
+ (r).base = (gb).value; \
+ } while (0)
+
+#define RETERR(x) \
+ do { \
+ result = (x); \
+ if (result != ISC_R_SUCCESS) \
+ goto out; \
+ } while (0)
+
+static void
+name_to_gbuffer(const dns_name_t *name, isc_buffer_t *buffer,
+ gss_buffer_desc *gbuffer) {
+ dns_name_t tname;
+ const dns_name_t *namep;
+ isc_region_t r;
+ isc_result_t result;
+
+ if (!dns_name_isabsolute(name)) {
+ namep = name;
+ } else {
+ unsigned int labels;
+ dns_name_init(&tname, NULL);
+ labels = dns_name_countlabels(name);
+ dns_name_getlabelsequence(name, 0, labels - 1, &tname);
+ namep = &tname;
+ }
+
+ result = dns_name_toprincipal(namep, buffer);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_buffer_putuint8(buffer, 0);
+ isc_buffer_usedregion(buffer, &r);
+ REGION_TO_GBUFFER(r, *gbuffer);
+}
+
+static void
+log_cred(const dns_gss_cred_id_t cred) {
+ OM_uint32 gret, minor, lifetime;
+ gss_name_t gname;
+ gss_buffer_desc gbuffer;
+ gss_cred_usage_t usage;
+ const char *usage_text;
+ char buf[1024];
+
+ gret = gss_inquire_cred(&minor, (gss_cred_id_t)cred, &gname, &lifetime,
+ &usage, NULL);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_inquire_cred: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ return;
+ }
+
+ gret = gss_display_name(&minor, gname, &gbuffer, NULL);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_display_name: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ } else {
+ switch (usage) {
+ case GSS_C_BOTH:
+ usage_text = "GSS_C_BOTH";
+ break;
+ case GSS_C_INITIATE:
+ usage_text = "GSS_C_INITIATE";
+ break;
+ case GSS_C_ACCEPT:
+ usage_text = "GSS_C_ACCEPT";
+ break;
+ default:
+ usage_text = "???";
+ }
+ gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value,
+ usage_text, (unsigned long)lifetime);
+ }
+
+ if (gret == GSS_S_COMPLETE) {
+ if (gbuffer.length != 0U) {
+ gret = gss_release_buffer(&minor, &gbuffer);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_release_buffer: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ }
+ }
+ }
+
+ gret = gss_release_name(&minor, &gname);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_release_name: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ }
+}
+
+/*
+ * check for the most common configuration errors.
+ *
+ * The errors checked for are:
+ * - tkey-gssapi-credential doesn't start with DNS/
+ * - the default realm in /etc/krb5.conf and the
+ * tkey-gssapi-credential bind config option don't match
+ *
+ * Note that if tkey-gssapi-keytab is set then these configure checks
+ * are not performed, and runtime errors from gssapi are used instead
+ */
+static void
+check_config(const char *gss_name) {
+ const char *p;
+ krb5_context krb5_ctx;
+ char *krb5_realm_name = NULL;
+
+ if (strncasecmp(gss_name, "DNS/", 4) != 0) {
+ gss_log(ISC_LOG_ERROR,
+ "tkey-gssapi-credential (%s) "
+ "should start with 'DNS/'",
+ gss_name);
+ return;
+ }
+
+ if (krb5_init_context(&krb5_ctx) != 0) {
+ gss_log(ISC_LOG_ERROR, "Unable to initialise krb5 context");
+ return;
+ }
+ if (krb5_get_default_realm(krb5_ctx, &krb5_realm_name) != 0) {
+ gss_log(ISC_LOG_ERROR, "Unable to get krb5 default realm");
+ krb5_free_context(krb5_ctx);
+ return;
+ }
+ p = strchr(gss_name, '@');
+ if (p == NULL) {
+ gss_log(ISC_LOG_ERROR,
+ "badly formatted "
+ "tkey-gssapi-credentials (%s)",
+ gss_name);
+ krb5_free_context(krb5_ctx);
+ return;
+ }
+ if (strcasecmp(p + 1, krb5_realm_name) != 0) {
+ gss_log(ISC_LOG_ERROR,
+ "default realm from krb5.conf (%s) "
+ "does not match tkey-gssapi-credential (%s)",
+ krb5_realm_name, gss_name);
+ krb5_free_context(krb5_ctx);
+ return;
+ }
+ krb5_free_context(krb5_ctx);
+}
+
+static OM_uint32
+mech_oid_set_create(OM_uint32 *minor, gss_OID_set *mech_oid_set) {
+ OM_uint32 gret;
+
+ gret = gss_create_empty_oid_set(minor, mech_oid_set);
+ if (gret != GSS_S_COMPLETE) {
+ return (gret);
+ }
+
+ gret = gss_add_oid_set_member(minor, GSS_KRB5_MECHANISM, mech_oid_set);
+ if (gret != GSS_S_COMPLETE) {
+ goto release;
+ }
+
+ gret = gss_add_oid_set_member(minor, GSS_SPNEGO_MECHANISM,
+ mech_oid_set);
+ if (gret != GSS_S_COMPLETE) {
+ goto release;
+ }
+
+release:
+ REQUIRE(gss_release_oid_set(minor, mech_oid_set) == GSS_S_COMPLETE);
+
+ return (gret);
+}
+
+static void
+mech_oid_set_release(gss_OID_set *mech_oid_set) {
+ OM_uint32 minor;
+
+ REQUIRE(gss_release_oid_set(&minor, mech_oid_set) == GSS_S_COMPLETE);
+}
+
+isc_result_t
+dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
+ dns_gss_cred_id_t *cred) {
+ isc_result_t result;
+ isc_buffer_t namebuf;
+ gss_name_t gname;
+ gss_buffer_desc gnamebuf;
+ unsigned char array[DNS_NAME_MAXTEXT + 1];
+ OM_uint32 gret, minor;
+ OM_uint32 lifetime;
+ gss_cred_usage_t usage;
+ char buf[1024];
+ gss_OID_set mech_oid_set = NULL;
+
+ REQUIRE(cred != NULL && *cred == NULL);
+
+ /*
+ * XXXSRA In theory we could use GSS_C_NT_HOSTBASED_SERVICE
+ * here when we're in the acceptor role, which would let us
+ * default the hostname and use a compiled in default service
+ * name of "DNS", giving one less thing to configure in
+ * named.conf. Unfortunately, this creates a circular
+ * dependency due to DNS-based realm lookup in at least one
+ * GSSAPI implementation (Heimdal). Oh well.
+ */
+ if (name != NULL) {
+ isc_buffer_init(&namebuf, array, sizeof(array));
+ name_to_gbuffer(name, &namebuf, &gnamebuf);
+ gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
+ if (gret != GSS_S_COMPLETE) {
+ check_config((char *)array);
+
+ gss_log(3, "failed gss_import_name: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ return (ISC_R_FAILURE);
+ }
+ } else {
+ gname = NULL;
+ }
+
+ /* Get the credentials. */
+ if (gname != NULL) {
+ gss_log(3, "acquiring credentials for %s",
+ (char *)gnamebuf.value);
+ } else {
+ /* XXXDCL does this even make any sense? */
+ gss_log(3, "acquiring credentials for ?");
+ }
+
+ if (initiate) {
+ usage = GSS_C_INITIATE;
+ } else {
+ usage = GSS_C_ACCEPT;
+ }
+
+ gret = mech_oid_set_create(&minor, &mech_oid_set);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed to create OID_set: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ return (ISC_R_FAILURE);
+ }
+
+ gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE, mech_oid_set,
+ usage, (gss_cred_id_t *)cred, NULL, &lifetime);
+
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed to acquire %s credentials for %s: %s",
+ initiate ? "initiate" : "accept",
+ (gname != NULL) ? (char *)gnamebuf.value : "?",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ if (gname != NULL) {
+ check_config((char *)array);
+ }
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ gss_log(4, "acquired %s credentials for %s",
+ initiate ? "initiate" : "accept",
+ (gname != NULL) ? (char *)gnamebuf.value : "?");
+
+ log_cred(*cred);
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ mech_oid_set_release(&mech_oid_set);
+
+ if (gname != NULL) {
+ gret = gss_release_name(&minor, &gname);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_release_name: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ }
+ }
+
+ return (result);
+}
+
+bool
+dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain) {
+ char sbuf[DNS_NAME_FORMATSIZE];
+ char rbuf[DNS_NAME_FORMATSIZE];
+ char *sname;
+ char *rname;
+ isc_buffer_t buffer;
+ isc_result_t result;
+
+ /*
+ * It is far, far easier to write the names we are looking at into
+ * a string, and do string operations on them.
+ */
+ isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
+ result = dns_name_toprincipal(signer, &buffer);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_buffer_putuint8(&buffer, 0);
+ dns_name_format(realm, rbuf, sizeof(rbuf));
+
+ /*
+ * Find the realm portion. This is the part after the @. If it
+ * does not exist, we don't have something we like, so we fail our
+ * compare.
+ */
+ rname = strchr(sbuf, '@');
+ if (rname == NULL) {
+ return (false);
+ }
+ *rname = '\0';
+ rname++;
+
+ if (strcmp(rname, rbuf) != 0) {
+ return (false);
+ }
+
+ /*
+ * Find the host portion of the signer's name. We do this by
+ * searching for the first / character. We then check to make
+ * certain the instance name is "host"
+ *
+ * This will work for
+ * host/example.com@EXAMPLE.COM
+ */
+ sname = strchr(sbuf, '/');
+ if (sname == NULL) {
+ return (false);
+ }
+ *sname = '\0';
+ sname++;
+ if (strcmp(sbuf, "host") != 0) {
+ return (false);
+ }
+
+ /*
+ * If name is non NULL check that it matches against the
+ * machine name as expected.
+ */
+ if (name != NULL) {
+ dns_fixedname_t fixed;
+ dns_name_t *machine;
+
+ machine = dns_fixedname_initname(&fixed);
+ result = dns_name_fromstring(machine, sname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ if (subdomain) {
+ return (dns_name_issubdomain(name, machine));
+ }
+ return (dns_name_equal(name, machine));
+ }
+
+ return (true);
+}
+
+bool
+dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain) {
+ char sbuf[DNS_NAME_FORMATSIZE];
+ char rbuf[DNS_NAME_FORMATSIZE];
+ char *sname;
+ char *rname;
+ isc_buffer_t buffer;
+ isc_result_t result;
+
+ /*
+ * It is far, far easier to write the names we are looking at into
+ * a string, and do string operations on them.
+ */
+ isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
+ result = dns_name_toprincipal(signer, &buffer);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_buffer_putuint8(&buffer, 0);
+ dns_name_format(realm, rbuf, sizeof(rbuf));
+
+ /*
+ * Find the realm portion. This is the part after the @. If it
+ * does not exist, we don't have something we like, so we fail our
+ * compare.
+ */
+ rname = strchr(sbuf, '@');
+ if (rname == NULL) {
+ return (false);
+ }
+ sname = strchr(sbuf, '$');
+ if (sname == NULL) {
+ return (false);
+ }
+
+ /*
+ * Verify that the $ and @ follow one another.
+ */
+ if (rname - sname != 1) {
+ return (false);
+ }
+
+ /*
+ * Find the host portion of the signer's name. Zero out the $ so
+ * it terminates the signer's name, and skip past the @ for
+ * the realm.
+ *
+ * All service principals in Microsoft format seem to be in
+ * machinename$@EXAMPLE.COM
+ * format.
+ */
+ rname++;
+ *sname = '\0';
+
+ if (strcmp(rname, rbuf) != 0) {
+ return (false);
+ }
+
+ /*
+ * Now, we check that the realm matches (case sensitive) and that
+ * 'name' matches against 'machinename' qualified with 'realm'.
+ */
+ if (name != NULL) {
+ dns_fixedname_t fixed;
+ dns_name_t *machine;
+
+ machine = dns_fixedname_initname(&fixed);
+ result = dns_name_fromstring2(machine, sbuf, realm, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+ if (subdomain) {
+ return (dns_name_issubdomain(name, machine));
+ }
+ return (dns_name_equal(name, machine));
+ }
+
+ return (true);
+}
+
+isc_result_t
+dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
+ OM_uint32 gret, minor;
+ char buf[1024];
+
+ REQUIRE(cred != NULL && *cred != NULL);
+
+ gret = gss_release_cred(&minor, (gss_cred_id_t *)cred);
+ if (gret != GSS_S_COMPLETE) {
+ /* Log the error, but still free the credential's memory */
+ gss_log(3, "failed releasing credential: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ }
+ *cred = NULL;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Format a gssapi error message info into a char ** on the given memory
+ * context. This is used to return gssapi error messages back up the
+ * call chain for reporting to the user.
+ */
+static void
+gss_err_message(isc_mem_t *mctx, uint32_t major, uint32_t minor,
+ char **err_message) {
+ char buf[1024];
+ char *estr;
+
+ if (err_message == NULL || mctx == NULL) {
+ /* the caller doesn't want any error messages */
+ return;
+ }
+
+ estr = gss_error_tostring(major, minor, buf, sizeof(buf));
+ if (estr != NULL) {
+ (*err_message) = isc_mem_strdup(mctx, estr);
+ }
+}
+
+isc_result_t
+dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
+ isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
+ isc_mem_t *mctx, char **err_message) {
+ isc_region_t r;
+ isc_buffer_t namebuf;
+ gss_name_t gname;
+ OM_uint32 gret, minor, ret_flags, flags;
+ gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
+ isc_result_t result;
+ gss_buffer_desc gnamebuf;
+ unsigned char array[DNS_NAME_MAXTEXT + 1];
+
+ /* Client must pass us a valid gss_ctx_id_t here */
+ REQUIRE(gssctx != NULL);
+ REQUIRE(mctx != NULL);
+
+ isc_buffer_init(&namebuf, array, sizeof(array));
+ name_to_gbuffer(name, &namebuf, &gnamebuf);
+
+ /* Get the name as a GSS name */
+ gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
+ if (gret != GSS_S_COMPLETE) {
+ gss_err_message(mctx, gret, minor, err_message);
+ result = ISC_R_FAILURE;
+ goto out;
+ }
+
+ if (intoken != NULL) {
+ /* Don't call gss_release_buffer for gintoken! */
+ REGION_TO_GBUFFER(*intoken, gintoken);
+ gintokenp = &gintoken;
+ } else {
+ gintokenp = NULL;
+ }
+
+ /*
+ * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS
+ * servers don't like it.
+ */
+ flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
+
+ gret = gss_init_sec_context(
+ &minor, GSS_C_NO_CREDENTIAL, (gss_ctx_id_t *)gssctx, gname,
+ GSS_SPNEGO_MECHANISM, flags, 0, NULL, gintokenp, NULL,
+ &gouttoken, &ret_flags, NULL);
+
+ if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
+ gss_err_message(mctx, gret, minor, err_message);
+ if (err_message != NULL && *err_message != NULL) {
+ gss_log(3, "Failure initiating security context: %s",
+ *err_message);
+ } else {
+ gss_log(3, "Failure initiating security context");
+ }
+
+ result = ISC_R_FAILURE;
+ goto out;
+ }
+
+ /*
+ * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
+ * MUTUAL and INTEG flags, fail if either not set.
+ */
+
+ /*
+ * RFC 2744 states the a valid output token has a non-zero length.
+ */
+ if (gouttoken.length != 0U) {
+ GBUFFER_TO_REGION(gouttoken, r);
+ RETERR(isc_buffer_copyregion(outtoken, &r));
+ }
+
+ if (gret == GSS_S_COMPLETE) {
+ result = ISC_R_SUCCESS;
+ } else {
+ result = DNS_R_CONTINUE;
+ }
+
+out:
+ if (gouttoken.length != 0U) {
+ (void)gss_release_buffer(&minor, &gouttoken);
+ }
+ (void)gss_release_name(&minor, &gname);
+ return (result);
+}
+
+isc_result_t
+dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
+ isc_region_t *intoken, isc_buffer_t **outtoken,
+ dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
+ isc_mem_t *mctx) {
+ isc_region_t r;
+ isc_buffer_t namebuf;
+ gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
+ gouttoken = GSS_C_EMPTY_BUFFER;
+ OM_uint32 gret, minor;
+ gss_ctx_id_t context = GSS_C_NO_CONTEXT;
+ gss_name_t gname = NULL;
+ isc_result_t result;
+ char buf[1024];
+
+ REQUIRE(outtoken != NULL && *outtoken == NULL);
+
+ REGION_TO_GBUFFER(*intoken, gintoken);
+
+ if (*ctxout == NULL) {
+ context = GSS_C_NO_CONTEXT;
+ } else {
+ context = *ctxout;
+ }
+
+ if (gssapi_keytab != NULL) {
+#if defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32)
+ gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3,
+ "failed "
+ "gsskrb5_register_acceptor_identity(%s): %s",
+ gssapi_keytab,
+ gss_error_tostring(gret, 0, buf, sizeof(buf)));
+ return (DNS_R_INVALIDTKEY);
+ }
+#else /* if defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32) */
+ /*
+ * Minimize memory leakage by only setting KRB5_KTNAME
+ * if it needs to change.
+ */
+ const char *old = getenv("KRB5_KTNAME");
+ if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
+ size_t size;
+ char *kt;
+
+ size = strlen(gssapi_keytab) + 13;
+ kt = malloc(size);
+ if (kt == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ snprintf(kt, size, "KRB5_KTNAME=%s", gssapi_keytab);
+ if (putenv(kt) != 0) {
+ return (ISC_R_NOMEMORY);
+ }
+ }
+#endif /* if defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32) */
+ }
+
+ log_cred(cred);
+
+ gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
+ GSS_C_NO_CHANNEL_BINDINGS, &gname, NULL,
+ &gouttoken, NULL, NULL, NULL);
+
+ result = ISC_R_FAILURE;
+
+ switch (gret) {
+ case GSS_S_COMPLETE:
+ case GSS_S_CONTINUE_NEEDED:
+ break;
+ case GSS_S_DEFECTIVE_TOKEN:
+ case GSS_S_DEFECTIVE_CREDENTIAL:
+ case GSS_S_BAD_SIG:
+ case GSS_S_DUPLICATE_TOKEN:
+ case GSS_S_OLD_TOKEN:
+ case GSS_S_NO_CRED:
+ case GSS_S_CREDENTIALS_EXPIRED:
+ case GSS_S_BAD_BINDINGS:
+ case GSS_S_NO_CONTEXT:
+ case GSS_S_BAD_MECH:
+ case GSS_S_FAILURE:
+ result = DNS_R_INVALIDTKEY;
+ /* fall through */
+ default:
+ gss_log(3, "failed gss_accept_sec_context: %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ if (gouttoken.length > 0U) {
+ (void)gss_release_buffer(&minor, &gouttoken);
+ }
+ return (result);
+ }
+
+ if (gouttoken.length > 0U) {
+ isc_buffer_allocate(mctx, outtoken,
+ (unsigned int)gouttoken.length);
+ GBUFFER_TO_REGION(gouttoken, r);
+ RETERR(isc_buffer_copyregion(*outtoken, &r));
+ (void)gss_release_buffer(&minor, &gouttoken);
+ }
+
+ if (gret == GSS_S_COMPLETE) {
+ gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_display_name: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ RETERR(ISC_R_FAILURE);
+ }
+
+ /*
+ * Compensate for a bug in Solaris8's implementation
+ * of gss_display_name(). Should be harmless in any
+ * case, since principal names really should not
+ * contain null characters.
+ */
+ if (gnamebuf.length > 0U &&
+ ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
+ {
+ gnamebuf.length--;
+ }
+
+ gss_log(3, "gss-api source name (accept) is %.*s",
+ (int)gnamebuf.length, (char *)gnamebuf.value);
+
+ GBUFFER_TO_REGION(gnamebuf, r);
+ isc_buffer_init(&namebuf, r.base, r.length);
+ isc_buffer_add(&namebuf, r.length);
+
+ RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname, 0,
+ NULL));
+
+ if (gnamebuf.length != 0U) {
+ gret = gss_release_buffer(&minor, &gnamebuf);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_release_buffer: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ }
+ }
+ } else {
+ result = DNS_R_CONTINUE;
+ }
+
+ *ctxout = context;
+
+out:
+ if (gname != NULL) {
+ gret = gss_release_name(&minor, &gname);
+ if (gret != GSS_S_COMPLETE) {
+ gss_log(3, "failed gss_release_name: %s",
+ gss_error_tostring(gret, minor, buf,
+ sizeof(buf)));
+ }
+ }
+
+ return (result);
+}
+
+isc_result_t
+dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
+ OM_uint32 gret, minor;
+ char buf[1024];
+
+ UNUSED(mctx);
+
+ REQUIRE(gssctx != NULL && *gssctx != NULL);
+
+ /* Delete the context from the GSS provider */
+ gret = gss_delete_sec_context(&minor, (gss_ctx_id_t *)gssctx,
+ GSS_C_NO_BUFFER);
+ if (gret != GSS_S_COMPLETE) {
+ /* Log the error, but still free the context's memory */
+ gss_log(3, "Failure deleting security context %s",
+ gss_error_tostring(gret, minor, buf, sizeof(buf)));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+char *
+gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
+ gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
+ msg_major = GSS_C_EMPTY_BUFFER;
+ OM_uint32 msg_ctx, minor_stat;
+
+ /* Handle major status */
+ msg_ctx = 0;
+ (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
+ GSS_C_NULL_OID, &msg_ctx, &msg_major);
+
+ /* Handle minor status */
+ msg_ctx = 0;
+ (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
+ GSS_C_NULL_OID, &msg_ctx, &msg_minor);
+
+ snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
+ (char *)msg_major.value, (char *)msg_minor.value);
+
+ if (msg_major.length != 0U) {
+ (void)gss_release_buffer(&minor_stat, &msg_major);
+ }
+ if (msg_minor.length != 0U) {
+ (void)gss_release_buffer(&minor_stat, &msg_minor);
+ }
+ return (buf);
+}
+
+#else /* ifdef GSSAPI */
+
+isc_result_t
+dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
+ dns_gss_cred_id_t *cred) {
+ REQUIRE(cred != NULL && *cred == NULL);
+
+ UNUSED(name);
+ UNUSED(initiate);
+ UNUSED(cred);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+bool
+dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain) {
+ UNUSED(signer);
+ UNUSED(name);
+ UNUSED(realm);
+ UNUSED(subdomain);
+ return (false);
+}
+
+bool
+dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain) {
+ UNUSED(signer);
+ UNUSED(name);
+ UNUSED(realm);
+ UNUSED(subdomain);
+ return (false);
+}
+
+isc_result_t
+dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
+ UNUSED(cred);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
+ isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
+ isc_mem_t *mctx, char **err_message) {
+ UNUSED(name);
+ UNUSED(intoken);
+ UNUSED(outtoken);
+ UNUSED(gssctx);
+ UNUSED(mctx);
+ UNUSED(err_message);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
+ isc_region_t *intoken, isc_buffer_t **outtoken,
+ dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
+ isc_mem_t *mctx) {
+ UNUSED(cred);
+ UNUSED(gssapi_keytab);
+ UNUSED(intoken);
+ UNUSED(outtoken);
+ UNUSED(ctxout);
+ UNUSED(principal);
+ UNUSED(mctx);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
+ UNUSED(mctx);
+ UNUSED(gssctx);
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+char *
+gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
+ snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.", major,
+ minor);
+
+ return (buf);
+}
+#endif /* ifdef GSSAPI */
+
+void
+gss_log(int level, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_TKEY,
+ ISC_LOG_DEBUG(level), fmt, ap);
+ va_end(ap);
+}
+
+/*! \file */
diff --git a/lib/dns/hmac_link.c b/lib/dns/hmac_link.c
new file mode 100644
index 0000000..2872ff2
--- /dev/null
+++ b/lib/dns/hmac_link.c
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdbool.h>
+#ifndef WIN32
+#include <arpa/inet.h>
+#endif /* WIN32 */
+
+#include <isc/buffer.h>
+#include <isc/hmac.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/nonce.h>
+#include <isc/random.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <pk11/site.h>
+
+#include <dst/result.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) { \
+ return (hmac_parse(ISC_MD_##alg, key, lexer, pub)); \
+ } \
+ static dst_func_t hmac##alg##_functions = { \
+ hmac##alg##_createctx, \
+ NULL, /*%< createctx2 */ \
+ hmac##alg##_destroyctx, \
+ hmac##alg##_adddata, \
+ hmac##alg##_sign, \
+ hmac##alg##_verify, \
+ NULL, /*%< verify2 */ \
+ NULL, /*%< computesecret */ \
+ hmac##alg##_compare, \
+ NULL, /*%< paramcompare */ \
+ hmac##alg##_generate, \
+ hmac##alg##_isprivate, \
+ hmac##alg##_destroy, \
+ hmac##alg##_todns, \
+ hmac##alg##_fromdns, \
+ hmac##alg##_tofile, \
+ hmac##alg##_parse, \
+ NULL, /*%< cleanup */ \
+ NULL, /*%< fromlabel */ \
+ NULL, /*%< dump */ \
+ NULL, /*%< restore */ \
+ }; \
+ isc_result_t dst__hmac##alg##_init(dst_func_t **funcp) { \
+ REQUIRE(funcp != NULL); \
+ if (*funcp == NULL) { \
+ *funcp = &hmac##alg##_functions; \
+ } \
+ return (ISC_R_SUCCESS); \
+ }
+
+static isc_result_t
+hmac_fromdns(const isc_md_type_t *type, dst_key_t *key, isc_buffer_t *data);
+
+struct dst_hmac_key {
+ uint8_t key[ISC_MAX_BLOCK_SIZE];
+};
+
+static isc_result_t
+getkeybits(dst_key_t *key, struct dst_private_element *element) {
+ uint16_t *bits = (uint16_t *)element->data;
+
+ if (element->length != 2) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+
+ key->key_bits = ntohs(*bits);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+hmac_createctx(const isc_md_type_t *type, const dst_key_t *key,
+ dst_context_t *dctx) {
+ isc_result_t result;
+ const dst_hmac_key_t *hkey = key->keydata.hmac_key;
+ isc_hmac_t *ctx = isc_hmac_new(); /* Either returns or abort()s */
+
+ result = isc_hmac_init(ctx, hkey->key, isc_md_type_get_block_size(type),
+ type);
+ if (result != ISC_R_SUCCESS) {
+ isc_hmac_free(ctx);
+ return (DST_R_UNSUPPORTEDALG);
+ }
+
+ dctx->ctxdata.hmac_ctx = ctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+hmac_destroyctx(dst_context_t *dctx) {
+ isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx;
+ REQUIRE(ctx != NULL);
+
+ isc_hmac_free(ctx);
+ dctx->ctxdata.hmac_ctx = NULL;
+}
+
+static isc_result_t
+hmac_adddata(const dst_context_t *dctx, const isc_region_t *data) {
+ isc_result_t result;
+ isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx;
+
+ REQUIRE(ctx != NULL);
+
+ result = isc_hmac_update(ctx, data->base, data->length);
+ if (result != ISC_R_SUCCESS) {
+ return (DST_R_OPENSSLFAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+hmac_sign(const dst_context_t *dctx, isc_buffer_t *sig) {
+ isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx;
+ REQUIRE(ctx != NULL);
+ unsigned int digestlen;
+ unsigned char digest[ISC_MAX_MD_SIZE];
+
+ if (isc_hmac_final(ctx, digest, &digestlen) != ISC_R_SUCCESS) {
+ return (DST_R_OPENSSLFAILURE);
+ }
+
+ if (isc_hmac_reset(ctx) != ISC_R_SUCCESS) {
+ return (DST_R_OPENSSLFAILURE);
+ }
+
+ if (isc_buffer_availablelength(sig) < digestlen) {
+ return (ISC_R_NOSPACE);
+ }
+
+ isc_buffer_putmem(sig, digest, digestlen);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+hmac_verify(const dst_context_t *dctx, const isc_region_t *sig) {
+ isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx;
+ unsigned int digestlen;
+ unsigned char digest[ISC_MAX_MD_SIZE];
+
+ REQUIRE(ctx != NULL);
+
+ if (isc_hmac_final(ctx, digest, &digestlen) != ISC_R_SUCCESS) {
+ return (DST_R_OPENSSLFAILURE);
+ }
+
+ if (isc_hmac_reset(ctx) != ISC_R_SUCCESS) {
+ return (DST_R_OPENSSLFAILURE);
+ }
+
+ if (sig->length > digestlen) {
+ return (DST_R_VERIFYFAILURE);
+ }
+
+ return (isc_safe_memequal(digest, sig->base, sig->length)
+ ? ISC_R_SUCCESS
+ : DST_R_VERIFYFAILURE);
+}
+
+static bool
+hmac_compare(const isc_md_type_t *type, const dst_key_t *key1,
+ const dst_key_t *key2) {
+ dst_hmac_key_t *hkey1, *hkey2;
+
+ hkey1 = key1->keydata.hmac_key;
+ hkey2 = key2->keydata.hmac_key;
+
+ if (hkey1 == NULL && hkey2 == NULL) {
+ return (true);
+ } else if (hkey1 == NULL || hkey2 == NULL) {
+ return (false);
+ }
+
+ return (isc_safe_memequal(hkey1->key, hkey2->key,
+ isc_md_type_get_block_size(type)));
+}
+
+static isc_result_t
+hmac_generate(const isc_md_type_t *type, dst_key_t *key) {
+ isc_buffer_t b;
+ isc_result_t ret;
+ unsigned int bytes, len;
+ unsigned char data[ISC_MAX_MD_SIZE] = { 0 };
+
+ len = isc_md_type_get_block_size(type);
+
+ bytes = (key->key_size + 7) / 8;
+
+ if (bytes > len) {
+ bytes = len;
+ key->key_size = len * 8;
+ }
+
+ isc_nonce_buf(data, bytes);
+
+ isc_buffer_init(&b, data, bytes);
+ isc_buffer_add(&b, bytes);
+
+ ret = hmac_fromdns(type, key, &b);
+
+ isc_safe_memwipe(data, sizeof(data));
+
+ return (ret);
+}
+
+static bool
+hmac_isprivate(const dst_key_t *key) {
+ UNUSED(key);
+ return (true);
+}
+
+static void
+hmac_destroy(dst_key_t *key) {
+ dst_hmac_key_t *hkey = key->keydata.hmac_key;
+ isc_safe_memwipe(hkey, sizeof(*hkey));
+ isc_mem_put(key->mctx, hkey, sizeof(*hkey));
+ key->keydata.hmac_key = NULL;
+}
+
+static isc_result_t
+hmac_todns(const dst_key_t *key, isc_buffer_t *data) {
+ REQUIRE(key != NULL && key->keydata.hmac_key != NULL);
+ dst_hmac_key_t *hkey = key->keydata.hmac_key;
+ unsigned int bytes;
+
+ bytes = (key->key_size + 7) / 8;
+ if (isc_buffer_availablelength(data) < bytes) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putmem(data, hkey->key, bytes);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+hmac_fromdns(const isc_md_type_t *type, dst_key_t *key, isc_buffer_t *data) {
+ dst_hmac_key_t *hkey;
+ unsigned int keylen;
+ isc_region_t r;
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ hkey = isc_mem_get(key->mctx, sizeof(dst_hmac_key_t));
+
+ memset(hkey->key, 0, sizeof(hkey->key));
+
+ /* Hash the key if the key is longer then chosen MD block size */
+ if (r.length > (unsigned int)isc_md_type_get_block_size(type)) {
+ if (isc_md(type, r.base, r.length, hkey->key, &keylen) !=
+ ISC_R_SUCCESS)
+ {
+ isc_mem_put(key->mctx, hkey, sizeof(dst_hmac_key_t));
+ return (DST_R_OPENSSLFAILURE);
+ }
+ } else {
+ memmove(hkey->key, r.base, r.length);
+ keylen = r.length;
+ }
+
+ key->key_size = keylen * 8;
+ key->keydata.hmac_key = hkey;
+
+ isc_buffer_forward(data, r.length);
+
+ return (ISC_R_SUCCESS);
+}
+
+static int
+hmac__get_tag_key(const isc_md_type_t *type) {
+ if (type == ISC_MD_MD5) {
+ return (TAG_HMACMD5_KEY);
+ } else if (type == ISC_MD_SHA1) {
+ return (TAG_HMACSHA1_KEY);
+ } else if (type == ISC_MD_SHA224) {
+ return (TAG_HMACSHA224_KEY);
+ } else if (type == ISC_MD_SHA256) {
+ return (TAG_HMACSHA256_KEY);
+ } else if (type == ISC_MD_SHA384) {
+ return (TAG_HMACSHA384_KEY);
+ } else if (type == ISC_MD_SHA512) {
+ return (TAG_HMACSHA512_KEY);
+ } else {
+ UNREACHABLE();
+ }
+}
+
+static int
+hmac__get_tag_bits(const isc_md_type_t *type) {
+ if (type == ISC_MD_MD5) {
+ return (TAG_HMACMD5_BITS);
+ } else if (type == ISC_MD_SHA1) {
+ return (TAG_HMACSHA1_BITS);
+ } else if (type == ISC_MD_SHA224) {
+ return (TAG_HMACSHA224_BITS);
+ } else if (type == ISC_MD_SHA256) {
+ return (TAG_HMACSHA256_BITS);
+ } else if (type == ISC_MD_SHA384) {
+ return (TAG_HMACSHA384_BITS);
+ } else if (type == ISC_MD_SHA512) {
+ return (TAG_HMACSHA512_BITS);
+ } else {
+ UNREACHABLE();
+ }
+}
+
+static isc_result_t
+hmac_tofile(const isc_md_type_t *type, const dst_key_t *key,
+ const char *directory) {
+ dst_hmac_key_t *hkey;
+ dst_private_t priv;
+ int bytes = (key->key_size + 7) / 8;
+ uint16_t bits;
+
+ if (key->keydata.hmac_key == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ return (DST_R_EXTERNALKEY);
+ }
+
+ hkey = key->keydata.hmac_key;
+
+ priv.elements[0].tag = hmac__get_tag_key(type);
+ priv.elements[0].length = bytes;
+ priv.elements[0].data = hkey->key;
+
+ bits = htons(key->key_bits);
+
+ priv.elements[1].tag = hmac__get_tag_bits(type);
+ priv.elements[1].length = sizeof(bits);
+ priv.elements[1].data = (uint8_t *)&bits;
+
+ priv.nelements = 2;
+
+ return (dst__privstruct_writefile(key, &priv, directory));
+}
+
+static int
+hmac__to_dst_alg(const isc_md_type_t *type) {
+ if (type == ISC_MD_MD5) {
+ return (DST_ALG_HMACMD5);
+ } else if (type == ISC_MD_SHA1) {
+ return (DST_ALG_HMACSHA1);
+ } else if (type == ISC_MD_SHA224) {
+ return (DST_ALG_HMACSHA224);
+ } else if (type == ISC_MD_SHA256) {
+ return (DST_ALG_HMACSHA256);
+ } else if (type == ISC_MD_SHA384) {
+ return (DST_ALG_HMACSHA384);
+ } else if (type == ISC_MD_SHA512) {
+ return (DST_ALG_HMACSHA512);
+ } else {
+ UNREACHABLE();
+ }
+}
+
+static isc_result_t
+hmac_parse(const isc_md_type_t *type, dst_key_t *key, isc_lex_t *lexer,
+ dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t result, tresult;
+ isc_buffer_t b;
+ isc_mem_t *mctx = key->mctx;
+ unsigned int i;
+
+ UNUSED(pub);
+ /* read private key file */
+ result = dst__privstruct_parse(key, hmac__to_dst_alg(type), lexer, mctx,
+ &priv);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (key->external) {
+ result = DST_R_EXTERNALKEY;
+ }
+
+ key->key_bits = 0;
+ for (i = 0; i < priv.nelements && result == ISC_R_SUCCESS; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_HMACMD5_KEY:
+ case TAG_HMACSHA1_KEY:
+ case TAG_HMACSHA224_KEY:
+ case TAG_HMACSHA256_KEY:
+ case TAG_HMACSHA384_KEY:
+ case TAG_HMACSHA512_KEY:
+ isc_buffer_init(&b, priv.elements[i].data,
+ priv.elements[i].length);
+ isc_buffer_add(&b, priv.elements[i].length);
+ tresult = hmac_fromdns(type, key, &b);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ break;
+ case TAG_HMACMD5_BITS:
+ case TAG_HMACSHA1_BITS:
+ case TAG_HMACSHA224_BITS:
+ case TAG_HMACSHA256_BITS:
+ case TAG_HMACSHA384_BITS:
+ case TAG_HMACSHA512_BITS:
+ tresult = getkeybits(key, &priv.elements[i]);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ break;
+ default:
+ result = DST_R_INVALIDPRIVATEKEY;
+ break;
+ }
+ }
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (result);
+}
+
+hmac_register_algorithm(md5);
+hmac_register_algorithm(sha1);
+hmac_register_algorithm(sha224);
+hmac_register_algorithm(sha256);
+hmac_register_algorithm(sha384);
+hmac_register_algorithm(sha512);
+
+/*! \file */
diff --git a/lib/dns/include/.clang-format b/lib/dns/include/.clang-format
new file mode 120000
index 0000000..0e62f72
--- /dev/null
+++ b/lib/dns/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/dns/include/Makefile.in b/lib/dns/include/Makefile.in
new file mode 100644
index 0000000..cf869c0
--- /dev/null
+++ b/lib/dns/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = dns dst
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/dns/include/dns/Makefile.in b/lib/dns/include/dns/Makefile.in
new file mode 100644
index 0000000..88b9bcf
--- /dev/null
+++ b/lib/dns/include/dns/Makefile.in
@@ -0,0 +1,63 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+HEADERS = acl.h adb.h badcache.h bit.h byaddr.h \
+ cache.h callbacks.h catz.h cert.h \
+ client.h clientinfo.h compress.h \
+ db.h dbiterator.h dbtable.h diff.h dispatch.h \
+ dlz.h dlz_dlopen.h dns64.h dnsrps.h dnssec.h ds.h dsdigest.h \
+ dnstap.h dyndb.h ecs.h \
+ edns.h ecdb.h events.h fixedname.h forward.h geoip.h \
+ ipkeylist.h iptable.h \
+ journal.h kasp.h keydata.h keyflags.h keymgr.h keytable.h \
+ keyvalues.h lib.h librpz.h lmdb.h lookup.h log.h \
+ master.h masterdump.h message.h \
+ name.h ncache.h nsec.h nsec3.h nta.h opcode.h order.h \
+ peer.h portlist.h private.h \
+ rbt.h rcode.h rdata.h rdataclass.h rdatalist.h \
+ rdataset.h rdatasetiter.h rdataslab.h rdatatype.h request.h \
+ resolver.h result.h rootns.h rpz.h rriterator.h rrl.h \
+ sdb.h sdlz.h secalg.h secproto.h soa.h ssu.h stats.h \
+ tcpmsg.h time.h timer.h tkey.h tsec.h tsig.h ttl.h types.h \
+ update.h validator.h version.h view.h xfrin.h \
+ zone.h zonekey.h zoneverify.h zt.h
+
+GENHEADERS = enumclass.h enumtype.h rdatastruct.h
+
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/dns
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/dns || exit 1; \
+ done
+ for i in ${GENHEADERS}; do \
+ ${INSTALL_DATA} $$i ${DESTDIR}${includedir}/dns || exit 1; \
+ done
+
+uninstall::
+ for i in ${GENHEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/dns/$$i || exit 1; \
+ done
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/dns/$$i || exit 1; \
+ done
diff --git a/lib/dns/include/dns/acl.h b/lib/dns/include/dns/acl.h
new file mode 100644
index 0000000..43fe15b
--- /dev/null
+++ b/lib/dns/include/dns/acl.h
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_ACL_H
+#define DNS_ACL_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/acl.h
+ * \brief
+ * Address match list handling.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/netaddr.h>
+#include <isc/refcount.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_aclipprefix dns_aclipprefix_t;
+
+struct dns_aclipprefix {
+ isc_netaddr_t address; /* IP4/IP6 */
+ unsigned int prefixlen;
+};
+
+struct dns_aclelement {
+ dns_aclelementtype_t type;
+ bool negative;
+ dns_name_t keyname;
+#if defined(HAVE_GEOIP2)
+ dns_geoip_elem_t geoip_elem;
+#endif /* HAVE_GEOIP2 */
+ dns_acl_t *nestedacl;
+ int node_num;
+};
+
+#define dns_acl_node_count(acl) acl->iptable->radix->num_added_node
+
+struct dns_acl {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t refcount;
+ dns_iptable_t *iptable;
+ dns_aclelement_t *elements;
+ bool has_negatives;
+ unsigned int alloc; /*%< Elements allocated */
+ unsigned int length; /*%< Elements initialized */
+ char *name; /*%< Temporary use only */
+ ISC_LINK(dns_acl_t) nextincache; /*%< Ditto */
+};
+
+struct dns_aclenv {
+ dns_acl_t *localhost;
+ dns_acl_t *localnets;
+ bool match_mapped;
+#if defined(HAVE_GEOIP2)
+ dns_geoip_databases_t *geoip;
+#endif /* HAVE_GEOIP2 */
+};
+
+#define DNS_ACL_MAGIC ISC_MAGIC('D', 'a', 'c', 'l')
+#define DNS_ACL_VALID(a) ISC_MAGIC_VALID(a, DNS_ACL_MAGIC)
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target);
+/*%<
+ * Create a new ACL, including an IP table and an array with room
+ * for 'n' ACL elements. The elements are uninitialized and the
+ * length is 0.
+ */
+
+isc_result_t
+dns_acl_any(isc_mem_t *mctx, dns_acl_t **target);
+/*%<
+ * Create a new ACL that matches everything.
+ */
+
+isc_result_t
+dns_acl_none(isc_mem_t *mctx, dns_acl_t **target);
+/*%<
+ * Create a new ACL that matches nothing.
+ */
+
+bool
+dns_acl_isany(dns_acl_t *acl);
+/*%<
+ * Test whether ACL is set to "{ any; }"
+ */
+
+bool
+dns_acl_isnone(dns_acl_t *acl);
+/*%<
+ * Test whether ACL is set to "{ none; }"
+ */
+
+isc_result_t
+dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, bool pos);
+/*%<
+ * Merge the contents of one ACL into another. Call dns_iptable_merge()
+ * for the IP tables, then concatenate the element arrays.
+ *
+ * If pos is set to false, then the nested ACL is to be negated. This
+ * means reverse the sense of each *positive* element or IP table node,
+ * but leave negatives alone, so as to prevent a double-negative causing
+ * an unexpected positive match in the parent ACL.
+ */
+
+void
+dns_acl_attach(dns_acl_t *source, dns_acl_t **target);
+/*%<
+ * Attach to acl 'source'.
+ *
+ * Requires:
+ *\li 'source' to be a valid acl.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_acl_detach(dns_acl_t **aclp);
+/*%<
+ * Detach the acl. On final detach the acl must not be linked on any
+ * list.
+ *
+ * Requires:
+ *\li '*aclp' to be a valid acl.
+ *
+ * Insists:
+ *\li '*aclp' is not linked on final detach.
+ */
+
+bool
+dns_acl_isinsecure(const dns_acl_t *a);
+/*%<
+ * Return #true iff the acl 'a' is considered insecure, that is,
+ * if it contains IP addresses other than those of the local host.
+ * This is intended for applications such as printing warning
+ * messages for suspect ACLs; it is not intended for making access
+ * control decisions. We make no guarantee that an ACL for which
+ * this function returns #false is safe.
+ */
+
+bool
+dns_acl_allowed(isc_netaddr_t *addr, const dns_name_t *signer, dns_acl_t *acl,
+ dns_aclenv_t *aclenv);
+/*%<
+ * Return #true iff the 'addr', 'signer', or ECS values are
+ * permitted by 'acl' in environment 'aclenv'.
+ */
+
+isc_result_t
+dns_aclenv_init(isc_mem_t *mctx, dns_aclenv_t *env);
+/*%<
+ * Initialize ACL environment, setting up localhost and localnets ACLs
+ */
+
+void
+dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s);
+
+void
+dns_aclenv_destroy(dns_aclenv_t *env);
+
+isc_result_t
+dns_acl_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
+ const dns_acl_t *acl, const dns_aclenv_t *env, int *match,
+ const dns_aclelement_t **matchelt);
+/*%<
+ * General, low-level ACL matching. This is expected to
+ * be useful even for weird stuff like the topology and sortlist statements.
+ *
+ * Match the address 'reqaddr', and optionally the key name 'reqsigner',
+ * against 'acl'. 'reqsigner' may be NULL.
+ *
+ * If there is a match, '*match' will be set to an integer whose absolute
+ * value corresponds to the order in which the matching value was inserted
+ * into the ACL. For a positive match, this value will be positive; for a
+ * negative match, it will be negative.
+ *
+ * If there is no match, *match will be set to zero.
+ *
+ * If there is a match in the element list (either positive or negative)
+ * and 'matchelt' is non-NULL, *matchelt will be pointed to the matching
+ * element.
+ *
+ * 'env' points to the current ACL environment, including the
+ * current values of localhost and localnets and (if applicable)
+ * the GeoIP context.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Always succeeds.
+ */
+
+bool
+dns_aclelement_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
+ const dns_aclelement_t *e, const dns_aclenv_t *env,
+ const dns_aclelement_t **matchelt);
+/*%<
+ * Like dns_acl_match, but matches against the single ACL element 'e'
+ * rather than a complete ACL, and returns true iff it matched.
+ *
+ * To determine whether the match was positive or negative, the
+ * caller should examine e->negative. Since the element 'e' may be
+ * a reference to a named ACL or a nested ACL, a matching element
+ * returned through 'matchelt' is not necessarily 'e' itself.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_ACL_H */
diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h
new file mode 100644
index 0000000..4e05fd6
--- /dev/null
+++ b/lib/dns/include/dns/adb.h
@@ -0,0 +1,835 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_ADB_H
+#define DNS_ADB_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/adb.h
+ *\brief
+ * DNS Address Database
+ *
+ * This module implements an address database (ADB) for mapping a name
+ * to an isc_sockaddr_t. It also provides statistical information on
+ * how good that address might be.
+ *
+ * A client will pass in a dns_name_t, and the ADB will walk through
+ * the rdataset looking up addresses associated with the name. If it
+ * is found on the internal lists, a structure is filled in with the
+ * address information and stats for found addresses.
+ *
+ * If the name cannot be found on the internal lists, a new entry will
+ * be created for a name if all the information needed can be found
+ * in the zone table or cache. This new address will then be returned.
+ *
+ * If a request must be made to remote servers to satisfy a name lookup,
+ * this module will start fetches to try to complete these addresses. When
+ * at least one more completes, an event is sent to the caller. If none of
+ * them resolve before the fetch times out, an event indicating this is
+ * sent instead.
+ *
+ * Records are stored internally until a timer expires. The timer is the
+ * smaller of the TTL or signature validity period.
+ *
+ * Lameness is stored per <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 */
+ isc_dscp_t dscp;
+
+ unsigned int flags; /*%< [rw] */
+ dns_adbentry_t *entry; /*%< private */
+ ISC_LINK(dns_adbaddrinfo_t) publink;
+};
+
+/*!<
+ * The event sent to the caller task is just a plain old isc_event_t. It
+ * contains no data other than a simple status, passed in the "type" field
+ * to indicate that another address resolved, or all partially resolved
+ * addresses have failed to resolve.
+ *
+ * "sender" is the dns_adbfind_t used to issue this query.
+ *
+ * This is simply a standard event, with the "type" set to:
+ *
+ *\li #DNS_EVENT_ADBMOREADDRESSES -- another address resolved.
+ *\li #DNS_EVENT_ADBNOMOREADDRESSES -- all pending addresses failed,
+ * were canceled, or otherwise will
+ * not be usable.
+ *\li #DNS_EVENT_ADBCANCELED -- The request was canceled by a
+ * 3rd party.
+ *\li #DNS_EVENT_ADBNAMEDELETED -- The name was deleted, so this request
+ * was canceled.
+ *
+ * In each of these cases, the addresses returned by the initial call
+ * to dns_adb_createfind() can still be used until they are no longer needed.
+ */
+
+/****
+**** FUNCTIONS
+****/
+
+isc_result_t
+dns_adb_create(isc_mem_t *mem, dns_view_t *view, isc_timermgr_t *tmgr,
+ isc_taskmgr_t *taskmgr, dns_adb_t **newadb);
+/*%<
+ * Create a new ADB.
+ *
+ * Notes:
+ *
+ *\li Generally, applications should not create an ADB directly, but
+ * should instead call dns_view_createresolver().
+ *
+ * Requires:
+ *
+ *\li 'mem' must be a valid memory context.
+ *
+ *\li 'view' be a pointer to a valid view.
+ *
+ *\li 'tmgr' be a pointer to a valid timer manager.
+ *
+ *\li 'taskmgr' be a pointer to a valid task manager.
+ *
+ *\li 'newadb' != NULL && '*newadb' == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS after happiness.
+ *\li #ISC_R_NOMEMORY after resource allocation failure.
+ */
+
+void
+dns_adb_attach(dns_adb_t *adb, dns_adb_t **adbp);
+/*%
+ * Attach to an 'adb' to 'adbp'.
+ *
+ * Requires:
+ *\li 'adb' to be a valid dns_adb_t, created via dns_adb_create().
+ *\li 'adbp' to be a valid pointer to a *dns_adb_t which is initialized
+ * to NULL.
+ */
+
+void
+dns_adb_detach(dns_adb_t **adb);
+/*%
+ * Delete the ADB. Sets *ADB to NULL. Cancels any outstanding requests.
+ *
+ * Requires:
+ *
+ *\li 'adb' be non-NULL and '*adb' be a valid dns_adb_t, created via
+ * dns_adb_create().
+ */
+
+void
+dns_adb_whenshutdown(dns_adb_t *adb, isc_task_t *task, isc_event_t **eventp);
+/*%
+ * Send '*eventp' to 'task' when 'adb' has shutdown.
+ *
+ * Requires:
+ *
+ *\li '*adb' is a valid dns_adb_t.
+ *
+ *\li eventp != NULL && *eventp is a valid event.
+ *
+ * Ensures:
+ *
+ *\li *eventp == NULL
+ *
+ *\li The event's sender field is set to the value of adb when the event
+ * is sent.
+ */
+
+void
+dns_adb_shutdown(dns_adb_t *adb);
+/*%<
+ * Shutdown 'adb'.
+ *
+ * Requires:
+ *
+ * \li '*adb' is a valid dns_adb_t.
+ */
+
+isc_result_t
+dns_adb_createfind(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action,
+ void *arg, const dns_name_t *name, const dns_name_t *qname,
+ dns_rdatatype_t qtype, unsigned int options,
+ isc_stdtime_t now, dns_name_t *target, in_port_t port,
+ unsigned int depth, isc_counter_t *qc, dns_adbfind_t **find);
+/*%<
+ * Main interface for clients. The adb will look up the name given in
+ * "name" and will build up a list of found addresses, and perhaps start
+ * internal fetches to resolve names that are unknown currently.
+ *
+ * If other addresses resolve after this call completes, an event will
+ * be sent to the <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.
+ */
+
+unsigned int
+dns_adb_probesize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, int lookups);
+/*%
+ * Return suggested EDNS UDP size based on observed responses / failures.
+ * 'lookups' is the number of times the current lookup has been attempted.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+void
+dns_adb_plainresponse(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+/*%
+ * Record a successful plain DNS response.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+void
+dns_adb_timeout(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+/*%
+ * Record a plain DNS UDP query failed.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+void
+dns_adb_ednsto(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int size);
+/*%
+ * Record a failed EDNS UDP response and the advertised EDNS UDP buffer size
+ * used.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+bool
+dns_adb_noedns(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+/*%
+ * Return whether EDNS should be disabled for this server.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+isc_result_t
+dns_adb_findaddrinfo(dns_adb_t *adb, const isc_sockaddr_t *sa,
+ dns_adbaddrinfo_t **addrp, isc_stdtime_t now);
+/*%<
+ * Return a dns_adbaddrinfo_t that is associated with address 'sa'.
+ *
+ * Requires:
+ *
+ *\li adb is valid.
+ *
+ *\li sa is valid.
+ *
+ *\li addrp != NULL && *addrp == NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_SHUTTINGDOWN
+ */
+
+void
+dns_adb_freeaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **addrp);
+/*%<
+ * Free a dns_adbaddrinfo_t allocated by dns_adb_findaddrinfo().
+ *
+ * Requires:
+ *
+ *\li adb is valid.
+ *
+ *\li *addrp is a valid dns_adbaddrinfo_t *.
+ */
+
+void
+dns_adb_flush(dns_adb_t *adb);
+/*%<
+ * Flushes all cached data from the adb.
+ *
+ * Requires:
+ *\li adb is valid.
+ */
+
+void
+dns_adb_setadbsize(dns_adb_t *adb, size_t size);
+/*%<
+ * Set a target memory size. If memory usage exceeds the target
+ * size entries will be removed before they would have expired on
+ * a random basis.
+ *
+ * If 'size' is 0 then memory usage is unlimited.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ */
+
+void
+dns_adb_flushname(dns_adb_t *adb, const dns_name_t *name);
+/*%<
+ * Flush 'name' from the adb cache.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ *\li 'name' is valid.
+ */
+
+void
+dns_adb_flushnames(dns_adb_t *adb, const dns_name_t *name);
+/*%<
+ * Flush 'name' and all subdomains from the adb cache.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ *\li 'name' is valid.
+ */
+
+void
+dns_adb_setcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ const unsigned char *cookie, size_t len);
+/*%<
+ * Record the COOKIE associated with this address. If
+ * cookie is NULL or len is zero the recorded COOKIE is cleared.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ *\li 'addr' is valid.
+ */
+
+size_t
+dns_adb_getcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
+ unsigned char *cookie, size_t len);
+/*
+ * Retrieve the saved COOKIE value and store it in 'cookie' which has
+ * size 'len'.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ *\li 'addr' is valid.
+ *
+ * Returns:
+ * The size of the cookie or zero if it doesn't fit in the buffer
+ * or it doesn't exist.
+ */
+
+void
+dns_adb_setquota(dns_adb_t *adb, uint32_t quota, uint32_t freq, double low,
+ double high, double discount);
+/*%<
+ * Set the baseline ADB quota, and configure parameters for the
+ * quota adjustment algorithm.
+ *
+ * If the number of fetches currently waiting for responses from this
+ * address exceeds the current quota, then additional fetches are spilled.
+ *
+ * 'quota' is the highest permissible quota; it will adjust itself
+ * downward in response to detected congestion.
+ *
+ * After every 'freq' fetches have either completed or timed out, an
+ * exponentially weighted moving average of the ratio of timeouts
+ * to responses is calculated. If the EWMA goes above a 'high'
+ * threshold, then the quota is adjusted down one step; if it drops
+ * below a 'low' threshold, then the quota is adjusted back up one
+ * step.
+ *
+ * The quota adjustment is based on the function (1 / 1 + (n/10)^(3/2)),
+ * for values of n from 0 to 99. It starts at 100% of the baseline
+ * quota, and descends after 100 steps to 2%.
+ *
+ * 'discount' represents the discount rate of the moving average. Higher
+ * values cause older values to be discounted sooner, providing a faster
+ * response to changes in the timeout ratio.
+ *
+ * Requires:
+ *\li 'adb' is valid.
+ */
+
+bool
+dns_adbentry_overquota(dns_adbentry_t *entry);
+/*%<
+ * Returns true if the specified ADB has too many active fetches.
+ *
+ * Requires:
+ *\li 'entry' is valid.
+ */
+
+void
+dns_adb_beginudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+void
+dns_adb_endudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr);
+/*%
+ * Begin/end a UDP fetch on a particular address.
+ *
+ * These functions increment or decrement the fetch counter for
+ * the ADB entry so that the fetch quota can be enforced.
+ *
+ * Requires:
+ *
+ *\li adb be valid.
+ *
+ *\li addr be valid.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_ADB_H */
diff --git a/lib/dns/include/dns/badcache.h b/lib/dns/include/dns/badcache.h
new file mode 100644
index 0000000..c45ea0c
--- /dev/null
+++ b/lib/dns/include/dns/badcache.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_BADCACHE_H
+#define DNS_BADCACHE_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/badcache.h
+ * \brief
+ * Defines dns_badcache_t, the "bad cache" object.
+ *
+ * Notes:
+ *\li A bad cache object is a hash table of name/type tuples,
+ * indicating whether a given tuple known to be "bad" in some
+ * sense (e.g., queries for that name and type have been
+ * returning SERVFAIL). This is used for both the "bad server
+ * cache" in the resolver and for the "servfail cache" in
+ * the view.
+ *
+ * Reliability:
+ *
+ * Resources:
+ *
+ * Security:
+ *
+ * Standards:
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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
+
+#endif /* DNS_BADCACHE_H */
diff --git a/lib/dns/include/dns/bit.h b/lib/dns/include/dns/bit.h
new file mode 100644
index 0000000..55696e2
--- /dev/null
+++ b/lib/dns/include/dns/bit.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_BIT_H
+#define DNS_BIT_H 1
+
+/*! \file dns/bit.h */
+
+#include <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)))
+
+#endif /* DNS_BIT_H */
diff --git a/lib/dns/include/dns/byaddr.h b/lib/dns/include/dns/byaddr.h
new file mode 100644
index 0000000..f527ed3
--- /dev/null
+++ b/lib/dns/include/dns/byaddr.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_BYADDR_H
+#define DNS_BYADDR_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/byaddr.h
+ * \brief
+ * The byaddr module provides reverse lookup services for IPv4 and IPv6
+ * addresses.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li RFCs: 1034, 1035, 2181, TBS
+ *\li Drafts: TBS
+ */
+
+#include <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
+
+#endif /* DNS_BYADDR_H */
diff --git a/lib/dns/include/dns/cache.h b/lib/dns/include/dns/cache.h
new file mode 100644
index 0000000..b840a54
--- /dev/null
+++ b/lib/dns/include/dns/cache.h
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_CACHE_H
+#define DNS_CACHE_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/cache.h
+ * \brief
+ * Defines dns_cache_t, the cache object.
+ *
+ * Notes:
+ *\li A cache object contains DNS data of a single class.
+ * Multiple classes will be handled by creating multiple
+ * views, each with a different class and its own cache.
+ *
+ * MP:
+ *\li See notes at the individual functions.
+ *
+ * Reliability:
+ *
+ * Resources:
+ *
+ * Security:
+ *
+ * Standards:
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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_setfilename(dns_cache_t *cache, const char *filename);
+/*%<
+ * If 'filename' is non-NULL, make the cache persistent.
+ * The cache's data will be stored in the given file.
+ * If 'filename' is NULL, make the cache non-persistent.
+ * Files that are no longer used are not unlinked automatically.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Various file-related failures
+ */
+
+isc_result_t
+dns_cache_load(dns_cache_t *cache);
+/*%<
+ * If the cache has a file name, load the cache contents from the file.
+ * Previous cache contents are not discarded.
+ * If no file name has been set, do nothing and return success.
+ *
+ * MT:
+ *\li Multiple simultaneous attempts to load or dump the cache
+ * will be serialized with respect to one another, but
+ * the cache may be read and updated while the dump is
+ * in progress. Updates performed during loading
+ * may or may not be preserved, and reads may return
+ * either the old or the newly loaded data.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ * \li Various failures depending on the database implementation type
+ */
+
+isc_result_t
+dns_cache_dump(dns_cache_t *cache);
+/*%<
+ * If the cache has a file name, write the cache contents to disk,
+ * overwriting any preexisting file. If no file name has been set,
+ * do nothing and return success.
+ *
+ * MT:
+ *\li Multiple simultaneous attempts to load or dump the cache
+ * will be serialized with respect to one another, but
+ * the cache may be read and updated while the dump is
+ * in progress. Updates performed during the dump may
+ * or may not be reflected in the dumped file.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ * \li Various failures depending on the database implementation type
+ */
+
+isc_result_t
+dns_cache_clean(dns_cache_t *cache, isc_stdtime_t now);
+/*%<
+ * Force immediate cleaning of the cache, freeing all rdatasets
+ * whose TTL has expired as of 'now' and that have no pending
+ * references.
+ */
+
+const char *
+dns_cache_getname(dns_cache_t *cache);
+/*%<
+ * Get the cache name.
+ */
+
+void
+dns_cache_setcachesize(dns_cache_t *cache, size_t size);
+/*%<
+ * Set the maximum cache size. 0 means unlimited.
+ */
+
+size_t
+dns_cache_getcachesize(dns_cache_t *cache);
+/*%<
+ * Get the maximum cache size.
+ */
+
+void
+dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl);
+/*%<
+ * Sets the maximum length of time that cached answers may be retained
+ * past their normal TTL. Default value for the library is 0, disabling
+ * the use of stale data.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ */
+
+dns_ttl_t
+dns_cache_getservestalettl(dns_cache_t *cache);
+/*%<
+ * Gets the maximum length of time that cached answers may be kept past
+ * normal expiry.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ */
+
+void
+dns_cache_setservestalerefresh(dns_cache_t *cache, dns_ttl_t interval);
+/*%<
+ * Sets the length of time to wait before attempting to refresh a rrset
+ * if a previous attempt in doing so has failed.
+ * During this time window if stale rrset are available in cache they
+ * will be directly returned to client.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ */
+
+dns_ttl_t
+dns_cache_getservestalerefresh(dns_cache_t *cache);
+/*%<
+ * Gets the 'stale-refresh-time' value, set by a previous call to
+ * 'dns_cache_setservestalerefresh'.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ */
+
+isc_result_t
+dns_cache_flush(dns_cache_t *cache);
+/*%<
+ * Flushes all data from the cache.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_cache_flushnode(dns_cache_t *cache, const dns_name_t *name, bool tree);
+/*
+ * Flush a given name from the cache. If 'tree' is true, then
+ * also flush all names under 'name'.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ *\li 'name' to be valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li other error returns.
+ */
+
+isc_result_t
+dns_cache_flushname(dns_cache_t *cache, const dns_name_t *name);
+/*
+ * Flush a given name from the cache. Equivalent to
+ * dns_cache_flushpartial(cache, name, false).
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ *\li 'name' to be valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li other error returns.
+ */
+
+isc_stats_t *
+dns_cache_getstats(dns_cache_t *cache);
+/*
+ * Return a pointer to the stats collection object for 'cache'
+ */
+
+void
+dns_cache_dumpstats(dns_cache_t *cache, FILE *fp);
+/*
+ * Dump cache statistics and status in text to 'fp'
+ */
+
+void
+dns_cache_updatestats(dns_cache_t *cache, isc_result_t result);
+/*
+ * Update cache statistics based on result code in 'result'
+ */
+
+#ifdef HAVE_LIBXML2
+int
+dns_cache_renderxml(dns_cache_t *cache, void *writer0);
+/*
+ * Render cache statistics and status in XML for 'writer'.
+ */
+#endif /* HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+isc_result_t
+dns_cache_renderjson(dns_cache_t *cache, void *cstats0);
+/*
+ * Render cache statistics and status in JSON
+ */
+#endif /* HAVE_JSON_C */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_CACHE_H */
diff --git a/lib/dns/include/dns/callbacks.h b/lib/dns/include/dns/callbacks.h
new file mode 100644
index 0000000..4d71915
--- /dev/null
+++ b/lib/dns/include/dns/callbacks.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_CALLBACKS_H
+#define DNS_CALLBACKS_H 1
+
+/*! \file dns/callbacks.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <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;
+
+ /*%
+ * This is called when reading in a database image from a 'map'
+ * format zone file.
+ */
+ dns_deserializefunc_t deserialize;
+
+ /*%
+ * dns_master_load*() call this when loading a raw zonefile,
+ * to pass back information obtained from the file header
+ */
+ dns_rawdatafunc_t rawdata;
+ dns_zone_t *zone;
+
+ /*%
+ * dns_load_master / dns_rdata_fromtext call this to issue a error.
+ */
+ void (*error)(struct dns_rdatacallbacks *, const char *, ...);
+ /*%
+ * dns_load_master / dns_rdata_fromtext call this to issue a warning.
+ */
+ void (*warn)(struct dns_rdatacallbacks *, const char *, ...);
+ /*%
+ * Private data handles for use by the above callback functions.
+ */
+ void *add_private;
+ void *deserialize_private;
+ void *error_private;
+ void *warn_private;
+};
+
+/***
+ *** Initialization
+ ***/
+
+void
+dns_rdatacallbacks_init(dns_rdatacallbacks_t *callbacks);
+/*%<
+ * Initialize 'callbacks'.
+ *
+ * \li 'magic' is set to DNS_CALLBACK_MAGIC
+ *
+ * \li 'error' and 'warn' are set to default callbacks that print the
+ * error message through the DNS library log context.
+ *
+ *\li All other elements are initialized to NULL.
+ *
+ * Requires:
+ * \li 'callbacks' is a valid dns_rdatacallbacks_t,
+ */
+
+void
+dns_rdatacallbacks_init_stdio(dns_rdatacallbacks_t *callbacks);
+/*%<
+ * Like dns_rdatacallbacks_init, but logs to stdio.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_CALLBACKS_H */
diff --git a/lib/dns/include/dns/catz.h b/lib/dns/include/dns/catz.h
new file mode 100644
index 0000000..7502829
--- /dev/null
+++ b/lib/dns/include/dns/catz.h
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_CATZ_H
+#define DNS_CATZ_H 1
+
+#include <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 definition */
+ dns_ipkeylist_t masters;
+
+ /* both as text in config format, NULL if none */
+ isc_buffer_t *allow_query;
+ isc_buffer_t *allow_transfer;
+
+ /*
+ * Options that are only set in named.conf
+ */
+ /* zone-directory definition */
+ char *zonedir;
+
+ /* zone should not be stored on disk (no 'file' statement in def */
+ bool in_memory;
+ /*
+ * Minimal interval between catalog zone updates, if a new version
+ * of catalog zone is received before this time the update will be
+ * postponed. This is a global option for the whole catalog zone.
+ */
+ uint32_t min_update_interval;
+};
+
+void
+dns_catz_options_init(dns_catz_options_t *options);
+/*%<
+ * Initialize 'options' to NULL values.
+ *
+ * Requires:
+ * \li 'options' to be non NULL.
+ */
+
+void
+dns_catz_options_free(dns_catz_options_t *options, isc_mem_t *mctx);
+/*%<
+ * Free 'options' contents into 'mctx'. ('options' itself is not freed.)
+ *
+ * Requires:
+ * \li 'options' to be non NULL.
+ * \li 'mctx' to be a valid memory context.
+ */
+
+isc_result_t
+dns_catz_options_copy(isc_mem_t *mctx, const dns_catz_options_t *opts,
+ dns_catz_options_t *nopts);
+/*%<
+ * Duplicate 'opts' into 'nopts', allocating space from 'mctx'.
+ *
+ * Requires:
+ * \li 'mctx' to be a valid memory context.
+ * \li 'options' to be non NULL and valid options.
+ * \li 'nopts' to be non NULL.
+ */
+
+isc_result_t
+dns_catz_options_setdefault(isc_mem_t *mctx, const dns_catz_options_t *defaults,
+ dns_catz_options_t *opts);
+/*%<
+ * Replace empty values in 'opts' with values from 'defaults'
+ *
+ * Requires:
+ * \li 'mctx' to be a valid memory context.
+ * \li 'defaults' to be non NULL and valid options.
+ * \li 'opts' to be non NULL.
+ */
+
+dns_name_t *
+dns_catz_entry_getname(dns_catz_entry_t *entry);
+/*%<
+ * Get domain name for 'entry'
+ *
+ * Requires:
+ * \li 'entry' to be non NULL.
+ *
+ * Returns:
+ * \li domain name for entry.
+ */
+
+isc_result_t
+dns_catz_entry_new(isc_mem_t *mctx, const dns_name_t *domain,
+ dns_catz_entry_t **nentryp);
+/*%<
+ * Allocate a new catz_entry on 'mctx', with the name 'domain'
+ *
+ * Requires:
+ * \li 'mctx' to be a valid memory context.
+ * \li 'domain' to be valid dns_name or NULL.
+ * \li 'nentryp' to be non NULL, *nentryp to be NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS on success
+ * \li ISC_R_NOMEMORY on allocation failure
+ */
+
+isc_result_t
+dns_catz_entry_copy(dns_catz_zone_t *zone, const dns_catz_entry_t *entry,
+ dns_catz_entry_t **nentryp);
+/*%<
+ * Allocate a new catz_entry and deep copy 'entry' into 'nentryp'.
+ *
+ * Requires:
+ * \li 'mctx' to be a valid memory context.
+ * \li 'entry' to be non NULL.
+ * \li 'nentryp' to be non NULL, *nentryp to be NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS on success
+ * \li ISC_R_NOMEMORY on allocation failure
+ */
+
+void
+dns_catz_entry_attach(dns_catz_entry_t *entry, dns_catz_entry_t **entryp);
+/*%<
+ * Attach an entry
+ *
+ * Requires:
+ * \li 'entry' is a valid dns_catz_entry_t.
+ * \li 'entryp' is not NULL and '*entryp' is NULL.
+ */
+
+void
+dns_catz_entry_detach(dns_catz_zone_t *zone, dns_catz_entry_t **entryp);
+/*%<
+ * Detach an entry, free if no further references
+ *
+ * Requires:
+ * \li 'zone' is a valid dns_catz_zone_t.
+ * \li 'entryp' is not NULL and '*entryp' is not NULL.
+ */
+
+bool
+dns_catz_entry_validate(const dns_catz_entry_t *entry);
+/*%<
+ * Validate whether entry is correct.
+ * (NOT YET IMPLEMENTED: always returns true)
+ *
+ * Requires:
+ *\li 'entry' is a valid dns_catz_entry_t.
+ */
+
+bool
+dns_catz_entry_cmp(const dns_catz_entry_t *ea, const dns_catz_entry_t *eb);
+/*%<
+ * Deep compare two entries
+ *
+ * Requires:
+ * \li 'ea' is a valid dns_catz_entry_t.
+ * \li 'eb' is a valid dns_catz_entry_t.
+ *
+ * Returns:
+ * \li 'true' if entries are the same.
+ * \li 'false' if the entries differ.
+ */
+
+void
+dns_catz_zone_attach(dns_catz_zone_t *zone, dns_catz_zone_t **zonep);
+/*%<
+ * Attach a catzone
+ *
+ * Requires:
+ * \li 'zone' is a valid dns_catz_zone_t.
+ * \li 'zonep' is not NULL and '*zonep' is NULL.
+ */
+
+void
+dns_catz_zone_detach(dns_catz_zone_t **zonep);
+/*%<
+ * Detach a zone, free if no further references
+ *
+ * Requires:
+ * \li 'zonep' is not NULL and '*zonep' is not NULL.
+ */
+
+isc_result_t
+dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **zonep,
+ const dns_name_t *name);
+/*%<
+ * Allocate a new catz zone on catzs mctx
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'zonep' is not NULL and '*zonep' is NULL.
+ * \li 'name' is a valid dns_name_t.
+ *
+ */
+
+dns_name_t *
+dns_catz_zone_getname(dns_catz_zone_t *zone);
+/*%<
+ * Get catalog zone name
+ *
+ * Requires:
+ * \li 'zone' is a valid dns_catz_zone_t.
+ */
+
+dns_catz_options_t *
+dns_catz_zone_getdefoptions(dns_catz_zone_t *zone);
+/*%<
+ * Get default member zone options for catalog zone 'zone'
+ *
+ * Requires:
+ * \li 'zone' is a valid dns_catz_zone_t.
+ */
+
+void
+dns_catz_zone_resetdefoptions(dns_catz_zone_t *zone);
+/*%<
+ * Reset the default member zone options for catalog zone 'zone' to
+ * the default values.
+ *
+ * Requires:
+ * \li 'zone' is a valid dns_catz_zone_t.
+ */
+
+isc_result_t
+dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone);
+/*%<
+ * Merge 'newzone' into 'target', calling addzone/delzone/modzone
+ * (from zone->catzs->zmm) for appropriate member zones.
+ *
+ * Requires:
+ * \li 'orig' is a valid dns_catz_zone_t.
+ * \li 'newzone' is not NULL and '*newzone' is not NULL.
+ *
+ */
+
+isc_result_t
+dns_catz_update_process(dns_catz_zones_t *catzs, dns_catz_zone_t *zone,
+ const dns_name_t *src_name, dns_rdataset_t *rdataset);
+/*%<
+ * Process a single rdataset from a catalog zone 'zone' update, src_name is the
+ * record name.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'zone' is a valid dns_catz_zone_t.
+ * \li 'src_name' is a valid dns_name_t.
+ * \li 'rdataset' is valid rdataset.
+ */
+
+isc_result_t
+dns_catz_generate_masterfilename(dns_catz_zone_t *zone, dns_catz_entry_t *entry,
+ isc_buffer_t **buffer);
+/*%<
+ * Generate master file name and put it into *buffer (might be reallocated).
+ * The general format of the file name is:
+ * __catz__catalog.zone.name__member_zone_name.db
+ * But if it's too long it's shortened to:
+ * __catz__unique_hash_generated_from_the_above.db
+ *
+ * Requires:
+ * \li 'zone' is a valid dns_catz_zone_t.
+ * \li 'entry' is a valid dns_catz_entry_t.
+ * \li 'buffer' is not NULL and '*buffer' is not NULL.
+ */
+
+isc_result_t
+dns_catz_generate_zonecfg(dns_catz_zone_t *zone, dns_catz_entry_t *entry,
+ isc_buffer_t **buf);
+/*%<
+ * Generate a zone config entry (in text form) from dns_catz_entry and puts
+ * it into *buf. buf might be reallocated.
+ *
+ * Requires:
+ * \li 'zone' is a valid dns_catz_zone_t.
+ * \li 'entry' is a valid dns_catz_entry_t.
+ * \li 'buf' is not NULL and '*buf' is NULL.
+ *
+ */
+
+/* Methods provided by named to dynamically modify the member zones */
+/* xxxwpk TODO config! */
+typedef isc_result_t (*dns_catz_zoneop_fn_t)(dns_catz_entry_t *entry,
+ dns_catz_zone_t *origin,
+ dns_view_t *view,
+ isc_taskmgr_t *taskmgr,
+ void *udata);
+struct dns_catz_zonemodmethods {
+ dns_catz_zoneop_fn_t addzone;
+ dns_catz_zoneop_fn_t modzone;
+ dns_catz_zoneop_fn_t delzone;
+ void *udata;
+};
+
+isc_result_t
+dns_catz_new_zones(dns_catz_zones_t **catzsp, dns_catz_zonemodmethods_t *zmm,
+ isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr);
+/*%<
+ * Allocate a new catz_zones object, a collection storing all catalog zones
+ * for a view.
+ *
+ * Requires:
+ * \li 'catzsp' is not NULL and '*catzsp' is NULL.
+ * \li 'zmm' is not NULL.
+ *
+ */
+
+isc_result_t
+dns_catz_add_zone(dns_catz_zones_t *catzs, const dns_name_t *name,
+ dns_catz_zone_t **catzp);
+/*%<
+ * Allocate a new catz named 'name' and put it in 'catzs' collection.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'name' is a valid dns_name_t.
+ * \li 'zonep' is not NULL and *zonep is NULL.
+ *
+ */
+
+dns_catz_zone_t *
+dns_catz_get_zone(dns_catz_zones_t *catzs, const dns_name_t *name);
+/*%<
+ * Returns a zone named 'name' from collection 'catzs'
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'name' is a valid dns_name_t.
+ */
+
+void
+dns_catz_catzs_attach(dns_catz_zones_t *catzs, dns_catz_zones_t **catzsp);
+/*%<
+ * Attach 'catzs' to 'catzsp'.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'catzsp' is not NULL and *catzsp is NULL.
+ */
+
+void
+dns_catz_catzs_detach(dns_catz_zones_t **catzsp);
+/*%<
+ * Detach 'catzsp', free if no further references.
+ *
+ * Requires:
+ * \li 'catzsp' is not NULL and *catzsp is not NULL.
+ */
+
+void
+dns_catz_catzs_set_view(dns_catz_zones_t *catzs, dns_view_t *view);
+/*%<
+ * Set a view for 'catzs'.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'catzs->view' is NULL or 'catzs->view' == 'view'.
+ */
+
+isc_result_t
+dns_catz_dbupdate_callback(dns_db_t *db, void *fn_arg);
+/*%<
+ * Callback for update of catalog zone database.
+ * If there was no catalog zone update recently it launches an
+ * update_taskaction immediately.
+ * If there was an update recently it schedules update_taskaction for some time
+ * in the future.
+ * If there is an update scheduled it replaces old db version with a new one.
+ *
+ * Requires:
+ * \li 'db' is a valid database.
+ * \li 'fn_arg' is not NULL (casted to dns_catz_zones_t*).
+ */
+
+void
+dns_catz_update_taskaction(isc_task_t *task, isc_event_t *event);
+/*%<
+ * Task that launches dns_catz_update_from_db.
+ *
+ * Requires:
+ * \li 'event' is not NULL.
+ */
+
+void
+dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs);
+/*%<
+ * Process an updated database for a catalog zone.
+ * It creates a new catz, iterates over database to fill it with content, and
+ * then merges new catz into old catz.
+ *
+ * Requires:
+ * \li 'db' is a valid DB.
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ *
+ */
+
+void
+dns_catz_prereconfig(dns_catz_zones_t *catzs);
+/*%<
+ * Called before reconfig, clears 'active' flag on all the zones in set
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ *
+ */
+
+void
+dns_catz_postreconfig(dns_catz_zones_t *catzs);
+/*%<
+ * Called after reconfig, walks through all zones in set, removes those
+ * inactive and force reload of those with changed configuration.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ */
+
+void
+dns_catz_get_iterator(dns_catz_zone_t *catz, isc_ht_iter_t **itp);
+/*%<
+ * Get the hashtable iterator on catalog zone members, point '*itp' to it.
+ *
+ * Requires:
+ * \li 'catzs' is a valid dns_catz_zones_t.
+ * \li 'itp' is not NULL and '*itp' is NULL.
+ *
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_CATZ_H_ */
diff --git a/lib/dns/include/dns/cert.h b/lib/dns/include/dns/cert.h
new file mode 100644
index 0000000..cdfa245
--- /dev/null
+++ b/lib/dns/include/dns/cert.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_CERT_H
+#define DNS_CERT_H 1
+
+/*! \file dns/cert.h */
+
+#include <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
+
+#endif /* DNS_CERT_H */
diff --git a/lib/dns/include/dns/client.h b/lib/dns/include/dns/client.h
new file mode 100644
index 0000000..fdcb698
--- /dev/null
+++ b/lib/dns/include/dns/client.h
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_CLIENT_H
+#define DNS_CLIENT_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ *
+ * \brief
+ * The DNS client module provides convenient programming interfaces to various
+ * DNS services, such as name resolution with or without DNSSEC validation or
+ * dynamic DNS update. This module is primarily expected to be used by other
+ * applications than BIND9-related ones that need such advanced DNS features.
+ *
+ * MP:
+ *\li In the typical usage of this module, application threads will not share
+ * the same data structures created and manipulated in this module.
+ * However, the module still ensures appropriate synchronization of such
+ * data structures.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li This module does not handle any low-level data directly, and so no
+ * security issue specific to this module is anticipated.
+ */
+
+#include <isc/event.h>
+#include <isc/sockaddr.h>
+
+#include <dns/tsig.h>
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+typedef enum {
+ updateop_none = 0,
+ updateop_add = 1,
+ updateop_delete = 2,
+ updateop_exist = 3,
+ updateop_notexist = 4,
+ updateop_max = 5
+} dns_client_updateop_t;
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * Optional flags for dns_client_create(x).
+ */
+/*%< Enable caching resolution results (experimental). */
+#define DNS_CLIENTCREATEOPT_USECACHE 0x8000
+
+/*%
+ * Optional flags for dns_client_(start)resolve.
+ */
+/*%< Do not return DNSSEC data (e.g. RRSIGS) with response. */
+#define DNS_CLIENTRESOPT_NODNSSEC 0x01
+/*%< Allow running external context. */
+#define DNS_CLIENTRESOPT_RESERVED 0x02
+/*%< Don't validate responses. */
+#define DNS_CLIENTRESOPT_NOVALIDATE 0x04
+/*%< Don't set the CD flag on upstream queries. */
+#define DNS_CLIENTRESOPT_NOCDFLAG 0x08
+/*%< Use TCP transport. */
+#define DNS_CLIENTRESOPT_TCP 0x10
+
+/*%
+ * Optional flags for dns_client_(start)request.
+ */
+/*%< Allow running external context. */
+#define DNS_CLIENTREQOPT_RESERVED 0x01
+/*%< Use TCP transport. */
+#define DNS_CLIENTREQOPT_TCP 0x02
+
+/*%
+ * Optional flags for dns_client_(start)update.
+ */
+/*%< Allow running external context. */
+#define DNS_CLIENTUPDOPT_RESERVED 0x01
+/*%< Use TCP transport. */
+#define DNS_CLIENTUPDOPT_TCP 0x02
+
+/*%
+ * View name used in dns_client.
+ */
+#define DNS_CLIENTVIEW_NAME "_dnsclient"
+
+/*%
+ * A dns_clientresevent_t is sent when name resolution performed by a client
+ * completes. 'result' stores the result code of the entire resolution
+ * procedure. 'vresult' specifically stores the result code of DNSSEC
+ * validation if it is performed. When name resolution successfully completes,
+ * 'answerlist' is typically non empty, containing answer names along with
+ * RRsets. It is the receiver's responsibility to free this list by calling
+ * dns_client_freeresanswer() before freeing the event structure.
+ */
+typedef struct dns_clientresevent {
+ ISC_EVENT_COMMON(struct dns_clientresevent);
+ isc_result_t result;
+ isc_result_t vresult;
+ dns_namelist_t answerlist;
+} dns_clientresevent_t; /* too long? */
+
+/*%
+ * A dns_clientreqevent_t is sent when a DNS request is completed by a client.
+ * 'result' stores the result code of the entire transaction.
+ * If the transaction is successfully completed but the response packet cannot
+ * be parsed, 'result' will store the result code of dns_message_parse().
+ * If the response packet is received, 'rmessage' will contain the response
+ * message, whether it is successfully parsed or not.
+ */
+typedef struct dns_clientreqevent {
+ ISC_EVENT_COMMON(struct dns_clientreqevent);
+ isc_result_t result;
+ dns_message_t *rmessage;
+} dns_clientreqevent_t; /* too long? */
+
+isc_result_t
+dns_client_create(isc_mem_t *mctx, isc_appctx_t *actx, isc_taskmgr_t *taskmgr,
+ isc_socketmgr_t *socketmgr, isc_timermgr_t *timermgr,
+ unsigned int options, dns_client_t **clientp,
+ const isc_sockaddr_t *localaddr4,
+ const isc_sockaddr_t *localaddr6);
+/*%<
+ * Create a DNS client object with minimal internal resources, such as
+ * a default view for the IN class and IPv4/IPv6 dispatches for the view.
+ *
+ * dns_client_create() takes 'manager' arguments so that the caller can
+ * control the behavior of the client through the underlying event framework.
+ * 'localaddr4' and 'localaddr6' specify the local addresses to use for
+ * each address family; if both are set to NULL, then wildcard addresses
+ * will be used for both families. If only one is NULL, then the other
+ * address will be used as the local address, and the NULL protocol family
+ * will not be used.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'actx' is a valid application context.
+ *
+ *\li 'taskmgr' is a valid task manager.
+ *
+ *\li 'socketmgr' is a valid socket manager.
+ *
+ *\li 'timermgr' is a valid timer manager.
+ *
+ *\li clientp != NULL && *clientp == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+void
+dns_client_destroy(dns_client_t **clientp);
+/*%<
+ * Destroy 'client'.
+ *
+ * Requires:
+ *
+ *\li '*clientp' is a valid client.
+ *
+ * Ensures:
+ *
+ *\li *clientp == NULL.
+ */
+
+isc_result_t
+dns_client_setservers(dns_client_t *client, dns_rdataclass_t rdclass,
+ const dns_name_t *name_space, isc_sockaddrlist_t *addrs);
+/*%<
+ * Specify a list of addresses of recursive name servers that the client will
+ * use for name resolution. A view for the 'rdclass' class must be created
+ * beforehand. If 'name_space' is non NULL, the specified server will be used
+ * if and only if the query name is a subdomain of 'name_space'. When servers
+ * for multiple 'name_space's are provided, and a query name is covered by
+ * more than one 'name_space', the servers for the best (longest) matching
+ * name_space will be used. If 'name_space' is NULL, it works as if
+ * dns_rootname (.) were specified.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'name_space' is NULL or a valid name.
+ *
+ *\li 'addrs' != NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+isc_result_t
+dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass,
+ const dns_name_t *name_space);
+/*%<
+ * Remove configured recursive name servers for the 'rdclass' and 'name_space'
+ * from the client. See the description of dns_client_setservers() for
+ * the requirements about 'rdclass' and 'name_space'.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'name_space' is NULL or a valid name.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+isc_result_t
+dns_client_resolve(dns_client_t *client, const dns_name_t *name,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int options, dns_namelist_t *namelist);
+
+isc_result_t
+dns_client_startresolve(dns_client_t *client, const dns_name_t *name,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type,
+ unsigned int options, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_clientrestrans_t **transp);
+/*%<
+ * Perform name resolution for 'name', 'rdclass', and 'type'.
+ *
+ * If any trusted keys are configured and the query name is considered to
+ * belong to a secure zone, these functions also validate the responses
+ * using DNSSEC by default. If the DNS_CLIENTRESOPT_NOVALIDATE flag is set
+ * in 'options', DNSSEC validation is disabled regardless of the configured
+ * trusted keys or the query name. With DNS_CLIENTRESOPT_NODNSSEC
+ * DNSSEC data is not returned with response. DNS_CLIENTRESOPT_NOCDFLAG
+ * disables the CD flag on queries, DNS_CLIENTRESOPT_TCP switches to
+ * the TCP (vs. UDP) transport.
+ *
+ * dns_client_resolve() provides a synchronous service. This function starts
+ * name resolution internally and blocks until it completes. On success,
+ * 'namelist' will contain a list of answer names, each of which has
+ * corresponding RRsets. The caller must provide a valid empty list, and
+ * is responsible for freeing the list content via dns_client_freeresanswer().
+ * If the name resolution fails due to an error in DNSSEC validation,
+ * dns_client_resolve() returns the result code indicating the validation
+ * error. Otherwise, it returns the result code of the entire resolution
+ * process, either success or failure.
+ *
+ * It is expected that the client object passed to dns_client_resolve() was
+ * created via dns_client_create() and has external managers and contexts.
+ *
+ * dns_client_startresolve() is an asynchronous version of dns_client_resolve()
+ * and does not block. When name resolution is completed, 'action' will be
+ * called with the argument of a 'dns_clientresevent_t' object, which contains
+ * the resulting list of answer names (on success). On return, '*transp' is
+ * set to an opaque transaction ID so that the caller can cancel this
+ * resolution process.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'addrs' != NULL.
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'namelist' != NULL and is not empty.
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li 'transp' != NULL && *transp == NULL;
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+void
+dns_client_cancelresolve(dns_clientrestrans_t *trans);
+/*%<
+ * Cancel an ongoing resolution procedure started via
+ * dns_client_startresolve().
+ *
+ * Notes:
+ *
+ *\li If the resolution procedure has not completed, post its CLIENTRESDONE
+ * event with a result code of #ISC_R_CANCELED.
+ *
+ * Requires:
+ *
+ *\li 'trans' is a valid transaction ID.
+ */
+
+void
+dns_client_destroyrestrans(dns_clientrestrans_t **transp);
+/*%<
+ * Destroy name resolution transaction state identified by '*transp'.
+ *
+ * Requires:
+ *
+ *\li '*transp' is a valid transaction ID.
+ *
+ *\li The caller has received the CLIENTRESDONE event (either because the
+ * resolution completed or because dns_client_cancelresolve() was called).
+ *
+ * Ensures:
+ *
+ *\li *transp == NULL.
+ */
+
+void
+dns_client_freeresanswer(dns_client_t *client, dns_namelist_t *namelist);
+/*%<
+ * Free resources allocated for the content of 'namelist'.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'namelist' != NULL.
+ */
+
+isc_result_t
+dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass,
+ dns_rdatatype_t rdtype, const dns_name_t *keyname,
+ isc_buffer_t *keydatabuf);
+/*%<
+ * Add a DNSSEC trusted key for the 'rdclass' class. A view for the 'rdclass'
+ * class must be created beforehand. 'rdtype' is the type of the RR data
+ * for the key, either DNSKEY or DS. 'keyname' is the DNS name of the key,
+ * and 'keydatabuf' stores the RR data.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'keyname' is a valid name.
+ *
+ *\li 'keydatabuf' is a valid buffer.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+isc_result_t
+dns_client_request(dns_client_t *client, dns_message_t *qmessage,
+ dns_message_t *rmessage, const isc_sockaddr_t *server,
+ unsigned int options, unsigned int parseoptions,
+ dns_tsec_t *tsec, unsigned int timeout,
+ unsigned int udptimeout, unsigned int udpretries);
+
+isc_result_t
+dns_client_startrequest(dns_client_t *client, dns_message_t *qmessage,
+ dns_message_t *rmessage, const isc_sockaddr_t *server,
+ unsigned int options, unsigned int parseoptions,
+ dns_tsec_t *tsec, unsigned int timeout,
+ unsigned int udptimeout, unsigned int udpretries,
+ isc_task_t *task, isc_taskaction_t action, void *arg,
+ dns_clientreqtrans_t **transp);
+
+/*%<
+ * Send a DNS request containing a query message 'query' to 'server'.
+ *
+ * 'parseoptions' will be used when the response packet is parsed, and will be
+ * passed to dns_message_parse() via dns_request_getresponse(). See
+ * dns_message_parse() for more details.
+ *
+ * 'tsec' is a transaction security object containing, e.g. a TSIG key for
+ * authenticating the request/response transaction. This is optional and can
+ * be NULL, in which case this library performs the transaction without any
+ * transaction authentication.
+ *
+ * 'timeout', 'udptimeout', and 'udpretries' are passed to
+ * dns_request_createvia3(). See dns_request_createvia3() for more details.
+ *
+ * dns_client_request() provides a synchronous service. This function sends
+ * the request and blocks until a response is received. On success,
+ * 'rmessage' will contain the response message. The caller must provide a
+ * valid initialized message.
+ *
+ * It is expected that the client object passed to dns_client_request() was
+ * created via dns_client_create() and has external managers and contexts.
+ *
+ * dns_client_startrequest() is an asynchronous version of dns_client_request()
+ * and does not block. When the transaction is completed, 'action' will be
+ * called with the argument of a 'dns_clientreqevent_t' object, which contains
+ * the response message (on success). On return, '*transp' is set to an opaque
+ * transaction ID so that the caller can cancel this request.
+ *
+ * DNS_CLIENTREQOPT_TCP switches to the TCP (vs. UDP) transport.
+ *
+ * Requires:
+ *
+ *\li 'client' is a valid client.
+ *
+ *\li 'qmessage' and 'rmessage' are valid initialized message.
+ *
+ *\li 'server' is a valid socket address structure.
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li 'transp' != NULL && *transp == NULL;
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ *
+ *\li Any result that dns_message_parse() can return.
+ */
+
+void
+dns_client_cancelrequest(dns_clientreqtrans_t *transp);
+/*%<
+ * Cancel an ongoing DNS request procedure started via
+ * dns_client_startrequest().
+ *
+ * Notes:
+ *
+ *\li If the request procedure has not completed, post its CLIENTREQDONE
+ * event with a result code of #ISC_R_CANCELED.
+ *
+ * Requires:
+ *
+ *\li 'trans' is a valid transaction ID.
+ */
+
+void
+dns_client_destroyreqtrans(dns_clientreqtrans_t **transp);
+/*%
+ * Destroy DNS request transaction state identified by '*transp'.
+ *
+ * Requires:
+ *
+ *\li '*transp' is a valid transaction ID.
+ *
+ *\li The caller has received the CLIENTREQDONE event (either because the
+ * request completed or because dns_client_cancelrequest() was called).
+ *
+ * Ensures:
+ *
+ *\li *transp == NULL.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_CLIENT_H */
diff --git a/lib/dns/include/dns/clientinfo.h b/lib/dns/include/dns/clientinfo.h
new file mode 100644
index 0000000..3928d21
--- /dev/null
+++ b/lib/dns/include/dns/clientinfo.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_CLIENTINFO_H
+#define DNS_CLIENTINFO_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/clientinfo.h
+ * \brief
+ * The DNS clientinfo interface allows libdns to retrieve information
+ * about the client from the caller.
+ *
+ * The clientinfo interface is used by the DNS DB and DLZ interfaces;
+ * it allows databases to modify their answers on the basis of information
+ * about the client, such as source IP address.
+ *
+ * dns_clientinfo_t contains a pointer to an opaque structure containing
+ * client information in some form. dns_clientinfomethods_t contains a
+ * list of methods which operate on that opaque structure to return
+ * potentially useful data. Both structures also contain versioning
+ * information.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <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, dns_ecs_t *ecs,
+ void *versionp);
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_CLIENTINFO_H */
diff --git a/lib/dns/include/dns/compress.h b/lib/dns/include/dns/compress.h
new file mode 100644
index 0000000..7e19af6
--- /dev/null
+++ b/lib/dns/include/dns/compress.h
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_COMPRESS_H
+#define DNS_COMPRESS_H 1
+
+#include <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
+
+#endif /* DNS_COMPRESS_H */
diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h
new file mode 100644
index 0000000..84b1cb1
--- /dev/null
+++ b/lib/dns/include/dns/db.h
@@ -0,0 +1,1800 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DB_H
+#define DNS_DB_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/db.h
+ * \brief
+ * The DNS DB interface allows named rdatasets to be stored and retrieved.
+ *
+ * The dns_db_t type is like a "virtual class". To actually use
+ * DBs, an implementation of the class is required.
+ *
+ * XXX more XXX
+ *
+ * MP:
+ * \li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ * \li No anticipated impact.
+ *
+ * Resources:
+ * \li TBS
+ *
+ * Security:
+ * \li No anticipated impact.
+ *
+ * Standards:
+ * \li None.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <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
+
+/*****
+***** Types
+*****/
+
+typedef struct dns_dbmethods {
+ void (*attach)(dns_db_t *source, dns_db_t **targetp);
+ void (*detach)(dns_db_t **dbp);
+ isc_result_t (*beginload)(dns_db_t *db,
+ dns_rdatacallbacks_t *callbacks);
+ isc_result_t (*endload)(dns_db_t *db, dns_rdatacallbacks_t *callbacks);
+ isc_result_t (*serialize)(dns_db_t *db, dns_dbversion_t *version,
+ FILE *file);
+ isc_result_t (*dump)(dns_db_t *db, dns_dbversion_t *version,
+ const char *filename,
+ dns_masterformat_t masterformat);
+ void (*currentversion)(dns_db_t *db, dns_dbversion_t **versionp);
+ isc_result_t (*newversion)(dns_db_t *db, dns_dbversion_t **versionp);
+ void (*attachversion)(dns_db_t *db, dns_dbversion_t *source,
+ dns_dbversion_t **targetp);
+ void (*closeversion)(dns_db_t *db, dns_dbversion_t **versionp,
+ bool commit);
+ isc_result_t (*findnode)(dns_db_t *db, const dns_name_t *name,
+ bool create, dns_dbnode_t **nodep);
+ isc_result_t (*find)(dns_db_t *db, const dns_name_t *name,
+ dns_dbversion_t *version, dns_rdatatype_t type,
+ unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+ isc_result_t (*findzonecut)(dns_db_t *db, const dns_name_t *name,
+ unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_name_t *dcname,
+ dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+ void (*attachnode)(dns_db_t *db, dns_dbnode_t *source,
+ dns_dbnode_t **targetp);
+ void (*detachnode)(dns_db_t *db, dns_dbnode_t **targetp);
+ isc_result_t (*expirenode)(dns_db_t *db, dns_dbnode_t *node,
+ isc_stdtime_t now);
+ void (*printnode)(dns_db_t *db, dns_dbnode_t *node, FILE *out);
+ isc_result_t (*createiterator)(dns_db_t *db, unsigned int options,
+ dns_dbiterator_t **iteratorp);
+ isc_result_t (*findrdataset)(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version,
+ dns_rdatatype_t type,
+ dns_rdatatype_t covers, isc_stdtime_t now,
+ dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+ isc_result_t (*allrdatasets)(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp);
+ isc_result_t (*addrdataset)(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, isc_stdtime_t now,
+ dns_rdataset_t *rdataset,
+ unsigned int options,
+ dns_rdataset_t *addedrdataset);
+ isc_result_t (*subtractrdataset)(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version,
+ dns_rdataset_t *rdataset,
+ unsigned int options,
+ dns_rdataset_t *newrdataset);
+ isc_result_t (*deleterdataset)(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version,
+ dns_rdatatype_t type,
+ dns_rdatatype_t covers);
+ bool (*issecure)(dns_db_t *db);
+ unsigned int (*nodecount)(dns_db_t *db);
+ bool (*ispersistent)(dns_db_t *db);
+ void (*overmem)(dns_db_t *db, bool overmem);
+ void (*settask)(dns_db_t *db, isc_task_t *);
+ isc_result_t (*getoriginnode)(dns_db_t *db, dns_dbnode_t **nodep);
+ void (*transfernode)(dns_db_t *db, dns_dbnode_t **sourcep,
+ dns_dbnode_t **targetp);
+ isc_result_t (*getnsec3parameters)(dns_db_t *db,
+ dns_dbversion_t *version,
+ dns_hash_t *hash, uint8_t *flags,
+ uint16_t *iterations,
+ unsigned char *salt,
+ size_t *salt_len);
+ isc_result_t (*findnsec3node)(dns_db_t *db, const dns_name_t *name,
+ bool create, dns_dbnode_t **nodep);
+ isc_result_t (*setsigningtime)(dns_db_t *db, dns_rdataset_t *rdataset,
+ isc_stdtime_t resign);
+ isc_result_t (*getsigningtime)(dns_db_t *db, dns_rdataset_t *rdataset,
+ dns_name_t *name);
+ void (*resigned)(dns_db_t *db, dns_rdataset_t *rdataset,
+ dns_dbversion_t *version);
+ bool (*isdnssec)(dns_db_t *db);
+ dns_stats_t *(*getrrsetstats)(dns_db_t *db);
+ void (*rpz_attach)(dns_db_t *db, void *rpzs, uint8_t rpz_num);
+ isc_result_t (*rpz_ready)(dns_db_t *db);
+ isc_result_t (*findnodeext)(dns_db_t *db, const dns_name_t *name,
+ bool create,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo,
+ dns_dbnode_t **nodep);
+ isc_result_t (*findext)(dns_db_t *db, const dns_name_t *name,
+ dns_dbversion_t *version, dns_rdatatype_t type,
+ unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo,
+ dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+ isc_result_t (*setcachestats)(dns_db_t *db, isc_stats_t *stats);
+ size_t (*hashsize)(dns_db_t *db);
+ isc_result_t (*nodefullname)(dns_db_t *db, dns_dbnode_t *node,
+ dns_name_t *name);
+ isc_result_t (*getsize)(dns_db_t *db, dns_dbversion_t *version,
+ uint64_t *records, uint64_t *bytes);
+ isc_result_t (*setservestalettl)(dns_db_t *db, dns_ttl_t ttl);
+ isc_result_t (*getservestalettl)(dns_db_t *db, dns_ttl_t *ttl);
+ isc_result_t (*setservestalerefresh)(dns_db_t *db, uint32_t interval);
+ isc_result_t (*getservestalerefresh)(dns_db_t *db, uint32_t *interval);
+ isc_result_t (*setgluecachestats)(dns_db_t *db, isc_stats_t *stats);
+ isc_result_t (*adjusthashsize)(dns_db_t *db, size_t size);
+} dns_dbmethods_t;
+
+typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t *mctx,
+ const dns_name_t *name,
+ dns_dbtype_t type,
+ dns_rdataclass_t rdclass,
+ unsigned int argc, char *argv[],
+ void *driverarg, dns_db_t **dbp);
+
+typedef isc_result_t (*dns_dbupdate_callback_t)(dns_db_t *db, void *fn_arg);
+
+#define DNS_DB_MAGIC ISC_MAGIC('D', 'N', 'S', 'D')
+#define DNS_DB_VALID(db) ISC_MAGIC_VALID(db, DNS_DB_MAGIC)
+
+/*%
+ * This structure is actually just the common prefix of a DNS db
+ * implementation's version of a dns_db_t.
+ * \brief
+ * Direct use of this structure by clients is forbidden. DB implementations
+ * may change the structure. 'magic' must be DNS_DB_MAGIC for any of the
+ * dns_db_ routines to work. DB implementations must maintain all DB
+ * invariants.
+ */
+struct dns_db {
+ unsigned int magic;
+ unsigned int impmagic;
+ dns_dbmethods_t *methods;
+ uint16_t attributes;
+ dns_rdataclass_t rdclass;
+ dns_name_t origin;
+ isc_mem_t *mctx;
+ ISC_LIST(dns_dbonupdatelistener_t) update_listeners;
+};
+
+#define DNS_DBATTR_CACHE 0x01
+#define DNS_DBATTR_STUB 0x02
+
+struct dns_dbonupdatelistener {
+ dns_dbupdate_callback_t onupdate;
+ void *onupdate_arg;
+ ISC_LINK(dns_dbonupdatelistener_t) link;
+};
+
+/*@{*/
+/*%
+ * Options that can be specified for dns_db_find().
+ */
+#define DNS_DBFIND_GLUEOK 0x0001
+#define DNS_DBFIND_VALIDATEGLUE 0x0002
+#define DNS_DBFIND_NOWILD 0x0004
+#define DNS_DBFIND_PENDINGOK 0x0008
+#define DNS_DBFIND_NOEXACT 0x0010
+#define DNS_DBFIND_FORCENSEC 0x0020
+#define DNS_DBFIND_COVERINGNSEC 0x0040
+#define DNS_DBFIND_FORCENSEC3 0x0080
+#define DNS_DBFIND_ADDITIONALOK 0x0100
+#define DNS_DBFIND_NOZONECUT 0x0200
+
+/*
+ * DNS_DBFIND_STALEOK: This flag is set when BIND fails to refresh a RRset due
+ * to timeout (resolver-query-timeout). Its intent is to try to look for stale
+ * data in cache as a fallback, but only if stale answers are enabled in
+ * configuration.
+ */
+#define DNS_DBFIND_STALEOK 0x0400
+
+/*
+ * DNS_DBFIND_STALEENABLED: This flag is used as a hint to the database that
+ * it may use stale data. It is always set during query lookup if stale
+ * answers are enabled, but only effectively used during stale-refresh-time
+ * window. Also during this window, the resolver will not try to resolve the
+ * query, in other words no attempt to refresh the data in cache is made when
+ * the stale-refresh-time window is active.
+ */
+#define DNS_DBFIND_STALEENABLED 0x0800
+
+/*
+ * DNS_DBFIND_STALETIMEOUT: This flag is used when we want stale data from the
+ * database, but not due to a failure in resolution, it also doesn't require
+ * stale-refresh-time window timer to be active. As long as there is stale
+ * data available, it should be returned.
+ */
+#define DNS_DBFIND_STALETIMEOUT 0x1000
+
+/*
+ * DNS_DBFIND_STALESTART: This flag is used to activate stale-refresh-time
+ * window.
+ */
+#define DNS_DBFIND_STALESTART 0x2000
+/*@}*/
+
+/*@{*/
+/*%
+ * Options that can be specified for dns_db_addrdataset().
+ */
+#define DNS_DBADD_MERGE 0x01
+#define DNS_DBADD_FORCE 0x02
+#define DNS_DBADD_EXACT 0x04
+#define DNS_DBADD_EXACTTTL 0x08
+#define DNS_DBADD_PREFETCH 0x10
+/*@}*/
+
+/*%
+ * Options that can be specified for dns_db_subtractrdataset().
+ */
+#define DNS_DBSUB_EXACT 0x01
+#define DNS_DBSUB_WANTOLD 0x02
+
+/*@{*/
+/*%
+ * Iterator options
+ */
+#define DNS_DB_RELATIVENAMES 0x1
+#define DNS_DB_NSEC3ONLY 0x2
+#define DNS_DB_NONSEC3 0x4
+/*@}*/
+
+#define DNS_DB_STALEOK 0x01
+#define DNS_DB_EXPIREDOK 0x02
+
+/*****
+***** Methods
+*****/
+
+/***
+ *** Basic DB Methods
+ ***/
+
+isc_result_t
+dns_db_create(isc_mem_t *mctx, const char *db_type, const dns_name_t *origin,
+ dns_dbtype_t type, dns_rdataclass_t rdclass, unsigned int argc,
+ char *argv[], dns_db_t **dbp);
+/*%<
+ * Create a new database using implementation 'db_type'.
+ *
+ * Notes:
+ * \li All names in the database must be subdomains of 'origin' and in class
+ * 'rdclass'. The database makes its own copy of the origin, so the
+ * caller may do whatever they like with 'origin' and its storage once the
+ * call returns.
+ *
+ * \li DB implementation-specific parameters are passed using argc and argv.
+ *
+ * Requires:
+ *
+ * \li dbp != NULL and *dbp == NULL
+ *
+ * \li 'origin' is a valid absolute domain name.
+ *
+ * \li mctx is a valid memory context
+ *
+ * Ensures:
+ *
+ * \li A copy of 'origin' has been made for the databases use, and the
+ * caller is free to do whatever they want with the name and storage
+ * associated with 'origin'.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ * \li #ISC_R_NOTFOUND db_type not found
+ *
+ * \li Many other errors are possible, depending on what db_type was
+ * specified.
+ */
+
+void
+dns_db_attach(dns_db_t *source, dns_db_t **targetp);
+/*%<
+ * Attach *targetp to source.
+ *
+ * Requires:
+ *
+ * \li 'source' is a valid database.
+ *
+ * \li 'targetp' points to a NULL dns_db_t *.
+ *
+ * Ensures:
+ *
+ * \li *targetp is attached to source.
+ */
+
+void
+dns_db_detach(dns_db_t **dbp);
+/*%<
+ * Detach *dbp from its database.
+ *
+ * Requires:
+ *
+ * \li 'dbp' points to a valid database.
+ *
+ * Ensures:
+ *
+ * \li *dbp is NULL.
+ *
+ * \li If '*dbp' is the last reference to the database,
+ * all resources used by the database will be freed
+ */
+
+bool
+dns_db_iscache(dns_db_t *db);
+/*%<
+ * Does 'db' have cache semantics?
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li #true 'db' has cache semantics
+ * \li #false otherwise
+ */
+
+bool
+dns_db_iszone(dns_db_t *db);
+/*%<
+ * Does 'db' have zone semantics?
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li #true 'db' has zone semantics
+ * \li #false otherwise
+ */
+
+bool
+dns_db_isstub(dns_db_t *db);
+/*%<
+ * Does 'db' have stub semantics?
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li #true 'db' has zone semantics
+ * \li #false otherwise
+ */
+
+bool
+dns_db_issecure(dns_db_t *db);
+/*%<
+ * Is 'db' secure?
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * Returns:
+ * \li #true 'db' is secure.
+ * \li #false 'db' is not secure.
+ */
+
+bool
+dns_db_isdnssec(dns_db_t *db);
+/*%<
+ * Is 'db' secure or partially secure?
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * Returns:
+ * \li #true 'db' is secure or is partially.
+ * \li #false 'db' is not secure.
+ */
+
+dns_name_t *
+dns_db_origin(dns_db_t *db);
+/*%<
+ * The origin of the database.
+ *
+ * Note: caller must not try to change this name.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ *
+ * \li The origin of the database.
+ */
+
+dns_rdataclass_t
+dns_db_class(dns_db_t *db);
+/*%<
+ * The class of the database.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ *
+ * \li The class of the database.
+ */
+
+isc_result_t
+dns_db_beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks);
+/*%<
+ * Begin loading 'db'.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li This is the first attempt to load 'db'.
+ *
+ * \li 'callbacks' is a pointer to an initialized dns_rdatacallbacks_t
+ * structure.
+ *
+ * Ensures:
+ *
+ * \li On success, callbacks->add will be a valid dns_addrdatasetfunc_t
+ * suitable for loading records into 'db' from a raw or text zone
+ * file. callbacks->add_private will be a valid DB load context
+ * which should be used as 'arg' when callbacks->add is called.
+ * callbacks->deserialize will be a valid dns_deserialize_func_t
+ * suitable for loading 'db' from a map format zone file.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used, syntax errors in the master file, etc.
+ */
+
+isc_result_t
+dns_db_endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks);
+/*%<
+ * Finish loading 'db'.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database that is being loaded.
+ *
+ * \li 'callbacks' is a valid dns_rdatacallbacks_t structure.
+ *
+ * \li callbacks->add_private is not NULL and is a valid database load context.
+ *
+ * Ensures:
+ *
+ * \li 'callbacks' is returned to its state prior to calling dns_db_beginload()
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used, syntax errors in the master file, etc.
+ */
+
+isc_result_t
+dns_db_load(dns_db_t *db, const char *filename, dns_masterformat_t format,
+ unsigned int options);
+/*%<
+ * Load master file 'filename' into 'db'.
+ *
+ * Notes:
+ * \li This routine is equivalent to calling
+ *
+ *\code
+ * dns_db_beginload();
+ * dns_master_loadfile();
+ * dns_db_endload();
+ *\endcode
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li This is the first attempt to load 'db'.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used, syntax errors in the master file, etc.
+ */
+
+isc_result_t
+dns_db_serialize(dns_db_t *db, dns_dbversion_t *version, FILE *rbtfile);
+/*%<
+ * Dump version 'version' of 'db' to map-format file 'filename'.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'version' is a valid version.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used, OS file errors, etc.
+ */
+
+isc_result_t
+dns_db_dump(dns_db_t *db, dns_dbversion_t *version, const char *filename);
+/*%<
+ * Dump version 'version' of 'db' to master file 'filename'.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'version' is a valid version.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used, OS file errors, etc.
+ */
+
+/***
+ *** Version Methods
+ ***/
+
+void
+dns_db_currentversion(dns_db_t *db, dns_dbversion_t **versionp);
+/*%<
+ * Open the current version for reading.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * \li versionp != NULL && *verisonp == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, '*versionp' is attached to the current version.
+ *
+ */
+
+isc_result_t
+dns_db_newversion(dns_db_t *db, dns_dbversion_t **versionp);
+/*%<
+ * Open a new version for reading and writing.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * \li versionp != NULL && *verisonp == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, '*versionp' is attached to the current version.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+void
+dns_db_attachversion(dns_db_t *db, dns_dbversion_t *source,
+ dns_dbversion_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * \li source is a valid open version
+ *
+ * \li targetp != NULL && *targetp == NULL
+ *
+ * Ensures:
+ *
+ * \li '*targetp' is attached to source.
+ */
+
+void
+dns_db_closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit);
+/*%<
+ * Close version '*versionp'.
+ *
+ * Note: if '*versionp' is a read-write version and 'commit' is true,
+ * then all changes made in the version will take effect, otherwise they
+ * will be rolled back. The value of 'commit' is ignored for read-only
+ * versions.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with zone semantics.
+ *
+ * \li '*versionp' refers to a valid version.
+ *
+ * \li If committing a writable version, then there must be no other
+ * outstanding references to the version (e.g. an active rdataset
+ * iterator).
+ *
+ * Ensures:
+ *
+ * \li *versionp == NULL
+ *
+ * \li If *versionp is a read-write version, and commit is true, then
+ * the version will become the current version. If !commit, then all
+ * changes made in the version will be undone, and the version will
+ * not become the current version.
+ */
+
+/***
+ *** Node Methods
+ ***/
+
+isc_result_t
+dns_db_findnode(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep);
+
+isc_result_t
+dns_db_findnodeext(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo, dns_dbnode_t **nodep);
+/*%<
+ * Find the node with name 'name'.
+ *
+ * dns_db_findnodeext() (findnode extended) also accepts parameters
+ * 'methods' and 'clientinfo', which, when provided, enable the database to
+ * retrieve information about the client from the caller, and modify its
+ * response on the basis of that information.
+ *
+ * Notes:
+ * \li If 'create' is true and no node with name 'name' exists, then
+ * such a node will be created.
+ *
+ * \li This routine is for finding or creating a node with the specified
+ * name. There are no partial matches. It is not suitable for use
+ * in building responses to ordinary DNS queries; clients which wish
+ * to do that should use dns_db_find() instead.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'name' is a valid, non-empty, absolute name.
+ *
+ * \li nodep != NULL && *nodep == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, *nodep is attached to the node with name 'name'.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND If !create and name not found.
+ * \li #ISC_R_NOMEMORY Can only happen if create is true.
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+
+isc_result_t
+dns_db_findext(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find the best match for 'name' and 'type' in version 'version' of 'db'.
+ *
+ * dns_db_findext() (find extended) also accepts parameters 'methods'
+ * and 'clientinfo', which when provided enable the database to retrieve
+ * information about the client from the caller, and modify its response
+ * on the basis of this information.
+ *
+ * Notes:
+ *
+ * \li If type == dns_rdataset_any, then rdataset will not be bound.
+ *
+ * \li If 'options' does not have #DNS_DBFIND_GLUEOK set, then no glue will
+ * be returned. For zone databases, glue is as defined in RFC2181.
+ * For cache databases, glue is any rdataset with a trust of
+ * dns_trust_glue.
+ *
+ * \li If 'options' does not have #DNS_DBFIND_ADDITIONALOK set, then no
+ * additional records will be returned. Only caches can have
+ * rdataset with trust dns_trust_additional.
+ *
+ * \li If 'options' does not have #DNS_DBFIND_PENDINGOK set, then no
+ * pending data will be returned. This option is only meaningful for
+ * cache databases.
+ *
+ * \li If the #DNS_DBFIND_NOWILD option is set, then wildcard matching will
+ * be disabled. This option is only meaningful for zone databases.
+ *
+ * \li If the #DNS_DBFIND_NOZONECUT option is set, the database is
+ * assumed to contain no zone cuts above 'name'. An implementation
+ * may therefore choose to search for a match beginning at 'name'
+ * rather than walking down the tree to check check for delegations.
+ * If #DNS_DBFIND_NOWILD is not set, wildcard matching will be
+ * attempted at each node starting at the direct ancestor of 'name'
+ * and working up to the zone origin. This option is only meaningful
+ * when querying redirect zones.
+ *
+ * \li If the #DNS_DBFIND_FORCENSEC option is set, the database is assumed to
+ * have NSEC records, and these will be returned when appropriate. This
+ * is only necessary when querying a database that was not secure
+ * when created.
+ *
+ * \li If the DNS_DBFIND_COVERINGNSEC option is set, then look for a
+ * NSEC record that potentially covers 'name' if a answer cannot
+ * be found. Note the returned NSEC needs to be checked to ensure
+ * that it is correct. This only affects answers returned from the
+ * cache.
+ *
+ * \li If the #DNS_DBFIND_FORCENSEC3 option is set, then we are looking
+ * in the NSEC3 tree and not the main tree. Without this option being
+ * set NSEC3 records will not be found.
+ *
+ * \li To respond to a query for SIG records, the caller should create a
+ * rdataset iterator and extract the signatures from each rdataset.
+ *
+ * \li Making queries of type ANY with #DNS_DBFIND_GLUEOK is not recommended,
+ * because the burden of determining whether a given rdataset is valid
+ * glue or not falls upon the caller.
+ *
+ * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is a
+ * cache database, an rdataset will not be found unless it expires after
+ * 'now'. Any ANY query will not match unless at least one rdataset at
+ * the node expires after 'now'. If 'now' is zero, then the current time
+ * will be used.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'type' is not SIG, or a meta-RR type other than 'ANY' (e.g. 'OPT').
+ *
+ * \li 'nodep' is NULL, or nodep is a valid pointer and *nodep == NULL.
+ *
+ * \li 'foundname' is a valid name with a dedicated buffer.
+ *
+ * \li 'rdataset' is NULL, or is a valid unassociated rdataset.
+ *
+ * Ensures,
+ * on a non-error completion:
+ *
+ * \li If nodep != NULL, then it is bound to the found node.
+ *
+ * \li If foundname != NULL, then it contains the full name of the
+ * found node.
+ *
+ * \li If rdataset != NULL and type != dns_rdatatype_any, then
+ * rdataset is bound to the found rdataset.
+ *
+ * Non-error results are:
+ *
+ * \li #ISC_R_SUCCESS The desired node and type were
+ * found.
+ *
+ * \li #DNS_R_GLUE The desired node and type were
+ * found, but are glue. This
+ * result can only occur if
+ * the DNS_DBFIND_GLUEOK option
+ * is set. This result can only
+ * occur if 'db' is a zone
+ * database. If type ==
+ * dns_rdatatype_any, then the
+ * node returned may contain, or
+ * consist entirely of invalid
+ * glue (i.e. data occluded by a
+ * zone cut). The caller must
+ * take care not to return invalid
+ * glue to a client.
+ *
+ * \li #DNS_R_DELEGATION The data requested is beneath
+ * a zone cut. node, foundname,
+ * and rdataset reference the
+ * NS RRset of the zone cut.
+ * If 'db' is a cache database,
+ * then this is the deepest known
+ * delegation.
+ *
+ * \li #DNS_R_ZONECUT type == dns_rdatatype_any, and
+ * the desired node is a zonecut.
+ * The caller must take care not
+ * to return inappropriate glue
+ * to a client. This result can
+ * only occur if 'db' is a zone
+ * database and DNS_DBFIND_GLUEOK
+ * is set.
+ *
+ * \li #DNS_R_DNAME The data requested is beneath
+ * a DNAME. node, foundname,
+ * and rdataset reference the
+ * DNAME RRset.
+ *
+ * \li #DNS_R_CNAME The rdataset requested was not
+ * found, but there is a CNAME
+ * at the desired name. node,
+ * foundname, and rdataset
+ * reference the CNAME RRset.
+ *
+ * \li #DNS_R_NXDOMAIN The desired name does not
+ * exist.
+ *
+ * \li #DNS_R_NXRRSET The desired name exists, but
+ * the desired type does not.
+ *
+ * \li #ISC_R_NOTFOUND The desired name does not
+ * exist, and no delegation could
+ * be found. This result can only
+ * occur if 'db' is a cache
+ * database. The caller should
+ * use its nameserver(s) of last
+ * resort (e.g. root hints).
+ *
+ * \li #DNS_R_NCACHENXDOMAIN The desired name does not
+ * exist. 'node' is bound to the
+ * cache node with the desired
+ * name, and 'rdataset' contains
+ * the negative caching proof.
+ *
+ * \li #DNS_R_NCACHENXRRSET The desired type does not
+ * exist. 'node' is bound to the
+ * cache node with the desired
+ * name, and 'rdataset' contains
+ * the negative caching proof.
+ *
+ * \li #DNS_R_EMPTYNAME The name exists but there is
+ * no data at the name.
+ *
+ * \li #DNS_R_COVERINGNSEC The returned data is a NSEC
+ * that potentially covers 'name'.
+ *
+ * \li #DNS_R_EMPTYWILD The name is a wildcard without
+ * resource records.
+ *
+ * Error results:
+ *
+ * \li #ISC_R_NOMEMORY
+ *
+ * \li #DNS_R_BADDB Data that is required to be
+ * present in the DB, e.g. an NSEC
+ * record in a secure zone, is not
+ * present.
+ *
+ * \li Other results are possible, and should all be treated as
+ * errors.
+ */
+
+isc_result_t
+dns_db_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_name_t *dcname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find the deepest known zonecut which encloses 'name' in 'db'.
+ *
+ * Notes:
+ *
+ * \li If the #DNS_DBFIND_NOEXACT option is set, then the zonecut returned
+ * (if any) will be the deepest known ancestor of 'name'.
+ *
+ * \li If 'now' is zero, then the current time will be used.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database with cache semantics.
+ *
+ * \li 'nodep' is NULL, or nodep is a valid pointer and *nodep == NULL.
+ *
+ * \li 'foundname' is a valid name with a dedicated buffer.
+ *
+ * \li 'dcname' is a valid name with a dedicated buffer.
+ *
+ * \li 'rdataset' is NULL, or is a valid unassociated rdataset.
+ *
+ * Ensures, on a non-error completion:
+ *
+ * \li If nodep != NULL, then it is bound to the found node.
+ *
+ * \li If foundname != NULL, then it contains the full name of the
+ * found node.
+ *
+ * \li If dcname != NULL, then it contains the deepest cached name
+ * that exists in the database.
+ *
+ * \li If rdataset != NULL and type != dns_rdatatype_any, then
+ * rdataset is bound to the found rdataset.
+ *
+ * Non-error results are:
+ *
+ * \li #ISC_R_SUCCESS
+ *
+ * \li #ISC_R_NOTFOUND
+ *
+ * \li Other results are possible, and should all be treated as
+ * errors.
+ */
+
+void
+dns_db_attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp);
+/*%<
+ * Attach *targetp to source.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'source' is a valid node.
+ *
+ * \li 'targetp' points to a NULL dns_dbnode_t *.
+ *
+ * Ensures:
+ *
+ * \li *targetp is attached to source.
+ */
+
+void
+dns_db_detachnode(dns_db_t *db, dns_dbnode_t **nodep);
+/*%<
+ * Detach *nodep from its node.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'nodep' points to a valid node.
+ *
+ * Ensures:
+ *
+ * \li *nodep is NULL.
+ */
+
+void
+dns_db_transfernode(dns_db_t *db, dns_dbnode_t **sourcep,
+ dns_dbnode_t **targetp);
+/*%<
+ * Transfer a node between pointer.
+ *
+ * This is equivalent to calling dns_db_attachnode() then dns_db_detachnode().
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li '*sourcep' is a valid node.
+ *
+ * \li 'targetp' points to a NULL dns_dbnode_t *.
+ *
+ * Ensures:
+ *
+ * \li '*sourcep' is NULL.
+ */
+
+isc_result_t
+dns_db_expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now);
+/*%<
+ * Mark as stale all records at 'node' which expire at or before 'now'.
+ *
+ * Note: if 'now' is zero, then the current time will be used.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid cache database.
+ *
+ * \li 'node' is a valid node.
+ */
+
+void
+dns_db_printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out);
+/*%<
+ * Print a textual representation of the contents of the node to
+ * 'out'.
+ *
+ * Note: this function is intended for debugging, not general use.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ */
+
+/***
+ *** DB Iterator Creation
+ ***/
+
+isc_result_t
+dns_db_createiterator(dns_db_t *db, unsigned int options,
+ dns_dbiterator_t **iteratorp);
+/*%<
+ * Create an iterator for version 'version' of 'db'.
+ *
+ * Notes:
+ *
+ * \li One or more of the following options can be set.
+ * #DNS_DB_RELATIVENAMES
+ * #DNS_DB_NSEC3ONLY
+ * #DNS_DB_NONSEC3
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li iteratorp != NULL && *iteratorp == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, *iteratorp will be a valid database iterator.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ */
+
+/***
+ *** Rdataset Methods
+ ***/
+
+/*
+ * XXXRTH Should we check for glue and pending data in dns_db_findrdataset()?
+ */
+
+isc_result_t
+dns_db_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+
+/*%<
+ * Search for an rdataset of type 'type' at 'node' that are in version
+ * 'version' of 'db'. If found, make 'rdataset' refer to it.
+ *
+ * Notes:
+ *
+ * \li If 'version' is NULL, then the current version will be used.
+ *
+ * \li Care must be used when using this routine to build a DNS response:
+ * 'node' should have been found with dns_db_find(), not
+ * dns_db_findnode(). No glue checking is done. No checking for
+ * pending data is done.
+ *
+ * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is a
+ * cache database, an rdataset will not be found unless it expires after
+ * 'now'. If 'now' is zero, then the current time will be used.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ *
+ * \li 'rdataset' is a valid, disassociated rdataset.
+ *
+ * \li 'sigrdataset' is a valid, disassociated rdataset, or it is NULL.
+ *
+ * \li If 'covers' != 0, 'type' must be RRSIG.
+ *
+ * \li 'type' is not a meta-RR type such as 'ANY' or 'OPT'.
+ *
+ * Ensures:
+ *
+ * \li On success, 'rdataset' is associated with the found rdataset.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp);
+/*%<
+ * Make '*iteratorp' an rdataset iterator for all rdatasets at 'node' in
+ * version 'version' of 'db'.
+ *
+ * Notes:
+ *
+ * \li If 'version' is NULL, then the current version will be used.
+ *
+ * \li 'options' controls which rdatasets are selected when interating over
+ * the node.
+ * 'DNS_DB_STALEOK' return stale rdatasets as well as current rdatasets.
+ * 'DNS_DB_EXPIREDOK' return expired rdatasets as well as current
+ * rdatasets.
+ *
+ * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is a
+ * cache database, an rdataset will not be found unless it expires after
+ * 'now'. Any ANY query will not match unless at least one rdataset at
+ * the node expires after 'now'. If 'now' is zero, then the current time
+ * will be used.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ *
+ * \li iteratorp != NULL && *iteratorp == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, '*iteratorp' is a valid rdataset iterator.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset,
+ unsigned int options, dns_rdataset_t *addedrdataset);
+/*%<
+ * Add 'rdataset' to 'node' in version 'version' of 'db'.
+ *
+ * Notes:
+ *
+ * \li If the database has zone semantics, the #DNS_DBADD_MERGE option is set,
+ * and an rdataset of the same type as 'rdataset' already exists at
+ * 'node' then the contents of 'rdataset' will be merged with the existing
+ * rdataset. If the option is not set, then rdataset will replace any
+ * existing rdataset of the same type. If not merging and the
+ * #DNS_DBADD_FORCE option is set, then the data will update the database
+ * without regard to trust levels. If not forcing the data, then the
+ * rdataset will only be added if its trust level is >= the trust level of
+ * any existing rdataset. Forcing is only meaningful for cache databases.
+ * If #DNS_DBADD_EXACT is set then there must be no rdata in common between
+ * the old and new rdata sets. If #DNS_DBADD_EXACTTTL is set then both
+ * the old and new rdata sets must have the same ttl.
+ *
+ * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is
+ * a cache database, then the added rdataset will expire no later than
+ * now + rdataset->ttl.
+ *
+ * \li If 'addedrdataset' is not NULL, then it will be attached to the
+ * resulting new rdataset in the database, or to the existing data if
+ * the existing data was better.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ *
+ * \li 'rdataset' is a valid, associated rdataset with the same class
+ * as 'db'.
+ *
+ * \li 'addedrdataset' is NULL, or a valid, unassociated rdataset.
+ *
+ * \li The database has zone semantics and 'version' is a valid
+ * read-write version, or the database has cache semantics
+ * and version is NULL.
+ *
+ * \li If the database has cache semantics, the #DNS_DBADD_MERGE option must
+ * not be set.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #DNS_R_UNCHANGED The operation did not change
+ * anything. \li #ISC_R_NOMEMORY \li #DNS_R_NOTEXACT
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_subtractrdataset(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, dns_rdataset_t *rdataset,
+ unsigned int options, dns_rdataset_t *newrdataset);
+/*%<
+ * Remove any rdata in 'rdataset' from 'node' in version 'version' of
+ * 'db'.
+ *
+ * Notes:
+ *
+ * \li If 'newrdataset' is not NULL, then it will be attached to the
+ * resulting new rdataset in the database, unless the rdataset has
+ * become nonexistent. If DNS_DBSUB_EXACT is set then all elements
+ * of 'rdataset' must exist at 'node'.
+ *
+ *\li If DNS_DBSUB_WANTOLD is set and the entire rdataset was deleted
+ * then return the original rdatatset in newrdataset if that existed.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ *
+ * \li 'rdataset' is a valid, associated rdataset with the same class
+ * as 'db'.
+ *
+ * \li 'newrdataset' is NULL, or a valid, unassociated rdataset.
+ *
+ * \li The database has zone semantics and 'version' is a valid
+ * read-write version.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #DNS_R_UNCHANGED The operation did not change
+ * anything. \li #DNS_R_NXRRSET All rdata of the same
+ *type as
+ * those in 'rdataset' have been deleted. \li #DNS_R_NOTEXACT
+ * Some part of 'rdataset' did not exist and DNS_DBSUB_EXACT was set.
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_deleterdataset(dns_db_t *db, dns_dbnode_t *node,
+ dns_dbversion_t *version, dns_rdatatype_t type,
+ dns_rdatatype_t covers);
+/*%<
+ * Make it so that no rdataset of type 'type' exists at 'node' in version
+ * version 'version' of 'db'.
+ *
+ * Notes:
+ *
+ * \li If 'type' is dns_rdatatype_any, then no rdatasets will exist in
+ * 'version' (provided that the dns_db_deleterdataset() isn't followed
+ * by one or more dns_db_addrdataset() calls).
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'node' is a valid node.
+ *
+ * \li The database has zone semantics and 'version' is a valid
+ * read-write version, or the database has cache semantics
+ * and version is NULL.
+ *
+ * \li 'type' is not a meta-RR type, except for dns_rdatatype_any, which is
+ * allowed.
+ *
+ * \li If 'covers' != 0, 'type' must be SIG.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #DNS_R_UNCHANGED No rdatasets of 'type' existed
+ * before the operation was attempted.
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_getsoaserial(dns_db_t *db, dns_dbversion_t *ver, uint32_t *serialp);
+/*%<
+ * Get the current SOA serial number from a zone database.
+ *
+ * Requires:
+ * \li 'db' is a valid database with zone semantics.
+ * \li 'ver' is a valid version.
+ */
+
+void
+dns_db_overmem(dns_db_t *db, bool overmem);
+/*%<
+ * Enable / disable aggressive cache cleaning.
+ */
+
+unsigned int
+dns_db_nodecount(dns_db_t *db);
+/*%<
+ * Count the number of nodes in 'db'.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li The number of nodes in the database
+ */
+
+size_t
+dns_db_hashsize(dns_db_t *db);
+/*%<
+ * For database implementations using a hash table, report the
+ * current number of buckets.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li The number of buckets in the database's hash table, or
+ * 0 if not implemented.
+ */
+
+isc_result_t
+dns_db_adjusthashsize(dns_db_t *db, size_t size);
+/*%<
+ * For database implementations using a hash table, adjust the size of
+ * the hash table to store objects with a maximum total memory footprint
+ * of 'size' bytes. If 'size' is set to 0, it means no finite limit is
+ * requested.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ * \li 'size' is maximum memory footprint of the database in bytes
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS The registration succeeded
+ * \li #ISC_R_NOMEMORY Out of memory
+ */
+
+void
+dns_db_settask(dns_db_t *db, isc_task_t *task);
+/*%<
+ * If task is set then the final detach maybe performed asynchronously.
+ *
+ * Requires:
+ * \li 'db' is a valid database.
+ * \li 'task' to be valid or NULL.
+ */
+
+bool
+dns_db_ispersistent(dns_db_t *db);
+/*%<
+ * Is 'db' persistent? A persistent database does not need to be loaded
+ * from disk or written to disk.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * Returns:
+ * \li #true 'db' is persistent.
+ * \li #false 'db' is not persistent.
+ */
+
+isc_result_t
+dns_db_register(const char *name, dns_dbcreatefunc_t create, void *driverarg,
+ isc_mem_t *mctx, dns_dbimplementation_t **dbimp);
+
+/*%<
+ * Register a new database implementation and add it to the list of
+ * supported implementations.
+ *
+ * Requires:
+ *
+ * \li 'name' is not NULL
+ * \li 'order' is a valid function pointer
+ * \li 'mctx' is a valid memory context
+ * \li dbimp != NULL && *dbimp == NULL
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS The registration succeeded
+ * \li #ISC_R_NOMEMORY Out of memory
+ * \li #ISC_R_EXISTS A database implementation with the same name exists
+ *
+ * Ensures:
+ *
+ * \li *dbimp points to an opaque structure which must be passed to
+ * dns_db_unregister().
+ */
+
+void
+dns_db_unregister(dns_dbimplementation_t **dbimp);
+/*%<
+ * Remove a database implementation from the list of supported
+ * implementations. No databases of this type can be active when this
+ * is called.
+ *
+ * Requires:
+ * \li dbimp != NULL && *dbimp == NULL
+ *
+ * Ensures:
+ *
+ * \li Any memory allocated in *dbimp will be freed.
+ */
+
+isc_result_t
+dns_db_getoriginnode(dns_db_t *db, dns_dbnode_t **nodep);
+/*%<
+ * Get the origin DB node corresponding to the DB's zone. This function
+ * should typically succeed unless the underlying DB implementation doesn't
+ * support the feature.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid zone database.
+ * \li 'nodep' != NULL && '*nodep' == NULL
+ *
+ * Ensures:
+ * \li On success, '*nodep' will point to the DB node of the zone's origin.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND - the DB implementation does not support this feature.
+ */
+
+isc_result_t
+dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version,
+ dns_hash_t *hash, uint8_t *flags,
+ uint16_t *iterations, unsigned char *salt,
+ size_t *salt_length);
+/*%<
+ * Get the NSEC3 parameters that are associated with this zone.
+ *
+ * Requires:
+ * \li 'db' is a valid zone database.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND - the DB implementation does not support this feature
+ * or this zone does not have NSEC3 records.
+ */
+
+isc_result_t
+dns_db_getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records,
+ uint64_t *xfrsize);
+/*%<
+ * On success if 'records' is not NULL, it is set to the number of records
+ * in the given version of the database. If 'xfrisize' is not NULL, it is
+ * set to the approximate number of bytes needed to transfer the records,
+ * counting name, TTL, type, class, and rdata for each RR. (This is meant
+ * to be a rough approximation of the size of a full zone transfer, though
+ * it does not take into account DNS message overhead or name compression.)
+ *
+ * Requires:
+ * \li 'db' is a valid zone database.
+ * \li 'version' is NULL or a valid version.
+ * \li 'records' is NULL or a pointer to return the record count in.
+ * \li 'xfrsize' is NULL or a pointer to return the byte count in.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED
+ */
+
+isc_result_t
+dns_db_findnsec3node(dns_db_t *db, const dns_name_t *name, bool create,
+ dns_dbnode_t **nodep);
+/*%<
+ * Find the NSEC3 node with name 'name'.
+ *
+ * Notes:
+ * \li If 'create' is true and no node with name 'name' exists, then
+ * such a node will be created.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database.
+ *
+ * \li 'name' is a valid, non-empty, absolute name.
+ *
+ * \li nodep != NULL && *nodep == NULL
+ *
+ * Ensures:
+ *
+ * \li On success, *nodep is attached to the node with name 'name'.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND If !create and name not found.
+ * \li #ISC_R_NOMEMORY Can only happen if create is true.
+ *
+ * \li Other results are possible, depending upon the database
+ * implementation used.
+ */
+
+isc_result_t
+dns_db_setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset,
+ isc_stdtime_t resign);
+/*%<
+ * Sets the re-signing time associated with 'rdataset' to 'resign'.
+ *
+ * Requires:
+ * \li 'db' is a valid zone database.
+ * \li 'rdataset' is or is to be associated with 'db'.
+ * \li 'rdataset' is not pending removed from the heap via an
+ * uncommitted call to dns_db_resigned().
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_getsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, dns_name_t *name);
+/*%<
+ * Return the rdataset with the earliest signing time in the zone.
+ * Note: the rdataset is version agnostic.
+ *
+ * Requires:
+ * \li 'db' is a valid zone database.
+ * \li 'rdataset' to be initialized but not associated.
+ * \li 'name' to be NULL or have a buffer associated with it.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND - No dataset exists.
+ */
+
+void
+dns_db_resigned(dns_db_t *db, dns_rdataset_t *rdataset,
+ dns_dbversion_t *version);
+/*%<
+ * Mark 'rdataset' as not being available to be returned by
+ * dns_db_getsigningtime(). If the changes associated with 'version'
+ * are committed this will be permanent. If the version is not committed
+ * this change will be rolled back when the version is closed. Until
+ * 'version' is either committed or rolled back, 'rdataset' can no longer
+ * be acted upon by dns_db_setsigningtime().
+ *
+ * Requires:
+ * \li 'db' is a valid zone database.
+ * \li 'rdataset' to be associated with 'db'.
+ * \li 'version' to be open for writing.
+ */
+
+dns_stats_t *
+dns_db_getrrsetstats(dns_db_t *db);
+/*%<
+ * Get statistics information counting RRsets stored in the DB, when available.
+ * The statistics may not be available depending on the DB implementation.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database (cache only).
+ *
+ * Returns:
+ * \li when available, a pointer to a statistics object created by
+ * dns_rdatasetstats_create(); otherwise NULL.
+ */
+
+isc_result_t
+dns_db_setcachestats(dns_db_t *db, isc_stats_t *stats);
+/*%<
+ * Set the location in which to collect cache statistics.
+ * This option may not exist depending on the DB implementation.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database (cache only).
+ *
+ * Returns:
+ * \li when available, a pointer to a statistics object created by
+ * dns_rdatasetstats_create(); otherwise NULL.
+ */
+
+void
+dns_db_rpz_attach(dns_db_t *db, void *rpzs, uint8_t rpz_num) ISC_DEPRECATED;
+/*%<
+ * Attach the response policy information for a view to a database for a
+ * zone for the view.
+ */
+
+isc_result_t
+dns_db_rpz_ready(dns_db_t *db) ISC_DEPRECATED;
+/*%<
+ * Finish loading a response policy zone.
+ */
+
+isc_result_t
+dns_db_updatenotify_register(dns_db_t *db, dns_dbupdate_callback_t fn,
+ void *fn_arg);
+/*%<
+ * Register a notify-on-update callback function to a database.
+ * Duplicate callbacks are suppressed.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database
+ * \li 'fn' is not NULL
+ *
+ */
+
+isc_result_t
+dns_db_updatenotify_unregister(dns_db_t *db, dns_dbupdate_callback_t fn,
+ void *fn_arg);
+/*%<
+ * Unregister a notify-on-update callback.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database
+ * \li 'db' has update callback registered
+ *
+ */
+
+isc_result_t
+dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name);
+/*%<
+ * Get the name associated with a database node.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database
+ * \li 'node' and 'name' are not NULL
+ */
+
+isc_result_t
+dns_db_setservestalettl(dns_db_t *db, dns_ttl_t ttl);
+/*%<
+ * Sets the maximum length of time that cached answers may be retained
+ * past their normal TTL. Default value for the library is 0, disabling
+ * the use of stale data.
+ *
+ * Requires:
+ * \li 'db' is a valid cache database.
+ * \li 'ttl' is the number of seconds to retain data past its normal expiry.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_getservestalettl(dns_db_t *db, dns_ttl_t *ttl);
+/*%<
+ * Gets maximum length of time that cached answers may be kept past
+ * normal TTL expiration.
+ *
+ * Requires:
+ * \li 'db' is a valid cache database.
+ * \li 'ttl' is the number of seconds to retain data past its normal expiry.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_setservestalerefresh(dns_db_t *db, uint32_t interval);
+/*%<
+ * Sets the length of time to wait before attempting to refresh a rrset
+ * if a previous attempt in doing so has failed.
+ * During this time window if stale rrset are available in cache they
+ * will be directly returned to client.
+ *
+ * Requires:
+ * \li 'db' is a valid cache database.
+ * \li 'interval' is number of seconds before attempting to refresh data.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_getservestalerefresh(dns_db_t *db, uint32_t *interval);
+/*%<
+ * Gets the length of time in which stale answers are directly returned from
+ * cache before attempting to refresh them, in case a previous attempt in
+ * doing so has failed.
+ *
+ * Requires:
+ * \li 'db' is a valid cache database.
+ * \li 'interval' is number of seconds before attempting to refresh data.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_setgluecachestats(dns_db_t *db, isc_stats_t *stats);
+/*%<
+ * Set the location in which to collect glue cache statistics.
+ * This option may not exist depending on the DB implementation.
+ *
+ * Requires:
+ *
+ * \li 'db' is a valid database (cache only).
+ *
+ * Returns:
+ * \li when available, a pointer to a statistics object created by
+ * dns_rdatasetstats_create(); otherwise NULL.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_DB_H */
diff --git a/lib/dns/include/dns/dbiterator.h b/lib/dns/include/dns/dbiterator.h
new file mode 100644
index 0000000..32f204a
--- /dev/null
+++ b/lib/dns/include/dns/dbiterator.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DBITERATOR_H
+#define DNS_DBITERATOR_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/dbiterator.h
+ * \brief
+ * The DNS DB Iterator interface allows iteration of all of the nodes in a
+ * database.
+ *
+ * The dns_dbiterator_t type is like a "virtual class". To actually use
+ * it, an implementation of the class is required. This implementation is
+ * supplied by the database.
+ *
+ * It is the client's responsibility to call dns_db_detachnode() on all
+ * nodes returned.
+ *
+ * XXX &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
+
+#endif /* DNS_DBITERATOR_H */
diff --git a/lib/dns/include/dns/dbtable.h b/lib/dns/include/dns/dbtable.h
new file mode 100644
index 0000000..dc1a991
--- /dev/null
+++ b/lib/dns/include/dns/dbtable.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DBTABLE_H
+#define DNS_DBTABLE_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/dbtable.h
+ * \brief
+ * DNS DB Tables
+ *
+ * XXX TBS XXX
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li None.
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li None.
+ */
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+#define DNS_DBTABLEFIND_NOEXACT 0x01
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_dbtable_create(isc_mem_t *mctx, dns_rdataclass_t rdclass,
+ dns_dbtable_t **dbtablep);
+/*%<
+ * Make a new dbtable of class 'rdclass'
+ *
+ * Requires:
+ *\li mctx != NULL
+ * \li dbtablep != NULL && *dptablep == NULL
+ *\li 'rdclass' is a valid class
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_UNEXPECTED
+ */
+
+void
+dns_dbtable_attach(dns_dbtable_t *source, dns_dbtable_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid dbtable.
+ *
+ *\li 'targetp' points to a NULL dns_dbtable_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ */
+
+void
+dns_dbtable_detach(dns_dbtable_t **dbtablep);
+/*%<
+ * Detach *dbtablep from its dbtable.
+ *
+ * Requires:
+ *
+ *\li '*dbtablep' points to a valid dbtable.
+ *
+ * Ensures:
+ *
+ *\li *dbtablep is NULL.
+ *
+ *\li If '*dbtablep' is the last reference to the dbtable,
+ * all resources used by the dbtable will be freed
+ */
+
+isc_result_t
+dns_dbtable_add(dns_dbtable_t *dbtable, dns_db_t *db);
+/*%<
+ * Add 'db' to 'dbtable'.
+ *
+ * Requires:
+ *\li 'dbtable' is a valid dbtable.
+ *
+ *\li 'db' is a valid database with the same class as 'dbtable'
+ */
+
+void
+dns_dbtable_remove(dns_dbtable_t *dbtable, dns_db_t *db);
+/*%<
+ * Remove 'db' from 'dbtable'.
+ *
+ * Requires:
+ *\li 'db' was previously added to 'dbtable'.
+ */
+
+void
+dns_dbtable_adddefault(dns_dbtable_t *dbtable, dns_db_t *db);
+/*%<
+ * Use 'db' as the result of a dns_dbtable_find() if no better match is
+ * available.
+ */
+
+void
+dns_dbtable_getdefault(dns_dbtable_t *dbtable, dns_db_t **db);
+/*%<
+ * Get the 'db' used as the result of a dns_dbtable_find()
+ * if no better match is available.
+ */
+
+void
+dns_dbtable_removedefault(dns_dbtable_t *dbtable);
+/*%<
+ * Remove the default db from 'dbtable'.
+ */
+
+isc_result_t
+dns_dbtable_find(dns_dbtable_t *dbtable, const dns_name_t *name,
+ unsigned int options, dns_db_t **dbp);
+/*%<
+ * Find the deepest match to 'name' in the dbtable, and return it
+ *
+ * Notes:
+ *\li If the DNS_DBTABLEFIND_NOEXACT option is set, the best partial
+ * match (if any) to 'name' will be returned.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS on success
+ *\li something else: no default and match
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_DBTABLE_H */
diff --git a/lib/dns/include/dns/diff.h b/lib/dns/include/dns/diff.h
new file mode 100644
index 0000000..48e1b2e
--- /dev/null
+++ b/lib/dns/include/dns/diff.h
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DIFF_H
+#define DNS_DIFF_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/diff.h
+ * \brief
+ * A diff is a convenience type representing a list of changes to be
+ * made to a database.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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
+
+#endif /* DNS_DIFF_H */
diff --git a/lib/dns/include/dns/dispatch.h b/lib/dns/include/dns/dispatch.h
new file mode 100644
index 0000000..6061843
--- /dev/null
+++ b/lib/dns/include/dns/dispatch.h
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DISPATCH_H
+#define DNS_DISPATCH_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/dispatch.h
+ * \brief
+ * DNS Dispatch Management
+ * Shared UDP and single-use TCP dispatches for queries and responses.
+ *
+ * MP:
+ *
+ *\li All locking is performed internally to each dispatch.
+ * Restrictions apply to dns_dispatch_removeresponse().
+ *
+ * Reliability:
+ *
+ * Resources:
+ *
+ * Security:
+ *
+ *\li Depends on the isc_socket_t and dns_message_t for prevention of
+ * buffer overruns.
+ *
+ * Standards:
+ *
+ *\li None.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+#include <isc/mutex.h>
+#include <isc/socket.h>
+#include <isc/types.h>
+
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * This event is sent to a task when a response comes in.
+ * No part of this structure should ever be modified by the caller,
+ * other than parts of the buffer. The holy parts of the buffer are
+ * the base and size of the buffer. All other parts of the buffer may
+ * be used. On event delivery the used region contains the packet.
+ *
+ * "id" is the received message id,
+ *
+ * "addr" is the host that sent it to us,
+ *
+ * "buffer" holds state on the received data.
+ *
+ * The "free" routine for this event will clean up itself as well as
+ * any buffer space allocated from common pools.
+ */
+
+struct dns_dispatchevent {
+ ISC_EVENT_COMMON(dns_dispatchevent_t); /*%< standard event common */
+ isc_result_t result; /*%< result code */
+ int32_t id; /*%< message id */
+ isc_sockaddr_t addr; /*%< address recv'd from */
+ struct in6_pktinfo pktinfo; /*%< reply info for v6 */
+ isc_buffer_t buffer; /*%< data buffer */
+ uint32_t attributes; /*%< mirrored from socket.h */
+};
+
+/*%
+ * This is a set of one or more dispatches which can be retrieved
+ * round-robin fashion.
+ */
+struct dns_dispatchset {
+ isc_mem_t *mctx;
+ dns_dispatch_t **dispatches;
+ int ndisp;
+ int cur;
+ isc_mutex_t lock;
+};
+
+/*@{*/
+/*%
+ * Attributes for added dispatchers.
+ *
+ * Values with the mask 0xffff0000 are application defined.
+ * Values with the mask 0x0000ffff are library defined.
+ *
+ * Insane values (like setting both TCP and UDP) are not caught. Don't
+ * do that.
+ *
+ * _PRIVATE
+ * The dispatcher cannot be shared.
+ *
+ * _TCP, _UDP
+ * The dispatcher is a TCP or UDP socket.
+ *
+ * _IPV4, _IPV6
+ * The dispatcher uses an IPv4 or IPv6 socket.
+ *
+ * _NOLISTEN
+ * The dispatcher should not listen on the socket.
+ *
+ * _MAKEQUERY
+ * The dispatcher can be used to issue queries to other servers, and
+ * accept replies from them.
+ *
+ * _RANDOMPORT
+ * Previously used to indicate that the port of a dispatch UDP must be
+ * chosen randomly. This behavior now always applies and the attribute
+ * is obsoleted.
+ *
+ * _EXCLUSIVE
+ * A separate socket will be used on-demand for each transaction.
+ */
+#define DNS_DISPATCHATTR_PRIVATE 0x00000001U
+#define DNS_DISPATCHATTR_TCP 0x00000002U
+#define DNS_DISPATCHATTR_UDP 0x00000004U
+#define DNS_DISPATCHATTR_IPV4 0x00000008U
+#define DNS_DISPATCHATTR_IPV6 0x00000010U
+#define DNS_DISPATCHATTR_NOLISTEN 0x00000020U
+#define DNS_DISPATCHATTR_MAKEQUERY 0x00000040U
+#define DNS_DISPATCHATTR_CONNECTED 0x00000080U
+#define DNS_DISPATCHATTR_FIXEDID 0x00000100U
+#define DNS_DISPATCHATTR_EXCLUSIVE 0x00000200U
+#define DNS_DISPATCHATTR_CANREUSE 0x00000400U
+/*@}*/
+
+/*
+ */
+#define DNS_DISPATCHOPT_FIXEDID 0x00000001U
+
+isc_result_t
+dns_dispatchmgr_create(isc_mem_t *mctx, dns_dispatchmgr_t **mgrp);
+/*%<
+ * Creates a new dispatchmgr object.
+ *
+ * Requires:
+ *\li "mctx" be a valid memory context.
+ *
+ *\li mgrp != NULL && *mgrp == NULL
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+void
+dns_dispatchmgr_destroy(dns_dispatchmgr_t **mgrp);
+/*%<
+ * Destroys the dispatchmgr when it becomes empty. This could be
+ * immediately.
+ *
+ * Requires:
+ *\li mgrp != NULL && *mgrp is a valid dispatchmgr.
+ */
+
+void
+dns_dispatchmgr_setblackhole(dns_dispatchmgr_t *mgr, dns_acl_t *blackhole);
+/*%<
+ * Sets the dispatcher's "blackhole list," a list of addresses that will
+ * be ignored by all dispatchers created by the dispatchmgr.
+ *
+ * Requires:
+ * \li mgrp is a valid dispatchmgr
+ * \li blackhole is a valid acl
+ */
+
+dns_acl_t *
+dns_dispatchmgr_getblackhole(dns_dispatchmgr_t *mgr);
+/*%<
+ * Gets a pointer to the dispatcher's current blackhole list,
+ * without incrementing its reference count.
+ *
+ * Requires:
+ *\li mgr is a valid dispatchmgr
+ * Returns:
+ *\li A pointer to the current blackhole list, or NULL.
+ */
+
+void
+dns_dispatchmgr_setblackportlist(dns_dispatchmgr_t *mgr,
+ dns_portlist_t *portlist);
+/*%<
+ * This function is deprecated. Use dns_dispatchmgr_setavailports() instead.
+ *
+ * Requires:
+ *\li mgr is a valid dispatchmgr
+ */
+
+dns_portlist_t *
+dns_dispatchmgr_getblackportlist(dns_dispatchmgr_t *mgr);
+/*%<
+ * This function is deprecated and always returns NULL.
+ *
+ * Requires:
+ *\li mgr is a valid dispatchmgr
+ */
+
+isc_result_t
+dns_dispatchmgr_setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset,
+ isc_portset_t *v6portset);
+/*%<
+ * Sets a list of UDP ports that can be used for outgoing UDP messages.
+ *
+ * Requires:
+ *\li mgr is a valid dispatchmgr
+ *\li v4portset is NULL or a valid port set
+ *\li v6portset is NULL or a valid port set
+ */
+
+void
+dns_dispatchmgr_setstats(dns_dispatchmgr_t *mgr, isc_stats_t *stats);
+/*%<
+ * Sets statistics counter for the dispatchmgr. This function is expected to
+ * be called only on zone creation (when necessary).
+ * Once installed, it cannot be removed or replaced. Also, there is no
+ * interface to get the installed stats from the zone; the caller must keep the
+ * stats to reference (e.g. dump) it later.
+ *
+ * Requires:
+ *\li mgr is a valid dispatchmgr with no managed dispatch.
+ *\li stats is a valid statistics supporting resolver statistics counters
+ * (see dns/stats.h).
+ */
+
+isc_result_t
+dns_dispatch_getudp(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr,
+ isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr,
+ unsigned int buffersize, unsigned int maxbuffers,
+ unsigned int maxrequests, unsigned int buckets,
+ unsigned int increment, unsigned int attributes,
+ unsigned int mask, dns_dispatch_t **dispp);
+
+isc_result_t
+dns_dispatch_getudp_dup(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr,
+ isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr,
+ unsigned int buffersize, unsigned int maxbuffers,
+ unsigned int maxrequests, unsigned int buckets,
+ unsigned int increment, unsigned int attributes,
+ unsigned int mask, dns_dispatch_t **dispp,
+ dns_dispatch_t *dup);
+/*%<
+ * Attach to existing dns_dispatch_t if one is found with dns_dispatchmgr_find,
+ * otherwise create a new UDP dispatch.
+ *
+ * Requires:
+ *\li All pointer parameters be valid for their respective types.
+ *
+ *\li dispp != NULL && *disp == NULL
+ *
+ *\li 512 <= buffersize <= 64k
+ *
+ *\li maxbuffers > 0
+ *
+ *\li buckets < 2097169
+ *
+ *\li increment > buckets
+ *
+ *\li (attributes & DNS_DISPATCHATTR_TCP) == 0
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- success.
+ *
+ *\li Anything else -- failure.
+ */
+
+isc_result_t
+dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, isc_socket_t *sock,
+ isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr,
+ const isc_sockaddr_t *destaddr, unsigned int buffersize,
+ unsigned int maxbuffers, unsigned int maxrequests,
+ unsigned int buckets, unsigned int increment,
+ unsigned int attributes, dns_dispatch_t **dispp);
+/*%<
+ * Create a new dns_dispatch and attach it to the provided isc_socket_t.
+ *
+ * For all dispatches, "buffersize" is the maximum packet size we will
+ * accept.
+ *
+ * "maxbuffers" and "maxrequests" control the number of buffers in the
+ * overall system and the number of buffers which can be allocated to
+ * requests.
+ *
+ * "buckets" is the number of buckets to use, and should be prime.
+ *
+ * "increment" is used in a collision avoidance function, and needs to be
+ * a prime > buckets, and not 2.
+ *
+ * Requires:
+ *
+ *\li mgr is a valid dispatch manager.
+ *
+ *\li sock is a valid.
+ *
+ *\li task is a valid task that can be used internally to this dispatcher.
+ *
+ * \li 512 <= buffersize <= 64k
+ *
+ *\li maxbuffers > 0.
+ *
+ *\li maxrequests <= maxbuffers.
+ *
+ *\li buckets < 2097169 (the next prime after 65536 * 32)
+ *
+ *\li increment > buckets (and prime).
+ *
+ *\li attributes includes #DNS_DISPATCHATTR_TCP and does not include
+ * #DNS_DISPATCHATTR_UDP.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- success.
+ *
+ *\li Anything else -- failure.
+ */
+
+void
+dns_dispatch_attach(dns_dispatch_t *disp, dns_dispatch_t **dispp);
+/*%<
+ * Attach to a dispatch handle.
+ *
+ * Requires:
+ *\li disp is valid.
+ *
+ *\li dispp != NULL && *dispp == NULL
+ */
+
+void
+dns_dispatch_detach(dns_dispatch_t **dispp);
+/*%<
+ * Detaches from the dispatch.
+ *
+ * Requires:
+ *\li dispp != NULL and *dispp be a valid dispatch.
+ */
+
+void
+dns_dispatch_starttcp(dns_dispatch_t *disp);
+/*%<
+ * Start processing of a TCP dispatch once the socket connects.
+ *
+ * Requires:
+ *\li 'disp' is valid.
+ */
+
+isc_result_t
+dns_dispatch_gettcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *destaddr,
+ const isc_sockaddr_t *localaddr, bool *connected,
+ dns_dispatch_t **dispp);
+/*
+ * Attempt to connect to a existing TCP connection (connection completed
+ * if connected == NULL).
+ */
+
+isc_result_t
+dns_dispatch_addresponse(dns_dispatch_t *disp, unsigned int options,
+ const isc_sockaddr_t *dest, isc_task_t *task,
+ isc_taskaction_t action, void *arg, uint16_t *idp,
+ dns_dispentry_t **resp, isc_socketmgr_t *sockmgr);
+/*%<
+ * Add a response entry for this dispatch.
+ *
+ * "*idp" is filled in with the assigned message ID, and *resp is filled in
+ * to contain the magic token used to request event flow stop.
+ *
+ * Arranges for the given task to get a callback for response packets. When
+ * the event is delivered, it must be returned using dns_dispatch_freeevent()
+ * or through dns_dispatch_removeresponse() for another to be delivered.
+ *
+ * Requires:
+ *\li "idp" be non-NULL.
+ *
+ *\li "task" "action" and "arg" be set as appropriate.
+ *
+ *\li "dest" be non-NULL and valid.
+ *
+ *\li "resp" be non-NULL and *resp be NULL
+ *
+ *\li "sockmgr" be NULL or a valid socket manager. If 'disp' has
+ * the DNS_DISPATCHATTR_EXCLUSIVE attribute, this must not be NULL,
+ * which also means dns_dispatch_addresponse() cannot be used.
+ *
+ * Ensures:
+ *
+ *\li &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_removeresponse(dns_dispentry_t **resp,
+ dns_dispatchevent_t **sockevent);
+/*%<
+ * Stops the flow of responses for the provided id and destination.
+ * If "sockevent" is non-NULL, the dispatch event and associated buffer is
+ * also returned to the system.
+ *
+ * Requires:
+ *\li "resp" != NULL and "*resp" contain a value previously allocated
+ * by dns_dispatch_addresponse();
+ *
+ *\li May only be called from within the task given as the 'task'
+ * argument to dns_dispatch_addresponse() when allocating '*resp'.
+ */
+
+isc_socket_t *
+dns_dispatch_getentrysocket(dns_dispentry_t *resp);
+
+isc_socket_t *
+dns_dispatch_getsocket(dns_dispatch_t *disp);
+/*%<
+ * Return the socket associated with this dispatcher.
+ *
+ * Requires:
+ *\li disp is valid.
+ *
+ * Returns:
+ *\li The socket the dispatcher is using.
+ */
+
+isc_result_t
+dns_dispatch_getlocaladdress(dns_dispatch_t *disp, isc_sockaddr_t *addrp);
+/*%<
+ * Return the local address for this dispatch.
+ * This currently only works for dispatches using UDP sockets.
+ *
+ * Requires:
+ *\li disp is valid.
+ *\li addrp to be non null.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTIMPLEMENTED
+ */
+
+void
+dns_dispatch_cancel(dns_dispatch_t *disp);
+/*%<
+ * cancel outstanding clients
+ *
+ * Requires:
+ *\li disp is valid.
+ */
+
+unsigned int
+dns_dispatch_getattributes(dns_dispatch_t *disp);
+/*%<
+ * Return the attributes (DNS_DISPATCHATTR_xxx) of this dispatch. Only the
+ * non-changeable attributes are expected to be referenced by the caller.
+ *
+ * Requires:
+ *\li disp is valid.
+ */
+
+void
+dns_dispatch_changeattributes(dns_dispatch_t *disp, unsigned int attributes,
+ unsigned int mask);
+/*%<
+ * Set the bits described by "mask" to the corresponding values in
+ * "attributes".
+ *
+ * That is:
+ *
+ * \code
+ * new = (old & ~mask) | (attributes & mask)
+ * \endcode
+ *
+ * This function has a side effect when #DNS_DISPATCHATTR_NOLISTEN changes.
+ * When the flag becomes off, the dispatch will start receiving on the
+ * corresponding socket. When the flag becomes on, receive events on the
+ * corresponding socket will be canceled.
+ *
+ * Requires:
+ *\li disp is valid.
+ *
+ *\li attributes are reasonable for the dispatch. That is, setting the UDP
+ * attribute on a TCP socket isn't reasonable.
+ */
+
+void
+dns_dispatch_importrecv(dns_dispatch_t *disp, isc_event_t *event);
+/*%<
+ * Inform the dispatcher of a socket receive. This is used for sockets
+ * shared between dispatchers and clients. If the dispatcher fails to copy
+ * or send the event, nothing happens.
+ *
+ * If the attribute DNS_DISPATCHATTR_NOLISTEN is not set, then
+ * the dispatch is already handling a recv; return immediately.
+ *
+ * Requires:
+ *\li disp is valid, and the attribute DNS_DISPATCHATTR_NOLISTEN is set.
+ * event != NULL
+ */
+
+dns_dispatch_t *
+dns_dispatchset_get(dns_dispatchset_t *dset);
+/*%<
+ * Retrieve the next dispatch from dispatch set 'dset', and increment
+ * the round-robin counter.
+ *
+ * Requires:
+ *\li dset != NULL
+ */
+
+isc_result_t
+dns_dispatchset_create(isc_mem_t *mctx, isc_socketmgr_t *sockmgr,
+ isc_taskmgr_t *taskmgr, dns_dispatch_t *source,
+ dns_dispatchset_t **dsetp, int n);
+/*%<
+ * Given a valid dispatch 'source', create a dispatch set containing
+ * 'n' UDP dispatches, with the remainder filled out by clones of the
+ * source.
+ *
+ * Requires:
+ *\li source is a valid UDP dispatcher
+ *\li dsetp != NULL, *dsetp == NULL
+ */
+
+void
+dns_dispatchset_cancelall(dns_dispatchset_t *dset, isc_task_t *task);
+/*%<
+ * Cancel socket operations for the dispatches in 'dset'.
+ */
+
+void
+dns_dispatchset_destroy(dns_dispatchset_t **dsetp);
+/*%<
+ * Dereference all the dispatches in '*dsetp', free the dispatchset
+ * memory, and set *dsetp to NULL.
+ *
+ * Requires:
+ *\li dset is valid
+ */
+
+void
+dns_dispatch_setdscp(dns_dispatch_t *disp, isc_dscp_t dscp);
+isc_dscp_t
+dns_dispatch_getdscp(dns_dispatch_t *disp);
+/*%<
+ * Set/get the DSCP value to be used when sending responses to clients,
+ * as defined in the "listen-on" or "listen-on-v6" statements.
+ *
+ * Requires:
+ *\li disp is valid.
+ */
+
+isc_result_t
+dns_dispatch_getnext(dns_dispentry_t *resp, dns_dispatchevent_t **sockevent);
+/*%<
+ * Free the sockevent and trigger the sending of the next item off the
+ * dispatch queue if present.
+ *
+ * Requires:
+ *\li resp is valid
+ *\li *sockevent to be valid
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_DISPATCH_H */
diff --git a/lib/dns/include/dns/dlz.h b/lib/dns/include/dns/dlz.h
new file mode 100644
index 0000000..962db29
--- /dev/null
+++ b/lib/dns/include/dns/dlz.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
+ * conceived and contributed by Rob Butler.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file dns/dlz.h */
+
+#ifndef DLZ_H
+#define DLZ_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*
+ * DLZ Interface
+ *
+ * The DLZ interface allows zones to be looked up using a driver instead of
+ * Bind's default in memory zone table.
+ *
+ *
+ * Reliability:
+ * No anticipated impact.
+ *
+ * Resources:
+ *
+ * Security:
+ * No anticipated impact.
+ *
+ * Standards:
+ * None.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <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
+
+#endif /* DLZ_H */
diff --git a/lib/dns/include/dns/dlz_dlopen.h b/lib/dns/include/dns/dlz_dlopen.h
new file mode 100644
index 0000000..058a23e
--- /dev/null
+++ b/lib/dns/include/dns/dlz_dlopen.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file dns/dlz_dlopen.h */
+
+#ifndef DLZ_DLOPEN_H
+#define DLZ_DLOPEN_H
+
+#include <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
+
+#endif /* ifndef DLZ_DLOPEN_H */
diff --git a/lib/dns/include/dns/dns64.h b/lib/dns/include/dns/dns64.h
new file mode 100644
index 0000000..07bc06f
--- /dev/null
+++ b/lib/dns/include/dns/dns64.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DNS64_H
+#define DNS_DNS64_H 1
+
+#include <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, const dns_aclenv_t *env,
+ unsigned int flags, unsigned char *a, unsigned char *aaaa);
+/*
+ * dns_dns64_aaaafroma() determines whether to perform a DNS64 address
+ * synthesis from 'a' based on 'dns64', 'reqaddr', 'reqsigner', 'env',
+ * 'flags' and 'aaaa'. If synthesis is performed then the result is
+ * written to '*aaaa'.
+ *
+ * The synthesised address will be of the form:
+ *
+ * <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, const dns_aclenv_t *env,
+ unsigned int flags, dns_rdataset_t *rdataset, bool *aaaaok,
+ size_t aaaaoklen);
+/*
+ * Determine if there are any non-excluded AAAA records in from the
+ * matching dns64 records in the list starting at 'dns64'. If there
+ * is a non-excluded address return true. If all addresses are
+ * excluded in the matched records return false. If no records
+ * match then return true.
+ *
+ * If aaaaok is defined then dns_dns64_aaaaok() return a array of which
+ * addresses in 'rdataset' were deemed to not be exclude by any matching
+ * record. If there are no matching records then all entries are set
+ * to true.
+ *
+ * Requires
+ * 'rdataset' to be valid and to be for type AAAA and class IN.
+ * 'aaaaoklen' must match the number of records in 'rdataset'
+ * if 'aaaaok' in non NULL.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_DNS64_H */
diff --git a/lib/dns/include/dns/dnsrps.h b/lib/dns/include/dns/dnsrps.h
new file mode 100644
index 0000000..aa903b8
--- /dev/null
+++ b/lib/dns/include/dns/dnsrps.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DNSRPS_H
+#define DNS_DNSRPS_H
+
+#include <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
+
+#endif /* DNS_DNSRPS_H */
diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h
new file mode 100644
index 0000000..9791ef1
--- /dev/null
+++ b/lib/dns/include/dns/dnssec.h
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DNSSEC_H
+#define DNS_DNSSEC_H 1
+
+/*! \file dns/dnssec.h */
+
+#include <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
+
+LIBDNS_EXTERNAL_DATA extern isc_stats_t *dns_dnssec_stats;
+
+/*%< Maximum number of keys supported in a zone. */
+#define DNS_MAXZONEKEYS 32
+
+/*
+ * Indicates how the signer found this key: in the key repository, at the
+ * zone apex, or specified by the user.
+ */
+typedef enum {
+ dns_keysource_unknown,
+ dns_keysource_repository,
+ dns_keysource_zoneapex,
+ dns_keysource_user
+} dns_keysource_t;
+
+/*
+ * A DNSSEC key and hints about its intended use gleaned from metadata
+ */
+struct dns_dnsseckey {
+ dst_key_t *key;
+ bool hint_publish; /*% metadata says to publish */
+ bool force_publish; /*% publish regardless of metadata
+ * */
+ bool hint_sign; /*% metadata says to sign with this
+ * key */
+ bool force_sign; /*% sign with key regardless of
+ * metadata */
+ bool hint_revoke; /*% metadata says revoke key */
+ bool hint_remove; /*% metadata says *don't* publish */
+ bool is_active; /*% key is already active */
+ bool first_sign; /*% key is newly becoming active */
+ bool purge; /*% remove key files */
+ unsigned int prepublish; /*% how long until active? */
+ dns_keysource_t source; /*% how the key was found */
+ bool ksk; /*% this is a key-signing key */
+ bool zsk; /*% this is a zone-signing key */
+ bool legacy; /*% this is old-style key with no
+ * metadata (possibly generated by
+ * an older version of BIND9) and
+ * should be ignored when searching
+ * for keys to import into the zone */
+ unsigned int index; /*% position in list */
+ ISC_LINK(dns_dnsseckey_t) link;
+};
+
+isc_result_t
+dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata,
+ isc_mem_t *mctx, dst_key_t **key);
+/*%<
+ * Creates a DST key from a DNS record. Basically a wrapper around
+ * dst_key_fromdns().
+ *
+ * Requires:
+ *\li 'name' is not NULL
+ *\li 'rdata' is not NULL
+ *\li 'mctx' is not NULL
+ *\li 'key' is not NULL
+ *\li '*key' is NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li DST_R_INVALIDPUBLICKEY
+ *\li various errors from dns_name_totext
+ */
+
+isc_result_t
+dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
+ isc_stdtime_t *inception, isc_stdtime_t *expire,
+ isc_mem_t *mctx, isc_buffer_t *buffer, dns_rdata_t *sigrdata);
+/*%<
+ * Generates a RRSIG record covering this rdataset. This has no effect
+ * on existing RRSIG records.
+ *
+ * Requires:
+ *\li 'name' (the owner name of the record) is a valid name
+ *\li 'set' is a valid rdataset
+ *\li 'key' is a valid key
+ *\li 'inception' is not NULL
+ *\li 'expire' is not NULL
+ *\li 'mctx' is not NULL
+ *\li 'buffer' is not NULL
+ *\li 'sigrdata' is not NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_NOSPACE
+ *\li #DNS_R_INVALIDTIME - the expiration is before the inception
+ *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either
+ * it is not a zone key or its flags prevent
+ * authentication)
+ *\li DST_R_*
+ */
+
+isc_result_t
+dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
+ bool ignoretime, unsigned int maxbits, isc_mem_t *mctx,
+ dns_rdata_t *sigrdata, dns_name_t *wild);
+/*%<
+ * Verifies the RRSIG record covering this rdataset signed by a specific
+ * key. This does not determine if the key's owner is authorized to sign
+ * this record, as this requires a resolver or database.
+ * If 'ignoretime' is true, temporal validity will not be checked.
+ *
+ * 'maxbits' specifies the maximum number of rsa exponent bits accepted.
+ *
+ * Requires:
+ *\li 'name' (the owner name of the record) is a valid name
+ *\li 'set' is a valid rdataset
+ *\li 'key' is a valid key
+ *\li 'mctx' is not NULL
+ *\li 'sigrdata' is a valid rdata containing a SIG record
+ *\li 'wild' if non-NULL then is a valid and has a buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #DNS_R_FROMWILDCARD - the signature is valid and is from
+ * a wildcard expansion. dns_dnssec_verify2() only.
+ * 'wild' contains the name of the wildcard if non-NULL.
+ *\li #DNS_R_SIGINVALID - the signature fails to verify
+ *\li #DNS_R_SIGEXPIRED - the signature has expired
+ *\li #DNS_R_SIGFUTURE - the signature's validity period has not begun
+ *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either
+ * it is not a zone key or its flags prevent
+ * authentication)
+ *\li DST_R_*
+ */
+
+/*@{*/
+isc_result_t
+dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node,
+ const dns_name_t *name, const char *directory,
+ isc_stdtime_t now, isc_mem_t *mctx,
+ unsigned int maxkeys, dst_key_t **keys,
+ unsigned int *nkeys);
+
+/*%<
+ * Finds a set of zone keys.
+ * XXX temporary - this should be handled in dns_zone_t.
+ */
+/*@}*/
+
+bool
+dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now);
+/*%<
+ *
+ * Returns true if 'key' is active as of the time specified
+ * in 'now' (i.e., if the activation date has passed, inactivation or
+ * deletion date has not yet been reached, and the key is not revoked
+ * -- or if it is a legacy key without metadata). Otherwise returns
+ * false.
+ *
+ * Requires:
+ *\li 'key' is a valid key
+ */
+
+isc_result_t
+dns_dnssec_signmessage(dns_message_t *msg, dst_key_t *key);
+/*%<
+ * Signs a message with a SIG(0) record. This is implicitly called by
+ * dns_message_renderend() if msg->sig0key is not NULL.
+ *
+ * Requires:
+ *\li 'msg' is a valid message
+ *\li 'key' is a valid key that can be used for signing
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li DST_R_*
+ */
+
+isc_result_t
+dns_dnssec_verifymessage(isc_buffer_t *source, dns_message_t *msg,
+ dst_key_t *key);
+/*%<
+ * Verifies a message signed by a SIG(0) record. This is not
+ * called implicitly by dns_message_parse(). If dns_message_signer()
+ * is called before dns_dnssec_verifymessage(), it will return
+ * #DNS_R_NOTVERIFIEDYET. dns_dnssec_verifymessage() will set
+ * the verified_sig0 flag in msg if the verify succeeds, and
+ * the sig0status field otherwise.
+ *
+ * Requires:
+ *\li 'source' is a valid buffer containing the unparsed message
+ *\li 'msg' is a valid message
+ *\li 'key' is a valid key
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_NOTFOUND - no SIG(0) was found
+ *\li #DNS_R_SIGINVALID - the SIG record is not well-formed or
+ * was not generated by the key.
+ *\li DST_R_*
+ */
+
+bool
+dns_dnssec_selfsigns(dns_rdata_t *rdata, const dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ bool ignoretime, isc_mem_t *mctx);
+
+bool
+dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ bool ignoretime, isc_mem_t *mctx);
+/*%<
+ * Verify that 'rdataset' is validly signed in 'sigrdataset' by
+ * the key in 'rdata'.
+ *
+ * dns_dnssec_selfsigns() requires that rdataset be a DNSKEY or KEY
+ * rrset. dns_dnssec_signs() works on any rrset.
+ */
+
+isc_result_t
+dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey,
+ dns_dnsseckey_t **dkp);
+/*%<
+ * Create and initialize a dns_dnsseckey_t structure.
+ *
+ * Requires:
+ *\li 'dkp' is not NULL and '*dkp' is NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp);
+/*%<
+ * Reclaim a dns_dnsseckey_t structure.
+ *
+ * Requires:
+ *\li 'dkp' is not NULL and '*dkp' is not NULL.
+ *
+ * Ensures:
+ *\li '*dkp' is NULL.
+ */
+
+void
+dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now);
+/*%<
+ * Get hints on DNSSEC key whether this key can be published
+ * and/or is active. Timing metadata is compared to 'now'.
+ *
+ * Requires:
+ *\li 'key' is a pointer to a DNSSEC key and is not NULL.
+ */
+
+isc_result_t
+dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory,
+ isc_stdtime_t now, isc_mem_t *mctx,
+ dns_dnsseckeylist_t *keylist);
+/*%<
+ * Search 'directory' for K* key files matching the name in 'origin'.
+ * Append all such keys, along with use hints gleaned from their
+ * metadata, onto 'keylist'. Skip any unsupported algorithms.
+ *
+ * Requires:
+ *\li 'keylist' is not NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTFOUND
+ *\li #ISC_R_NOMEMORY
+ *\li any error returned by dns_name_totext(), isc_dir_open(), or
+ * dst_key_fromnamedfile()
+ *
+ * Ensures:
+ *\li On error, keylist is unchanged
+ */
+
+isc_result_t
+dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory,
+ isc_mem_t *mctx, dns_rdataset_t *keyset,
+ dns_rdataset_t *keysigs, dns_rdataset_t *soasigs,
+ bool savekeys, bool publickey,
+ dns_dnsseckeylist_t *keylist);
+/*%<
+ * Append the contents of a DNSKEY rdataset 'keyset' to 'keylist'.
+ * Omit duplicates. If 'publickey' is false, search 'directory' for
+ * matching key files, and load the private keys that go with
+ * the public ones. If 'savekeys' is true, mark the keys so
+ * they will not be deleted or inactivated regardless of metadata.
+ *
+ * 'keysigs' and 'soasigs', if not NULL and associated, contain the
+ * RRSIGS for the DNSKEY and SOA records respectively and are used to mark
+ * whether a key is already active in the zone.
+ */
+
+isc_result_t
+dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys,
+ dns_dnsseckeylist_t *removed, const dns_name_t *origin,
+ dns_ttl_t hint_ttl, dns_diff_t *diff, isc_mem_t *mctx,
+ void (*report)(const char *, ...));
+/*%<
+ * Update the list of keys in 'keys' with new key information in 'newkeys'.
+ *
+ * For each key in 'newkeys', see if it has a match in 'keys'.
+ * - If not, and if the metadata says the key should be published:
+ * add it to 'keys', and place a dns_difftuple into 'diff' so
+ * the key can be added to the DNSKEY set. If the metadata says it
+ * should be active, set the first_sign flag.
+ * - If so, and if the metadata says it should be removed:
+ * remove it from 'keys', and place a dns_difftuple into 'diff' so
+ * the key can be removed from the DNSKEY set. if 'removed' is non-NULL,
+ * copy the key into that list; otherwise destroy it.
+ * - Otherwise, make sure keys has current metadata.
+ *
+ * 'hint_ttl' is the TTL to use for the DNSKEY RRset if there is no
+ * existing RRset, and if none of the keys to be added has a default TTL
+ * (in which case we would use the shortest one). If the TTL is longer
+ * than the time until a new key will be activated, then we have to delay
+ * the key's activation.
+ *
+ * 'report' points to a function for reporting status.
+ *
+ * On completion, any remaining keys in 'newkeys' are freed.
+ */
+
+isc_result_t
+dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys,
+ dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
+ isc_stdtime_t now, dns_ttl_t hint_ttl, dns_diff_t *diff,
+ isc_mem_t *mctx);
+/*%<
+ * Update the CDS and CDNSKEY RRsets, adding and removing keys as needed.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li Other values indicate error
+ */
+
+isc_result_t
+dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ dns_ttl_t ttl, dns_diff_t *diff, isc_mem_t *mctx,
+ bool expect_cds_delete, bool expect_cdnskey_delete);
+/*%<
+ * Add or remove the CDS DELETE record and the CDNSKEY DELETE record.
+ * If 'expect_cds_delete' is true, the CDS DELETE record should be present.
+ * Otherwise, the CDS DELETE record must be removed from the RRsets (if
+ * present). If 'expect_cdnskey_delete' is true, the CDNSKEY DELETE record
+ * should be present. Otherwise, the CDNSKEY DELETE record must be removed
+ * from the RRsets (if present).
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li Other values indicate error
+ */
+
+isc_result_t
+dns_dnssec_matchdskey(dns_name_t *name, dns_rdata_t *dsrdata,
+ dns_rdataset_t *keyset, dns_rdata_t *keyrdata);
+/*%<
+ * Given a DS rdata and a DNSKEY RRset, find the DNSKEY rdata that matches
+ * the DS, and place it in 'keyrdata'.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTFOUND
+ *\li Other values indicate error
+ */
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_DNSSEC_H */
diff --git a/lib/dns/include/dns/dnstap.h b/lib/dns/include/dns/dnstap.h
new file mode 100644
index 0000000..e1da53d
--- /dev/null
+++ b/lib/dns/include/dns/dnstap.h
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef _DNSTAP_H
+#define _DNSTAP_H
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * The dt (dnstap) module provides fast passive logging of DNS messages.
+ * Protocol Buffers. The protobuf schema for Dnstap messages is in the
+ * file dnstap.proto, which is compiled to dnstap.pb-c.c and dnstap.pb-c.h.
+ */
+
+#include <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
+ */
+
+#endif /* _DNSTAP_H */
diff --git a/lib/dns/include/dns/ds.h b/lib/dns/include/dns/ds.h
new file mode 100644
index 0000000..6e02d4c
--- /dev/null
+++ b/lib/dns/include/dns/ds.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DS_H
+#define DNS_DS_H 1
+
+#include <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
+
+#endif /* DNS_DS_H */
diff --git a/lib/dns/include/dns/dsdigest.h b/lib/dns/include/dns/dsdigest.h
new file mode 100644
index 0000000..b466fed
--- /dev/null
+++ b/lib/dns/include/dns/dsdigest.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DSDIGEST_H
+#define DNS_DSDIGEST_H 1
+
+/*! \file dns/dsdigest.h */
+
+#include <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
+
+#endif /* DNS_DSDIGEST_H */
diff --git a/lib/dns/include/dns/dyndb.h b/lib/dns/include/dns/dyndb.h
new file mode 100644
index 0000000..cc2820a
--- /dev/null
+++ b/lib/dns/include/dns/dyndb.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_DYNDB_H
+#define DNS_DYNDB_H
+
+#include <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;
+ unsigned int *memdebug;
+};
+
+#define DNS_DYNDBCTX_MAGIC ISC_MAGIC('D', 'd', 'b', 'c')
+#define DNS_DYNDBCTX_VALID(d) ISC_MAGIC_VALID(d, DNS_DYNDBCTX_MAGIC)
+
+/*
+ * API version
+ *
+ * When the API changes, increment DNS_DYNDB_VERSION. If the
+ * change is backward-compatible (e.g., adding a new function call
+ * but not changing or removing an old one), increment DNS_DYNDB_AGE;
+ * if not, set DNS_DYNDB_AGE to 0.
+ */
+#ifndef DNS_DYNDB_VERSION
+#define DNS_DYNDB_VERSION 1
+#define DNS_DYNDB_AGE 0
+#endif /* ifndef DNS_DYNDB_VERSION */
+
+typedef isc_result_t
+dns_dyndb_register_t(isc_mem_t *mctx, const char *name, const char *parameters,
+ const char *file, unsigned long line,
+ const dns_dyndbctx_t *dctx, void **instp);
+/*%
+ * Called when registering a new driver instance. 'name' must be unique.
+ * 'parameters' contains the driver configuration text. 'dctx' is the
+ * initialization context set up in dns_dyndb_createctx().
+ *
+ * '*instp' will be set to the driver instance handle if the function
+ * is successful.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Other errors are possible
+ */
+
+typedef void
+dns_dyndb_destroy_t(void **instp);
+/*%
+ * Destroy a driver instance. Dereference any reference-counted
+ * variables passed in 'dctx' and 'inst' in the register function.
+ *
+ * \c *instp must be set to \c NULL by the function before it returns.
+ */
+
+typedef int
+dns_dyndb_version_t(unsigned int *flags);
+/*%
+ * Return the API version number a dyndb module was compiled with.
+ *
+ * If the returned version number is no greater than than
+ * DNS_DYNDB_VERSION, and no less than DNS_DYNDB_VERSION - DNS_DYNDB_AGE,
+ * then the module is API-compatible with named.
+ *
+ * 'flags' is currently unused and may be NULL, but could be used in
+ * the future to pass back driver capabilities or other information.
+ */
+
+isc_result_t
+dns_dyndb_load(const char *libname, const char *name, const char *parameters,
+ const char *file, unsigned long line, isc_mem_t *mctx,
+ const dns_dyndbctx_t *dctx);
+/*%
+ * Load a dyndb module.
+ *
+ * This loads a dyndb module using dlopen() or equivalent, calls its register
+ * function (see dns_dyndb_register_t above), and if successful, adds
+ * the instance handle to a list of dyndb instances so it can be cleaned
+ * up later.
+ *
+ * 'file' and 'line' can be used to indicate the name of the file and
+ * the line number from which the parameters were taken, so that logged
+ * error messages, if any, will display the correct locations.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Other errors are possible
+ */
+
+void
+dns_dyndb_cleanup(bool exiting);
+/*%
+ * Shut down and destroy all running dyndb modules.
+ *
+ * 'exiting' indicates whether the server is shutting down,
+ * as opposed to merely being reconfigured.
+ */
+
+isc_result_t
+dns_dyndb_createctx(isc_mem_t *mctx, const void *hashinit, isc_log_t *lctx,
+ dns_view_t *view, dns_zonemgr_t *zmgr, isc_task_t *task,
+ isc_timermgr_t *tmgr, dns_dyndbctx_t **dctxp);
+/*%
+ * Create a dyndb initialization context structure, with
+ * pointers to structures in the server that the dyndb module will
+ * need to access (view, zone manager, memory context, hash initializer,
+ * etc). This structure is expected to last only until all dyndb
+ * modules have been loaded and initialized; after that it will be
+ * destroyed with dns_dyndb_destroyctx().
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Other errors are possible
+ */
+
+void
+dns_dyndb_destroyctx(dns_dyndbctx_t **dctxp);
+/*%
+ * Destroys a dyndb initialization context structure; all
+ * reference-counted members are detached and the structure is freed.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_DYNDB_H */
diff --git a/lib/dns/include/dns/ecdb.h b/lib/dns/include/dns/ecdb.h
new file mode 100644
index 0000000..d94cbac
--- /dev/null
+++ b/lib/dns/include/dns/ecdb.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_ECDB_H
+#define DNS_ECDB_H 1
+
+/*****
+***** Module Info
+*****/
+
+/* TBD */
+
+/***
+ *** Imports
+ ***/
+
+#include <dns/types.h>
+
+/***
+ *** Types
+ ***/
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+/* TBD: describe those */
+
+isc_result_t
+dns_ecdb_register(isc_mem_t *mctx, dns_dbimplementation_t **dbimp);
+
+void
+dns_ecdb_unregister(dns_dbimplementation_t **dbimp);
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_ECDB_H */
diff --git a/lib/dns/include/dns/ecs.h b/lib/dns/include/dns/ecs.h
new file mode 100644
index 0000000..b4b0d6b
--- /dev/null
+++ b/lib/dns/include/dns/ecs.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_ECS_H
+#define DNS_ECS_H 1
+
+#include <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
+
+#endif /* DNS_ECS_H */
diff --git a/lib/dns/include/dns/edns.h b/lib/dns/include/dns/edns.h
new file mode 100644
index 0000000..214abe8
--- /dev/null
+++ b/lib/dns/include/dns/edns.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_EDNS_H
+#define DNS_EDNS_H 1
+
+/*%
+ * The maximum version on EDNS supported by this build.
+ */
+#define DNS_EDNS_VERSION 0
+#ifdef DRAFT_ANDREWS_EDNS1
+#undef DNS_EDNS_VERSION
+/*
+ * Warning: this currently disables sending COOKIE requests in resolver.c
+ */
+#define DNS_EDNS_VERSION 1 /* draft-andrews-edns1 */
+#endif /* ifdef DRAFT_ANDREWS_EDNS1 */
+
+#endif /* ifndef DNS_EDNS_H */
diff --git a/lib/dns/include/dns/events.h b/lib/dns/include/dns/events.h
new file mode 100644
index 0000000..608f35f
--- /dev/null
+++ b/lib/dns/include/dns/events.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_EVENTS_H
+#define DNS_EVENTS_H 1
+
+#include <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)
+
+#endif /* DNS_EVENTS_H */
diff --git a/lib/dns/include/dns/fixedname.h b/lib/dns/include/dns/fixedname.h
new file mode 100644
index 0000000..edd55f4
--- /dev/null
+++ b/lib/dns/include/dns/fixedname.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_FIXEDNAME_H
+#define DNS_FIXEDNAME_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/fixedname.h
+ * \brief
+ * Fixed-size Names
+ *
+ * dns_fixedname_t is a convenience type containing a name, an offsets
+ * table, and a dedicated buffer big enough for the longest possible
+ * name. This is typically used for stack-allocated names.
+ *
+ * MP:
+ *\li The caller must ensure any required synchronization.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li Per dns_fixedname_t:
+ *\code
+ * sizeof(dns_name_t) + sizeof(dns_offsets_t) +
+ * sizeof(isc_buffer_t) + 255 bytes + structure padding
+ *\endcode
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li None.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <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
+
+#endif /* DNS_FIXEDNAME_H */
diff --git a/lib/dns/include/dns/forward.h b/lib/dns/include/dns/forward.h
new file mode 100644
index 0000000..12430c3
--- /dev/null
+++ b/lib/dns/include/dns/forward.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_FORWARD_H
+#define DNS_FORWARD_H 1
+
+/*! \file dns/forward.h */
+
+#include <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_dscp_t dscp;
+ ISC_LINK(dns_forwarder_t) link;
+};
+
+typedef ISC_LIST(struct dns_forwarder) dns_forwarderlist_t;
+
+struct dns_forwarders {
+ dns_forwarderlist_t fwdrs;
+ dns_fwdpolicy_t fwdpolicy;
+};
+
+isc_result_t
+dns_fwdtable_create(isc_mem_t *mctx, dns_fwdtable_t **fwdtablep);
+/*%<
+ * Creates a new forwarding table.
+ *
+ * Requires:
+ * \li mctx is a valid memory context.
+ * \li fwdtablep != NULL && *fwdtablep == NULL
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_fwdtable_addfwd(dns_fwdtable_t *fwdtable, const dns_name_t *name,
+ dns_forwarderlist_t *fwdrs, dns_fwdpolicy_t policy);
+isc_result_t
+dns_fwdtable_add(dns_fwdtable_t *fwdtable, const dns_name_t *name,
+ isc_sockaddrlist_t *addrs, dns_fwdpolicy_t policy);
+/*%<
+ * Adds an entry to the forwarding table. The entry associates
+ * a domain with a list of forwarders and a forwarding policy. The
+ * addrs/fwdrs list is copied if not empty, so the caller should free
+ * its copy.
+ *
+ * Requires:
+ * \li fwdtable is a valid forwarding table.
+ * \li name is a valid name
+ * \li addrs/fwdrs is a valid list of isc_sockaddr/dns_forwarder
+ * structures, which may be empty.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_fwdtable_delete(dns_fwdtable_t *fwdtable, const dns_name_t *name);
+/*%<
+ * Removes an entry for 'name' from the forwarding table. If an entry
+ * that exactly matches 'name' does not exist, ISC_R_NOTFOUND will be returned.
+ *
+ * Requires:
+ * \li fwdtable is a valid forwarding table.
+ * \li name is a valid name
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND
+ */
+
+isc_result_t
+dns_fwdtable_find(dns_fwdtable_t *fwdtable, const dns_name_t *name,
+ dns_name_t *foundname, dns_forwarders_t **forwardersp);
+/*%<
+ * Finds a domain in the forwarding table. The closest matching parent
+ * domain is returned.
+ *
+ * Requires:
+ * \li fwdtable is a valid forwarding table.
+ * \li name is a valid name
+ * \li forwardersp != NULL && *forwardersp == NULL
+ * \li foundname to be NULL or a valid name with buffer.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND
+ */
+
+void
+dns_fwdtable_destroy(dns_fwdtable_t **fwdtablep);
+/*%<
+ * Destroys a forwarding table.
+ *
+ * Requires:
+ * \li fwtablep != NULL && *fwtablep != NULL
+ *
+ * Ensures:
+ * \li all memory associated with the forwarding table is freed.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_FORWARD_H */
diff --git a/lib/dns/include/dns/geoip.h b/lib/dns/include/dns/geoip.h
new file mode 100644
index 0000000..721c297
--- /dev/null
+++ b/lib/dns/include/dns/geoip.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_GEOIP_H
+#define DNS_GEOIP_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/geoip.h
+ * \brief
+ * GeoIP/GeoIP2 data types and function prototypes.
+ */
+
+#if defined(HAVE_GEOIP2)
+
+/***
+ *** Imports
+ ***/
+
+#include <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 */
+
+#endif /* DNS_GEOIP_H */
diff --git a/lib/dns/include/dns/ipkeylist.h b/lib/dns/include/dns/ipkeylist.h
new file mode 100644
index 0000000..b3dbfb6
--- /dev/null
+++ b/lib/dns/include/dns/ipkeylist.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_IPKEYLIST_H
+#define DNS_IPKEYLIST_H 1
+
+#include <inttypes.h>
+
+#include <isc/types.h>
+
+#include <dns/types.h>
+
+/*%
+ * A structure holding a list of addresses, dscps and keys. Used to
+ * store masters for a slave zone, created by parsing config options.
+ */
+struct dns_ipkeylist {
+ isc_sockaddr_t *addrs;
+ isc_dscp_t *dscps;
+ dns_name_t **keys;
+ dns_name_t **labels;
+ uint32_t count;
+ uint32_t allocated;
+};
+
+void
+dns_ipkeylist_init(dns_ipkeylist_t *ipkl);
+/*%<
+ * Reset ipkl to empty state
+ *
+ * Requires:
+ *\li 'ipkl' to be non NULL.
+ */
+
+void
+dns_ipkeylist_clear(isc_mem_t *mctx, dns_ipkeylist_t *ipkl);
+/*%<
+ * Free `ipkl` contents using `mctx`.
+ *
+ * After this call, `ipkl` is a freshly cleared structure with all
+ * pointers set to `NULL` and count set to 0.
+ *
+ * Requires:
+ *\li 'mctx' to be a valid memory context.
+ *\li 'ipkl' to be non NULL.
+ */
+
+isc_result_t
+dns_ipkeylist_copy(isc_mem_t *mctx, const dns_ipkeylist_t *src,
+ dns_ipkeylist_t *dst);
+/*%<
+ * Deep copy `src` into empty `dst`, allocating `dst`'s contents.
+ *
+ * Requires:
+ *\li 'mctx' to be a valid memory context.
+ *\li 'src' to be non NULL
+ *\li 'dst' to be non NULL and point to an empty \ref dns_ipkeylist_t
+ * with all pointers set to `NULL` and count set to 0.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- success
+ *\li any other value -- failure
+ */
+isc_result_t
+dns_ipkeylist_resize(isc_mem_t *mctx, dns_ipkeylist_t *ipkl, unsigned int n);
+/*%<
+ * Resize ipkl to contain n elements. Size (count) is not changed, and the
+ * added space is zeroed.
+ *
+ * Requires:
+ * \li 'mctx' to be a valid memory context.
+ * \li 'ipk' to be non NULL
+ * \li 'n' >= ipkl->count
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS if success
+ * \li #ISC_R_NOMEMORY if there's no memory, ipkeylist is left untouched
+ */
+
+#endif /* ifndef DNS_IPKEYLIST_H */
diff --git a/lib/dns/include/dns/iptable.h b/lib/dns/include/dns/iptable.h
new file mode 100644
index 0000000..83ecf04
--- /dev/null
+++ b/lib/dns/include/dns/iptable.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_IPTABLE_H
+#define DNS_IPTABLE_H 1
+
+#include <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
+
+#endif /* DNS_IPTABLE_H */
diff --git a/lib/dns/include/dns/journal.h b/lib/dns/include/dns/journal.h
new file mode 100644
index 0000000..2b9875e
--- /dev/null
+++ b/lib/dns/include/dns/journal.h
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_JOURNAL_H
+#define DNS_JOURNAL_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/journal.h
+ * \brief
+ * Database journaling.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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
+
+#endif /* DNS_JOURNAL_H */
diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h
new file mode 100644
index 0000000..48d773a
--- /dev/null
+++ b/lib/dns/include/dns/kasp.h
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_KASP_H
+#define DNS_KASP_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/kasp.h
+ * \brief
+ * DNSSEC Key and Signing Policy (KASP)
+ *
+ * A "kasp" is a DNSSEC policy, that determines how a zone should be
+ * signed and maintained.
+ */
+
+#include <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 (86400 * 5)
+#define DNS_KASP_SIG_VALIDITY (86400 * 14)
+#define DNS_KASP_SIG_VALIDITY_DNSKEY (86400 * 14)
+#define DNS_KASP_KEY_TTL (3600)
+#define DNS_KASP_DS_TTL (86400)
+#define DNS_KASP_PUBLISH_SAFETY (3600)
+#define DNS_KASP_PURGE_KEYS (86400 * 90)
+#define DNS_KASP_RETIRE_SAFETY (3600)
+#define DNS_KASP_ZONE_MAXTTL (86400)
+#define DNS_KASP_ZONE_PROPDELAY (300)
+#define DNS_KASP_PARENT_PROPDELAY (3600)
+
+/* Key roles */
+#define DNS_KASP_KEY_ROLE_KSK 0x01
+#define DNS_KASP_KEY_ROLE_ZSK 0x02
+
+isc_result_t
+dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp);
+/*%<
+ * Create a KASP.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'name' is a valid C string.
+ *
+ *\li kaspp != NULL && *kaspp == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+void
+dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid, frozen kasp.
+ *
+ *\li 'targetp' points to a NULL dns_kasp_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ *
+ *\li While *targetp is attached, the kasp will not shut down.
+ */
+
+void
+dns_kasp_detach(dns_kasp_t **kaspp);
+/*%<
+ * Detach KASP.
+ *
+ * Requires:
+ *
+ *\li 'kaspp' points to a valid dns_kasp_t *
+ *
+ * Ensures:
+ *
+ *\li *kaspp is NULL.
+ */
+
+void
+dns_kasp_freeze(dns_kasp_t *kasp);
+/*%<
+ * Freeze kasp. No changes can be made to kasp configuration while frozen.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, unfrozen kasp.
+ *
+ * Ensures:
+ *
+ *\li 'kasp' is frozen.
+ */
+
+void
+dns_kasp_thaw(dns_kasp_t *kasp);
+/*%<
+ * Thaw kasp.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Ensures:
+ *
+ *\li 'kasp' is no longer frozen.
+ */
+
+const char *
+dns_kasp_getname(dns_kasp_t *kasp);
+/*%<
+ * Get kasp name.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid kasp.
+ *
+ * Returns:
+ *
+ *\li name of 'kasp'.
+ */
+
+uint32_t
+dns_kasp_signdelay(dns_kasp_t *kasp);
+/*%<
+ * Get the delay that is needed to ensure that all existing RRsets have been
+ * re-signed with a successor key. This is the signature validity minus the
+ * signature refresh time (that indicates how far before signature expiry an
+ * RRSIG should be refreshed).
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li signature refresh interval.
+ */
+
+uint32_t
+dns_kasp_sigrefresh(dns_kasp_t *kasp);
+/*%<
+ * Get signature refresh interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li signature refresh interval.
+ */
+
+void
+dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set signature refresh interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_sigvalidity(dns_kasp_t *kasp);
+uint32_t
+dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp);
+/*%<
+ * Get signature validity.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li signature validity.
+ */
+
+void
+dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value);
+void
+dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set signature validity.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+dns_ttl_t
+dns_kasp_dnskeyttl(dns_kasp_t *kasp);
+/*%<
+ * Get DNSKEY TTL.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li DNSKEY TTL.
+ */
+
+void
+dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl);
+/*%<
+ * Set DNSKEY TTL.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_purgekeys(dns_kasp_t *kasp);
+/*%<
+ * Get purge keys interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Purge keys interval.
+ */
+
+void
+dns_kasp_setpurgekeys(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set purge keys interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_publishsafety(dns_kasp_t *kasp);
+/*%<
+ * Get publish safety interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Publish safety interval.
+ */
+
+void
+dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set publish safety interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_retiresafety(dns_kasp_t *kasp);
+/*%<
+ * Get retire safety interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Retire safety interval.
+ */
+
+void
+dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set retire safety interval.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+dns_ttl_t
+dns_kasp_zonemaxttl(dns_kasp_t *kasp);
+/*%<
+ * Get maximum zone TTL.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Maximum zone TTL.
+ */
+
+void
+dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl);
+/*%<
+ * Set maximum zone TTL.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_zonepropagationdelay(dns_kasp_t *kasp);
+/*%<
+ * Get zone propagation delay.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Zone propagation delay.
+ */
+
+void
+dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set zone propagation delay.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+dns_ttl_t
+dns_kasp_dsttl(dns_kasp_t *kasp);
+/*%<
+ * Get DS TTL (should match that of the parent DS record).
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Expected parent DS TTL.
+ */
+
+void
+dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl);
+/*%<
+ * Set DS TTL.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+uint32_t
+dns_kasp_parentpropagationdelay(dns_kasp_t *kasp);
+/*%<
+ * Get parent zone propagation delay.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li Parent zone propagation delay.
+ */
+
+void
+dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value);
+/*%<
+ * Set parent propagation delay.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ */
+
+isc_result_t
+dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp);
+/*%<
+ * Search for a kasp with name 'name' in 'list'.
+ * If found, '*kaspp' is (strongly) attached to it.
+ *
+ * Requires:
+ *
+ *\li 'kaspp' points to a NULL dns_kasp_t *.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS A matching kasp was found.
+ *\li #ISC_R_NOTFOUND No matching kasp was found.
+ */
+
+dns_kasp_keylist_t
+dns_kasp_keys(dns_kasp_t *kasp);
+/*%<
+ * Get the list of kasp keys.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+bool
+dns_kasp_keylist_empty(dns_kasp_t *kasp);
+/*%<
+ * Check if the keylist is empty.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid kasp.
+ *
+ * Returns:
+ *
+ *\li true if the keylist is empty, false otherwise.
+ */
+
+void
+dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key);
+/*%<
+ * Add a key.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, thawed kasp.
+ *\li 'key' is not NULL.
+ */
+
+isc_result_t
+dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp);
+/*%<
+ * Create a key inside a KASP.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid kasp.
+ *
+ *\li keyp != NULL && *keyp == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+void
+dns_kasp_key_destroy(dns_kasp_key_t *key);
+/*%<
+ * Destroy a KASP key.
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ */
+
+uint32_t
+dns_kasp_key_algorithm(dns_kasp_key_t *key);
+/*%<
+ * Get the key algorithm.
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ *
+ * Returns:
+ *
+ *\li Key algorithm.
+ */
+
+unsigned int
+dns_kasp_key_size(dns_kasp_key_t *key);
+/*%<
+ * Get the key size.
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ *
+ * Returns:
+ *
+ *\li Configured key size, or default key size for key algorithm if no size
+ * configured.
+ */
+
+uint32_t
+dns_kasp_key_lifetime(dns_kasp_key_t *key);
+/*%<
+ * The lifetime of this key (how long may this key be active?)
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ *
+ * Returns:
+ *
+ *\li Lifetime of key.
+ *
+ */
+
+bool
+dns_kasp_key_ksk(dns_kasp_key_t *key);
+/*%<
+ * Does this key act as a KSK?
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ *
+ * Returns:
+ *
+ *\li True, if the key role has DNS_KASP_KEY_ROLE_KSK set.
+ *\li False, otherwise.
+ *
+ */
+
+bool
+dns_kasp_key_zsk(dns_kasp_key_t *key);
+/*%<
+ * Does this key act as a ZSK?
+ *
+ * Requires:
+ *
+ *\li key != NULL
+ *
+ * Returns:
+ *
+ *\li True, if the key role has DNS_KASP_KEY_ROLE_ZSK set.
+ *\li False, otherwise.
+ *
+ */
+
+bool
+dns_kasp_nsec3(dns_kasp_t *kasp);
+/*%<
+ * Return true if NSEC3 chain should be used.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *
+ */
+
+uint8_t
+dns_kasp_nsec3iter(dns_kasp_t *kasp);
+/*%<
+ * The number of NSEC3 iterations to use.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *\li 'kasp->nsec3' is true.
+ *
+ */
+
+uint8_t
+dns_kasp_nsec3flags(dns_kasp_t *kasp);
+/*%<
+ * The NSEC3 flags field value.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *\li 'kasp->nsec3' is true.
+ *
+ */
+
+uint8_t
+dns_kasp_nsec3saltlen(dns_kasp_t *kasp);
+/*%<
+ * The NSEC3 salt length.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, frozen kasp.
+ *\li 'kasp->nsec3' is true.
+ *
+ */
+
+void
+dns_kasp_setnsec3(dns_kasp_t *kasp, bool nsec3);
+/*%<
+ * Set to use NSEC3 if 'nsec3' is 'true', otherwise policy will use NSEC.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, unfrozen kasp.
+ *
+ */
+
+void
+dns_kasp_setnsec3param(dns_kasp_t *kasp, uint8_t iter, bool optout,
+ uint8_t saltlen);
+/*%<
+ * Set the desired NSEC3 parameters.
+ *
+ * Requires:
+ *
+ *\li 'kasp' is a valid, unfrozen kasp.
+ *\li 'kasp->nsec3' is true.
+ *
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_KASP_H */
diff --git a/lib/dns/include/dns/keydata.h b/lib/dns/include/dns/keydata.h
new file mode 100644
index 0000000..43a3ef7
--- /dev/null
+++ b/lib/dns/include/dns/keydata.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_KEYDATA_H
+#define DNS_KEYDATA_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/keydata.h
+ * \brief
+ * KEYDATA utilities.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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
+
+#endif /* DNS_KEYDATA_H */
diff --git a/lib/dns/include/dns/keyflags.h b/lib/dns/include/dns/keyflags.h
new file mode 100644
index 0000000..a603b6f
--- /dev/null
+++ b/lib/dns/include/dns/keyflags.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_KEYFLAGS_H
+#define DNS_KEYFLAGS_H 1
+
+/*! \file dns/keyflags.h */
+
+#include <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
+
+#endif /* DNS_KEYFLAGS_H */
diff --git a/lib/dns/include/dns/keymgr.h b/lib/dns/include/dns/keymgr.h
new file mode 100644
index 0000000..7b31eb8
--- /dev/null
+++ b/lib/dns/include/dns/keymgr.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_KEYMGR_H
+#define DNS_KEYMGR_H 1
+
+/*! \file dns/keymgr.h */
+
+#include <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
+
+#endif /* DNS_KEYMGR_H */
diff --git a/lib/dns/include/dns/keytable.h b/lib/dns/include/dns/keytable.h
new file mode 100644
index 0000000..9557ab8
--- /dev/null
+++ b/lib/dns/include/dns/keytable.h
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_KEYTABLE_H
+#define DNS_KEYTABLE_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * The keytable module provides services for storing and retrieving DNSSEC
+ * trusted keys, as well as the ability to find the deepest matching key
+ * for a given domain name.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ */
+
+#include <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
+
+isc_result_t
+dns_keytable_create(isc_mem_t *mctx, dns_keytable_t **keytablep);
+/*%<
+ * Create a keytable.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li keytablep != NULL && *keytablep == NULL
+ *
+ * Ensures:
+ *
+ *\li On success, *keytablep is a valid, empty key table.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result indicates failure.
+ */
+
+void
+dns_keytable_attach(dns_keytable_t *source, dns_keytable_t **targetp);
+/*%<
+ * Attach *targetp to source.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid keytable.
+ *
+ *\li 'targetp' points to a NULL dns_keytable_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ */
+
+void
+dns_keytable_detach(dns_keytable_t **keytablep);
+/*%<
+ * Detach *keytablep from its keytable.
+ *
+ * Requires:
+ *
+ *\li 'keytablep' points to a valid keytable.
+ *
+ * Ensures:
+ *
+ *\li *keytablep is NULL.
+ *
+ *\li If '*keytablep' is the last reference to the keytable,
+ * all resources used by the keytable will be freed
+ */
+
+isc_result_t
+dns_keytable_add(dns_keytable_t *keytable, bool managed, bool initial,
+ dns_name_t *name, dns_rdata_ds_t *ds);
+/*%<
+ * Add a key to 'keytable'. The keynode associated with 'name'
+ * is updated with the DS specified in 'ds'.
+ *
+ * The value of keynode->managed is set to 'managed', and the
+ * value of keynode->initial is set to 'initial'. (Note: 'initial'
+ * should only be used when adding managed-keys from configuration.
+ * This indicates the key is in "initializing" state, and has not yet
+ * been confirmed with a key refresh query. Once a key refresh query
+ * has validated, we update the keynode with initial == false.)
+ *
+ * Notes:
+ *
+ *\li If the key already exists in the table, adding it again
+ * has no effect and ISC_R_SUCCESS is returned.
+ *
+ * Requires:
+ *
+ *\li 'keytable' points to a valid keytable.
+ *\li 'ds' is not NULL.
+ *\li if 'initial' is true then 'managed' must also be true.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_EXISTS
+ *
+ *\li Any other result indicates failure.
+ */
+
+isc_result_t
+dns_keytable_marksecure(dns_keytable_t *keytable, const dns_name_t *name);
+/*%<
+ * Add a null key to 'keytable' for name 'name'. This marks the
+ * name as a secure domain, but doesn't supply any key data to allow the
+ * domain to be validated. (Used when automated trust anchor management
+ * has gotten broken by a zone misconfiguration; for example, when the
+ * active key has been revoked but the stand-by key was still in its 30-day
+ * waiting period for validity.)
+ *
+ * Notes:
+ *
+ *\li If a key already exists in the table, ISC_R_EXISTS is
+ * returned and nothing is done.
+ *
+ * Requires:
+ *
+ *\li 'keytable' points to a valid keytable.
+ *
+ *\li keyp != NULL && *keyp is a valid dst_key_t *.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_EXISTS
+ *
+ *\li Any other result indicates failure.
+ */
+
+isc_result_t
+dns_keytable_delete(dns_keytable_t *keytable, const dns_name_t *keyname);
+/*%<
+ * Delete all trust anchors from 'keytable' matching name 'keyname'
+ *
+ * Requires:
+ *
+ *\li 'keytable' points to a valid keytable.
+ *
+ *\li 'name' is not NULL
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result indicates failure.
+ */
+
+isc_result_t
+dns_keytable_deletekey(dns_keytable_t *keytable, const dns_name_t *keyname,
+ dns_rdata_dnskey_t *dnskey);
+/*%<
+ * Remove the trust anchor matching the name 'keyname' and the DNSKEY
+ * rdata struct 'dnskey' from 'keytable'.
+ *
+ * Requires:
+ *
+ *\li 'keytable' points to a valid keytable.
+ *\li 'dnskey' is not NULL
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result indicates failure.
+ */
+
+isc_result_t
+dns_keytable_find(dns_keytable_t *keytable, const dns_name_t *keyname,
+ dns_keynode_t **keynodep);
+/*%<
+ * Search for the first instance of a trust anchor named 'name' in
+ * 'keytable', without regard to keyid and algorithm.
+ *
+ * Requires:
+ *
+ *\li 'keytable' is a valid keytable.
+ *
+ *\li 'name' is a valid absolute name.
+ *
+ *\li keynodep != NULL && *keynodep == NULL
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTFOUND
+ *
+ *\li Any other result indicates an error.
+ */
+
+isc_result_t
+dns_keytable_finddeepestmatch(dns_keytable_t *keytable, const dns_name_t *name,
+ dns_name_t *foundname);
+/*%<
+ * Search for the deepest match of 'name' in 'keytable'.
+ *
+ * Requires:
+ *
+ *\li 'keytable' is a valid keytable.
+ *
+ *\li 'name' is a valid absolute name.
+ *
+ *\li 'foundname' is a name with a dedicated buffer.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTFOUND
+ *
+ *\li Any other result indicates an error.
+ */
+
+void
+dns_keytable_detachkeynode(dns_keytable_t *keytable, dns_keynode_t **keynodep);
+/*%<
+ * Detach a keynode found via dns_keytable_find().
+ *
+ * Requires:
+ *
+ *\li *keynodep is a valid keynode returned by a call to dns_keytable_find().
+ *
+ * Ensures:
+ *
+ *\li *keynodep == NULL
+ */
+
+isc_result_t
+dns_keytable_issecuredomain(dns_keytable_t *keytable, const dns_name_t *name,
+ dns_name_t *foundname, bool *wantdnssecp);
+/*%<
+ * Is 'name' at or beneath a trusted key?
+ *
+ * Requires:
+ *
+ *\li 'keytable' is a valid keytable.
+ *
+ *\li 'name' is a valid absolute name.
+ *
+ *\li 'foundanme' is NULL or is a pointer to an initialized dns_name_t
+ *
+ *\li '*wantsdnssecp' is a valid bool.
+ *
+ * Ensures:
+ *
+ *\li On success, *wantsdnssecp will be true if and only if 'name'
+ * is at or beneath a trusted key. If 'foundname' is not NULL, then
+ * it will be updated to contain the name of the closest enclosing
+ * trust anchor.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result is an error.
+ */
+
+isc_result_t
+dns_keytable_dump(dns_keytable_t *keytable, FILE *fp);
+/*%<
+ * Dump the keytable on fp.
+ */
+
+isc_result_t
+dns_keytable_totext(dns_keytable_t *keytable, isc_buffer_t **buf);
+/*%<
+ * Dump the keytable to buffer at 'buf'
+ */
+
+bool
+dns_keynode_dsset(dns_keynode_t *keynode, dns_rdataset_t *rdataset);
+/*%<
+ * Clone the DS RRset associated with 'keynode' into 'rdataset' if
+ * it exists. 'dns_rdataset_disassociate(rdataset)' needs to be
+ * called when done.
+ *
+ * Returns:
+ *\li true if there is a DS RRset.
+ *\li false if there isn't DS RRset.
+ *
+ * Requires:
+ *\li 'keynode' is valid.
+ *\li 'rdataset' is valid or NULL.
+ */
+
+bool
+dns_keynode_managed(dns_keynode_t *keynode);
+/*%<
+ * Is this flagged as a managed key?
+ */
+
+bool
+dns_keynode_initial(dns_keynode_t *keynode);
+/*%<
+ * Is this flagged as an initializing key?
+ */
+
+void
+dns_keynode_trust(dns_keynode_t *keynode);
+/*%<
+ * Sets keynode->initial to false in order to mark the key as
+ * trusted: no longer an initializing key.
+ */
+
+isc_result_t
+dns_keytable_forall(dns_keytable_t *keytable,
+ void (*func)(dns_keytable_t *, dns_keynode_t *,
+ dns_name_t *, void *),
+ void *arg);
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_KEYTABLE_H */
diff --git a/lib/dns/include/dns/keyvalues.h b/lib/dns/include/dns/keyvalues.h
new file mode 100644
index 0000000..e27ada1
--- /dev/null
+++ b/lib/dns/include/dns/keyvalues.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_KEYVALUES_H
+#define DNS_KEYVALUES_H 1
+
+/*! \file dns/keyvalues.h */
+
+/*
+ * Flags field of the KEY RR rdata
+ */
+#define DNS_KEYFLAG_TYPEMASK 0xC000 /*%< Mask for "type" bits */
+#define DNS_KEYTYPE_AUTHCONF 0x0000 /*%< Key usable for both */
+#define DNS_KEYTYPE_CONFONLY 0x8000 /*%< Key usable for confidentiality */
+#define DNS_KEYTYPE_AUTHONLY 0x4000 /*%< Key usable for authentication */
+#define DNS_KEYTYPE_NOKEY 0xC000 /*%< No key usable for either; no key */
+#define DNS_KEYTYPE_NOAUTH DNS_KEYTYPE_CONFONLY
+#define DNS_KEYTYPE_NOCONF DNS_KEYTYPE_AUTHONLY
+
+#define DNS_KEYFLAG_RESERVED2 0x2000 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_EXTENDED 0x1000 /*%< key has extended flags */
+#define DNS_KEYFLAG_RESERVED4 0x0800 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_RESERVED5 0x0400 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_OWNERMASK 0x0300 /*%< these bits determine the type */
+#define DNS_KEYOWNER_USER 0x0000 /*%< key is assoc. with user */
+#define DNS_KEYOWNER_ENTITY 0x0200 /*%< key is assoc. with entity eg host */
+#define DNS_KEYOWNER_ZONE 0x0100 /*%< key is zone key */
+#define DNS_KEYOWNER_RESERVED 0x0300 /*%< reserved meaning */
+#define DNS_KEYFLAG_REVOKE 0x0080 /*%< key revoked (per rfc5011) */
+#define DNS_KEYFLAG_RESERVED9 0x0040 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_RESERVED10 0x0020 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_RESERVED11 0x0010 /*%< reserved - must be zero */
+#define DNS_KEYFLAG_SIGNATORYMASK \
+ 0x000F /*%< key can sign RR's of same name \
+ */
+
+#define DNS_KEYFLAG_RESERVEDMASK \
+ (DNS_KEYFLAG_RESERVED2 | DNS_KEYFLAG_RESERVED4 | \
+ DNS_KEYFLAG_RESERVED5 | DNS_KEYFLAG_RESERVED9 | \
+ DNS_KEYFLAG_RESERVED10 | DNS_KEYFLAG_RESERVED11)
+#define DNS_KEYFLAG_KSK 0x0001 /*%< key signing key */
+
+#define DNS_KEYFLAG_RESERVEDMASK2 0xFFFF /*%< no bits defined here */
+
+/* The Algorithm field of the KEY and SIG RR's is an integer, {1..254} */
+#define DNS_KEYALG_RSAMD5 1 /*%< RSA with MD5 */
+#define DNS_KEYALG_RSA 1 /*%< Used just for tagging */
+#define DNS_KEYALG_DH 2 /*%< Diffie Hellman KEY */
+#define DNS_KEYALG_DSA 3 /*%< DSA KEY */
+#define DNS_KEYALG_NSEC3DSA 6
+#define DNS_KEYALG_DSS DNS_ALG_DSA
+#define DNS_KEYALG_ECC 4
+#define DNS_KEYALG_RSASHA1 5
+#define DNS_KEYALG_NSEC3RSASHA1 7
+#define DNS_KEYALG_RSASHA256 8
+#define DNS_KEYALG_RSASHA512 10
+#define DNS_KEYALG_ECCGOST 12
+#define DNS_KEYALG_ECDSA256 13
+#define DNS_KEYALG_ECDSA384 14
+#define DNS_KEYALG_ED25519 15
+#define DNS_KEYALG_ED448 16
+#define DNS_KEYALG_INDIRECT 252
+#define DNS_KEYALG_PRIVATEDNS 253
+#define DNS_KEYALG_PRIVATEOID 254 /*%< Key begins with OID giving alg */
+#define DNS_KEYALG_MAX 255
+
+/* Protocol values */
+#define DNS_KEYPROTO_RESERVED 0
+#define DNS_KEYPROTO_TLS 1
+#define DNS_KEYPROTO_EMAIL 2
+#define DNS_KEYPROTO_DNSSEC 3
+#define DNS_KEYPROTO_IPSEC 4
+#define DNS_KEYPROTO_ANY 255
+
+/* Signatures */
+#define DNS_SIG_RSAMINBITS 512 /*%< Size of a mod or exp in bits */
+#define DNS_SIG_RSAMAXBITS 2552
+/* Total of binary mod and exp */
+#define DNS_SIG_RSAMAXBYTES ((DNS_SIG_RSAMAXBITS + 7 / 8) * 2 + 3)
+/*%< Max length of text sig block */
+#define DNS_SIG_RSAMAXBASE64 (((DNS_SIG_RSAMAXBYTES + 2) / 3) * 4)
+#define DNS_SIG_RSAMINSIZE ((DNS_SIG_RSAMINBITS + 7) / 8)
+#define DNS_SIG_RSAMAXSIZE ((DNS_SIG_RSAMAXBITS + 7) / 8)
+
+#define DNS_SIG_ECDSA256SIZE 64
+#define DNS_SIG_ECDSA384SIZE 96
+
+#define DNS_KEY_ECDSA256SIZE 64
+#define DNS_KEY_ECDSA384SIZE 96
+
+#define DNS_SIG_ED25519SIZE 64
+#define DNS_SIG_ED448SIZE 114
+
+#define DNS_KEY_ED25519SIZE 32
+#define DNS_KEY_ED448SIZE 57
+
+#endif /* DNS_KEYVALUES_H */
diff --git a/lib/dns/include/dns/lib.h b/lib/dns/include/dns/lib.h
new file mode 100644
index 0000000..649b483
--- /dev/null
+++ b/lib/dns/include/dns/lib.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_LIB_H
+#define DNS_LIB_H 1
+
+/*! \file dns/lib.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * Tuning: external query load in packets per seconds.
+ */
+LIBDNS_EXTERNAL_DATA extern unsigned int dns_pps;
+
+isc_result_t
+dns_lib_init(void);
+/*%<
+ * A set of initialization procedures used in the DNS library. This function
+ * is provided for an application that is not aware of the underlying ISC or
+ * DNS libraries much.
+ */
+
+void
+dns_lib_shutdown(void);
+/*%<
+ * Free temporary resources allocated in dns_lib_init().
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_LIB_H */
diff --git a/lib/dns/include/dns/librpz.h b/lib/dns/include/dns/librpz.h
new file mode 100644
index 0000000..110b798
--- /dev/null
+++ b/lib/dns/include/dns/librpz.h
@@ -0,0 +1,956 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Define the interface from a DNS resolver to the Response Policy Zone
+ * library, librpz.
+ *
+ * This file should be included only the interface functions between the
+ * resolver and librpz to avoid name space pollution.
+ *
+ * Copyright (c) 2016-2017 Farsight Security, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * version 1.2.12
+ */
+
+#ifndef LIBRPZ_H
+#define LIBRPZ_H
+
+#include <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 */
+
+#ifdef HAVE_BUILTIN_EXPECT
+#define LIBRPZ_LIKELY(c) __builtin_expect(!!(c), 1)
+#define LIBRPZ_UNLIKELY(c) __builtin_expect(!!(c), 0)
+#else /* ifdef HAVE_BUILTIN_EXPECT */
+#define LIBRPZ_LIKELY(c) (c)
+#define LIBRPZ_UNLIKELY(c) (c)
+#endif /* ifdef HAVE_BUILTIN_EXPECT */
+
+typedef bool(librpz_parse_log_opt_t)(librpz_emsg_t *emsg, const char *arg);
+LIBDEF_F(parse_log_opt)
+
+typedef void(librpz_vpemsg_t)(librpz_emsg_t *emsg, const char *p, va_list args);
+LIBDEF_F(vpemsg)
+typedef void(librpz_pemsg_t)(librpz_emsg_t *emsg, const char *p, ...)
+ LIBRPZ_PF(2, 3);
+LIBDEF_F(pemsg)
+
+typedef void(librpz_vlog_t)(librpz_log_level_t level, void *ctx, const char *p,
+ va_list args);
+LIBDEF_F(vlog)
+typedef void(librpz_log_t)(librpz_log_level_t level, void *ctx, const char *p,
+ ...) LIBRPZ_PF(3, 4);
+LIBDEF_F(log)
+
+typedef void(librpz_fatal_t)(int ex_code, const char *p, ...) LIBRPZ_PF(2, 3);
+extern void
+librpz_fatal(int ex_code, const char *p, ...) LIBRPZ_PF(2, 3) LIBRPZ_NORET;
+
+typedef void(librpz_rpz_assert_t)(const char *file, unsigned line,
+ const char *p, ...) LIBRPZ_PF(3, 4);
+extern void
+librpz_rpz_assert(const char *file, unsigned line, const char *p, ...)
+ LIBRPZ_PF(3, 4) LIBRPZ_NORET;
+
+typedef void(librpz_rpz_vassert_t)(const char *file, uint line, const char *p,
+ va_list args);
+extern void
+librpz_rpz_vassert(const char *file, uint line, const char *p,
+ va_list args) LIBRPZ_NORET;
+
+/*
+ * As far as clients are concerned, all relative pointers or indexes in a
+ * version of the mapped file except trie node parent pointers remain valid
+ * forever. A client must release a version so that it can be garbage
+ * collected by the file system. When dnsrpzd needs to expand the file,
+ * it copies the old file to a new, larger file. Clients can continue
+ * using the old file.
+ *
+ * Versions can also appear in a single file. Old nodes and trie values
+ * within the file are not destroyed until all clients using the version
+ * that contained the old values release the version.
+ *
+ * A client is marked as using version by connecting to the daemon. It is
+ * marked as using all subsequent versions. A client releases all versions
+ * by closing the connection or a range of versions by updating is slot
+ * in the shared memory version table.
+ *
+ * As far as clients are concerned, there are the following possible librpz
+ * failures:
+ * - malloc() or other fatal internal librpz problems indicated by
+ * a failing return from a librpz function
+ * All operations will fail until client handle is destroyed and
+ * recreated with librpz_client_detach() and librpz_client_create().
+ * - corrupt database detected by librpz code, corrupt database detected
+ * by dnsrpzd, or disconnection from the daemon.
+ * Current operations will fail.
+ *
+ * Clients assume that the file has already been unlinked before
+ * the corrupt flag is set so that they do not race with the server
+ * over the corruption of a single file. A client that finds the
+ * corrupt set knows that dnsrpzd has already crashed with
+ * abort() and is restarting. The client can re-connect to dnsrpzd
+ * and retransmit its configuration, backing off as usual if anything
+ * goes wrong.
+ *
+ * Searches of the database by a client do not need locks against dnsrpzd or
+ * other clients, but a lock is used to protect changes to the connection
+ * by competing threads in the client. The client provides functions
+ * to serialize the concurrent use of any single client handle.
+ * Functions that do nothing are appropriate for applications that are
+ * not "threaded" or that do not share client handles among threads.
+ * Otherwise, functions must be provided to librpz_clientcreate().
+ * Something like the following works with pthreads:
+ *
+ * static void
+ * lock(void *mutex) { assert(pthread_mutex_lock(mutex) == 0); }
+ *
+ * static void
+ * unlock(void *mutex) { assert(pthread_mutex_unlock(mutex) == 0); }
+ *
+ * static void
+ * mutex_destroy(void *mutex) { assert(pthread_mutex_destroy(mutex) == 0); }
+ *
+ *
+ *
+ * At every instant, all of the data and pointers in the mapped file are valid.
+ * Changes to trie node or other data are always made so that it and
+ * all pointers in and to it remain valid for a time. Old versions are
+ * eventually discarded.
+ *
+ * Dnsrpzd periodically defines a new version by setting aside all changes
+ * made since the previous version was defined. Subsequent changes
+ * made (only!) by dnsrpzd will be part of the next version.
+ *
+ * To discard an old version, dnsrpzd must know that all clients have stopped
+ * using that version. Clients do that by using part of the mapped file
+ * to tell dnsrpzd the oldest version that each client is using.
+ * Dnsrpzd assigns each connecting client an entry in the cversions array
+ * in the mapped file. The client puts version numbers into that entry
+ * to signal to dnsrpzd which versions that can be discarded.
+ * Dnsrpzd is free, as far as that client is concerned, to discard all
+ * numerically smaller versions. A client can disclaim all versions with
+ * the version number VERSIONS_ALL or 0.
+ *
+ * The race between a client changing its entry and dnsrpzd discarding a
+ * version is resolved by allowing dnsrpzd to discard all versions
+ * smaller or equal to the client's version number. If dnsrpzd is in
+ * the midst of discarding or about to discard version N when the
+ * client asserts N, no harm is done. The client depends only on
+ * the consistency of version N+1.
+ *
+ * This version mechanism depends in part on not being exercised too frequently
+ * Version numbers are 32 bits long and dnsrpzd creates new versions
+ * at most once every 30 seconds.
+ */
+
+/*
+ * Lock functions for concurrent use of a single librpz_client_t client handle.
+ */
+typedef void(librpz_mutex_t)(void *mutex);
+
+/*
+ * List of connections to dnsrpzd daemons.
+ */
+typedef struct librpz_clist librpz_clist_t;
+
+/*
+ * Client's handle on dnsrpzd.
+ */
+typedef struct librpz_client librpz_client_t;
+
+/**
+ * Create the list of connections to the dnsrpzd daemon.
+ * @param[out] emsg: error message
+ * @param lock: start exclusive access to the client handle
+ * @param unlock: end exclusive access to the client handle
+ * @param mutex_destroy: release the lock
+ * @param mutex: pointer to the lock for the client handle
+ * @param log_ctx: NULL or resolver's context log messages
+ */
+typedef librpz_clist_t *(librpz_clist_create_t)(librpz_emsg_t *emsg,
+ librpz_mutex_t *lock,
+ librpz_mutex_t *unlock,
+ librpz_mutex_t *mutex_destroy,
+ void *mutex, void *log_ctx);
+LIBDEF_F(clist_create)
+
+/**
+ * Release the list of dnsrpzd connections.
+ */
+typedef void(librpz_clist_detach_t)(librpz_clist_t **clistp);
+LIBDEF_F(clist_detach)
+
+/**
+ * Create a librpz client handle.
+ * @param[out] emsg: error message
+ * @param clist: of dnsrpzd connections
+ * @param cstr: string of configuration settings separated by ';' or '\n'
+ * @param use_expired: true to not ignore expired zones
+ * @return client handle or NULL if the handle could not be created
+ */
+typedef librpz_client_t *(librpz_client_create_t)(librpz_emsg_t *emsg,
+ librpz_clist_t *clist,
+ const char *cstr,
+ bool use_expired);
+LIBDEF_F(client_create)
+
+/**
+ * Start (if necessary) dnsrpzd and connect to it.
+ * @param[out] emsg: error message
+ * @param client handle
+ * @param optional: true if it is ok if starting the daemon is not allowed
+ */
+typedef bool(librpz_connect_t)(librpz_emsg_t *emsg, librpz_client_t *client,
+ bool optional);
+LIBDEF_F(connect)
+
+/**
+ * Start to destroy a librpz client handle.
+ * It will not be destroyed until the last set of RPZ queries represented
+ * by a librpz_rsp_t ends.
+ * @param client handle to be released
+ * @return false on error
+ */
+typedef void(librpz_client_detach_t)(librpz_client_t **clientp);
+LIBDEF_F(client_detach)
+
+/**
+ * State for a set of RPZ queries for a single DNS response
+ * or for listing the database.
+ */
+typedef struct librpz_rsp librpz_rsp_t;
+
+/**
+ * Start a set of RPZ queries for a single DNS response.
+ * @param[out] emsg: error message for false return or *rspp=NULL
+ * @param[out] rspp created context or NULL
+ * @param[out] min_ns_dotsp: NULL or pointer to configured MIN-NS-DOTS value
+ * @param client state
+ * @param have_rd: RD=1 in the DNS request
+ * @param have_do: DO=1 in the DNS request
+ * @return false on error
+ */
+typedef bool(librpz_rsp_create_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp,
+ int *min_ns_dotsp, librpz_client_t *client,
+ bool have_rd, bool have_do);
+LIBDEF_F(rsp_create)
+
+/**
+ * Finish RPZ work for a DNS response.
+ */
+typedef void(librpz_rsp_detach_t)(librpz_rsp_t **rspp);
+LIBDEF_F(rsp_detach)
+
+/**
+ * Get the final, accumulated result of a set of RPZ queries.
+ * Yield LIBRPZ_POLICY_UNDEFINED if
+ * - there were no hits,
+ * - there was a dispositive hit, be we have not recursed and are required
+ * to recurse so that evil DNS authorities will not know we are using RPZ
+ * - we have a hit and have recursed, but later data such as NSIP could
+ * override
+ * @param[out] emsg
+ * @param[out] result describes the hit
+ * or result->policy=LIBRPZ_POLICY_UNDEFINED without a hit
+ * @param[out] result: current policy rewrite values
+ * @param recursed: recursion has now been done even if it was not done
+ * when the hit was found
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_result_t)(librpz_emsg_t *emsg, librpz_result_t *result,
+ bool recursed, const librpz_rsp_t *rsp);
+LIBDEF_F(rsp_result)
+
+/**
+ * Might looking for a trigger be worthwhile?
+ * @param trig: look for this type of trigger
+ * @param ipv6: true if trig is LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP,
+ * or LIBRPZ_TRIG_NSIP and the IP address is IPv6
+ * @return: true if looking could be worthwhile
+ */
+typedef bool(librpz_have_trig_t)(librpz_trig_t trig, bool ipv6,
+ const librpz_rsp_t *rsp);
+LIBDEF_F(have_trig)
+
+/**
+ * Might looking for NSDNAME and NSIP triggers be worthwhile?
+ * @return: true if looking could be worthwhile
+ */
+typedef bool(librpz_have_ns_trig_t)(const librpz_rsp_t *rsp);
+LIBDEF_F(have_ns_trig)
+
+/**
+ * Convert the found client IP trie key to a CIDR block
+ * @param[out] emsg
+ * @param[out] prefix trigger
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_clientip_prefix_t)(librpz_emsg_t *emsg,
+ librpz_prefix_t *prefix,
+ librpz_rsp_t *rsp);
+LIBDEF_F(rsp_clientip_prefix)
+
+/**
+ * Compute the owner name of the found or result trie key, usually to log it.
+ * An IP address key might be returned as 8.0.0.0.127.rpz-client-ip.
+ * example.com. might be a qname trigger. example.com.rpz-nsdname. could
+ * be an NSDNAME trigger.
+ * @param[out] emsg
+ * @param[out] owner domain
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_domain_t)(librpz_emsg_t *emsg,
+ librpz_domain_buf_t *owner,
+ librpz_rsp_t *rsp);
+LIBDEF_F(rsp_domain)
+
+/**
+ * Get the next RR of the LIBRPZ_POLICY_RECORD result after an initial use of
+ * librpz_rsp_result() or librpz_itr_node() or after a previous use of
+ * librpz_rsp_rr(). The RR is in uncompressed wire format including type,
+ * class, ttl and length in network byte order.
+ * @param[out] emsg
+ * @param[out] typep: optional host byte order record type or ns_t_invalid (0)
+ * @param[out] classp: class such as ns_c_in
+ * @param[out] ttlp: TTL
+ * @param[out] rrp: optional malloc() buffer containing the next RR or
+ * NULL after the last RR
+ * @param[out] result: current policy rewrite values
+ * @param qname: used construct a wildcard CNAME
+ * @param qname_size
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_rr_t)(librpz_emsg_t *emsg, uint16_t *typep,
+ uint16_t *classp, uint32_t *ttlp,
+ librpz_rr_t **rrp, librpz_result_t *result,
+ const uint8_t *qname, size_t qname_size,
+ librpz_rsp_t *rsp);
+LIBDEF_F(rsp_rr)
+
+/**
+ * Get the next RR of the LIBRPZ_POLICY_RECORD result.
+ * @param[out] emsg
+ * @param[out] ttlp: TTL
+ * @param[out] rrp: malloc() buffer with SOA RR without owner name
+ * @param[out] result: current policy rewrite values
+ * @param[out] origin: SOA owner name
+ * @param[out] origin_size
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_soa_t)(librpz_emsg_t *emsg, uint32_t *ttlp,
+ librpz_rr_t **rrp, librpz_domain_buf_t *origin,
+ librpz_result_t *result, librpz_rsp_t *rsp);
+LIBDEF_F(rsp_soa)
+
+/**
+ * Get the SOA serial number for a policy zone to compare with a known value
+ * to check whether a zone transfer is complete.
+ */
+typedef bool(librpz_soa_serial_t)(librpz_emsg_t *emsg, uint32_t *serialp,
+ const char *domain_nm, librpz_rsp_t *rsp);
+LIBDEF_F(soa_serial)
+
+/**
+ * Save the current policy checking state.
+ * @param[out] emsg
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_push_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+LIBDEF_F(rsp_push)
+#define LIBRPZ_RSP_STACK_DEPTH 3
+
+/**
+ * Restore the previous policy checking state.
+ * @param[out] emsg
+ * @param[out] result: NULL or restored policy rewrite values
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_pop_t)(librpz_emsg_t *emsg, librpz_result_t *result,
+ librpz_rsp_t *rsp);
+LIBDEF_F(rsp_pop)
+
+/**
+ * Discard the most recently save policy checking state.
+ * @param[out] emsg
+ * @param[out] result: NULL or restored policy rewrite values
+ * @return false on error
+ */
+typedef bool(librpz_rsp_pop_discard_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+LIBDEF_F(rsp_pop_discard)
+
+/**
+ * Disable a zone.
+ * @param[out] emsg
+ * @param znum
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_rsp_forget_zone_t)(librpz_emsg_t *emsg, librpz_cznum_t znum,
+ librpz_rsp_t *rsp);
+LIBDEF_F(rsp_forget_zone)
+
+/**
+ * Apply RPZ to an IP address.
+ * @param[out] emsg
+ * @param addr: address to check
+ * @param ipv6: true for 16 byte IPv6 instead of 4 byte IPv4
+ * @param trig LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP, or LIBRPZ_TRIG_NSIP
+ * @param hit_id: caller chosen
+ * @param recursed: recursion has been done
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_ck_ip_t)(librpz_emsg_t *emsg, const void *addr, uint family,
+ librpz_trig_t trig, librpz_result_id_t hit_id,
+ bool recursed, librpz_rsp_t *rsp);
+LIBDEF_F(ck_ip)
+
+/**
+ * Apply RPZ to a wire-format domain.
+ * @param[out] emsg
+ * @param domain in wire format
+ * @param domain_size
+ * @param trig LIBRPZ_TRIG_QNAME or LIBRPZ_TRIG_NSDNAME
+ * @param hit_id: caller chosen
+ * @param recursed: recursion has been done
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return false on error
+ */
+typedef bool(librpz_ck_domain_t)(librpz_emsg_t *emsg, const uint8_t *domain,
+ size_t domain_size, librpz_trig_t trig,
+ librpz_result_id_t hit_id, bool recursed,
+ librpz_rsp_t *rsp);
+LIBDEF_F(ck_domain)
+
+/**
+ * Ask dnsrpzd to refresh a zone.
+ * @param[out] emsg error message
+ * @param librpz_domain_t domain to refresh
+ * @param client context
+ * @return false after error
+ */
+typedef bool(librpz_zone_refresh_t)(librpz_emsg_t *emsg, const char *domain,
+ librpz_rsp_t *rsp);
+LIBDEF_F(zone_refresh)
+
+/**
+ * Get a string describing the database
+ * @param license: include the license
+ * @param cfiles: include the configuration file names
+ * @param listens: include the local notify IP addresses
+ * @param[out] emsg error message if the result is null
+ * @param client context
+ * @return malloc'ed string or NULL after error
+ */
+typedef char *(librpz_db_info_t)(librpz_emsg_t *emsg, bool license, bool cfiles,
+ bool listens, librpz_rsp_t *rsp);
+LIBDEF_F(db_info)
+
+/**
+ * Start a context for listing the nodes and/or zones in the mapped file
+ * @param[out] emsg: error message for false return or *rspp=NULL
+ * @param[out] rspp: created context or NULL
+ * @param client context
+ * @return false after error
+ */
+typedef bool(librpz_itr_start_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp,
+ librpz_client_t *client);
+LIBDEF_F(itr_start)
+
+/**
+ * Get mapped file memory allocation statistics.
+ * @param[out] emsg: error message
+ * @param rsp state from librpz_itr_start()
+ * @return malloc'ed string or NULL after error
+ */
+typedef char *(librpz_mf_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+LIBDEF_F(mf_stats)
+
+/**
+ * Get versions currently used by clients.
+ * @param[out] emsg: error message
+ * @param[in,out] rsp: state from librpz_itr_start()
+ * @return malloc'ed string or NULL after error
+ */
+typedef char *(librpz_vers_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+LIBDEF_F(vers_stats)
+
+/**
+ * Allocate a string describing the next zone or "" after the last zone.
+ * @param[out] emsg
+ * @param all_zones to list all instead of only requested zones
+ * @param[in,out] rsp state from librpz_rsp_start()
+ * @return malloc'ed string or NULL after error
+ */
+typedef char *(librpz_itr_zone_t)(librpz_emsg_t *emsg, bool all_zones,
+ librpz_rsp_t *rsp);
+LIBDEF_F(itr_zone)
+
+/**
+ * Describe the next trie node while dumping the database.
+ * @param[out] emsg
+ * @param[out] result describes node
+ * or result->policy=LIBRPZ_POLICY_UNDEFINED after the last node.
+ * @param all_zones to list all instead of only requested zones
+ * @param[in,out] rsp state from librpz_itr_start()
+ * @return: false on error
+ */
+typedef bool(librpz_itr_node_t)(librpz_emsg_t *emsg, librpz_result_t *result,
+ bool all_zones, librpz_rsp_t *rsp);
+LIBDEF_F(itr_node)
+
+/**
+ * RPZ policy to string with a backup buffer of POLICY2STR_SIZE size
+ */
+typedef const char *(librpz_policy2str_t)(librpz_policy_t policy, char *buf,
+ size_t buf_size);
+#define POLICY2STR_SIZE sizeof("policy xxxxxx")
+LIBDEF_F(policy2str)
+
+/**
+ * Trigger type to string.
+ */
+typedef const char *(librpz_trig2str_t)(librpz_trig_t trig);
+LIBDEF_F(trig2str)
+
+/**
+ * Convert a number of seconds to a zone file duration string
+ */
+typedef const char *(librpz_secs2str_t)(time_t secs, char *buf,
+ size_t buf_size);
+#define SECS2STR_SIZE sizeof("1234567w7d24h59m59s")
+LIBDEF_F(secs2str)
+
+/**
+ * Parse a duration with 's', 'm', 'h', 'd', and 'w' units.
+ */
+typedef bool(librpz_str2secs_t)(librpz_emsg_t *emsg, time_t *val,
+ const char *str0);
+LIBDEF_F(str2secs)
+
+/**
+ * Translate selected rtypes to strings
+ */
+typedef const char *(librpz_rtype2str_t)(uint type, char *buf, size_t buf_size);
+#define RTYPE2STR_SIZE sizeof("type xxxxx")
+LIBDEF_F(rtype2str)
+
+/**
+ * Local version of ns_name_ntop() for portability.
+ */
+typedef int(librpz_domain_ntop_t)(const u_char *src, char *dst, size_t dstsiz);
+LIBDEF_F(domain_ntop)
+
+/**
+ * Local version of ns_name_pton().
+ */
+typedef int(librpz_domain_pton2_t)(const char *src, u_char *dst, size_t dstsiz,
+ size_t *dstlen, bool lower);
+LIBDEF_F(domain_pton2)
+
+typedef union socku socku_t;
+typedef socku_t *(librpz_mk_inet_su_t)(socku_t *su, const struct in_addr *addrp,
+ in_port_t port);
+LIBDEF_F(mk_inet_su)
+
+typedef socku_t *(librpz_mk_inet6_su_t)(socku_t *su,
+ const struct in6_addr *addrp,
+ uint32_t scope_id, in_port_t port);
+LIBDEF_F(mk_inet6_su)
+
+typedef bool(librpz_str2su_t)(socku_t *sup, const char *str);
+LIBDEF_F(str2su)
+
+typedef char *(librpz_su2str_t)(char *str, size_t str_len, const socku_t *su);
+LIBDEF_F(su2str)
+#define SU2STR_SIZE (INET6_ADDRSTRLEN + 1 + 6 + 1)
+
+/**
+ * default path to dnsrpzd
+ */
+LIBDEF(const char *, librpz_dnsrpzd_path)
+
+#undef LIBDEF
+
+/*
+ * This is the dlopen() interface to librpz.
+ */
+typedef const struct {
+ const char *dnsrpzd_path;
+ const char *version;
+ librpz_parse_log_opt_t *parse_log_opt;
+ librpz_log_level_val_t *log_level_val;
+ librpz_set_log_t *set_log;
+ librpz_vpemsg_t *vpemsg;
+ librpz_pemsg_t *pemsg;
+ librpz_vlog_t *vlog;
+ librpz_log_t *log;
+ librpz_fatal_t *fatal LIBRPZ_NORET;
+ librpz_rpz_assert_t *rpz_assert LIBRPZ_NORET;
+ librpz_rpz_vassert_t *rpz_vassert LIBRPZ_NORET;
+ librpz_clist_create_t *clist_create;
+ librpz_clist_detach_t *clist_detach;
+ librpz_client_create_t *client_create;
+ librpz_connect_t *connect;
+ librpz_client_detach_t *client_detach;
+ librpz_rsp_create_t *rsp_create;
+ librpz_rsp_detach_t *rsp_detach;
+ librpz_rsp_result_t *rsp_result;
+ librpz_have_trig_t *have_trig;
+ librpz_have_ns_trig_t *have_ns_trig;
+ librpz_rsp_clientip_prefix_t *rsp_clientip_prefix;
+ librpz_rsp_domain_t *rsp_domain;
+ librpz_rsp_rr_t *rsp_rr;
+ librpz_rsp_soa_t *rsp_soa;
+ librpz_soa_serial_t *soa_serial;
+ librpz_rsp_push_t *rsp_push;
+ librpz_rsp_pop_t *rsp_pop;
+ librpz_rsp_pop_discard_t *rsp_pop_discard;
+ librpz_rsp_forget_zone_t *rsp_forget_zone;
+ librpz_ck_ip_t *ck_ip;
+ librpz_ck_domain_t *ck_domain;
+ librpz_zone_refresh_t *zone_refresh;
+ librpz_db_info_t *db_info;
+ librpz_itr_start_t *itr_start;
+ librpz_mf_stats_t *mf_stats;
+ librpz_vers_stats_t *vers_stats;
+ librpz_itr_zone_t *itr_zone;
+ librpz_itr_node_t *itr_node;
+ librpz_policy2str_t *policy2str;
+ librpz_trig2str_t *trig2str;
+ librpz_secs2str_t *secs2str;
+ librpz_str2secs_t *str2secs;
+ librpz_rtype2str_t *rtype2str;
+ librpz_domain_ntop_t *domain_ntop;
+ librpz_domain_pton2_t *domain_pton2;
+ librpz_mk_inet_su_t *mk_inet_su;
+ librpz_mk_inet6_su_t *mk_inet6_su;
+ librpz_str2su_t *str2su;
+ librpz_su2str_t *su2str;
+} librpz_0_t;
+extern librpz_0_t librpz_def_0;
+
+/*
+ * Future versions can be upward compatible by defining LIBRPZ_DEF as
+ * librpz_X_t.
+ */
+#define LIBRPZ_DEF librpz_def_0
+#define LIBRPZ_DEF_STR "librpz_def_0"
+
+typedef librpz_0_t librpz_t;
+extern librpz_t *librpz;
+
+#if LIBRPZ_LIB_OPEN == 2
+#include <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 */
+
+#endif /* LIBRPZ_H */
diff --git a/lib/dns/include/dns/lmdb.h b/lib/dns/include/dns/lmdb.h
new file mode 100644
index 0000000..f6986cf
--- /dev/null
+++ b/lib/dns/include/dns/lmdb.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#define DNS_LMDB_COMMON_FLAGS (MDB_CREATE | MDB_NOSUBDIR | MDB_NOLOCK)
+#ifndef __OpenBSD__
+#define DNS_LMDB_FLAGS (DNS_LMDB_COMMON_FLAGS)
+#else /* __OpenBSD__ */
+/*
+ * OpenBSD does not have a unified buffer cache, which requires both reads and
+ * writes to be performed using mmap().
+ */
+#define DNS_LMDB_FLAGS (DNS_LMDB_COMMON_FLAGS | MDB_WRITEMAP)
+#endif /* __OpenBSD__ */
diff --git a/lib/dns/include/dns/log.h b/lib/dns/include/dns/log.h
new file mode 100644
index 0000000..d78dda6
--- /dev/null
+++ b/lib/dns/include/dns/log.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file dns/log.h
+ */
+
+#ifndef DNS_LOG_H
+#define DNS_LOG_H 1
+
+#include <isc/lang.h>
+#include <isc/log.h>
+
+LIBDNS_EXTERNAL_DATA extern isc_log_t *dns_lctx;
+LIBDNS_EXTERNAL_DATA extern isc_logcategory_t dns_categories[];
+LIBDNS_EXTERNAL_DATA extern isc_logmodule_t dns_modules[];
+
+#define DNS_LOGCATEGORY_NOTIFY (&dns_categories[0])
+#define DNS_LOGCATEGORY_DATABASE (&dns_categories[1])
+#define DNS_LOGCATEGORY_SECURITY (&dns_categories[2])
+/* DNS_LOGCATEGORY_CONFIG superseded by CFG_LOGCATEGORY_CONFIG */
+#define DNS_LOGCATEGORY_DNSSEC (&dns_categories[4])
+#define DNS_LOGCATEGORY_RESOLVER (&dns_categories[5])
+#define DNS_LOGCATEGORY_XFER_IN (&dns_categories[6])
+#define DNS_LOGCATEGORY_XFER_OUT (&dns_categories[7])
+#define DNS_LOGCATEGORY_DISPATCH (&dns_categories[8])
+#define DNS_LOGCATEGORY_LAME_SERVERS (&dns_categories[9])
+#define DNS_LOGCATEGORY_DELEGATION_ONLY (&dns_categories[10])
+#define DNS_LOGCATEGORY_EDNS_DISABLED (&dns_categories[11])
+#define DNS_LOGCATEGORY_RPZ (&dns_categories[12])
+#define DNS_LOGCATEGORY_RRL (&dns_categories[13])
+#define DNS_LOGCATEGORY_CNAME (&dns_categories[14])
+#define DNS_LOGCATEGORY_SPILL (&dns_categories[15])
+#define DNS_LOGCATEGORY_DNSTAP (&dns_categories[16])
+#define DNS_LOGCATEGORY_ZONELOAD (&dns_categories[17])
+#define DNS_LOGCATEGORY_NSID (&dns_categories[18])
+
+/* Backwards compatibility. */
+#define DNS_LOGCATEGORY_GENERAL ISC_LOGCATEGORY_GENERAL
+
+#define DNS_LOGMODULE_DB (&dns_modules[0])
+#define DNS_LOGMODULE_RBTDB (&dns_modules[1])
+#define DNS_LOGMODULE_RBT (&dns_modules[2])
+#define DNS_LOGMODULE_RDATA (&dns_modules[3])
+#define DNS_LOGMODULE_MASTER (&dns_modules[4])
+#define DNS_LOGMODULE_MESSAGE (&dns_modules[5])
+#define DNS_LOGMODULE_CACHE (&dns_modules[6])
+#define DNS_LOGMODULE_CONFIG (&dns_modules[7])
+#define DNS_LOGMODULE_RESOLVER (&dns_modules[8])
+#define DNS_LOGMODULE_ZONE (&dns_modules[9])
+#define DNS_LOGMODULE_JOURNAL (&dns_modules[10])
+#define DNS_LOGMODULE_ADB (&dns_modules[11])
+#define DNS_LOGMODULE_XFER_IN (&dns_modules[12])
+#define DNS_LOGMODULE_XFER_OUT (&dns_modules[13])
+#define DNS_LOGMODULE_ACL (&dns_modules[14])
+#define DNS_LOGMODULE_VALIDATOR (&dns_modules[15])
+#define DNS_LOGMODULE_DISPATCH (&dns_modules[16])
+#define DNS_LOGMODULE_REQUEST (&dns_modules[17])
+#define DNS_LOGMODULE_MASTERDUMP (&dns_modules[18])
+#define DNS_LOGMODULE_TSIG (&dns_modules[19])
+#define DNS_LOGMODULE_TKEY (&dns_modules[20])
+#define DNS_LOGMODULE_SDB (&dns_modules[21])
+#define DNS_LOGMODULE_DIFF (&dns_modules[22])
+#define DNS_LOGMODULE_HINTS (&dns_modules[23])
+#define DNS_LOGMODULE_UNUSED1 (&dns_modules[24])
+#define DNS_LOGMODULE_DLZ (&dns_modules[25])
+#define DNS_LOGMODULE_DNSSEC (&dns_modules[26])
+#define DNS_LOGMODULE_CRYPTO (&dns_modules[27])
+#define DNS_LOGMODULE_PACKETS (&dns_modules[28])
+#define DNS_LOGMODULE_NTA (&dns_modules[29])
+#define DNS_LOGMODULE_DYNDB (&dns_modules[30])
+#define DNS_LOGMODULE_DNSTAP (&dns_modules[31])
+#define DNS_LOGMODULE_SSU (&dns_modules[32])
+
+ISC_LANG_BEGINDECLS
+
+void
+dns_log_init(isc_log_t *lctx);
+/*%
+ * Make the libdns categories and modules available for use with the
+ * ISC logging library.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *
+ *\li dns_log_init() is called only once.
+ *
+ * Ensures:
+ * \li The categories and modules defined above are available for
+ * use by isc_log_usechannnel() and isc_log_write().
+ */
+
+void
+dns_log_setcontext(isc_log_t *lctx);
+/*%
+ * Make the libdns library use the provided context for logging internal
+ * messages.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_LOG_H */
diff --git a/lib/dns/include/dns/lookup.h b/lib/dns/include/dns/lookup.h
new file mode 100644
index 0000000..2d1ce13
--- /dev/null
+++ b/lib/dns/include/dns/lookup.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_LOOKUP_H
+#define DNS_LOOKUP_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/lookup.h
+ * \brief
+ * The lookup module performs simple DNS lookups. It implements
+ * the full resolver algorithm, both looking for local data and
+ * resolving external names as necessary.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li RFCs: 1034, 1035, 2181, TBS
+ *\li Drafts: TBS
+ */
+
+#include <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
+
+#endif /* DNS_LOOKUP_H */
diff --git a/lib/dns/include/dns/master.h b/lib/dns/include/dns/master.h
new file mode 100644
index 0000000..78ebadd
--- /dev/null
+++ b/lib/dns/include/dns/master.h
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_MASTER_H
+#define DNS_MASTER_H 1
+
+/*! \file dns/master.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <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_SLAVE 0x00000020 /*%< Loading a slave master file. */
+#define DNS_MASTER_CHECKNS \
+ 0x00000040 /*%< \
+ * Check NS records to see \
+ * if they are an address \
+ */
+#define DNS_MASTER_FATALNS \
+ 0x00000080 /*%< \
+ * Treat DNS_MASTER_CHECKNS \
+ * matches as fatal \
+ */
+#define DNS_MASTER_CHECKNAMES 0x00000100
+#define DNS_MASTER_CHECKNAMESFAIL 0x00000200
+#define DNS_MASTER_CHECKWILDCARD \
+ 0x00000400 /* Check for internal wildcards. \
+ */
+#define DNS_MASTER_CHECKMX 0x00000800
+#define DNS_MASTER_CHECKMXFAIL 0x00001000
+
+#define DNS_MASTER_RESIGN 0x00002000
+#define DNS_MASTER_KEY 0x00004000 /*%< Loading a key zone master file. */
+#define DNS_MASTER_NOTTL 0x00008000 /*%< Don't require ttl. */
+#define DNS_MASTER_CHECKTTL 0x00010000 /*%< Check max-zone-ttl */
+
+ISC_LANG_BEGINDECLS
+
+/*
+ * Structures that implement the "raw" format for master dump.
+ * These are provided for a reference purpose only; in the actual
+ * encoding, we directly read/write each field so that the encoded data
+ * is always "packed", regardless of the hardware architecture.
+ */
+#define DNS_RAWFORMAT_VERSION 1
+
+/*
+ * Flags to indicate the status of the data in the raw file header
+ */
+#define DNS_MASTERRAW_COMPAT 0x01
+#define DNS_MASTERRAW_SOURCESERIALSET 0x02
+#define DNS_MASTERRAW_LASTXFRINSET 0x04
+
+/* Common header */
+struct dns_masterrawheader {
+ uint32_t format; /* must be
+ * dns_masterformat_raw
+ * or
+ * dns_masterformat_map */
+ uint32_t version; /* compatibility for future
+ * extensions */
+ uint32_t dumptime; /* timestamp on creation
+ * (currently unused) */
+ uint32_t flags; /* Flags */
+ uint32_t sourceserial; /* Source serial number (used
+ * by inline-signing zones) */
+ uint32_t lastxfrin; /* timestamp of last transfer
+ * (used by slave zones) */
+};
+
+/* The structure for each RRset */
+typedef struct {
+ uint32_t totallen; /* length of the data for this
+ * RRset, including the
+ * "header" part */
+ dns_rdataclass_t rdclass; /* 16-bit class */
+ dns_rdatatype_t type; /* 16-bit type */
+ dns_rdatatype_t covers; /* same as type */
+ dns_ttl_t ttl; /* 32-bit TTL */
+ uint32_t nrdata; /* number of RRs in this set */
+ /* followed by encoded owner name, and then rdata */
+} dns_masterrawrdataset_t;
+
+/*
+ * Method prototype: a callback to register each include file as
+ * it is encountered.
+ */
+typedef void (*dns_masterincludecb_t)(const char *file, void *arg);
+
+/***
+ *** Function
+ ***/
+
+isc_result_t
+dns_master_loadfile(const char *master_file, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, uint32_t resign,
+ dns_rdatacallbacks_t *callbacks,
+ dns_masterincludecb_t include_cb, void *include_arg,
+ isc_mem_t *mctx, dns_masterformat_t format,
+ dns_ttl_t maxttl);
+
+isc_result_t
+dns_master_loadstream(FILE *stream, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx);
+
+isc_result_t
+dns_master_loadbuffer(isc_buffer_t *buffer, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx);
+
+isc_result_t
+dns_master_loadlexer(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx);
+
+isc_result_t
+dns_master_loadfileinc(const char *master_file, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, uint32_t resign,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **ctxp, dns_masterincludecb_t include_cb,
+ void *include_arg, isc_mem_t *mctx,
+ dns_masterformat_t format, uint32_t maxttl);
+
+isc_result_t
+dns_master_loadstreaminc(FILE *stream, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **ctxp, isc_mem_t *mctx);
+
+isc_result_t
+dns_master_loadbufferinc(isc_buffer_t *buffer, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, dns_rdatacallbacks_t *callbacks,
+ isc_task_t *task, dns_loaddonefunc_t done,
+ void *done_arg, dns_loadctx_t **ctxp, isc_mem_t *mctx);
+
+isc_result_t
+dns_master_loadlexerinc(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **ctxp, isc_mem_t *mctx);
+
+/*%<
+ * Loads a RFC1305 master file from a file, stream, buffer, or existing
+ * lexer into rdatasets and then calls 'callbacks->commit' to commit the
+ * rdatasets. Rdata memory belongs to dns_master_load and will be
+ * reused / released when the callback completes. dns_load_master will
+ * abort if callbacks->commit returns any value other than ISC_R_SUCCESS.
+ *
+ * If 'DNS_MASTER_AGETTL' is set and the master file contains one or more
+ * $DATE directives, the TTLs of the data will be aged accordingly.
+ *
+ * 'callbacks->commit' is assumed to call 'callbacks->error' or
+ * 'callbacks->warn' to generate any error messages required.
+ *
+ * 'done' is called with 'done_arg' and a result code when the loading
+ * is completed or has failed. If the initial setup fails 'done' is
+ * not called.
+ *
+ * 'resign' the number of seconds before a RRSIG expires that it should
+ * be re-signed. 0 is used if not provided.
+ *
+ * Requires:
+ *\li 'master_file' points to a valid string.
+ *\li 'lexer' points to a valid lexer.
+ *\li 'top' points to a valid name.
+ *\li 'origin' points to a valid name.
+ *\li 'callbacks->commit' points to a valid function.
+ *\li 'callbacks->error' points to a valid function.
+ *\li 'callbacks->warn' points to a valid function.
+ *\li 'mctx' points to a valid memory context.
+ *\li 'task' and 'done' to be valid.
+ *\li 'lmgr' to be valid.
+ *\li 'ctxp != NULL && ctxp == NULL'.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS upon successfully loading the master file.
+ *\li ISC_R_SEENINCLUDE upon successfully loading the master file with
+ * a $INCLUDE statement.
+ *\li ISC_R_NOMEMORY out of memory.
+ *\li ISC_R_UNEXPECTEDEND expected to be able to read a input token and
+ * there was not one.
+ *\li ISC_R_UNEXPECTED
+ *\li DNS_R_NOOWNER failed to specify a ownername.
+ *\li DNS_R_NOTTL failed to specify a ttl.
+ *\li DNS_R_BADCLASS record class did not match zone class.
+ *\li DNS_R_CONTINUE load still in progress (dns_master_load*inc() only).
+ *\li Any dns_rdata_fromtext() error code.
+ *\li Any error code from callbacks->commit().
+ */
+
+void
+dns_loadctx_detach(dns_loadctx_t **ctxp);
+/*%<
+ * Detach from the load context.
+ *
+ * Requires:
+ *\li '*ctxp' to be valid.
+ *
+ * Ensures:
+ *\li '*ctxp == NULL'
+ */
+
+void
+dns_loadctx_attach(dns_loadctx_t *source, dns_loadctx_t **target);
+/*%<
+ * Attach to the load context.
+ *
+ * Requires:
+ *\li 'source' to be valid.
+ *\li 'target != NULL && *target == NULL'.
+ */
+
+void
+dns_loadctx_cancel(dns_loadctx_t *ctx);
+/*%<
+ * Cancel loading the zone file associated with this load context.
+ *
+ * Requires:
+ *\li 'ctx' to be valid
+ */
+
+void
+dns_master_initrawheader(dns_masterrawheader_t *header);
+/*%<
+ * Initializes the header for a raw master file, setting all
+ * values to zero.
+ */
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_MASTER_H */
diff --git a/lib/dns/include/dns/masterdump.h b/lib/dns/include/dns/masterdump.h
new file mode 100644
index 0000000..917e31f
--- /dev/null
+++ b/lib/dns/include/dns/masterdump.h
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_MASTERDUMP_H
+#define DNS_MASTERDUMP_H 1
+
+/*! \file dns/masterdump.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <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.
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_default;
+
+/*%
+ * A master file style that dumps zones to a very generic format easily
+ * imported/checked with external tools.
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_full;
+
+/*%
+ * A master file style that prints explicit TTL values on each
+ * record line, never using $TTL statements. The TTL has a tab
+ * stop of its own, but the class and type share one.
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t
+ dns_master_style_explicitttl;
+
+/*%
+ * A master style format designed for cache files. It prints explicit TTL
+ * values on each record line and never uses $ORIGIN or relative names.
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_cache;
+
+/*%
+ * A master style format designed for cache files. The same as above but
+ * this also prints expired entries.
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t
+ dns_master_style_cache_with_expired;
+
+/*%
+ * A master style that prints name, ttl, class, type, and value on
+ * every line. Similar to explicitttl above, but more verbose.
+ * Intended for generating master files which can be easily parsed
+ * by perl scripts and similar applications.
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_simple;
+
+/*%
+ * The style used for debugging, "dig" output, etc.
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_debug;
+
+/*%
+ * Similar to dns_master_style_debug but data is prepended with ";"
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_comment;
+
+/*%
+ * Similar to dns_master_style_debug but data is indented with "\t" (tab)
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_indent;
+
+/*%
+ * The style used for dumping "key" zones.
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_keyzone;
+
+/*%
+ * YAML-compatible output
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_yaml;
+
+/***
+ *** Functions
+ ***/
+
+void
+dns_dumpctx_attach(dns_dumpctx_t *source, dns_dumpctx_t **target);
+/*%<
+ * Attach to a dump context.
+ *
+ * Require:
+ *\li 'source' to be valid.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_dumpctx_detach(dns_dumpctx_t **dctxp);
+/*%<
+ * Detach from a dump context.
+ *
+ * Require:
+ *\li 'dctxp' to point to a valid dump context.
+ *
+ * Ensures:
+ *\li '*dctxp' is NULL.
+ */
+
+void
+dns_dumpctx_cancel(dns_dumpctx_t *dctx);
+/*%<
+ * Cancel a in progress dump.
+ *
+ * Require:
+ *\li 'dctx' to be valid.
+ */
+
+dns_dbversion_t *
+dns_dumpctx_version(dns_dumpctx_t *dctx);
+/*%<
+ * Return the version handle (if any) of the database being dumped.
+ *
+ * Require:
+ *\li 'dctx' to be valid.
+ */
+
+dns_db_t *
+dns_dumpctx_db(dns_dumpctx_t *dctx);
+/*%<
+ * Return the database being dumped.
+ *
+ * Require:
+ *\li 'dctx' to be valid.
+ */
+
+/*@{*/
+isc_result_t
+dns_master_dumptostreamasync(isc_mem_t *mctx, dns_db_t *db,
+ dns_dbversion_t *version,
+ const dns_master_style_t *style, FILE *f,
+ isc_task_t *task, dns_dumpdonefunc_t done,
+ void *done_arg, dns_dumpctx_t **dctxp);
+
+isc_result_t
+dns_master_dumptostream(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style,
+ dns_masterformat_t format,
+ dns_masterrawheader_t *header, FILE *f);
+/*%<
+ * Dump the database 'db' to the steam 'f' in the specified format by
+ * 'format'. If the format is dns_masterformat_text (the RFC1035 format),
+ * 'style' specifies the file style (e.g., &dns_master_style_default).
+ *
+ * If 'format' is dns_masterformat_raw, then 'header' can contain
+ * information to be written to the file header.
+ *
+ * Temporary dynamic memory may be allocated from 'mctx'.
+ *
+ * Require:
+ *\li 'task' to be valid.
+ *\li 'done' to be non NULL.
+ *\li 'dctxp' to be non NULL && '*dctxp' to be NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ *\li Any database or rrset iterator error.
+ *\li Any dns_rdata_totext() error code.
+ */
+/*@}*/
+
+/*@{*/
+
+isc_result_t
+dns_master_dumpasync(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style, const char *filename,
+ isc_task_t *task, dns_dumpdonefunc_t done, void *done_arg,
+ dns_dumpctx_t **dctxp, dns_masterformat_t format,
+ dns_masterrawheader_t *header);
+
+isc_result_t
+dns_master_dump(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style, const char *filename,
+ dns_masterformat_t format, dns_masterrawheader_t *header);
+
+/*%<
+ * Dump the database 'db' to the file 'filename' in the specified format by
+ * 'format'. If the format is dns_masterformat_text (the RFC1035 format),
+ * 'style' specifies the file style (e.g., &dns_master_style_default).
+ *
+ * If 'format' is dns_masterformat_raw, then 'header' can contain
+ * information to be written to the file header.
+ *
+ * Temporary dynamic memory may be allocated from 'mctx'.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ *\li Any database or rrset iterator error.
+ *\li Any dns_rdata_totext() error code.
+ */
+/*@}*/
+
+isc_result_t
+dns_master_rdatasettotext(const dns_name_t *owner_name,
+ dns_rdataset_t *rdataset,
+ const dns_master_style_t *style, dns_indent_t *indent,
+ isc_buffer_t *target);
+/*%<
+ * Convert 'rdataset' to text format, storing the result in 'target'.
+ *
+ * Notes:
+ *\li The rdata cursor position will be changed.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid non-question rdataset.
+ *
+ *\li 'rdataset' is not empty.
+ */
+
+isc_result_t
+dns_master_questiontotext(const dns_name_t *owner_name,
+ dns_rdataset_t *rdataset,
+ const dns_master_style_t *style,
+ isc_buffer_t *target);
+
+isc_result_t
+dns_master_dumpnodetostream(isc_mem_t *mctx, dns_db_t *db,
+ dns_dbversion_t *version, dns_dbnode_t *node,
+ const dns_name_t *name,
+ const dns_master_style_t *style, FILE *f);
+
+isc_result_t
+dns_master_dumpnode(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ dns_dbnode_t *node, const dns_name_t *name,
+ const dns_master_style_t *style, const char *filename);
+
+dns_masterstyle_flags_t
+dns_master_styleflags(const dns_master_style_t *style);
+
+isc_result_t
+dns_master_stylecreate(dns_master_style_t **style,
+ dns_masterstyle_flags_t flags, unsigned int ttl_column,
+ unsigned int class_column, unsigned int type_column,
+ unsigned int rdata_column, unsigned int line_length,
+ unsigned int tab_width, unsigned int split_width,
+ isc_mem_t *mctx);
+
+void
+dns_master_styledestroy(dns_master_style_t **style, isc_mem_t *mctx);
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_MASTERDUMP_H */
diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h
new file mode 100644
index 0000000..8214021
--- /dev/null
+++ b/lib/dns/include/dns/message.h
@@ -0,0 +1,1492 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/***
+ *** Imports
+ ***/
+
+#include <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 number of EDNS options we know about. */
+#define DNS_EDNSOPTIONS 7
+
+#define DNS_MESSAGE_REPLYPRESERVE (DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD)
+#define DNS_MESSAGEEXTFLAG_REPLYPRESERVE (DNS_MESSAGEEXTFLAG_DO)
+
+#define DNS_MESSAGE_HEADERLEN 12 /*%< 6 uint16_t's */
+
+#define DNS_MESSAGE_MAGIC ISC_MAGIC('M', 'S', 'G', '@')
+#define DNS_MESSAGE_VALID(msg) ISC_MAGIC_VALID(msg, DNS_MESSAGE_MAGIC)
+
+/*
+ * Ordering here matters. DNS_SECTION_ANY must be the lowest and negative,
+ * and DNS_SECTION_MAX must be one greater than the last used section.
+ */
+typedef int dns_section_t;
+#define DNS_SECTION_ANY (-1)
+#define DNS_SECTION_QUESTION 0
+#define DNS_SECTION_ANSWER 1
+#define DNS_SECTION_AUTHORITY 2
+#define DNS_SECTION_ADDITIONAL 3
+#define DNS_SECTION_MAX 4
+
+typedef int dns_pseudosection_t;
+#define DNS_PSEUDOSECTION_ANY (-1)
+#define DNS_PSEUDOSECTION_OPT 0
+#define DNS_PSEUDOSECTION_TSIG 1
+#define DNS_PSEUDOSECTION_SIG0 2
+#define DNS_PSEUDOSECTION_MAX 3
+
+typedef int dns_messagetextflag_t;
+#define DNS_MESSAGETEXTFLAG_NOCOMMENTS 0x0001
+#define DNS_MESSAGETEXTFLAG_NOHEADERS 0x0002
+#define DNS_MESSAGETEXTFLAG_ONESOA 0x0004
+#define DNS_MESSAGETEXTFLAG_OMITSOA 0x0008
+
+/*
+ * Dynamic update names for these sections.
+ */
+#define DNS_SECTION_ZONE DNS_SECTION_QUESTION
+#define DNS_SECTION_PREREQUISITE DNS_SECTION_ANSWER
+#define DNS_SECTION_UPDATE DNS_SECTION_AUTHORITY
+
+/*
+ * These tell the message library how the created dns_message_t will be used.
+ */
+#define DNS_MESSAGE_INTENTUNKNOWN 0 /*%< internal use only */
+#define DNS_MESSAGE_INTENTPARSE 1 /*%< parsing messages */
+#define DNS_MESSAGE_INTENTRENDER 2 /*%< rendering */
+
+/*
+ * Control behavior of parsing
+ */
+#define DNS_MESSAGEPARSE_PRESERVEORDER 0x0001 /*%< preserve rdata order */
+#define DNS_MESSAGEPARSE_BESTEFFORT \
+ 0x0002 /*%< return a message if a \
+ * recoverable parse error \
+ * occurs */
+#define DNS_MESSAGEPARSE_CLONEBUFFER \
+ 0x0004 /*%< save a copy of the \
+ * source buffer */
+#define DNS_MESSAGEPARSE_IGNORETRUNCATION \
+ 0x0008 /*%< truncation errors are \
+ * not fatal. */
+
+/*
+ * Control behavior of rendering
+ */
+#define DNS_MESSAGERENDER_ORDERED 0x0001 /*%< don't change order */
+#define DNS_MESSAGERENDER_PARTIAL 0x0002 /*%< allow a partial rdataset */
+#define DNS_MESSAGERENDER_OMITDNSSEC 0x0004 /*%< omit DNSSEC records */
+#define DNS_MESSAGERENDER_PREFER_A \
+ 0x0008 /*%< prefer A records in \
+ * additional section. */
+#define DNS_MESSAGERENDER_PREFER_AAAA \
+ 0x0010 /*%< prefer AAAA records in \
+ * additional section. */
+/* Obsolete: DNS_MESSAGERENDER_FILTER_AAAA 0x0020 */
+
+typedef struct dns_msgblock dns_msgblock_t;
+
+struct dns_sortlist_arg {
+ dns_aclenv_t *env;
+ const dns_acl_t *acl;
+ const dns_aclelement_t *element;
+};
+
+struct dns_message {
+ /* public from here down */
+ unsigned int magic;
+ isc_refcount_t refcount;
+
+ dns_messageid_t id;
+ unsigned int flags;
+ dns_rcode_t rcode;
+ dns_opcode_t opcode;
+ dns_rdataclass_t rdclass;
+
+ /* 4 real, 1 pseudo */
+ unsigned int counts[DNS_SECTION_MAX];
+
+ /* private from here down */
+ dns_namelist_t sections[DNS_SECTION_MAX];
+ dns_name_t *cursors[DNS_SECTION_MAX];
+ dns_rdataset_t *opt;
+ dns_rdataset_t *sig0;
+ dns_rdataset_t *tsig;
+
+ int state;
+ unsigned int from_to_wire : 2;
+ unsigned int header_ok : 1;
+ unsigned int question_ok : 1;
+ unsigned int tcp_continuation : 1;
+ unsigned int verified_sig : 1;
+ unsigned int verify_attempted : 1;
+ unsigned int free_query : 1;
+ unsigned int free_saved : 1;
+ unsigned int cc_ok : 1;
+ unsigned int cc_bad : 1;
+ unsigned int tkey : 1;
+ unsigned int rdclass_set : 1;
+
+ unsigned int opt_reserved;
+ unsigned int sig_reserved;
+ unsigned int reserved; /* reserved space (render) */
+
+ uint16_t padding;
+ unsigned int padding_off;
+
+ isc_buffer_t *buffer;
+ dns_compress_t *cctx;
+
+ isc_mem_t *mctx;
+ isc_mempool_t *namepool;
+ isc_mempool_t *rdspool;
+
+ isc_bufferlist_t scratchpad;
+ isc_bufferlist_t cleanup;
+
+ ISC_LIST(dns_msgblock_t) rdatas;
+ ISC_LIST(dns_msgblock_t) rdatalists;
+ ISC_LIST(dns_msgblock_t) offsets;
+
+ ISC_LIST(dns_rdata_t) freerdata;
+ ISC_LIST(dns_rdatalist_t) freerdatalist;
+
+ dns_rcode_t tsigstatus;
+ dns_rcode_t querytsigstatus;
+ dns_name_t *tsigname; /* Owner name of TSIG, if any
+ * */
+ dns_rdataset_t *querytsig;
+ dns_tsigkey_t *tsigkey;
+ dst_context_t *tsigctx;
+ int sigstart;
+ int timeadjust;
+
+ dns_name_t *sig0name; /* Owner name of SIG0, if any
+ * */
+ dst_key_t *sig0key;
+ dns_rcode_t sig0status;
+ isc_region_t query;
+ isc_region_t saved;
+
+ dns_rdatasetorderfunc_t order;
+ dns_sortlist_arg_t order_arg;
+
+ dns_indent_t indent;
+};
+
+struct dns_ednsopt {
+ uint16_t code;
+ uint16_t length;
+ unsigned char *value;
+};
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+void
+dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp);
+
+/*%<
+ * Create msg structure.
+ *
+ * This function will allocate some internal blocks of memory that are
+ * expected to be needed for parsing or rendering nearly any type of message.
+ *
+ * Requires:
+ *\li 'mctx' be a valid memory context.
+ *
+ *\li 'msgp' be non-null and '*msg' be NULL.
+ *
+ *\li 'intent' must be one of DNS_MESSAGE_INTENTPARSE or
+ * #DNS_MESSAGE_INTENTRENDER.
+ *
+ * Ensures:
+ *\li The data in "*msg" is set to indicate an unused and empty msg
+ * structure.
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY -- out of memory
+ *\li #ISC_R_SUCCESS -- success
+ */
+
+void
+dns_message_reset(dns_message_t *msg, unsigned int intent);
+/*%<
+ * Reset a message structure to default state. All internal lists are freed
+ * or reset to a default state as well. This is simply a more efficient
+ * way to call dns_message_detach() (assuming last reference is hold),
+ * followed by dns_message_create(), since it avoid many memory allocations.
+ *
+ * If any data loanouts (buffers, names, rdatas, etc) were requested,
+ * the caller must no longer use them after this call.
+ *
+ * The intended next use of the message will be 'intent'.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'intent' is DNS_MESSAGE_INTENTPARSE or DNS_MESSAGE_INTENTRENDER
+ */
+
+void
+dns_message_attach(dns_message_t *source, dns_message_t **target);
+/*%<
+ * Attach to message 'source'.
+ *
+ * Requires:
+ *\li 'source' to be a valid message.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_message_detach(dns_message_t **messagep);
+/*%<
+ * Detach *messagep from its message.
+ * list.
+ *
+ * Requires:
+ *\li '*messagep' to be a valid message.
+ */
+
+isc_result_t
+dns_message_sectiontotext(dns_message_t *msg, dns_section_t section,
+ const dns_master_style_t *style,
+ dns_messagetextflag_t flags, isc_buffer_t *target);
+
+isc_result_t
+dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section,
+ const dns_master_style_t *style,
+ dns_messagetextflag_t flags,
+ isc_buffer_t *target);
+/*%<
+ * Convert section 'section' or 'pseudosection' of message 'msg' to
+ * a cleartext representation
+ *
+ * Notes:
+ * \li See dns_message_totext for meanings of flags.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *
+ *\li 'style' is a valid master dump style.
+ *
+ *\li 'target' is a valid buffer.
+ *
+ *\li 'section' is a valid section label.
+ *
+ * Ensures:
+ *
+ *\li If the result is success:
+ * The used space in 'target' is updated.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *\li #ISC_R_NOMORE
+ *
+ *\li Note: On error return, *target may be partially filled with data.
+ */
+
+isc_result_t
+dns_message_headertotext(dns_message_t *msg, const dns_master_style_t *style,
+ dns_messagetextflag_t flags, isc_buffer_t *target);
+/*%<
+ * Convert the header section of message 'msg' to a cleartext
+ * representation. This is called from dns_message_totext().
+ *
+ * Notes on flags:
+ *\li If #DNS_MESSAGETEXTFLAG_NOHEADERS is set, header lines will be
+ * suppressed and this function is a no-op.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *
+ *\li 'target' is a valid buffer.
+ *
+ * Ensures:
+ *
+ *\li If the result is success:
+ * The used space in 'target' is updated.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *\li #ISC_R_NOMORE
+ *
+ *\li Note: On error return, *target may be partially filled with data.
+ */
+
+isc_result_t
+dns_message_totext(dns_message_t *msg, const dns_master_style_t *style,
+ dns_messagetextflag_t flags, isc_buffer_t *target);
+/*%<
+ * Convert all sections of message 'msg' to a cleartext representation
+ *
+ * Notes on flags:
+ *\li If #DNS_MESSAGETEXTFLAG_NOCOMMENTS is cleared, lines beginning with
+ * ";;" will be emitted indicating section name.
+ *\li If #DNS_MESSAGETEXTFLAG_NOHEADERS is cleared, header lines will be
+ * emitted.
+ *\li If #DNS_MESSAGETEXTFLAG_ONESOA is set then only print the first
+ * SOA record in the answer section.
+ *\li If *#DNS_MESSAGETEXTFLAG_OMITSOA is set don't print any SOA records
+ * in the answer section.
+ *
+ * The SOA flags are useful for suppressing the display of the second
+ * SOA record in an AXFR by setting #DNS_MESSAGETEXTFLAG_ONESOA on the
+ * first message in an AXFR stream and #DNS_MESSAGETEXTFLAG_OMITSOA on
+ * subsequent messages.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *
+ *\li 'style' is a valid master dump style.
+ *
+ *\li 'target' is a valid buffer.
+ *
+ * Ensures:
+ *
+ *\li If the result is success:
+ * The used space in 'target' is updated.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *\li #ISC_R_NOMORE
+ *
+ *\li Note: On error return, *target may be partially filled with data.
+ */
+
+isc_result_t
+dns_message_parse(dns_message_t *msg, isc_buffer_t *source,
+ unsigned int options);
+/*%<
+ * Parse raw wire data in 'source' as a DNS message.
+ *
+ * OPT records are detected and stored in the pseudo-section "opt".
+ * TSIGs are detected and stored in the pseudo-section "tsig".
+ *
+ * If #DNS_MESSAGEPARSE_PRESERVEORDER is set, or if the opcode of the message
+ * is UPDATE, a separate dns_name_t object will be created for each RR in the
+ * message. Each such dns_name_t will have a single rdataset containing the
+ * single RR, and the order of the RRs in the message is preserved.
+ * Otherwise, only one dns_name_t object will be created for each unique
+ * owner name in the section, and each such dns_name_t will have a list
+ * of rdatasets. To access the names and their data, use
+ * dns_message_firstname() and dns_message_nextname().
+ *
+ * If #DNS_MESSAGEPARSE_BESTEFFORT is set, errors in message content will
+ * not be considered FORMERRs. If the entire message can be parsed, it
+ * will be returned and DNS_R_RECOVERABLE will be returned.
+ *
+ * If #DNS_MESSAGEPARSE_IGNORETRUNCATION is set then return as many complete
+ * RR's as possible, DNS_R_RECOVERABLE will be returned.
+ *
+ * OPT and TSIG records are always handled specially, regardless of the
+ * 'preserve_order' setting.
+ *
+ * Requires:
+ *\li "msg" be valid.
+ *
+ *\li "buffer" be a wire format buffer.
+ *
+ * Ensures:
+ *\li The buffer's data format is correct.
+ *
+ *\li The buffer's contents verify as correct regarding header bits, buffer
+ * and rdata sizes, etc.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well
+ *\li #ISC_R_NOMEMORY -- no memory
+ *\li #DNS_R_RECOVERABLE -- the message parsed properly, but contained
+ * errors.
+ *\li Many other errors possible XXXMLG
+ */
+
+isc_result_t
+dns_message_renderbegin(dns_message_t *msg, dns_compress_t *cctx,
+ isc_buffer_t *buffer);
+/*%<
+ * Begin rendering on a message. Only one call can be made to this function
+ * per message.
+ *
+ * The compression context is "owned" by the message library until
+ * dns_message_renderend() is called. It must be invalidated by the caller.
+ *
+ * The buffer is "owned" by the message library until dns_message_renderend()
+ * is called.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'cctx' be valid.
+ *
+ *\li 'buffer' is a valid buffer.
+ *
+ * Side Effects:
+ *
+ *\li The buffer is cleared before it is used.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well
+ *\li #ISC_R_NOSPACE -- output buffer is too small
+ */
+
+isc_result_t
+dns_message_renderchangebuffer(dns_message_t *msg, isc_buffer_t *buffer);
+/*%<
+ * Reset the buffer. This can be used after growing the old buffer
+ * on a ISC_R_NOSPACE return from most of the render functions.
+ *
+ * On successful completion, the old buffer is no longer used by the
+ * library. The new buffer is owned by the library until
+ * dns_message_renderend() is called.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li dns_message_renderbegin() was called.
+ *
+ *\li buffer != NULL.
+ *
+ * Returns:
+ *\li #ISC_R_NOSPACE -- new buffer is too small
+ *\li #ISC_R_SUCCESS -- all is well.
+ */
+
+isc_result_t
+dns_message_renderreserve(dns_message_t *msg, unsigned int space);
+/*%<
+ * XXXMLG should use size_t rather than unsigned int once the buffer
+ * API is cleaned up
+ *
+ * Reserve "space" bytes in the given buffer.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li dns_message_renderbegin() was called.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ *\li #ISC_R_NOSPACE -- not enough free space in the buffer.
+ */
+
+void
+dns_message_renderrelease(dns_message_t *msg, unsigned int space);
+/*%<
+ * XXXMLG should use size_t rather than unsigned int once the buffer
+ * API is cleaned up
+ *
+ * Release "space" bytes in the given buffer that was previously reserved.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'space' is less than or equal to the total amount of space reserved
+ * via prior calls to dns_message_renderreserve().
+ *
+ *\li dns_message_renderbegin() was called.
+ */
+
+isc_result_t
+dns_message_rendersection(dns_message_t *msg, dns_section_t section,
+ unsigned int options);
+/*%<
+ * Render all names, rdatalists, etc from the given section at the
+ * specified priority or higher.
+ *
+ * Requires:
+ *\li 'msg' be valid.
+ *
+ *\li 'section' be a valid section.
+ *
+ *\li dns_message_renderbegin() was called.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all records were written, and there are
+ * no more records for this section.
+ *\li #ISC_R_NOSPACE -- Not enough room in the buffer to write
+ * all records requested.
+ *\li #DNS_R_MOREDATA -- All requested records written, and there
+ * are records remaining for this section.
+ */
+
+void
+dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target);
+/*%<
+ * Render the message header. This is implicitly called by
+ * dns_message_renderend().
+ *
+ * Requires:
+ *
+ *\li 'msg' be a valid message.
+ *
+ *\li dns_message_renderbegin() was called.
+ *
+ *\li 'target' is a valid buffer with enough space to hold a message header
+ */
+
+isc_result_t
+dns_message_renderend(dns_message_t *msg);
+/*%<
+ * Finish rendering to the buffer. Note that more data can be in the
+ * 'msg' structure. Destroying the structure will free this, or in a multi-
+ * part EDNS1 message this data can be rendered to another buffer later.
+ *
+ * Requires:
+ *
+ *\li 'msg' be a valid message.
+ *
+ *\li dns_message_renderbegin() was called.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ */
+
+void
+dns_message_renderreset(dns_message_t *msg);
+/*%<
+ * Reset the message so that it may be rendered again.
+ *
+ * Notes:
+ *
+ *\li If dns_message_renderbegin() has been called, dns_message_renderend()
+ * must be called before calling this function.
+ *
+ * Requires:
+ *
+ *\li 'msg' be a valid message with rendering intent.
+ */
+
+isc_result_t
+dns_message_firstname(dns_message_t *msg, dns_section_t section);
+/*%<
+ * Set internal per-section name pointer to the beginning of the section.
+ *
+ * The functions dns_message_firstname() and dns_message_nextname() may
+ * be used for iterating over the owner names in a section.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'section' be a valid section.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ *\li #ISC_R_NOMORE -- No names on given section.
+ */
+
+isc_result_t
+dns_message_nextname(dns_message_t *msg, dns_section_t section);
+/*%<
+ * Sets the internal per-section name pointer to point to the next name
+ * in that section.
+ *
+ * Requires:
+ *
+ * \li 'msg' be valid.
+ *
+ *\li 'section' be a valid section.
+ *
+ *\li dns_message_firstname() must have been called on this section,
+ * and the result was ISC_R_SUCCESS.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ *\li #ISC_R_NOMORE -- No more names in given section.
+ */
+
+void
+dns_message_currentname(dns_message_t *msg, dns_section_t section,
+ dns_name_t **name);
+/*%<
+ * Sets 'name' to point to the name where the per-section internal name
+ * pointer is currently set.
+ *
+ * This function returns the name in the database, so any data associated
+ * with it (via the name's "list" member) contains the actual rdatasets.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'name' be non-NULL, and *name be NULL.
+ *
+ *\li 'section' be a valid section.
+ *
+ *\li dns_message_firstname() must have been called on this section,
+ * and the result of it and any dns_message_nextname() calls was
+ * #ISC_R_SUCCESS.
+ */
+
+isc_result_t
+dns_message_findname(dns_message_t *msg, dns_section_t section,
+ const dns_name_t *target, dns_rdatatype_t type,
+ dns_rdatatype_t covers, dns_name_t **foundname,
+ dns_rdataset_t **rdataset);
+/*%<
+ * Search for a name in the specified section. If it is found, *name is
+ * set to point to the name, and *rdataset is set to point to the found
+ * rdataset (if type is specified as other than dns_rdatatype_any).
+ *
+ * Requires:
+ *\li 'msg' be valid.
+ *
+ *\li 'section' be a valid section.
+ *
+ *\li If a pointer to the name is desired, 'foundname' should be non-NULL.
+ * If it is non-NULL, '*foundname' MUST be NULL.
+ *
+ *\li If a type other than dns_datatype_any is searched for, 'rdataset'
+ * may be non-NULL, '*rdataset' be NULL, and will point at the found
+ * rdataset. If the type is dns_datatype_any, 'rdataset' must be NULL.
+ *
+ *\li 'target' be a valid name.
+ *
+ *\li 'type' be a valid type.
+ *
+ *\li If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type.
+ * Otherwise it should be 0.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ *\li #DNS_R_NXDOMAIN -- name does not exist in that section.
+ *\li #DNS_R_NXRRSET -- The name does exist, but the desired
+ * type does not.
+ */
+
+isc_result_t
+dns_message_findtype(const dns_name_t *name, dns_rdatatype_t type,
+ dns_rdatatype_t covers, dns_rdataset_t **rdataset);
+/*%<
+ * Search the name for the specified type. If it is found, *rdataset is
+ * filled in with a pointer to that rdataset.
+ *
+ * Requires:
+ *\li if '**rdataset' is non-NULL, *rdataset needs to be NULL.
+ *
+ *\li 'type' be a valid type, and NOT dns_rdatatype_any.
+ *
+ *\li If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type.
+ * Otherwise it should be 0.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ *\li #ISC_R_NOTFOUND -- the desired type does not exist.
+ */
+
+isc_result_t
+dns_message_find(const dns_name_t *name, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ dns_rdataset_t **rdataset);
+/*%<
+ * Search the name for the specified rdclass and type. If it is found,
+ * *rdataset is filled in with a pointer to that rdataset.
+ *
+ * Requires:
+ *\li if '**rdataset' is non-NULL, *rdataset needs to be NULL.
+ *
+ *\li 'type' be a valid type, and NOT dns_rdatatype_any.
+ *
+ *\li If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type.
+ * Otherwise it should be 0.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ *\li #ISC_R_NOTFOUND -- the desired type does not exist.
+ */
+
+void
+dns_message_movename(dns_message_t *msg, dns_name_t *name,
+ dns_section_t fromsection, dns_section_t tosection);
+/*%<
+ * Move a name from one section to another.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid.
+ *
+ *\li 'name' must be a name already in 'fromsection'.
+ *
+ *\li 'fromsection' must be a valid section.
+ *
+ *\li 'tosection' must be a valid section.
+ */
+
+void
+dns_message_addname(dns_message_t *msg, dns_name_t *name,
+ dns_section_t section);
+/*%<
+ * Adds the name to the given section.
+ *
+ * It is the caller's responsibility to enforce any unique name requirements
+ * in a section.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid, and be a renderable message.
+ *
+ *\li 'name' be a valid absolute name.
+ *
+ *\li 'section' be a named section.
+ */
+
+void
+dns_message_removename(dns_message_t *msg, dns_name_t *name,
+ dns_section_t section);
+/*%<
+ * Remove a existing name from a given section.
+ *
+ * It is the caller's responsibility to ensure the name is part of the
+ * given section.
+ *
+ * Requires:
+ *
+ *\li 'msg' be valid, and be a renderable message.
+ *
+ *\li 'name' be a valid absolute name.
+ *
+ *\li 'section' be a named section.
+ */
+
+/*
+ * LOANOUT FUNCTIONS
+ *
+ * Each of these functions loan a particular type of data to the caller.
+ * The storage for these will vanish when the message is destroyed or
+ * reset, and must NOT be used after these operations.
+ */
+
+isc_result_t
+dns_message_gettempname(dns_message_t *msg, dns_name_t **item);
+/*%<
+ * Return a name that can be used for any temporary purpose, including
+ * inserting into the message's linked lists. The name must be returned
+ * to the message code using dns_message_puttempname() or inserted into
+ * one of the message's sections before the message is destroyed.
+ *
+ * The name will be associated with a dns_fixedname object, and will
+ * be initialized.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item == NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ *\li #ISC_R_NOMEMORY -- No item can be allocated.
+ */
+
+isc_result_t
+dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item);
+/*%<
+ * Return a rdata that can be used for any temporary purpose, including
+ * inserting into the message's linked lists. The rdata will be freed
+ * when the message is destroyed or reset.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item == NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ *\li #ISC_R_NOMEMORY -- No item can be allocated.
+ */
+
+isc_result_t
+dns_message_gettemprdataset(dns_message_t *msg, dns_rdataset_t **item);
+/*%<
+ * Return a rdataset that can be used for any temporary purpose, including
+ * inserting into the message's linked lists. The name must be returned
+ * to the message code using dns_message_puttempname() or inserted into
+ * one of the message's sections before the message is destroyed.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item == NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ *\li #ISC_R_NOMEMORY -- No item can be allocated.
+ */
+
+isc_result_t
+dns_message_gettemprdatalist(dns_message_t *msg, dns_rdatalist_t **item);
+/*%<
+ * Return a rdatalist that can be used for any temporary purpose, including
+ * inserting into the message's linked lists. The rdatalist will be
+ * destroyed when the message is destroyed or reset.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item == NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- All is well.
+ *\li #ISC_R_NOMEMORY -- No item can be allocated.
+ */
+
+void
+dns_message_puttempname(dns_message_t *msg, dns_name_t **item);
+/*%<
+ * Return a borrowed name to the message's name free list.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item point to a name returned by
+ * dns_message_gettempname()
+ *
+ * Ensures:
+ *\li *item == NULL
+ */
+
+void
+dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item);
+/*%<
+ * Return a borrowed rdata to the message's rdata free list.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item point to a rdata returned by
+ * dns_message_gettemprdata()
+ *
+ * Ensures:
+ *\li *item == NULL
+ */
+
+void
+dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item);
+/*%<
+ * Return a borrowed rdataset to the message's rdataset free list.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item point to a rdataset returned by
+ * dns_message_gettemprdataset()
+ *
+ * Ensures:
+ *\li *item == NULL
+ */
+
+void
+dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item);
+/*%<
+ * Return a borrowed rdatalist to the message's rdatalist free list.
+ *
+ * Requires:
+ *\li msg be a valid message
+ *
+ *\li item != NULL && *item point to a rdatalist returned by
+ * dns_message_gettemprdatalist()
+ *
+ * Ensures:
+ *\li *item == NULL
+ */
+
+isc_result_t
+dns_message_peekheader(isc_buffer_t *source, dns_messageid_t *idp,
+ unsigned int *flagsp);
+/*%<
+ * Assume the remaining region of "source" is a DNS message. Peek into
+ * it and fill in "*idp" with the message id, and "*flagsp" with the flags.
+ *
+ * Requires:
+ *
+ *\li source != NULL
+ *
+ * Ensures:
+ *
+ *\li if (idp != NULL) *idp == message id.
+ *
+ *\li if (flagsp != NULL) *flagsp == message flags.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *
+ *\li #ISC_R_UNEXPECTEDEND -- buffer doesn't contain enough for a header.
+ */
+
+isc_result_t
+dns_message_reply(dns_message_t *msg, bool want_question_section);
+/*%<
+ * Start formatting a reply to the query in 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message with parsing intent, and contains a query.
+ *
+ * Ensures:
+ *
+ *\li The message will have a rendering intent. If 'want_question_section'
+ * is true, the message opcode is query or notify, and the question
+ * section is present and properly formatted, then the question section
+ * will be included in the reply. All other sections will be cleared.
+ * The QR flag will be set, the RD flag will be preserved, and all other
+ * flags will be cleared.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *
+ *\li #DNS_R_FORMERR -- the header or question section of the
+ * message is invalid, replying is impossible.
+ * If DNS_R_FORMERR is returned when
+ * want_question_section is false, then
+ * it's the header section that's bad;
+ * otherwise either of the header or question
+ * sections may be bad.
+ */
+
+dns_rdataset_t *
+dns_message_getopt(dns_message_t *msg);
+/*%<
+ * Get the OPT record for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *
+ * Returns:
+ *
+ *\li The OPT rdataset of 'msg', or NULL if there isn't one.
+ */
+
+isc_result_t
+dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt);
+/*%<
+ * Set the OPT record for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message with rendering intent
+ * and no sections have been rendered.
+ *
+ *\li 'opt' is a valid OPT record.
+ *
+ * Ensures:
+ *
+ *\li The OPT record has either been freed or ownership of it has
+ * been transferred to the message.
+ *
+ *\li If ISC_R_SUCCESS was returned, the OPT record will be rendered
+ * when dns_message_renderend() is called.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *
+ *\li #ISC_R_NOSPACE -- there is no space for the OPT record.
+ */
+
+dns_rdataset_t *
+dns_message_gettsig(dns_message_t *msg, const dns_name_t **owner);
+/*%<
+ * Get the TSIG record and owner for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *\li 'owner' is NULL or *owner is NULL.
+ *
+ * Returns:
+ *
+ *\li The TSIG rdataset of 'msg', or NULL if there isn't one.
+ *
+ * Ensures:
+ *
+ * \li If 'owner' is not NULL, it will point to the owner name.
+ */
+
+isc_result_t
+dns_message_settsigkey(dns_message_t *msg, dns_tsigkey_t *key);
+/*%<
+ * Set the tsig key for 'msg'. This is only necessary for when rendering a
+ * query or parsing a response. The key (if non-NULL) is attached to, and
+ * will be detached when the message is destroyed.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message with rendering intent,
+ * dns_message_renderbegin() has been called, and no sections have been
+ * rendered.
+ *\li 'key' is a valid tsig key or NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *
+ *\li #ISC_R_NOSPACE -- there is no space for the TSIG record.
+ */
+
+dns_tsigkey_t *
+dns_message_gettsigkey(dns_message_t *msg);
+/*%<
+ * Gets the tsig key for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message
+ */
+
+isc_result_t
+dns_message_setquerytsig(dns_message_t *msg, isc_buffer_t *querytsig);
+/*%<
+ * Indicates that 'querytsig' is the TSIG from the signed query for which
+ * 'msg' is the response. This is also used for chained TSIGs in TCP
+ * responses.
+ *
+ * Requires:
+ *
+ *\li 'querytsig' is a valid buffer as returned by dns_message_getquerytsig()
+ * or NULL
+ *
+ *\li 'msg' is a valid message
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_message_getquerytsig(dns_message_t *msg, isc_mem_t *mctx,
+ isc_buffer_t **querytsig);
+/*%<
+ * Gets the tsig from the TSIG from the signed query 'msg'. This is also used
+ * for chained TSIGs in TCP responses. Unlike dns_message_gettsig, this makes
+ * a copy of the data, so can be used if the message is destroyed.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid signed message
+ *\li 'mctx' is a valid memory context
+ *\li querytsig != NULL && *querytsig == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ * Ensures:
+ *\li 'tsig' points to NULL or an allocated buffer which must be freed
+ * by the caller.
+ */
+
+dns_rdataset_t *
+dns_message_getsig0(dns_message_t *msg, const dns_name_t **owner);
+/*%<
+ * Get the SIG(0) record and owner for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message.
+ *\li 'owner' is NULL or *owner is NULL.
+ *
+ * Returns:
+ *
+ *\li The SIG(0) rdataset of 'msg', or NULL if there isn't one.
+ *
+ * Ensures:
+ *
+ * \li If 'owner' is not NULL, it will point to the owner name.
+ */
+
+isc_result_t
+dns_message_setsig0key(dns_message_t *msg, dst_key_t *key);
+/*%<
+ * Set the SIG(0) key for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message with rendering intent,
+ * dns_message_renderbegin() has been called, and no sections have been
+ * rendered.
+ *\li 'key' is a valid sig key or NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- all is well.
+ *
+ *\li #ISC_R_NOSPACE -- there is no space for the SIG(0) record.
+ */
+
+dst_key_t *
+dns_message_getsig0key(dns_message_t *msg);
+/*%<
+ * Gets the SIG(0) key for 'msg'.
+ *
+ * Requires:
+ *
+ *\li 'msg' is a valid message
+ */
+
+void
+dns_message_takebuffer(dns_message_t *msg, isc_buffer_t **buffer);
+/*%<
+ * Give the *buffer to the message code to clean up when it is no
+ * longer needed. This is usually when the message is reset or
+ * destroyed.
+ *
+ * Requires:
+ *
+ *\li msg be a valid message.
+ *
+ *\li buffer != NULL && *buffer is a valid isc_buffer_t, which was
+ * dynamically allocated via isc_buffer_allocate().
+ */
+
+isc_result_t
+dns_message_signer(dns_message_t *msg, dns_name_t *signer);
+/*%<
+ * If this message was signed, return the identity of the signer.
+ * Unless ISC_R_NOTFOUND is returned, signer will reflect the name of the
+ * key that signed the message.
+ *
+ * Requires:
+ *
+ *\li msg is a valid parsed message.
+ *\li signer is a valid name
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS - the message was signed, and *signer
+ * contains the signing identity
+ *
+ *\li #ISC_R_NOTFOUND - no TSIG or SIG(0) record is present in the
+ * message
+ *
+ *\li #DNS_R_TSIGVERIFYFAILURE - the message was signed by a TSIG, but
+ * the signature failed to verify
+ *
+ *\li #DNS_R_TSIGERRORSET - the message was signed by a TSIG and
+ * verified, but the query was rejected by
+ * the server
+ *
+ *\li #DNS_R_NOIDENTITY - the message was signed by a TSIG and
+ * verified, but the key has no identity since
+ * it was generated by an unsigned TKEY process
+ *
+ *\li #DNS_R_SIGINVALID - the message was signed by a SIG(0), but
+ * the signature failed to verify
+ *
+ *\li #DNS_R_NOTVERIFIEDYET - the message was signed by a TSIG or SIG(0),
+ * but the signature has not been verified yet
+ */
+
+isc_result_t
+dns_message_checksig(dns_message_t *msg, dns_view_t *view);
+/*%<
+ * If this message was signed, verify the signature.
+ *
+ * Requires:
+ *
+ *\li msg is a valid parsed message.
+ *\li view is a valid view or NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS - the message was unsigned, or the message
+ * was signed correctly.
+ *
+ *\li #DNS_R_EXPECTEDTSIG - A TSIG was expected, but not seen
+ *\li #DNS_R_UNEXPECTEDTSIG - A TSIG was seen but not expected
+ *\li #DNS_R_TSIGVERIFYFAILURE - The TSIG failed to verify
+ */
+
+isc_result_t
+dns_message_rechecksig(dns_message_t *msg, dns_view_t *view);
+/*%<
+ * Reset the signature state and then if the message was signed,
+ * verify the message.
+ *
+ * Requires:
+ *
+ *\li msg is a valid parsed message.
+ *\li view is a valid view or NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS - the message was unsigned, or the message
+ * was signed correctly.
+ *
+ *\li #DNS_R_EXPECTEDTSIG - A TSIG was expected, but not seen
+ *\li #DNS_R_UNEXPECTEDTSIG - A TSIG was seen but not expected
+ *\li #DNS_R_TSIGVERIFYFAILURE - The TSIG failed to verify
+ */
+
+void
+dns_message_resetsig(dns_message_t *msg);
+/*%<
+ * Reset the signature state.
+ *
+ * Requires:
+ *\li 'msg' is a valid parsed message.
+ */
+
+isc_region_t *
+dns_message_getrawmessage(dns_message_t *msg);
+/*%<
+ * Retrieve the raw message in compressed wire format. The message must
+ * have been successfully parsed for it to have been saved.
+ *
+ * Requires:
+ *\li msg is a valid parsed message.
+ *
+ * Returns:
+ *\li NULL if there is no saved message.
+ * a pointer to a region which refers the dns message.
+ */
+
+void
+dns_message_setsortorder(dns_message_t *msg, dns_rdatasetorderfunc_t order,
+ dns_aclenv_t *env, const dns_acl_t *acl,
+ const dns_aclelement_t *element);
+/*%<
+ * Define the order in which RR sets get rendered by
+ * dns_message_rendersection() to be the ascending order
+ * defined by the integer value returned by 'order' when
+ * given each RR and a ns_sortlist_arg_t constructed from 'env',
+ * 'acl', and 'element' as arguments.
+ *
+ * If 'order' is NULL, a default order is used.
+ *
+ * Requires:
+ *\li msg be a valid message.
+ *\li If 'env' is NULL, 'order' must be NULL.
+ *\li If 'env' is not NULL, 'order' must not be NULL and at least one of
+ * 'acl' and 'element' must also not be NULL.
+ */
+
+void
+dns_message_settimeadjust(dns_message_t *msg, int timeadjust);
+/*%<
+ * Adjust the time used to sign/verify a message by timeadjust.
+ * Currently only TSIG.
+ *
+ * Requires:
+ *\li msg be a valid message.
+ */
+
+int
+dns_message_gettimeadjust(dns_message_t *msg);
+/*%<
+ * Return the current time adjustment.
+ *
+ * Requires:
+ *\li msg be a valid message.
+ */
+
+void
+dns_message_logpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ int level, isc_mem_t *mctx);
+
+void
+dns_message_logfmtpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ const dns_master_style_t *style, int level,
+ isc_mem_t *mctx);
+/*%<
+ * Log 'message' at the specified logging parameters.
+ *
+ * For dns_message_logpacket and dns_message_logfmtpacket expect the
+ * 'description' to end in a newline.
+ *
+ * For dns_message_logpacket2 and dns_message_logfmtpacket2
+ * 'description' will be emitted at the start of the message followed
+ * by the formatted address and a newline.
+ *
+ * Requires:
+ * \li message be a valid.
+ * \li description to be non NULL.
+ * \li address to be non NULL.
+ * \li category to be valid.
+ * \li module to be valid.
+ * \li style to be valid.
+ * \li mctx to be a valid.
+ */
+
+isc_result_t
+dns_message_buildopt(dns_message_t *msg, dns_rdataset_t **opt,
+ unsigned int version, uint16_t udpsize, unsigned int flags,
+ dns_ednsopt_t *ednsopts, size_t count);
+/*%<
+ * Built a opt record.
+ *
+ * Requires:
+ * \li msg be a valid message.
+ * \li opt to be a non NULL and *opt to be NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS on success.
+ * \li ISC_R_NOMEMORY
+ * \li ISC_R_NOSPACE
+ * \li other.
+ */
+
+void
+dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass);
+/*%<
+ * Set the expected class of records in the response.
+ *
+ * Requires:
+ * \li msg be a valid message with parsing intent.
+ */
+
+void
+dns_message_setpadding(dns_message_t *msg, uint16_t padding);
+/*%<
+ * Set the padding block size in the response.
+ * 0 means no padding (default).
+ *
+ * Requires:
+ * \li msg be a valid message.
+ */
+
+void
+dns_message_clonebuffer(dns_message_t *msg);
+/*%<
+ * Clone the query or saved buffers if they where not cloned
+ * when parsing.
+ *
+ * Requires:
+ * \li msg be a valid message.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/dns/include/dns/name.h b/lib/dns/include/dns/name.h
new file mode 100644
index 0000000..683f71d
--- /dev/null
+++ b/lib/dns/include/dns/name.h
@@ -0,0 +1,1410 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_NAME_H
+#define DNS_NAME_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/name.h
+ * \brief
+ * Provides facilities for manipulating DNS names and labels, including
+ * conversions to and from wire format and text format.
+ *
+ * Given the large number of names possible in a nameserver, and because
+ * names occur in rdata, it was important to come up with a very efficient
+ * way of storing name data, but at the same time allow names to be
+ * manipulated. The decision was to store names in uncompressed wire format,
+ * and not to make them fully abstracted objects; i.e. certain parts of the
+ * server know names are stored that way. This saves a lot of memory, and
+ * makes adding names to messages easy. Having much of the server know
+ * the representation would be perilous, and we certainly don't want each
+ * user of names to be manipulating such a low-level structure. This is
+ * where the Names and Labels module comes in. The module allows name or
+ * label handles to be created and attached to uncompressed wire format
+ * regions. All name operations and conversions are done through these
+ * handles.
+ *
+ * MP:
+ *\li Clients of this module must impose any required synchronization.
+ *
+ * Reliability:
+ *\li This module deals with low-level byte streams. Errors in any of
+ * the functions are likely to crash the server or corrupt memory.
+ *
+ * Resources:
+ *\li None.
+ *
+ * Security:
+ *
+ *\li *** WARNING ***
+ *
+ *\li dns_name_fromwire() deals with raw network data. An error in
+ * this routine could result in the failure or hijacking of the server.
+ *
+ * Standards:
+ *\li RFC1035
+ *\li Draft EDNS0 (0)
+ *\li Draft Binary Labels (2)
+ *
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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. */
+
+LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_rootname;
+LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_wildcardname;
+
+/*%<
+ * DNS_NAME_INITNONABSOLUTE and DNS_NAME_INITABSOLUTE are macros for
+ * initializing dns_name_t structures.
+ *
+ * Note[1]: 'length' is set to (sizeof(A) - 1) in DNS_NAME_INITNONABSOLUTE
+ * and sizeof(A) in DNS_NAME_INITABSOLUTE to allow C strings to be used
+ * to initialize 'ndata'.
+ *
+ * Note[2]: The final value of offsets for DNS_NAME_INITABSOLUTE should
+ * match (sizeof(A) - 1) which is the offset of the root label.
+ *
+ * Typical usage:
+ * unsigned char data[] = "\005value";
+ * unsigned char offsets[] = { 0 };
+ * dns_name_t value = DNS_NAME_INITNONABSOLUTE(data, offsets);
+ *
+ * unsigned char data[] = "\005value";
+ * unsigned char offsets[] = { 0, 6 };
+ * dns_name_t value = DNS_NAME_INITABSOLUTE(data, offsets);
+ */
+#define DNS_NAME_INITNONABSOLUTE(A, B) \
+ { \
+ DNS_NAME_MAGIC, A, (sizeof(A) - 1), sizeof(B), \
+ DNS_NAMEATTR_READONLY, B, NULL, \
+ { (void *)-1, (void *)-1 }, { \
+ NULL, NULL \
+ } \
+ }
+
+#define DNS_NAME_INITABSOLUTE(A, B) \
+ { \
+ DNS_NAME_MAGIC, A, sizeof(A), sizeof(B), \
+ DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE, B, \
+ NULL, { (void *)-1, (void *)-1 }, { \
+ NULL, NULL \
+ } \
+ }
+
+#define DNS_NAME_INITEMPTY \
+ { \
+ DNS_NAME_MAGIC, NULL, 0, 0, 0, NULL, NULL, \
+ { (void *)-1, (void *)-1 }, { \
+ NULL, NULL \
+ } \
+ }
+
+/*%
+ * Standard size of a wire format name
+ */
+#define DNS_NAME_MAXWIRE 255
+
+/*
+ * Text output filter procedure.
+ * 'target' is the buffer to be converted. The region to be converted
+ * is from 'buffer'->base + 'used_org' to the end of the used region.
+ */
+typedef isc_result_t(dns_name_totextfilter_t)(isc_buffer_t *target,
+ unsigned int used_org);
+
+/***
+ *** Initialization
+ ***/
+
+void
+dns_name_init(dns_name_t *name, unsigned char *offsets);
+/*%<
+ * Initialize 'name'.
+ *
+ * Notes:
+ * \li 'offsets' is never required to be non-NULL, but specifying a
+ * dns_offsets_t for 'offsets' will improve the performance of most
+ * name operations if the name is used more than once.
+ *
+ * Requires:
+ * \li 'name' is not NULL and points to a struct dns_name.
+ *
+ * \li offsets == NULL or offsets is a dns_offsets_t.
+ *
+ * Ensures:
+ * \li 'name' is a valid name.
+ * \li dns_name_countlabels(name) == 0
+ * \li dns_name_isabsolute(name) == false
+ */
+
+void
+dns_name_reset(dns_name_t *name);
+/*%<
+ * Reinitialize 'name'.
+ *
+ * Notes:
+ * \li This function distinguishes itself from dns_name_init() in two
+ * key ways:
+ *
+ * \li + If any buffer is associated with 'name' (via dns_name_setbuffer()
+ * or by being part of a dns_fixedname_t) the link to the buffer
+ * is retained but the buffer itself is cleared.
+ *
+ * \li + Of the attributes associated with 'name', all are retained except
+ * DNS_NAMEATTR_ABSOLUTE.
+ *
+ * Requires:
+ * \li 'name' is a valid name.
+ *
+ * Ensures:
+ * \li 'name' is a valid name.
+ * \li dns_name_countlabels(name) == 0
+ * \li dns_name_isabsolute(name) == false
+ */
+
+void
+dns_name_invalidate(dns_name_t *name);
+/*%<
+ * Make 'name' invalid.
+ *
+ * Requires:
+ * \li 'name' is a valid name.
+ *
+ * Ensures:
+ * \li If assertion checking is enabled, future attempts to use 'name'
+ * without initializing it will cause an assertion failure.
+ *
+ * \li If the name had a dedicated buffer, that association is ended.
+ */
+
+bool
+dns_name_isvalid(const dns_name_t *name);
+/*%<
+ * Check whether 'name' points to a valid dns_name
+ */
+
+/***
+ *** Dedicated Buffers
+ ***/
+
+void
+dns_name_setbuffer(dns_name_t *name, isc_buffer_t *buffer);
+/*%<
+ * Dedicate a buffer for use with 'name'.
+ *
+ * Notes:
+ * \li Specification of a target buffer in dns_name_fromwire(),
+ * dns_name_fromtext(), and dns_name_concatenate() is optional if
+ * 'name' has a dedicated buffer.
+ *
+ * \li The caller must not write to buffer until the name has been
+ * invalidated or is otherwise known not to be in use.
+ *
+ * \li If buffer is NULL and the name previously had a dedicated buffer,
+ * than that buffer is no longer dedicated to use with this name.
+ * The caller is responsible for ensuring that the storage used by
+ * the name remains valid.
+ *
+ * Requires:
+ * \li 'name' is a valid name.
+ *
+ * \li 'buffer' is a valid binary buffer and 'name' doesn't have a
+ * dedicated buffer already, or 'buffer' is NULL.
+ */
+
+bool
+dns_name_hasbuffer(const dns_name_t *name);
+/*%<
+ * Does 'name' have a dedicated buffer?
+ *
+ * Requires:
+ * \li 'name' is a valid name.
+ *
+ * Returns:
+ * \li true 'name' has a dedicated buffer.
+ * \li false 'name' does not have a dedicated buffer.
+ */
+
+/***
+ *** Properties
+ ***/
+
+bool
+dns_name_isabsolute(const dns_name_t *name);
+/*%<
+ * Does 'name' end in the root label?
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * Returns:
+ * \li TRUE The last label in 'name' is the root label.
+ * \li FALSE The last label in 'name' is not the root label.
+ */
+
+bool
+dns_name_iswildcard(const dns_name_t *name);
+/*%<
+ * Is 'name' a wildcard name?
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * \li dns_name_countlabels(name) > 0
+ *
+ * Returns:
+ * \li TRUE The least significant label of 'name' is '*'.
+ * \li FALSE The least significant label of 'name' is not '*'.
+ */
+
+unsigned int
+dns_name_hash(const dns_name_t *name, bool case_sensitive);
+/*%<
+ * Provide a hash value for 'name'.
+ *
+ * Note: if 'case_sensitive' is false, then names which differ only in
+ * case will have the same hash value.
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * Returns:
+ * \li A hash value
+ */
+
+unsigned int
+dns_name_fullhash(const dns_name_t *name, bool case_sensitive);
+/*%<
+ * Provide a hash value for 'name'. Unlike dns_name_hash(), this function
+ * always takes into account of the entire name to calculate the hash value.
+ *
+ * Note: if 'case_sensitive' is false, then names which differ only in
+ * case will have the same hash value.
+ *
+ * Requires:
+ *\li 'name' is a valid name
+ *
+ * Returns:
+ *\li A hash value
+ */
+
+/*
+ *** Comparisons
+ ***/
+
+dns_namereln_t
+dns_name_fullcompare(const dns_name_t *name1, const dns_name_t *name2,
+ int *orderp, unsigned int *nlabelsp);
+/*%<
+ * Determine the relative ordering under the DNSSEC order relation of
+ * 'name1' and 'name2', and also determine the hierarchical
+ * relationship of the names.
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ *
+ * Requires:
+ *\li 'name1' is a valid name
+ *
+ *\li dns_name_countlabels(name1) > 0
+ *
+ *\li 'name2' is a valid name
+ *
+ *\li dns_name_countlabels(name2) > 0
+ *
+ *\li orderp and nlabelsp are valid pointers.
+ *
+ *\li Either name1 is absolute and name2 is absolute, or neither is.
+ *
+ * Ensures:
+ *
+ *\li *orderp is < 0 if name1 < name2, 0 if name1 = name2, > 0 if
+ * name1 > name2.
+ *
+ *\li *nlabelsp is the number of common significant labels.
+ *
+ * Returns:
+ *\li dns_namereln_none There's no hierarchical relationship
+ * between name1 and name2.
+ *\li dns_namereln_contains name1 properly contains name2; i.e.
+ * name2 is a proper subdomain of name1.
+ *\li dns_namereln_subdomain name1 is a proper subdomain of name2.
+ *\li dns_namereln_equal name1 and name2 are equal.
+ *\li dns_namereln_commonancestor name1 and name2 share a common
+ * ancestor.
+ */
+
+int
+dns_name_compare(const dns_name_t *name1, const dns_name_t *name2);
+/*%<
+ * Determine the relative ordering under the DNSSEC order relation of
+ * 'name1' and 'name2'.
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ *
+ * Requires:
+ * \li 'name1' is a valid name
+ *
+ * \li 'name2' is a valid name
+ *
+ * \li Either name1 is absolute and name2 is absolute, or neither is.
+ *
+ * Returns:
+ * \li < 0 'name1' is less than 'name2'
+ * \li 0 'name1' is equal to 'name2'
+ * \li > 0 'name1' is greater than 'name2'
+ */
+
+bool
+dns_name_equal(const dns_name_t *name1, const dns_name_t *name2);
+/*%<
+ * Are 'name1' and 'name2' equal?
+ *
+ * Notes:
+ * \li Because it only needs to test for equality, dns_name_equal() can be
+ * significantly faster than dns_name_fullcompare() or dns_name_compare().
+ *
+ * \li Offsets tables are not used in the comparison.
+ *
+ * \li It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ *
+ * Requires:
+ * \li 'name1' is a valid name
+ *
+ * \li 'name2' is a valid name
+ *
+ * \li Either name1 is absolute and name2 is absolute, or neither is.
+ *
+ * Returns:
+ * \li true 'name1' and 'name2' are equal
+ * \li false 'name1' and 'name2' are not equal
+ */
+
+bool
+dns_name_caseequal(const dns_name_t *name1, const dns_name_t *name2);
+/*%<
+ * Case sensitive version of dns_name_equal().
+ */
+
+int
+dns_name_rdatacompare(const dns_name_t *name1, const dns_name_t *name2);
+/*%<
+ * Compare two names as if they are part of rdata in DNSSEC canonical
+ * form.
+ *
+ * Requires:
+ * \li 'name1' is a valid absolute name
+ *
+ * \li dns_name_countlabels(name1) > 0
+ *
+ * \li 'name2' is a valid absolute name
+ *
+ * \li dns_name_countlabels(name2) > 0
+ *
+ * Returns:
+ * \li < 0 'name1' is less than 'name2'
+ * \li 0 'name1' is equal to 'name2'
+ * \li > 0 'name1' is greater than 'name2'
+ */
+
+bool
+dns_name_issubdomain(const dns_name_t *name1, const dns_name_t *name2);
+/*%<
+ * Is 'name1' a subdomain of 'name2'?
+ *
+ * Notes:
+ * \li name1 is a subdomain of name2 if name1 is contained in name2, or
+ * name1 equals name2.
+ *
+ * \li It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ *
+ * Requires:
+ * \li 'name1' is a valid name
+ *
+ * \li 'name2' is a valid name
+ *
+ * \li Either name1 is absolute and name2 is absolute, or neither is.
+ *
+ * Returns:
+ * \li TRUE 'name1' is a subdomain of 'name2'
+ * \li FALSE 'name1' is not a subdomain of 'name2'
+ */
+
+bool
+dns_name_matcheswildcard(const dns_name_t *name, const dns_name_t *wname);
+/*%<
+ * Does 'name' match the wildcard specified in 'wname'?
+ *
+ * Notes:
+ * \li name matches the wildcard specified in wname if all labels
+ * following the wildcard in wname are identical to the same number
+ * of labels at the end of name.
+ *
+ * \li It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * \li dns_name_countlabels(name) > 0
+ *
+ * \li 'wname' is a valid name
+ *
+ * \li dns_name_countlabels(wname) > 0
+ *
+ * \li dns_name_iswildcard(wname) is true
+ *
+ * \li Either name is absolute and wname is absolute, or neither is.
+ *
+ * Returns:
+ * \li TRUE 'name' matches the wildcard specified in 'wname'
+ * \li FALSE 'name' does not match the wildcard specified in 'wname'
+ */
+
+/***
+ *** Labels
+ ***/
+
+unsigned int
+dns_name_countlabels(const dns_name_t *name);
+/*%<
+ * How many labels does 'name' have?
+ *
+ * Notes:
+ * \li In this case, as in other places, a 'label' is an ordinary label.
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * Ensures:
+ * \li The result is <= 128.
+ *
+ * Returns:
+ * \li The number of labels in 'name'.
+ */
+
+void
+dns_name_getlabel(const dns_name_t *name, unsigned int n, dns_label_t *label);
+/*%<
+ * Make 'label' refer to the 'n'th least significant label of 'name'.
+ *
+ * Notes:
+ * \li Numbering starts at 0.
+ *
+ * \li Given "rc.vix.com.", the label 0 is "rc", and label 3 is the
+ * root label.
+ *
+ * \li 'label' refers to the same memory as 'name', so 'name' must not
+ * be changed while 'label' is still in use.
+ *
+ * Requires:
+ * \li n < dns_name_countlabels(name)
+ */
+
+void
+dns_name_getlabelsequence(const dns_name_t *source, unsigned int first,
+ unsigned int n, dns_name_t *target);
+/*%<
+ * Make 'target' refer to the 'n' labels including and following 'first'
+ * in 'source'.
+ *
+ * Notes:
+ * \li Numbering starts at 0.
+ *
+ * \li Given "rc.vix.com.", the label 0 is "rc", and label 3 is the
+ * root label.
+ *
+ * \li 'target' refers to the same memory as 'source', so 'source'
+ * must not be changed while 'target' is still in use.
+ *
+ * Requires:
+ * \li 'source' and 'target' are valid names.
+ *
+ * \li first < dns_name_countlabels(name)
+ *
+ * \li first + n <= dns_name_countlabels(name)
+ */
+
+void
+dns_name_clone(const dns_name_t *source, dns_name_t *target);
+/*%<
+ * Make 'target' refer to the same name as 'source'.
+ *
+ * Notes:
+ *
+ * \li 'target' refers to the same memory as 'source', so 'source'
+ * must not be changed or freed while 'target' is still in use.
+ *
+ * \li This call is functionally equivalent to:
+ *
+ * \code
+ * dns_name_getlabelsequence(source, 0,
+ * dns_name_countlabels(source),
+ * target);
+ * \endcode
+ *
+ * but is more efficient. Also, dns_name_clone() works even if 'source'
+ * is empty.
+ *
+ * Requires:
+ *
+ * \li 'source' is a valid name.
+ *
+ * \li 'target' is a valid name that is not read-only.
+ */
+
+/***
+ *** Conversions
+ ***/
+
+void
+dns_name_fromregion(dns_name_t *name, const isc_region_t *r);
+/*%<
+ * Make 'name' refer to region 'r'.
+ *
+ * Note:
+ * \li If the conversion encounters a root label before the end of the
+ * region the conversion stops and the length is set to the length
+ * so far converted. A maximum of 255 bytes is converted.
+ *
+ * Requires:
+ * \li The data in 'r' is a sequence of one or more type 00 or type 01000001
+ * labels.
+ */
+
+void
+dns_name_toregion(const dns_name_t *name, isc_region_t *r);
+/*%<
+ * Make 'r' refer to 'name'.
+ *
+ * Requires:
+ *
+ * \li 'name' is a valid name.
+ *
+ * \li 'r' is a valid region.
+ */
+
+isc_result_t
+dns_name_fromwire(dns_name_t *name, isc_buffer_t *source,
+ dns_decompress_t *dctx, unsigned int options,
+ isc_buffer_t *target);
+/*%<
+ * Copy the possibly-compressed name at source (active region) into target,
+ * decompressing it.
+ *
+ * Notes:
+ * \li Decompression policy is controlled by 'dctx'.
+ *
+ * \li If DNS_NAME_DOWNCASE is set, any uppercase letters in 'source' will be
+ * downcased when they are copied into 'target'.
+ *
+ * Security:
+ *
+ * \li *** WARNING ***
+ *
+ * \li This routine will often be used when 'source' contains raw network
+ * data. A programming error in this routine could result in a denial
+ * of service, or in the hijacking of the server.
+ *
+ * Requires:
+ *
+ * \li 'name' is a valid name.
+ *
+ * \li 'source' is a valid buffer and the first byte of the active
+ * region should be the first byte of a DNS wire format domain name.
+ *
+ * \li 'target' is a valid buffer or 'target' is NULL and 'name' has
+ * a dedicated buffer.
+ *
+ * \li 'dctx' is a valid decompression context.
+ *
+ * Ensures:
+ *
+ * If result is success:
+ * \li If 'target' is not NULL, 'name' is attached to it.
+ *
+ * \li Uppercase letters are downcased in the copy iff
+ * DNS_NAME_DOWNCASE is set in options.
+ *
+ * \li The current location in source is advanced, and the used space
+ * in target is updated.
+ *
+ * Result:
+ * \li Success
+ * \li Bad Form: Label Length
+ * \li Bad Form: Unknown Label Type
+ * \li Bad Form: Name Length
+ * \li Bad Form: Compression type not allowed
+ * \li Bad Form: Bad compression pointer
+ * \li Bad Form: Input too short
+ * \li Resource Limit: Too many compression pointers
+ * \li Resource Limit: Not enough space in buffer
+ */
+
+isc_result_t
+dns_name_towire(const dns_name_t *name, dns_compress_t *cctx,
+ isc_buffer_t *target);
+isc_result_t
+dns_name_towire2(const dns_name_t *name, dns_compress_t *cctx,
+ isc_buffer_t *target, uint16_t *comp_offsetp);
+/*%<
+ * Convert 'name' into wire format, compressing it as specified by the
+ * compression context 'cctx', and storing the result in 'target'.
+ *
+ * Notes:
+ * \li If the compression context allows global compression, then the
+ * global compression table may be updated.
+ *
+ * Requires:
+ * \li 'name' is a valid name
+ *
+ * \li dns_name_countlabels(name) > 0
+ *
+ * \li dns_name_isabsolute(name) == TRUE
+ *
+ * \li target is a valid buffer.
+ *
+ * \li Any offsets specified in a global compression table are valid
+ * for buffer.
+ *
+ * Ensures:
+ *
+ * If the result is success:
+ *
+ * \li The used space in target is updated.
+ *
+ * Returns:
+ * \li Success
+ * \li Resource Limit: Not enough space in buffer
+ */
+
+isc_result_t
+dns_name_fromtext(dns_name_t *name, isc_buffer_t *source,
+ const dns_name_t *origin, unsigned int options,
+ isc_buffer_t *target);
+/*%<
+ * Convert the textual representation of a DNS name at source
+ * into uncompressed wire form stored in target.
+ *
+ * Notes:
+ * \li Relative domain names will have 'origin' appended to them
+ * unless 'origin' is NULL, in which case relative domain names
+ * will remain relative.
+ *
+ * \li If DNS_NAME_DOWNCASE is set in 'options', any uppercase letters
+ * in 'source' will be downcased when they are copied into 'target'.
+ *
+ * Requires:
+ *
+ * \li 'name' is a valid name.
+ *
+ * \li 'source' is a valid buffer.
+ *
+ * \li 'target' is a valid buffer or 'target' is NULL and 'name' has
+ * a dedicated buffer.
+ *
+ * Ensures:
+ *
+ * If result is success:
+ * \li If 'target' is not NULL, 'name' is attached to it.
+ *
+ * \li Uppercase letters are downcased in the copy iff
+ * DNS_NAME_DOWNCASE is set in 'options'.
+ *
+ * \li The current location in source is advanced, and the used space
+ * in target is updated.
+ *
+ * Result:
+ *\li #ISC_R_SUCCESS
+ *\li #DNS_R_EMPTYLABEL
+ *\li #DNS_R_LABELTOOLONG
+ *\li #DNS_R_BADESCAPE
+ *\li #DNS_R_BADDOTTEDQUAD
+ *\li #ISC_R_NOSPACE
+ *\li #ISC_R_UNEXPECTEDEND
+ */
+
+#define DNS_NAME_OMITFINALDOT 0x01U
+#define DNS_NAME_MASTERFILE 0x02U /* escape $ and @ */
+
+isc_result_t
+dns_name_toprincipal(const dns_name_t *name, isc_buffer_t *target);
+
+isc_result_t
+dns_name_totext(const dns_name_t *name, bool omit_final_dot,
+ isc_buffer_t *target);
+
+isc_result_t
+dns_name_totext2(const dns_name_t *name, unsigned int options,
+ isc_buffer_t *target);
+/*%<
+ * Convert 'name' into text format, storing the result in 'target'.
+ *
+ * Notes:
+ *\li If 'omit_final_dot' is true, then the final '.' in absolute
+ * names other than the root name will be omitted.
+ *
+ *\li If DNS_NAME_OMITFINALDOT is set in options, then the final '.'
+ * in absolute names other than the root name will be omitted.
+ *
+ *\li If DNS_NAME_MASTERFILE is set in options, '$' and '@' will also
+ * be escaped.
+ *
+ *\li If dns_name_countlabels == 0, the name will be "@", representing the
+ * current origin as described by RFC1035.
+ *
+ *\li The name is not NUL terminated.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name
+ *
+ *\li 'target' is a valid buffer.
+ *
+ *\li if dns_name_isabsolute == FALSE, then omit_final_dot == FALSE
+ *
+ * Ensures:
+ *
+ *\li If the result is success:
+ * the used space in target is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ */
+
+#define DNS_NAME_MAXTEXT 1023
+/*%<
+ * The maximum length of the text representation of a domain
+ * name as generated by dns_name_totext(). This does not
+ * include space for a terminating NULL.
+ *
+ * This definition is conservative - the actual maximum
+ * is 1004, derived as follows:
+ *
+ * A backslash-decimal escaped character takes 4 bytes.
+ * A wire-encoded name can be up to 255 bytes and each
+ * label is one length byte + at most 63 bytes of data.
+ * Maximizing the label lengths gives us a name of
+ * three 63-octet labels, one 61-octet label, and the
+ * root label:
+ *
+ * 1 + 63 + 1 + 63 + 1 + 63 + 1 + 61 + 1 = 255
+ *
+ * When printed, this is (3 * 63 + 61) * 4
+ * bytes for the escaped label data + 4 bytes for the
+ * dot terminating each label = 1004 bytes total.
+ */
+
+isc_result_t
+dns_name_tofilenametext(const dns_name_t *name, bool omit_final_dot,
+ isc_buffer_t *target);
+/*%<
+ * Convert 'name' into an alternate text format appropriate for filenames,
+ * storing the result in 'target'. The name data is downcased, guaranteeing
+ * that the filename does not depend on the case of the converted name.
+ *
+ * Notes:
+ *\li If 'omit_final_dot' is true, then the final '.' in absolute
+ * names other than the root name will be omitted.
+ *
+ *\li The name is not NUL terminated.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid absolute name
+ *
+ *\li 'target' is a valid buffer.
+ *
+ * Ensures:
+ *
+ *\li If the result is success:
+ * the used space in target is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ */
+
+isc_result_t
+dns_name_downcase(const dns_name_t *source, dns_name_t *name,
+ isc_buffer_t *target);
+/*%<
+ * Downcase 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' and 'name' are valid names.
+ *
+ *\li If source == name, then
+ * 'source' must not be read-only
+ *
+ *\li Otherwise,
+ * 'target' is a valid buffer or 'target' is NULL and
+ * 'name' has a dedicated buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *
+ * Note: if source == name, then the result will always be ISC_R_SUCCESS.
+ */
+
+isc_result_t
+dns_name_concatenate(const dns_name_t *prefix, const dns_name_t *suffix,
+ dns_name_t *name, isc_buffer_t *target);
+/*%<
+ * Concatenate 'prefix' and 'suffix'.
+ *
+ * Requires:
+ *
+ *\li 'prefix' is a valid name or NULL.
+ *
+ *\li 'suffix' is a valid name or NULL.
+ *
+ *\li 'name' is a valid name or NULL.
+ *
+ *\li 'target' is a valid buffer or 'target' is NULL and 'name' has
+ * a dedicated buffer.
+ *
+ *\li If 'prefix' is absolute, 'suffix' must be NULL or the empty name.
+ *
+ * Ensures:
+ *
+ *\li On success,
+ * If 'target' is not NULL and 'name' is not NULL, then 'name'
+ * is attached to it.
+ * The used space in target is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ *\li #DNS_R_NAMETOOLONG
+ */
+
+void
+dns_name_split(const dns_name_t *name, unsigned int suffixlabels,
+ dns_name_t *prefix, dns_name_t *suffix);
+/*%<
+ *
+ * Split 'name' into two pieces on a label boundary.
+ *
+ * Notes:
+ * \li 'name' is split such that 'suffix' holds the most significant
+ * 'suffixlabels' labels. All other labels are stored in 'prefix'.
+ *
+ *\li Copying name data is avoided as much as possible, so 'prefix'
+ * and 'suffix' will end up pointing at the data for 'name'.
+ *
+ *\li It is legitimate to pass a 'prefix' or 'suffix' that has
+ * its name data stored someplace other than the dedicated buffer.
+ * This is useful to avoid name copying in the calling function.
+ *
+ *\li It is also legitimate to pass a 'prefix' or 'suffix' that is
+ * the same dns_name_t as 'name'.
+ *
+ * Requires:
+ *\li 'name' is a valid name.
+ *
+ *\li 'suffixlabels' cannot exceed the number of labels in 'name'.
+ *
+ * \li 'prefix' is a valid name or NULL, and cannot be read-only.
+ *
+ *\li 'suffix' is a valid name or NULL, and cannot be read-only.
+ *
+ * Ensures:
+ *
+ *\li On success:
+ * If 'prefix' is not NULL it will contain the least significant
+ * labels.
+ * If 'suffix' is not NULL it will contain the most significant
+ * labels. dns_name_countlabels(suffix) will be equal to
+ * suffixlabels.
+ *
+ *\li On failure:
+ * Either 'prefix' or 'suffix' is invalidated (depending
+ * on which one the problem was encountered with).
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS No worries. (This function should always success).
+ */
+
+void
+dns_name_dup(const dns_name_t *source, isc_mem_t *mctx, dns_name_t *target);
+/*%<
+ * Make 'target' a dynamically allocated copy of 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid non-empty name.
+ *
+ *\li 'target' is a valid name that is not read-only.
+ *
+ *\li 'mctx' is a valid memory context.
+ */
+
+isc_result_t
+dns_name_dupwithoffsets(const dns_name_t *source, isc_mem_t *mctx,
+ dns_name_t *target);
+/*%<
+ * Make 'target' a read-only dynamically allocated copy of 'source'.
+ * 'target' will also have a dynamically allocated offsets table.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid non-empty name.
+ *
+ *\li 'target' is a valid name that is not read-only.
+ *
+ *\li 'target' has no offsets table.
+ *
+ *\li 'mctx' is a valid memory context.
+ */
+
+void
+dns_name_free(dns_name_t *name, isc_mem_t *mctx);
+/*%<
+ * Free 'name'.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name created previously in 'mctx' by dns_name_dup().
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ * Ensures:
+ *
+ *\li All dynamic resources used by 'name' are freed and the name is
+ * invalidated.
+ */
+
+isc_result_t
+dns_name_digest(const dns_name_t *name, dns_digestfunc_t digest, void *arg);
+/*%<
+ * Send 'name' in DNSSEC canonical form to 'digest'.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'digest' is a valid dns_digestfunc_t.
+ *
+ * Ensures:
+ *
+ *\li If successful, the DNSSEC canonical form of 'name' will have been
+ * sent to 'digest'.
+ *
+ *\li If digest() returns something other than ISC_R_SUCCESS, that result
+ * will be returned as the result of dns_name_digest().
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Many other results are possible if not successful.
+ *
+ */
+
+bool
+dns_name_dynamic(const dns_name_t *name);
+/*%<
+ * Returns whether there is dynamic memory associated with this name.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name.
+ *
+ * Returns:
+ *
+ *\li 'true' if the name is dynamic otherwise 'false'.
+ */
+
+isc_result_t
+dns_name_print(const dns_name_t *name, FILE *stream);
+/*%<
+ * Print 'name' on 'stream'.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'stream' is a valid stream.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Any error that dns_name_totext() can return.
+ */
+
+void
+dns_name_format(const dns_name_t *name, char *cp, unsigned int size);
+/*%<
+ * Format 'name' as text appropriate for use in log messages.
+ *
+ * Store the formatted name at 'cp', writing no more than
+ * 'size' bytes. The resulting string is guaranteed to be
+ * null terminated.
+ *
+ * The formatted name will have a terminating dot only if it is
+ * the root.
+ *
+ * This function cannot fail, instead any errors are indicated
+ * in the returned text.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'cp' points a valid character array of size 'size'.
+ *
+ *\li 'size' > 0.
+ *
+ */
+
+isc_result_t
+dns_name_tostring(const dns_name_t *source, char **target, isc_mem_t *mctx);
+/*%<
+ * Convert 'name' to string format, allocating sufficient memory to
+ * hold it (free with isc_mem_free()).
+ *
+ * Differs from dns_name_format in that it allocates its own memory.
+ *
+ * Requires:
+ *
+ *\li 'name' is a valid name.
+ *\li 'target' is not NULL.
+ *\li '*target' is NULL.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ *
+ *\li Any error that dns_name_totext() can return.
+ */
+
+isc_result_t
+dns_name_fromstring(dns_name_t *target, const char *src, unsigned int options,
+ isc_mem_t *mctx);
+isc_result_t
+dns_name_fromstring2(dns_name_t *target, const char *src,
+ const dns_name_t *origin, unsigned int options,
+ isc_mem_t *mctx);
+/*%<
+ * Convert a string to a name and place it in target, allocating memory
+ * as necessary. 'options' has the same semantics as that of
+ * dns_name_fromtext().
+ *
+ * If 'target' has a buffer then the name will be copied into it rather than
+ * memory being allocated.
+ *
+ * Requires:
+ *
+ * \li 'target' is a valid name that is not read-only.
+ * \li 'src' is not NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Any error that dns_name_fromtext() can return.
+ *
+ *\li Any error that dns_name_dup() can return.
+ */
+
+isc_result_t
+dns_name_settotextfilter(dns_name_totextfilter_t *proc);
+/*%<
+ * Set / clear a thread specific function 'proc' to be called at the
+ * end of dns_name_totext().
+ *
+ * Note: Under Windows you need to call "dns_name_settotextfilter(NULL);"
+ * prior to exiting the thread otherwise memory will be leaked.
+ * For other platforms, which are pthreads based, this is still a good
+ * idea but not required.
+ *
+ * Returns
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_UNEXPECTED
+ */
+
+#define DNS_NAME_FORMATSIZE (DNS_NAME_MAXTEXT + 1)
+/*%<
+ * Suggested size of buffer passed to dns_name_format().
+ * Includes space for the terminating NULL.
+ */
+
+isc_result_t
+dns_name_copy(const dns_name_t *source, dns_name_t *dest, isc_buffer_t *target);
+/*%<
+ * Copies the name in 'source' into 'dest'. The name data is copied to
+ * the 'target' buffer, which is then set as the buffer for 'dest'.
+ *
+ * Requires:
+ * \li 'source' is a valid name.
+ *
+ * \li 'dest' is an initialized name.
+ *
+ * \li 'target' is an initialized buffer.
+ *
+ * Ensures:
+ *
+ *\li On success, the used space in target is updated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE
+ */
+
+void
+dns_name_copynf(const dns_name_t *source, dns_name_t *dest);
+/*%<
+ * Copies the name in 'source' into 'dest'. The name data is copied to
+ * the dedicated buffer for 'dest'.
+ *
+ * Requires:
+ * \li 'source' is a valid name.
+ *
+ * \li 'dest' is an initialized name with a dedicated buffer.
+ */
+
+bool
+dns_name_ishostname(const dns_name_t *name, bool wildcard);
+/*%<
+ * Return if 'name' is a valid hostname. RFC 952 / RFC 1123.
+ * If 'wildcard' is true then allow the first label of name to
+ * be a wildcard.
+ * The root is also accepted.
+ *
+ * Requires:
+ * 'name' to be valid.
+ */
+
+bool
+dns_name_ismailbox(const dns_name_t *name);
+/*%<
+ * Return if 'name' is a valid mailbox. RFC 821.
+ *
+ * Requires:
+ * \li 'name' to be valid.
+ */
+
+bool
+dns_name_internalwildcard(const dns_name_t *name);
+/*%<
+ * Return if 'name' contains a internal wildcard name.
+ *
+ * Requires:
+ * \li 'name' to be valid.
+ */
+
+bool
+dns_name_isdnssd(const dns_name_t *owner);
+/*%<
+ * Determine if the 'owner' is a DNS-SD prefix.
+ */
+
+bool
+dns_name_isrfc1918(const dns_name_t *owner);
+/*%<
+ * Determine if the 'name' is in the RFC 1918 reverse namespace.
+ */
+
+bool
+dns_name_isula(const dns_name_t *owner);
+/*%<
+ * Determine if the 'name' is in the ULA reverse namespace.
+ */
+
+bool
+dns_name_istat(const dns_name_t *name);
+/*
+ * Determine if 'name' is a potential 'trust-anchor-telemetry' name.
+ */
+
+ISC_LANG_ENDDECLS
+
+/*
+ *** High Performance Macros
+ ***/
+
+/*
+ * WARNING: Use of these macros by applications may require recompilation
+ * of the application in some situations where calling the function
+ * would not.
+ *
+ * WARNING: No assertion checking is done for these macros.
+ */
+
+#define DNS_NAME_INIT(n, o) \
+ do { \
+ dns_name_t *_n = (n); \
+ /* memset(_n, 0, sizeof(*_n)); */ \
+ _n->magic = DNS_NAME_MAGIC; \
+ _n->ndata = NULL; \
+ _n->length = 0; \
+ _n->labels = 0; \
+ _n->attributes = 0; \
+ _n->offsets = (o); \
+ _n->buffer = NULL; \
+ ISC_LINK_INIT(_n, link); \
+ ISC_LIST_INIT(_n->list); \
+ } while (0)
+
+#define DNS_NAME_RESET(n) \
+ do { \
+ (n)->ndata = NULL; \
+ (n)->length = 0; \
+ (n)->labels = 0; \
+ (n)->attributes &= ~DNS_NAMEATTR_ABSOLUTE; \
+ if ((n)->buffer != NULL) \
+ isc_buffer_clear((n)->buffer); \
+ } while (0)
+
+#define DNS_NAME_SETBUFFER(n, b) (n)->buffer = (b)
+
+#define DNS_NAME_ISABSOLUTE(n) \
+ (((n)->attributes & DNS_NAMEATTR_ABSOLUTE) != 0 ? true : false)
+
+#define DNS_NAME_COUNTLABELS(n) ((n)->labels)
+
+#define DNS_NAME_TOREGION(n, r) \
+ do { \
+ (r)->base = (n)->ndata; \
+ (r)->length = (n)->length; \
+ } while (0)
+
+#define DNS_NAME_SPLIT(n, l, p, s) \
+ do { \
+ dns_name_t *_n = (n); \
+ dns_name_t *_p = (p); \
+ dns_name_t *_s = (s); \
+ unsigned int _l = (l); \
+ if (_p != NULL) \
+ dns_name_getlabelsequence(_n, 0, _n->labels - _l, _p); \
+ if (_s != NULL) \
+ dns_name_getlabelsequence(_n, _n->labels - _l, _l, \
+ _s); \
+ } while (0)
+
+#ifdef DNS_NAME_USEINLINE
+
+#define dns_name_init(n, o) DNS_NAME_INIT(n, o)
+#define dns_name_reset(n) DNS_NAME_RESET(n)
+#define dns_name_setbuffer(n, b) DNS_NAME_SETBUFFER(n, b)
+#define dns_name_countlabels(n) DNS_NAME_COUNTLABELS(n)
+#define dns_name_isabsolute(n) DNS_NAME_ISABSOLUTE(n)
+#define dns_name_toregion(n, r) DNS_NAME_TOREGION(n, r)
+#define dns_name_split(n, l, p, s) DNS_NAME_SPLIT(n, l, p, s)
+
+#endif /* DNS_NAME_USEINLINE */
+
+#endif /* DNS_NAME_H */
diff --git a/lib/dns/include/dns/ncache.h b/lib/dns/include/dns/ncache.h
new file mode 100644
index 0000000..783808c
--- /dev/null
+++ b/lib/dns/include/dns/ncache.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_NCACHE_H
+#define DNS_NCACHE_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/ncache.h
+ *\brief
+ * DNS Ncache
+ *
+ * XXX TBS XXX
+ *
+ * MP:
+ *\li The caller must ensure any required synchronization.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li RFC2308
+ */
+
+#include <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
+
+#endif /* DNS_NCACHE_H */
diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h
new file mode 100644
index 0000000..efe2f94
--- /dev/null
+++ b/lib/dns/include/dns/nsec.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_NSEC_H
+#define DNS_NSEC_H 1
+
+/*! \file dns/nsec.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.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, bool *answer);
+/*
+ * Report whether the DNSKEY RRset has a NSEC only algorithm. Unknown
+ * algorithms are assumed to support NSEC3. If DNSKEY is not found,
+ * *answer is set to false, and ISC_R_NOTFOUND is returned.
+ *
+ * Requires:
+ * 'answer' to be non NULL.
+ */
+
+unsigned int
+dns_nsec_compressbitmap(unsigned char *map, const unsigned char *raw,
+ unsigned int max_type);
+/*%<
+ * Convert a raw bitmap into a compressed windowed bit map. 'map' and 'raw'
+ * may overlap.
+ *
+ * Returns the length of the compressed windowed bit map.
+ */
+
+void
+dns_nsec_setbit(unsigned char *array, unsigned int type, unsigned int bit);
+/*%<
+ * Set type bit in raw 'array' to 'bit'.
+ */
+
+bool
+dns_nsec_isset(const unsigned char *array, unsigned int type);
+/*%<
+ * Test if the corresponding 'type' bit is set in 'array'.
+ */
+
+isc_result_t
+dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
+ const dns_name_t *nsecname, dns_rdataset_t *nsecset,
+ bool *exists, bool *data, dns_name_t *wild,
+ dns_nseclog_t log, void *arg);
+/*%
+ * Return ISC_R_SUCCESS if we can determine that the name doesn't exist
+ * or we can determine whether there is data or not at the name.
+ * If the name does not exist return the wildcard name.
+ *
+ * Return DNS_R_DNAME when the NSEC indicates that name is covered by
+ * a DNAME. 'wild' is not set in this case.
+ *
+ * Return ISC_R_IGNORE when the NSEC is not the appropriate one.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_NSEC_H */
diff --git a/lib/dns/include/dns/nsec3.h b/lib/dns/include/dns/nsec3.h
new file mode 100644
index 0000000..3f20816
--- /dev/null
+++ b/lib/dns/include/dns/nsec3.h
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_NSEC3_H
+#define DNS_NSEC3_H 1
+
+#include <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
+
+#endif /* DNS_NSEC3_H */
diff --git a/lib/dns/include/dns/nta.h b/lib/dns/include/dns/nta.h
new file mode 100644
index 0000000..905bf64
--- /dev/null
+++ b/lib/dns/include/dns/nta.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_NTA_H
+#define DNS_NTA_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * The NTA module provides services for storing and retrieving negative
+ * trust anchors, and determine whether a given domain is subject to
+ * DNSSEC validation.
+ */
+
+#include <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_dump(dns_ntatable_t *ntatable, FILE *fp);
+/*%<
+ * Dump the NTA table to the file opened as 'fp'.
+ */
+
+isc_result_t
+dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp);
+/*%<
+ * Save the NTA table to the file opened as 'fp', for later loading.
+ */
+
+void
+dns_ntatable_shutdown(dns_ntatable_t *ntatable);
+/*%<
+ * Cancel future checks to see if NTAs can be removed.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_NTA_H */
diff --git a/lib/dns/include/dns/opcode.h b/lib/dns/include/dns/opcode.h
new file mode 100644
index 0000000..8fc5020
--- /dev/null
+++ b/lib/dns/include/dns/opcode.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_OPCODE_H
+#define DNS_OPCODE_H 1
+
+/*! \file dns/opcode.h */
+
+#include <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
+
+#endif /* DNS_OPCODE_H */
diff --git a/lib/dns/include/dns/order.h b/lib/dns/include/dns/order.h
new file mode 100644
index 0000000..0d6c267
--- /dev/null
+++ b/lib/dns/include/dns/order.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_ORDER_H
+#define DNS_ORDER_H 1
+
+/*! \file dns/order.h */
+
+#include <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
+
+#endif /* DNS_ORDER_H */
diff --git a/lib/dns/include/dns/peer.h b/lib/dns/include/dns/peer.h
new file mode 100644
index 0000000..5f9a4de
--- /dev/null
+++ b/lib/dns/include/dns/peer.h
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_PEER_H
+#define DNS_PEER_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/peer.h
+ * \brief
+ * Data structures for peers (e.g. a 'server' config file statement)
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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)
+
+/***
+ *** Types
+ ***/
+
+struct dns_peerlist {
+ unsigned int magic;
+ isc_refcount_t refs;
+
+ isc_mem_t *mem;
+
+ ISC_LIST(dns_peer_t) elements;
+};
+
+struct dns_peer {
+ unsigned int magic;
+ isc_refcount_t refs;
+
+ isc_mem_t *mem;
+
+ isc_netaddr_t address;
+ unsigned int prefixlen;
+ bool bogus;
+ dns_transfer_format_t transfer_format;
+ uint32_t transfers;
+ bool support_ixfr;
+ bool provide_ixfr;
+ bool request_ixfr;
+ bool support_edns;
+ bool request_nsid;
+ bool send_cookie;
+ bool request_expire;
+ bool force_tcp;
+ bool tcp_keepalive;
+ dns_name_t *key;
+ isc_sockaddr_t *transfer_source;
+ isc_dscp_t transfer_dscp;
+ isc_sockaddr_t *notify_source;
+ isc_dscp_t notify_dscp;
+ isc_sockaddr_t *query_source;
+ isc_dscp_t query_dscp;
+ uint16_t udpsize; /* receive size */
+ uint16_t maxudp; /* transmit size */
+ uint16_t padding; /* pad block size */
+ uint8_t ednsversion; /* edns version */
+
+ uint32_t bitflags;
+
+ ISC_LINK(dns_peer_t) next;
+};
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_peerlist_new(isc_mem_t *mem, dns_peerlist_t **list);
+
+void
+dns_peerlist_attach(dns_peerlist_t *source, dns_peerlist_t **target);
+
+void
+dns_peerlist_detach(dns_peerlist_t **list);
+
+/*
+ * After return caller still holds a reference to peer.
+ */
+void
+dns_peerlist_addpeer(dns_peerlist_t *peers, dns_peer_t *peer);
+
+/*
+ * Ditto. */
+isc_result_t
+dns_peerlist_peerbyaddr(dns_peerlist_t *peers, const isc_netaddr_t *addr,
+ dns_peer_t **retval);
+
+/*
+ * What he said.
+ */
+isc_result_t
+dns_peerlist_currpeer(dns_peerlist_t *peers, dns_peer_t **retval);
+
+isc_result_t
+dns_peer_new(isc_mem_t *mem, const isc_netaddr_t *ipaddr, dns_peer_t **peer);
+
+isc_result_t
+dns_peer_newprefix(isc_mem_t *mem, const isc_netaddr_t *ipaddr,
+ unsigned int prefixlen, dns_peer_t **peer);
+
+void
+dns_peer_attach(dns_peer_t *source, dns_peer_t **target);
+
+void
+dns_peer_detach(dns_peer_t **list);
+
+isc_result_t
+dns_peer_setbogus(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getbogus(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setrequestixfr(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getrequestixfr(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setprovideixfr(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getprovideixfr(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setrequestnsid(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getrequestnsid(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setsendcookie(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getsendcookie(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setrequestexpire(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getrequestexpire(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setsupportedns(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getforcetcp(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_setforcetcp(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_gettcpkeepalive(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_settcpkeepalive(dns_peer_t *peer, bool newval);
+
+isc_result_t
+dns_peer_getsupportedns(dns_peer_t *peer, bool *retval);
+
+isc_result_t
+dns_peer_settransfers(dns_peer_t *peer, uint32_t newval);
+
+isc_result_t
+dns_peer_gettransfers(dns_peer_t *peer, uint32_t *retval);
+
+isc_result_t
+dns_peer_settransferformat(dns_peer_t *peer, dns_transfer_format_t newval);
+
+isc_result_t
+dns_peer_gettransferformat(dns_peer_t *peer, dns_transfer_format_t *retval);
+
+isc_result_t
+dns_peer_setkeybycharp(dns_peer_t *peer, const char *keyval);
+
+isc_result_t
+dns_peer_getkey(dns_peer_t *peer, dns_name_t **retval);
+
+isc_result_t
+dns_peer_setkey(dns_peer_t *peer, dns_name_t **keyval);
+
+isc_result_t
+dns_peer_settransfersource(dns_peer_t *peer,
+ const isc_sockaddr_t *transfer_source);
+
+isc_result_t
+dns_peer_gettransfersource(dns_peer_t *peer, isc_sockaddr_t *transfer_source);
+
+isc_result_t
+dns_peer_setudpsize(dns_peer_t *peer, uint16_t udpsize);
+
+isc_result_t
+dns_peer_getudpsize(dns_peer_t *peer, uint16_t *udpsize);
+
+isc_result_t
+dns_peer_setmaxudp(dns_peer_t *peer, uint16_t maxudp);
+
+isc_result_t
+dns_peer_getmaxudp(dns_peer_t *peer, uint16_t *maxudp);
+
+isc_result_t
+dns_peer_setpadding(dns_peer_t *peer, uint16_t padding);
+
+isc_result_t
+dns_peer_getpadding(dns_peer_t *peer, uint16_t *padding);
+
+isc_result_t
+dns_peer_setnotifysource(dns_peer_t *peer, const isc_sockaddr_t *notify_source);
+
+isc_result_t
+dns_peer_getnotifysource(dns_peer_t *peer, isc_sockaddr_t *notify_source);
+
+isc_result_t
+dns_peer_setquerysource(dns_peer_t *peer, const isc_sockaddr_t *query_source);
+
+isc_result_t
+dns_peer_getquerysource(dns_peer_t *peer, isc_sockaddr_t *query_source);
+
+isc_result_t
+dns_peer_setnotifydscp(dns_peer_t *peer, isc_dscp_t dscp);
+
+isc_result_t
+dns_peer_getnotifydscp(dns_peer_t *peer, isc_dscp_t *dscpp);
+
+isc_result_t
+dns_peer_settransferdscp(dns_peer_t *peer, isc_dscp_t dscp);
+
+isc_result_t
+dns_peer_gettransferdscp(dns_peer_t *peer, isc_dscp_t *dscpp);
+
+isc_result_t
+dns_peer_setquerydscp(dns_peer_t *peer, isc_dscp_t dscp);
+
+isc_result_t
+dns_peer_getquerydscp(dns_peer_t *peer, isc_dscp_t *dscpp);
+
+isc_result_t
+dns_peer_setednsversion(dns_peer_t *peer, uint8_t ednsversion);
+
+isc_result_t
+dns_peer_getednsversion(dns_peer_t *peer, uint8_t *ednsversion);
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_PEER_H */
diff --git a/lib/dns/include/dns/portlist.h b/lib/dns/include/dns/portlist.h
new file mode 100644
index 0000000..03306db
--- /dev/null
+++ b/lib/dns/include/dns/portlist.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file dns/portlist.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/net.h>
+#include <isc/types.h>
+
+#include <dns/types.h>
+
+#ifndef DNS_PORTLIST_H
+#define DNS_PORTLIST_H 1
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_portlist_create(isc_mem_t *mctx, dns_portlist_t **portlistp);
+/*%<
+ * Create a port list.
+ *
+ * Requires:
+ *\li 'mctx' to be valid.
+ *\li 'portlistp' to be non NULL and '*portlistp' to be NULL;
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+dns_portlist_add(dns_portlist_t *portlist, int af, in_port_t port);
+/*%<
+ * Add the given <port,af> tuple to the portlist.
+ *
+ * Requires:
+ *\li 'portlist' to be valid.
+ *\li 'af' to be AF_INET or AF_INET6
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+dns_portlist_remove(dns_portlist_t *portlist, int af, in_port_t port);
+/*%<
+ * Remove the given <port,af> tuple to the portlist.
+ *
+ * Requires:
+ *\li 'portlist' to be valid.
+ *\li 'af' to be AF_INET or AF_INET6
+ */
+
+bool
+dns_portlist_match(dns_portlist_t *portlist, int af, in_port_t port);
+/*%<
+ * Find the given <port,af> tuple to the portlist.
+ *
+ * Requires:
+ *\li 'portlist' to be valid.
+ *\li 'af' to be AF_INET or AF_INET6
+ *
+ * Returns
+ * \li #true if the tuple is found, false otherwise.
+ */
+
+void
+dns_portlist_attach(dns_portlist_t *portlist, dns_portlist_t **portlistp);
+/*%<
+ * Attach to a port list.
+ *
+ * Requires:
+ *\li 'portlist' to be valid.
+ *\li 'portlistp' to be non NULL and '*portlistp' to be NULL;
+ */
+
+void
+dns_portlist_detach(dns_portlist_t **portlistp);
+/*%<
+ * Detach from a port list.
+ *
+ * Requires:
+ *\li '*portlistp' to be valid.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_PORTLIST_H */
diff --git a/lib/dns/include/dns/private.h b/lib/dns/include/dns/private.h
new file mode 100644
index 0000000..8e1c4c7
--- /dev/null
+++ b/lib/dns/include/dns/private.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#include <dns/db.h>
+#include <dns/types.h>
+
+#ifndef DNS_PRIVATE_H
+#define DNS_PRIVATE_H
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_private_chains(dns_db_t *db, dns_dbversion_t *ver,
+ dns_rdatatype_t privatetype, bool *build_nsec,
+ bool *build_nsec3);
+/*%<
+ * Examine the NSEC, NSEC3PARAM and privatetype RRsets at the apex of the
+ * database to determine which of NSEC or NSEC3 chains we are currently
+ * maintaining. In normal operations only one of NSEC or NSEC3 is being
+ * maintained but when we are transitiong between NSEC and NSEC3 we need
+ * to update both sets of chains. If 'privatetype' is zero then the
+ * privatetype RRset will not be examined.
+ *
+ * Requires:
+ * \li 'db' is valid.
+ * \li 'version' is valid or NULL.
+ * \li 'build_nsec' is a pointer to a bool or NULL.
+ * \li 'build_nsec3' is a pointer to a bool or NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS, 'build_nsec' and 'build_nsec3' will be valid.
+ * \li other on error
+ */
+
+isc_result_t
+dns_private_totext(dns_rdata_t *privaterdata, isc_buffer_t *buffer);
+/*%<
+ * Convert a private-type RR 'privaterdata' to human-readable form,
+ * and place the result in 'buffer'. The text should indicate
+ * which action the private-type record specifies and whether the
+ * action has been completed.
+ *
+ * Requires:
+ * \li 'privaterdata' is a valid rdata containing at least five bytes
+ * \li 'buffer' is a valid buffer
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li other on error
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* ifndef DNS_PRIVATE_H */
diff --git a/lib/dns/include/dns/rbt.h b/lib/dns/include/dns/rbt.h
new file mode 100644
index 0000000..4a6b078
--- /dev/null
+++ b/lib/dns/include/dns/rbt.h
@@ -0,0 +1,1060 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RBT_H
+#define DNS_RBT_H 1
+
+/*! \file dns/rbt.h */
+
+#include <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 */
+ /*@}*/
+
+ /* flags needed for serialization to file */
+ unsigned int is_mmapped : 1;
+ unsigned int parent_is_relative : 1;
+ unsigned int left_is_relative : 1;
+ unsigned int right_is_relative : 1;
+ unsigned int down_is_relative : 1;
+ unsigned int data_is_relative : 1;
+
+ /*
+ * full name length; set during serialization, and used
+ * during deserialization to calculate database size.
+ * should be cleared after use.
+ */
+ unsigned int fullnamelen : 8; /*%< range is 1..255 */
+
+ /* node needs to be cleaned from rpz */
+ unsigned int rpz : 1;
+ unsigned int : 0; /* end of bitfields c/o tree lock */
+
+ /*%
+ * These are needed for hashing. The 'uppernode' points to the
+ * node's superdomain node in the parent subtree, so that it can
+ * be reached from a child that was found by a hash lookup.
+ */
+ unsigned int hashval;
+ dns_rbtnode_t *uppernode;
+ dns_rbtnode_t *hashnext;
+
+ dns_rbtnode_t *parent;
+ dns_rbtnode_t *left;
+ dns_rbtnode_t *right;
+ dns_rbtnode_t *down;
+
+ /*%
+ * Used for LRU cache. This linked list is used to mark nodes which
+ * have no data any longer, but we cannot unlink at that exact moment
+ * because we did not or could not obtain a write lock on the tree.
+ */
+ ISC_LINK(dns_rbtnode_t) deadlink;
+
+ /*@{*/
+ /*!
+ * These values are used in the RBT DB implementation. The appropriate
+ * node lock must be held before accessing them.
+ *
+ * Note: The two "unsigned int :0;" unnamed bitfields on either
+ * side of the bitfields below are scaffolding that border the
+ * set of bitfields which are accessed after acquiring the node
+ * lock. Please don't insert any other bitfield members between
+ * the unnamed bitfields unless they should also be accessed
+ * after acquiring the node lock.
+ *
+ * NOTE: Do not merge these fields into bitfields above, as
+ * they'll all be put in the same qword that could be accessed
+ * without the node lock as it shares the qword with other
+ * members. Leave these members here so that they occupy a
+ * separate region of memory.
+ */
+ void *data;
+ uint8_t : 0; /* start of bitfields c/o node lock */
+ uint8_t dirty : 1;
+ uint8_t wild : 1;
+ uint8_t : 0; /* end of bitfields c/o node lock */
+ uint16_t locknum; /* note that this is not in the bitfield */
+ isc_refcount_t references;
+ /*@}*/
+};
+
+typedef isc_result_t (*dns_rbtfindcallback_t)(dns_rbtnode_t *node,
+ dns_name_t *name,
+ void *callback_arg);
+
+typedef isc_result_t (*dns_rbtdatawriter_t)(FILE *file, unsigned char *data,
+ void *arg, uint64_t *crc);
+
+typedef isc_result_t (*dns_rbtdatafixer_t)(dns_rbtnode_t *rbtnode, void *base,
+ size_t offset, void *arg,
+ uint64_t *crc);
+
+typedef void (*dns_rbtdeleter_t)(void *, void *);
+
+/*****
+***** Chain Info
+*****/
+
+/*!
+ * A chain is used to keep track of the sequence of nodes to reach any given
+ * node from the root of the tree. Originally nodes did not have parent
+ * pointers in them (for memory usage reasons) so there was no way to find
+ * the path back to the root from any given node. Now that nodes have parent
+ * pointers, chains might be going away in a future release, though the
+ * movement functionality would remain.
+ *
+ * Chains may be used to iterate over a tree of trees. After setting up the
+ * chain's structure using dns_rbtnodechain_init(), it needs to be initialized
+ * to point to the lexically first or lexically last node in the tree of trees
+ * using dns_rbtnodechain_first() or dns_rbtnodechain_last(), respectively.
+ * Calling dns_rbtnodechain_next() or dns_rbtnodechain_prev() then moves the
+ * chain over to the next or previous node, respectively.
+ *
+ * In any event, parent information, whether via parent pointers or chains, is
+ * necessary information for iterating through the tree or for basic internal
+ * tree maintenance issues (ie, the rotations that are done to rebalance the
+ * tree when a node is added). The obvious implication of this is that for a
+ * chain to remain valid, the tree has to be locked down against writes for the
+ * duration of the useful life of the chain, because additions or removals can
+ * change the path from the root to the node the chain has targeted.
+ *
+ * The dns_rbtnodechain_ functions _first, _last, _prev and _next all take
+ * dns_name_t parameters for the name and the origin, which can be NULL. If
+ * non-NULL, 'name' will end up pointing to the name data and offsets that are
+ * stored at the node (and thus it will be read-only), so it should be a
+ * regular dns_name_t that has been initialized with dns_name_init. When
+ * 'origin' is non-NULL, it will get the name of the origin stored in it, so it
+ * needs to have its own buffer space and offsets, which is most easily
+ * accomplished with a dns_fixedname_t. It is _not_ necessary to reinitialize
+ * either 'name' or 'origin' between calls to the chain functions.
+ *
+ * NOTE WELL: even though the name data at the root of the tree of trees will
+ * be absolute (typically just "."), it will will be made into a relative name
+ * with an origin of "." -- an empty name when the node is ".". This is
+ * because a common on operation on 'name' and 'origin' is to use
+ * dns_name_concatenate() on them to generate the complete name. An empty name
+ * can be detected when dns_name_countlabels == 0, and is printed by
+ * dns_name_totext()/dns_name_format() as "@", consistent with RFC1035's
+ * definition of "@" as the current origin.
+ *
+ * dns_rbtnodechain_current is similar to the _first, _last, _prev and _next
+ * functions but additionally can provide the node to which the chain points.
+ */
+
+/*%
+ * The number of level blocks to allocate at a time. Currently the maximum
+ * number of levels is allocated directly in the structure, but future
+ * revisions of this code might have a static initial block with dynamic
+ * growth. Allocating space for 256 levels when the tree is almost never that
+ * deep is wasteful, but it's not clear that it matters, since the waste is
+ * only 2MB for 1000 concurrently active chains on a system with 64-bit
+ * pointers.
+ */
+#define DNS_RBT_LEVELBLOCK 254
+
+typedef struct dns_rbtnodechain {
+ unsigned int magic;
+ /*%
+ * The terminal node of the chain. It is not in levels[].
+ * This is ostensibly private ... but in a pinch it could be
+ * used tell that the chain points nowhere without needing to
+ * call dns_rbtnodechain_current().
+ */
+ dns_rbtnode_t *end;
+ /*%
+ * The maximum number of labels in a name is 128; bitstrings mean
+ * a conceptually very large number (which I have not bothered to
+ * compute) of logical levels because splitting can potentially occur
+ * at each bit. However, DNSSEC restricts the number of "logical"
+ * labels in a name to 255, meaning only 254 pointers are needed
+ * in the worst case.
+ */
+ dns_rbtnode_t *levels[DNS_RBT_LEVELBLOCK];
+ /*%
+ * level_count indicates how deep the chain points into the
+ * tree of trees, and is the index into the levels[] array.
+ * Thus, levels[level_count - 1] is the last level node stored.
+ * A chain that points to the top level of the tree of trees has
+ * a level_count of 0, the first level has a level_count of 1, and
+ * so on.
+ */
+ unsigned int level_count;
+ /*%
+ * level_matches tells how many levels matched above the node
+ * returned by dns_rbt_findnode(). A match (partial or exact) found
+ * in the first level thus results in level_matches being set to 1.
+ * This is used by the rbtdb to set the start point for a recursive
+ * search of superdomains until the RR it is looking for is found.
+ */
+ unsigned int level_matches;
+} dns_rbtnodechain_t;
+
+/*****
+***** Public interfaces.
+*****/
+isc_result_t
+dns_rbt_create(isc_mem_t *mctx, dns_rbtdeleter_t deleter, void *deleter_arg,
+ dns_rbt_t **rbtp);
+/*%<
+ * Initialize a red-black tree of trees.
+ *
+ * Notes:
+ *\li The deleter argument, if non-null, points to a function that is
+ * responsible for cleaning up any memory associated with the data
+ * pointer of a node when the node is deleted. It is passed the
+ * deleted node's data pointer as its first argument and deleter_arg
+ * as its second argument.
+ *
+ * Requires:
+ * \li mctx is a pointer to a valid memory context.
+ *\li rbtp != NULL && *rbtp == NULL
+ *\li arg == NULL iff deleter == NULL
+ *
+ * Ensures:
+ *\li If result is ISC_R_SUCCESS:
+ * *rbtp points to a valid red-black tree manager
+ *
+ *\li If result is failure:
+ * *rbtp does not point to a valid red-black tree manager.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_NOMEMORY Resource limit: Out of Memory
+ */
+
+isc_result_t
+dns_rbt_addname(dns_rbt_t *rbt, const dns_name_t *name, void *data);
+/*%<
+ * Add 'name' to the tree of trees, associated with 'data'.
+ *
+ * Notes:
+ *\li 'data' is never required to be non-NULL, but specifying it
+ * when the name is added is faster than searching for 'name'
+ * again and then setting the data pointer. The lack of a data pointer
+ * for a node also has other ramifications regarding whether
+ * dns_rbt_findname considers a node to exist, or dns_rbt_deletename
+ * joins nodes.
+ *
+ * Requires:
+ *\li rbt is a valid rbt manager.
+ *\li dns_name_isabsolute(name) == TRUE
+ *
+ * Ensures:
+ *\li 'name' is not altered in any way.
+ *
+ *\li Any external references to nodes in the tree are unaffected by
+ * node splits that are necessary to insert the new name.
+ *
+ *\li If result is #ISC_R_SUCCESS:
+ * 'name' is findable in the red/black tree of trees in O(log N).
+ * The data pointer of the node for 'name' is set to 'data'.
+ *
+ *\li If result is #ISC_R_EXISTS or #ISC_R_NOSPACE:
+ * The tree of trees is unaltered.
+ *
+ *\li If result is #ISC_R_NOMEMORY:
+ * No guarantees.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_EXISTS The name already exists with associated data.
+ *\li #ISC_R_NOSPACE The name had more logical labels than are allowed.
+ *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory
+ */
+
+isc_result_t
+dns_rbt_addnode(dns_rbt_t *rbt, const dns_name_t *name, dns_rbtnode_t **nodep);
+
+/*%<
+ * Just like dns_rbt_addname, but returns the address of the node.
+ *
+ * Requires:
+ *\li rbt is a valid rbt structure.
+ *\li dns_name_isabsolute(name) == TRUE
+ *\li nodep != NULL && *nodep == NULL
+ *
+ * Ensures:
+ *\li 'name' is not altered in any way.
+ *
+ *\li Any external references to nodes in the tree are unaffected by
+ * node splits that are necessary to insert the new name.
+ *
+ *\li If result is ISC_R_SUCCESS:
+ * 'name' is findable in the red/black tree of trees in O(log N).
+ * *nodep is the node that was added for 'name'.
+ *
+ *\li If result is ISC_R_EXISTS:
+ * The tree of trees is unaltered.
+ * *nodep is the existing node for 'name'.
+ *
+ *\li If result is ISC_R_NOMEMORY:
+ * No guarantees.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_EXISTS The name already exists, possibly without data.
+ *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory
+ */
+
+isc_result_t
+dns_rbt_findname(dns_rbt_t *rbt, const dns_name_t *name, unsigned int options,
+ dns_name_t *foundname, void **data);
+/*%<
+ * Get the data pointer associated with 'name'.
+ *
+ * Notes:
+ *\li When #DNS_RBTFIND_NOEXACT is set, the closest matching superdomain is
+ * returned (also subject to #DNS_RBTFIND_EMPTYDATA), even when there is
+ * an exact match in the tree.
+ *
+ *\li A node that has no data is considered not to exist for this function,
+ * unless the #DNS_RBTFIND_EMPTYDATA option is set.
+ *
+ * Requires:
+ *\li rbt is a valid rbt manager.
+ *\li dns_name_isabsolute(name) == TRUE
+ *\li data != NULL && *data == NULL
+ *
+ * Ensures:
+ *\li 'name' and the tree are not altered in any way.
+ *
+ *\li If result is ISC_R_SUCCESS:
+ * *data is the data associated with 'name'.
+ *
+ *\li If result is DNS_R_PARTIALMATCH:
+ * *data is the data associated with the deepest superdomain
+ * of 'name' which has data.
+ *
+ *\li If result is ISC_R_NOTFOUND:
+ * Neither the name nor a superdomain was found with data.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #DNS_R_PARTIALMATCH Superdomain found with data
+ *\li #ISC_R_NOTFOUND No match
+ *\li #ISC_R_NOSPACE Concatenating nodes to form foundname failed
+ */
+
+isc_result_t
+dns_rbt_findnode(dns_rbt_t *rbt, const dns_name_t *name, dns_name_t *foundname,
+ dns_rbtnode_t **node, dns_rbtnodechain_t *chain,
+ unsigned int options, dns_rbtfindcallback_t callback,
+ void *callback_arg);
+/*%<
+ * Find the node for 'name'.
+ *
+ * Notes:
+ *\li A node that has no data is considered not to exist for this function,
+ * unless the DNS_RBTFIND_EMPTYDATA option is set. This applies to both
+ * exact matches and partial matches.
+ *
+ *\li If the chain parameter is non-NULL, then the path through the tree
+ * to the DNSSEC predecessor of the searched for name is maintained,
+ * unless the DNS_RBTFIND_NOPREDECESSOR or DNS_RBTFIND_NOEXACT option
+ * is used. (For more details on those options, see below.)
+ *
+ *\li If there is no predecessor, then the chain will point to nowhere, as
+ * indicated by chain->end being NULL or dns_rbtnodechain_current
+ * returning ISC_R_NOTFOUND. Note that in a normal Internet DNS RBT
+ * there will always be a predecessor for all names except the root
+ * name, because '.' will exist and '.' is the predecessor of
+ * everything. But you can certainly construct a trivial tree and a
+ * search for it that has no predecessor.
+ *
+ *\li Within the chain structure, the 'levels' member of the structure holds
+ * the root node of each level except the first.
+ *
+ *\li The 'level_count' of the chain indicates how deep the chain to the
+ * predecessor name is, as an index into the 'levels[]' array. It does
+ * not count name elements, per se, but only levels of the tree of trees,
+ * the distinction arising because multiple labels from a name can be
+ * stored on only one level. It is also does not include the level
+ * that has the node, since that level is not stored in levels[].
+ *
+ *\li The chain's 'level_matches' is not directly related to the predecessor.
+ * It is the number of levels above the level of the found 'node',
+ * regardless of whether it was a partial match or exact match. When
+ * the node is found in the top level tree, or no node is found at all,
+ * level_matches is 0.
+ *
+ *\li When DNS_RBTFIND_NOEXACT is set, the closest matching superdomain is
+ * returned (also subject to DNS_RBTFIND_EMPTYDATA), even when
+ * there is an exact match in the tree. In this case, the chain
+ * will not point to the DNSSEC predecessor, but will instead point
+ * to the exact match, if there was any. Thus the preceding paragraphs
+ * should have "exact match" substituted for "predecessor" to describe
+ * how the various elements of the chain are set. This was done to
+ * ensure that the chain's state was sane, and to prevent problems that
+ * occurred when running the predecessor location code under conditions
+ * it was not designed for. It is not clear *where* the chain should
+ * point when DNS_RBTFIND_NOEXACT is set, so if you end up using a chain
+ * with this option because you want a particular node, let us know
+ * where you want the chain pointed, so this can be made more firm.
+ *
+ * Requires:
+ *\li rbt is a valid rbt manager.
+ *\li dns_name_isabsolute(name) == TRUE.
+ *\li node != NULL && *node == NULL.
+ *\li #DNS_RBTFIND_NOEXACT and DNS_RBTFIND_NOPREDECESSOR are mutually
+ * exclusive.
+ *
+ * Ensures:
+ *\li 'name' and the tree are not altered in any way.
+ *
+ *\li If result is ISC_R_SUCCESS:
+ *\verbatim
+ * *node is the terminal node for 'name'.
+ *
+ * 'foundname' and 'name' represent the same name (though not
+ * the same memory).
+ *
+ * 'chain' points to the DNSSEC predecessor, if any, of 'name'.
+ *
+ * chain->level_matches and chain->level_count are equal.
+ *\endverbatim
+ *
+ * If result is DNS_R_PARTIALMATCH:
+ *\verbatim
+ * *node is the data associated with the deepest superdomain
+ * of 'name' which has data.
+ *
+ * 'foundname' is the name of deepest superdomain (which has
+ * data, unless the DNS_RBTFIND_EMPTYDATA option is set).
+ *
+ * 'chain' points to the DNSSEC predecessor, if any, of 'name'.
+ *\endverbatim
+ *
+ *\li If result is ISC_R_NOTFOUND:
+ *\verbatim
+ * Neither the name nor a superdomain was found. *node is NULL.
+ *
+ * 'chain' points to the DNSSEC predecessor, if any, of 'name'.
+ *
+ * chain->level_matches is 0.
+ *\endverbatim
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #DNS_R_PARTIALMATCH Superdomain found with data
+ *\li #ISC_R_NOTFOUND No match, or superdomain with no data
+ *\li #ISC_R_NOSPACE Concatenating nodes to form foundname failed
+ */
+
+isc_result_t
+dns_rbt_deletename(dns_rbt_t *rbt, const dns_name_t *name, bool recurse);
+/*%<
+ * Delete 'name' from the tree of trees.
+ *
+ * Notes:
+ *\li When 'name' is removed, if recurse is true then all of its
+ * subnames are removed too.
+ *
+ * Requires:
+ *\li rbt is a valid rbt manager.
+ *\li dns_name_isabsolute(name) == TRUE
+ *
+ * Ensures:
+ *\li 'name' is not altered in any way.
+ *
+ *\li Does NOT ensure that any external references to nodes in the tree
+ * are unaffected by node joins.
+ *
+ *\li If result is ISC_R_SUCCESS:
+ * 'name' does not appear in the tree with data; however,
+ * the node for the name might still exist which can be
+ * found with dns_rbt_findnode (but not dns_rbt_findname).
+ *
+ *\li If result is ISC_R_NOTFOUND:
+ * 'name' does not appear in the tree with data, because
+ * it did not appear in the tree before the function was called.
+ *
+ *\li If result is something else:
+ * See result codes for dns_rbt_findnode (if it fails, the
+ * node is not deleted) or dns_rbt_deletenode (if it fails,
+ * the node is deleted, but the tree is not optimized when
+ * it could have been).
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_NOTFOUND No match
+ *\li something_else Any return code from dns_rbt_findnode except
+ * DNS_R_PARTIALMATCH (which causes ISC_R_NOTFOUND
+ * to be returned instead), and any code from
+ * dns_rbt_deletenode.
+ */
+
+isc_result_t
+dns_rbt_deletenode(dns_rbt_t *rbt, dns_rbtnode_t *node, bool recurse);
+/*%<
+ * Delete 'node' from the tree of trees.
+ *
+ * Notes:
+ *\li When 'node' is removed, if recurse is true then all nodes
+ * in levels down from it are removed too.
+ *
+ * Requires:
+ *\li rbt is a valid rbt manager.
+ *\li node != NULL.
+ *
+ * Ensures:
+ *\li Does NOT ensure that any external references to nodes in the tree
+ * are unaffected by node joins.
+ *
+ *\li If result is ISC_R_SUCCESS:
+ * 'node' does not appear in the tree with data; however,
+ * the node might still exist if it serves as a pointer to
+ * a lower tree level as long as 'recurse' was false, hence
+ * the node could can be found with dns_rbt_findnode when
+ * that function's empty_data_ok parameter is true.
+ *
+ *\li If result is ISC_R_NOMEMORY or ISC_R_NOSPACE:
+ * The node was deleted, but the tree structure was not
+ * optimized.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory when joining nodes.
+ *\li #ISC_R_NOSPACE dns_name_concatenate failed when joining nodes.
+ */
+
+void
+dns_rbt_namefromnode(dns_rbtnode_t *node, dns_name_t *name);
+/*%<
+ * Convert the sequence of labels stored at 'node' into a 'name'.
+ *
+ * Notes:
+ *\li This function does not return the full name, from the root, but
+ * just the labels at the indicated node.
+ *
+ *\li The name data pointed to by 'name' is the information stored
+ * in the node, not a copy. Altering the data at this pointer
+ * will likely cause grief.
+ *
+ * Requires:
+ * \li name->offsets == NULL
+ *
+ * Ensures:
+ * \li 'name' is DNS_NAMEATTR_READONLY.
+ *
+ * \li 'name' will point directly to the labels stored after the
+ * dns_rbtnode_t struct.
+ *
+ * \li 'name' will have offsets that also point to the information stored
+ * as part of the node.
+ */
+
+isc_result_t
+dns_rbt_fullnamefromnode(dns_rbtnode_t *node, dns_name_t *name);
+/*%<
+ * Like dns_rbt_namefromnode, but returns the full name from the root.
+ *
+ * Notes:
+ * \li Unlike dns_rbt_namefromnode, the name will not point directly
+ * to node data. Rather, dns_name_concatenate will be used to copy
+ * the name data from each node into the 'name' argument.
+ *
+ * Requires:
+ * \li name != NULL
+ * \li name has a dedicated buffer.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOSPACE (possible via dns_name_concatenate)
+ * \li DNS_R_NAMETOOLONG (possible via dns_name_concatenate)
+ */
+
+char *
+dns_rbt_formatnodename(dns_rbtnode_t *node, char *printname, unsigned int size);
+/*%<
+ * Format the full name of a node for printing, using dns_name_format().
+ *
+ * Notes:
+ * \li 'size' is the length of the printname buffer. This should be
+ * DNS_NAME_FORMATSIZE or larger.
+ *
+ * Requires:
+ * \li node and printname are not NULL.
+ *
+ * Returns:
+ * \li The 'printname' pointer.
+ */
+
+unsigned int
+dns_rbt_nodecount(dns_rbt_t *rbt);
+/*%<
+ * Obtain the number of nodes in the tree of trees.
+ *
+ * Requires:
+ * \li rbt is a valid rbt manager.
+ */
+
+size_t
+dns_rbt_hashsize(dns_rbt_t *rbt);
+/*%<
+ * Obtain the current number of buckets in the 'rbt' hash table.
+ *
+ * Requires:
+ * \li rbt is a valid rbt manager.
+ */
+
+isc_result_t
+dns_rbt_adjusthashsize(dns_rbt_t *rbt, size_t size);
+/*%<
+ * Adjust the number of buckets in the 'rbt' hash table, according to the
+ * expected maximum size of the rbt database.
+ *
+ * Requires:
+ * \li rbt is a valid rbt manager.
+ * \li size is expected maximum memory footprint of rbt.
+ */
+
+void
+dns_rbt_destroy(dns_rbt_t **rbtp);
+isc_result_t
+dns_rbt_destroy2(dns_rbt_t **rbtp, unsigned int quantum);
+/*%<
+ * Stop working with a red-black tree of trees.
+ * If 'quantum' is zero then the entire tree will be destroyed.
+ * If 'quantum' is non zero then up to 'quantum' nodes will be destroyed
+ * allowing the rbt to be incrementally destroyed by repeated calls to
+ * dns_rbt_destroy2(). Once dns_rbt_destroy2() has been called no other
+ * operations than dns_rbt_destroy()/dns_rbt_destroy2() should be
+ * performed on the tree of trees.
+ *
+ * Requires:
+ * \li *rbt is a valid rbt manager.
+ *
+ * Ensures on ISC_R_SUCCESS:
+ * \li All space allocated by the RBT library has been returned.
+ *
+ * \li *rbt is invalidated as an rbt manager.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_QUOTA if 'quantum' nodes have been destroyed.
+ */
+
+off_t
+dns_rbt_serialize_align(off_t target);
+/*%<
+ * Align the provided integer to a pointer-size boundary.
+ * This should be used if, during serialization of data to a will-be
+ * mmap()ed file, a pointer alignment is needed for some data.
+ */
+
+isc_result_t
+dns_rbt_serialize_tree(FILE *file, dns_rbt_t *rbt,
+ dns_rbtdatawriter_t datawriter, void *writer_arg,
+ off_t *offset);
+/*%<
+ * Write out the RBT structure and its data to a file.
+ *
+ * Notes:
+ * \li The file must be an actual file which allows seek() calls, so it cannot
+ * be a stream. Returns ISC_R_INVALIDFILE if not.
+ */
+
+isc_result_t
+dns_rbt_deserialize_tree(void *base_address, size_t filesize,
+ off_t header_offset, isc_mem_t *mctx,
+ dns_rbtdeleter_t deleter, void *deleter_arg,
+ dns_rbtdatafixer_t datafixer, void *fixer_arg,
+ dns_rbtnode_t **originp, dns_rbt_t **rbtp);
+/*%<
+ * Read a RBT structure and its data from a file.
+ *
+ * If 'originp' is not NULL, then it is pointed to the root node of the RBT.
+ *
+ * Notes:
+ * \li The file must be an actual file which allows seek() calls, so it cannot
+ * be a stream. This condition is not checked in the code.
+ */
+
+void
+dns_rbt_printtext(dns_rbt_t *rbt, void (*data_printer)(FILE *, void *),
+ FILE *f);
+/*%<
+ * Print an ASCII representation of the internal structure of the red-black
+ * tree of trees to the passed stream.
+ *
+ * data_printer is a callback function that is called to print the data
+ * in a node. It should print it to the passed FILE stream.
+ *
+ * Notes:
+ * \li The name stored at each node, along with the node's color, is printed.
+ * Then the down pointer, left and right pointers are displayed
+ * recursively in turn. NULL down pointers are silently omitted;
+ * NULL left and right pointers are printed.
+ */
+
+void
+dns_rbt_printdot(dns_rbt_t *rbt, bool show_pointers, FILE *f);
+/*%<
+ * Print a GraphViz dot representation of the internal structure of the
+ * red-black tree of trees to the passed stream.
+ *
+ * If show_pointers is TRUE, pointers are also included in the generated
+ * graph.
+ *
+ * Notes:
+ * \li The name stored at each node, along with the node's color is displayed.
+ * Then the down pointer, left and right pointers are displayed
+ * recursively in turn. NULL left, right and down pointers are
+ * silently omitted.
+ */
+
+void
+dns_rbt_printnodeinfo(dns_rbtnode_t *n, FILE *f);
+/*%<
+ * Print out various information about a node
+ *
+ * Requires:
+ *\li 'n' is a valid pointer.
+ *
+ *\li 'f' points to a valid open FILE structure that allows writing.
+ */
+
+size_t
+dns__rbt_getheight(dns_rbt_t *rbt);
+/*%<
+ * Return the maximum height of sub-root nodes found in the red-black
+ * forest.
+ *
+ * The height of a node is defined as the number of nodes in the longest
+ * path from the node to a leaf. For each subtree in the forest, this
+ * function determines the height of its root node. Then it returns the
+ * maximum such height in the forest.
+ *
+ * Note: This function exists for testing purposes. Non-test code must
+ * not use it.
+ *
+ * Requires:
+ * \li rbt is a valid rbt manager.
+ */
+
+bool
+dns__rbt_checkproperties(dns_rbt_t *rbt);
+/*%<
+ * Check red-black properties of the forest.
+ *
+ * Note: This function exists for testing purposes. Non-test code must
+ * not use it.
+ *
+ * Requires:
+ * \li rbt is a valid rbt manager.
+ */
+
+size_t
+dns__rbtnode_getdistance(dns_rbtnode_t *node);
+/*%<
+ * Return the distance (in nodes) from the node to its upper node of its
+ * subtree. The root node has a distance of 1. A child of the root node
+ * has a distance of 2.
+ */
+
+/*****
+***** Chain Functions
+*****/
+
+void
+dns_rbtnodechain_init(dns_rbtnodechain_t *chain);
+/*%<
+ * Initialize 'chain'.
+ *
+ * Requires:
+ *\li 'chain' is a valid pointer.
+ *
+ * Ensures:
+ *\li 'chain' is suitable for use.
+ */
+
+void
+dns_rbtnodechain_reset(dns_rbtnodechain_t *chain);
+/*%<
+ * Free any dynamic storage associated with 'chain', and then reinitialize
+ * 'chain'.
+ *
+ * Requires:
+ *\li 'chain' is a valid pointer.
+ *
+ * Ensures:
+ *\li 'chain' is suitable for use, and uses no dynamic storage.
+ */
+
+void
+dns_rbtnodechain_invalidate(dns_rbtnodechain_t *chain);
+/*%<
+ * Free any dynamic storage associated with 'chain', and then invalidates it.
+ *
+ * Notes:
+ *\li Future calls to any dns_rbtnodechain_ function will need to call
+ * dns_rbtnodechain_init on the chain first (except, of course,
+ * dns_rbtnodechain_init itself).
+ *
+ * Requires:
+ *\li 'chain' is a valid chain.
+ *
+ * Ensures:
+ *\li 'chain' is no longer suitable for use, and uses no dynamic storage.
+ */
+
+isc_result_t
+dns_rbtnodechain_current(dns_rbtnodechain_t *chain, dns_name_t *name,
+ dns_name_t *origin, dns_rbtnode_t **node);
+/*%<
+ * Provide the name, origin and node to which the chain is currently pointed.
+ *
+ * Notes:
+ *\li The tree need not have be locked against additions for the chain
+ * to remain valid, however there are no guarantees if any deletion
+ * has been made since the chain was established.
+ *
+ * Requires:
+ *\li 'chain' is a valid chain.
+ *
+ * Ensures:
+ *\li 'node', if non-NULL, is the node to which the chain was pointed
+ * by dns_rbt_findnode, dns_rbtnodechain_first or dns_rbtnodechain_last.
+ * If none were called for the chain since it was initialized or reset,
+ * or if the was no predecessor to the name searched for with
+ * dns_rbt_findnode, then '*node' is NULL and ISC_R_NOTFOUND is returned.
+ *
+ *\li 'name', if non-NULL, is the name stored at the terminal level of
+ * the chain. This is typically a single label, like the "www" of
+ * "www.isc.org", but need not be so. At the root of the tree of trees,
+ * if the node is "." then 'name' is ".", otherwise it is relative to ".".
+ * (Minimalist and atypical case: if the tree has just the name
+ * "isc.org." then the root node's stored name is "isc.org." but 'name'
+ * will be "isc.org".)
+ *
+ *\li 'origin', if non-NULL, is the sequence of labels in the levels
+ * above the terminal level, such as "isc.org." in the above example.
+ * 'origin' is always "." for the root node.
+ *
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS name, origin & node were successfully set.
+ *\li #ISC_R_NOTFOUND The chain does not point to any node.
+ *\li &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
+
+#endif /* DNS_RBT_H */
diff --git a/lib/dns/include/dns/rcode.h b/lib/dns/include/dns/rcode.h
new file mode 100644
index 0000000..dbc2dfe
--- /dev/null
+++ b/lib/dns/include/dns/rcode.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RCODE_H
+#define DNS_RCODE_H 1
+
+/*! \file dns/rcode.h */
+
+#include <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
+
+#endif /* DNS_RCODE_H */
diff --git a/lib/dns/include/dns/rdata.h b/lib/dns/include/dns/rdata.h
new file mode 100644
index 0000000..2118dc4
--- /dev/null
+++ b/lib/dns/include/dns/rdata.h
@@ -0,0 +1,812 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RDATA_H
+#define DNS_RDATA_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/rdata.h
+ * \brief
+ * Provides facilities for manipulating DNS rdata, including conversions to
+ * and from wire format and text format.
+ *
+ * Given the large amount of rdata possible in a nameserver, it was important
+ * to come up with a very efficient way of storing rdata, but at the same
+ * time allow it to be manipulated.
+ *
+ * The decision was to store rdata in uncompressed wire format,
+ * and not to make it a fully abstracted object; i.e. certain parts of the
+ * server know rdata is stored that way. This saves a lot of memory, and
+ * makes adding rdata to messages easy. Having much of the server know
+ * the representation would be perilous, and we certainly don't want each
+ * user of rdata to be manipulating such a low-level structure. This is
+ * where the rdata module comes in. The module allows rdata handles to be
+ * created and attached to uncompressed wire format regions. All rdata
+ * operations and conversions are done through these handles.
+ *
+ * Implementation Notes:
+ *
+ *\li The routines in this module are expected to be synthesized by the
+ * build process from a set of source files, one per rdata type. For
+ * portability, it's probably best that the building be done by a C
+ * program. Adding a new rdata type will be a simple matter of adding
+ * a file to a directory and rebuilding the server. *All* knowledge of
+ * the format of a particular rdata type is in this file.
+ *
+ * MP:
+ *\li Clients of this module must impose any required synchronization.
+ *
+ * Reliability:
+ *\li This module deals with low-level byte streams. Errors in any of
+ * the functions are likely to crash the server or corrupt memory.
+ *
+ *\li Rdata is typed, and the caller must know what type of rdata it has.
+ * A caller that gets this wrong could crash the server.
+ *
+ *\li The fromstruct() and tostruct() routines use a void * pointer to
+ * represent the structure. The caller must ensure that it passes a
+ * pointer to the appropriate type, or the server could crash or memory
+ * could be corrupted.
+ *
+ * Resources:
+ *\li None.
+ *
+ * Security:
+ *
+ *\li *** WARNING ***
+ * dns_rdata_fromwire() deals with raw network data. An error in
+ * this routine could result in the failure or hijacking of the server.
+ *
+ * Standards:
+ *\li RFC1035
+ *\li Draft EDNS0 (0)
+ *\li Draft EDNS1 (0)
+ *\li Draft Binary Labels (2)
+ *\li Draft Local Compression (1)
+ *\li Various RFCs for particular types; these will be documented in the
+ * sources files of the types.
+ *
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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, 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
+
+#endif /* DNS_RDATA_H */
diff --git a/lib/dns/include/dns/rdataclass.h b/lib/dns/include/dns/rdataclass.h
new file mode 100644
index 0000000..033d08d
--- /dev/null
+++ b/lib/dns/include/dns/rdataclass.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RDATACLASS_H
+#define DNS_RDATACLASS_H 1
+
+/*! \file dns/rdataclass.h */
+
+#include <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
+
+#endif /* DNS_RDATACLASS_H */
diff --git a/lib/dns/include/dns/rdatalist.h b/lib/dns/include/dns/rdatalist.h
new file mode 100644
index 0000000..3d49064
--- /dev/null
+++ b/lib/dns/include/dns/rdatalist.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RDATALIST_H
+#define DNS_RDATALIST_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/rdatalist.h
+ * \brief
+ * A DNS rdatalist is a list of rdata of a common type and class.
+ *
+ * MP:
+ *\li Clients of this module must impose any required synchronization.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li None.
+ */
+
+#include <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
+
+#endif /* DNS_RDATALIST_H */
diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h
new file mode 100644
index 0000000..de3f638
--- /dev/null
+++ b/lib/dns/include/dns/rdataset.h
@@ -0,0 +1,615 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RDATASET_H
+#define DNS_RDATASET_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/rdataset.h
+ * \brief
+ * A DNS rdataset is a handle that can be associated with a collection of
+ * rdata all having a common owner name, class, and type.
+ *
+ * The dns_rdataset_t type is like a "virtual class". To actually use
+ * rdatasets, an implementation of the method suite (e.g. "slabbed rdata") is
+ * required.
+ *
+ * XXX &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;
+ /*@}*/
+};
+
+/*!
+ * \def DNS_RDATASETATTR_RENDERED
+ * Used by message.c to indicate that the rdataset was rendered.
+ *
+ * \def DNS_RDATASETATTR_TTLADJUSTED
+ * Used by message.c to indicate that the rdataset's rdata had differing
+ * TTL values, and the rdataset->ttl holds the smallest.
+ *
+ * \def DNS_RDATASETATTR_LOADORDER
+ * Output the RRset in load order.
+ *
+ * \def DNS_RDATASETATTR_STALE_ADDED
+ * Set on rdatasets that were added during a stale-answer-client-timeout
+ * lookup. In other words, the RRset was added during a lookup of stale
+ * data and does not necessarily mean that the rdataset itself is stale.
+ */
+
+#define DNS_RDATASETATTR_NONE 0x00000000 /*%< No ordering. */
+#define DNS_RDATASETATTR_QUESTION 0x00000001
+#define DNS_RDATASETATTR_RENDERED 0x00000002 /*%< Used by message.c */
+#define DNS_RDATASETATTR_ANSWERED 0x00000004 /*%< Used by server. */
+#define DNS_RDATASETATTR_CACHE 0x00000008 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_ANSWER 0x00000010 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_ANSWERSIG 0x00000020 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_EXTERNAL 0x00000040 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_NCACHE 0x00000080 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_CHAINING 0x00000100 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_TTLADJUSTED 0x00000200 /*%< Used by message.c */
+#define DNS_RDATASETATTR_FIXEDORDER 0x00000400 /*%< Fixed ordering. */
+#define DNS_RDATASETATTR_RANDOMIZE 0x00000800 /*%< Random ordering. */
+#define DNS_RDATASETATTR_CHASE 0x00001000 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_NXDOMAIN 0x00002000
+#define DNS_RDATASETATTR_NOQNAME 0x00004000
+#define DNS_RDATASETATTR_CHECKNAMES 0x00008000 /*%< Used by resolver. */
+#define DNS_RDATASETATTR_REQUIRED 0x00010000
+#define DNS_RDATASETATTR_REQUIREDGLUE DNS_RDATASETATTR_REQUIRED
+#define DNS_RDATASETATTR_LOADORDER 0x00020000
+#define DNS_RDATASETATTR_RESIGN 0x00040000
+#define DNS_RDATASETATTR_CLOSEST 0x00080000
+#define DNS_RDATASETATTR_OPTOUT 0x00100000 /*%< OPTOUT proof */
+#define DNS_RDATASETATTR_NEGATIVE 0x00200000
+#define DNS_RDATASETATTR_PREFETCH 0x00400000
+#define DNS_RDATASETATTR_CYCLIC 0x00800000 /*%< Cyclic ordering. */
+#define DNS_RDATASETATTR_STALE 0x01000000
+#define DNS_RDATASETATTR_ANCIENT 0x02000000
+#define DNS_RDATASETATTR_STALE_WINDOW 0x04000000
+#define DNS_RDATASETATTR_STALE_ADDED 0x08000000
+
+/*%
+ * _OMITDNSSEC:
+ * Omit DNSSEC records when rendering ncache records.
+ */
+#define DNS_RDATASETTOWIRE_OMITDNSSEC 0x0001
+
+void
+dns_rdataset_init(dns_rdataset_t *rdataset);
+/*%<
+ * Make 'rdataset' a valid, disassociated rdataset.
+ *
+ * Requires:
+ *\li 'rdataset' is not NULL.
+ *
+ * Ensures:
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ */
+
+void
+dns_rdataset_invalidate(dns_rdataset_t *rdataset);
+/*%<
+ * Invalidate 'rdataset'.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ * Ensures:
+ *\li If assertion checking is enabled, future attempts to use 'rdataset'
+ * without initializing it will cause an assertion failure.
+ */
+
+void
+dns_rdataset_disassociate(dns_rdataset_t *rdataset);
+/*%<
+ * Disassociate 'rdataset' from its rdata, allowing it to be reused.
+ *
+ * Notes:
+ *\li The client must ensure it has no references to rdata in the rdataset
+ * before disassociating.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, associated rdataset.
+ *
+ * Ensures:
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ */
+
+bool
+dns_rdataset_isassociated(dns_rdataset_t *rdataset);
+/*%<
+ * Is 'rdataset' associated?
+ *
+ * Requires:
+ *\li 'rdataset' is a valid rdataset.
+ *
+ * Returns:
+ *\li #true 'rdataset' is associated.
+ *\li #false 'rdataset' is not associated.
+ */
+
+void
+dns_rdataset_makequestion(dns_rdataset_t *rdataset, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type);
+/*%<
+ * Make 'rdataset' a valid, associated, question rdataset, with a
+ * question class of 'rdclass' and type 'type'.
+ *
+ * Notes:
+ *\li Question rdatasets have a class and type, but no rdata.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ * Ensures:
+ *\li 'rdataset' is a valid, associated, question rdataset.
+ */
+
+void
+dns_rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target);
+/*%<
+ * Make 'target' refer to the same rdataset as 'source'.
+ *
+ * Requires:
+ *\li 'source' is a valid, associated rdataset.
+ *
+ *\li 'target' is a valid, dissociated rdataset.
+ *
+ * Ensures:
+ *\li 'target' references the same rdataset as 'source'.
+ */
+
+unsigned int
+dns_rdataset_count(dns_rdataset_t *rdataset);
+/*%<
+ * Return the number of records in 'rdataset'.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, associated rdataset.
+ *
+ * Returns:
+ *\li The number of records in 'rdataset'.
+ */
+
+isc_result_t
+dns_rdataset_first(dns_rdataset_t *rdataset);
+/*%<
+ * Move the rdata cursor to the first rdata in the rdataset (if any).
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, associated rdataset.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE There are no rdata in the set.
+ */
+
+isc_result_t
+dns_rdataset_next(dns_rdataset_t *rdataset);
+/*%<
+ * Move the rdata cursor to the next rdata in the rdataset (if any).
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, associated rdataset.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE There are no more rdata in the set.
+ */
+
+void
+dns_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata);
+/*%<
+ * Make 'rdata' refer to the current rdata.
+ *
+ * Notes:
+ *
+ *\li The data returned in 'rdata' is valid for the life of the
+ * rdataset; in particular, subsequent changes in the cursor position
+ * do not invalidate 'rdata'.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid, associated rdataset.
+ *
+ *\li The rdata cursor of 'rdataset' is at a valid location (i.e. the
+ * result of last call to a cursor movement command was ISC_R_SUCCESS).
+ *
+ * Ensures:
+ *\li 'rdata' refers to the rdata at the rdata cursor location of
+ *\li 'rdataset'.
+ */
+
+isc_result_t
+dns_rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ bool omit_final_dot, bool question, isc_buffer_t *target);
+/*%<
+ * Convert 'rdataset' to text format, storing the result in 'target'.
+ *
+ * Notes:
+ *\li The rdata cursor position will be changed.
+ *
+ *\li The 'question' flag should normally be #false. If it is
+ * #true, the TTL and rdata fields are not printed. This is
+ * for use when printing an rdata representing a question section.
+ *
+ *\li This interface is deprecated; use dns_master_rdatasettottext()
+ * and/or dns_master_questiontotext() instead.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid rdataset.
+ *
+ *\li 'rdataset' is not empty.
+ */
+
+isc_result_t
+dns_rdataset_towire(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_compress_t *cctx, isc_buffer_t *target,
+ unsigned int options, unsigned int *countp);
+/*%<
+ * Convert 'rdataset' to wire format, compressing names as specified
+ * in 'cctx', and storing the result in 'target'.
+ *
+ * Notes:
+ *\li The rdata cursor position will be changed.
+ *
+ *\li The number of RRs added to target will be added to *countp.
+ *
+ * Requires:
+ *\li 'rdataset' is a valid rdataset.
+ *
+ *\li 'rdataset' is not empty.
+ *
+ *\li 'countp' is a valid pointer.
+ *
+ * Ensures:
+ *\li On a return of ISC_R_SUCCESS, 'target' contains a wire format
+ * for the data contained in 'rdataset'. Any error return leaves
+ * the buffer unchanged.
+ *
+ *\li *countp has been incremented by the number of RRs added to
+ * target.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS - all ok
+ *\li #ISC_R_NOSPACE - 'target' doesn't have enough room
+ *
+ *\li Any error returned by dns_rdata_towire(), dns_rdataset_next(),
+ * dns_name_towire().
+ */
+
+isc_result_t
+dns_rdataset_towiresorted(dns_rdataset_t *rdataset,
+ const dns_name_t *owner_name, dns_compress_t *cctx,
+ isc_buffer_t *target, dns_rdatasetorderfunc_t order,
+ const void *order_arg, unsigned int options,
+ unsigned int *countp);
+/*%<
+ * Like dns_rdataset_towire(), but sorting the rdatasets according to
+ * the integer value returned by 'order' when called with the rdataset
+ * and 'order_arg' as arguments.
+ *
+ * Requires:
+ *\li All the requirements of dns_rdataset_towire(), and
+ * that order_arg is NULL if and only if order is NULL.
+ */
+
+isc_result_t
+dns_rdataset_towirepartial(dns_rdataset_t *rdataset,
+ const dns_name_t *owner_name, dns_compress_t *cctx,
+ isc_buffer_t *target, dns_rdatasetorderfunc_t order,
+ const void *order_arg, unsigned int options,
+ unsigned int *countp, void **state);
+/*%<
+ * Like dns_rdataset_towiresorted() except that a partial rdataset
+ * may be written.
+ *
+ * Requires:
+ *\li All the requirements of dns_rdataset_towiresorted().
+ * If 'state' is non NULL then the current position in the
+ * rdataset will be remembered if the rdataset in not
+ * completely written and should be passed on on subsequent
+ * calls (NOT CURRENTLY IMPLEMENTED).
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS if all of the records were written.
+ *\li #ISC_R_NOSPACE if unable to fit in all of the records. *countp
+ * will be updated to reflect the number of records
+ * written.
+ */
+
+isc_result_t
+dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
+ dns_additionaldatafunc_t add, void *arg);
+/*%<
+ * For each rdata in rdataset, call 'add' for each name and type in the
+ * rdata which is subject to additional section processing.
+ *
+ * Requires:
+ *
+ *\li 'rdataset' is a valid, non-question rdataset.
+ *
+ *\li 'add' is a valid dns_additionaldatafunc_t
+ *
+ * Ensures:
+ *
+ *\li If successful, dns_rdata_additionaldata() will have been called for
+ * each rdata in 'rdataset'.
+ *
+ *\li If a call to dns_rdata_additionaldata() is not successful, the
+ * result returned will be the result of dns_rdataset_additionaldata().
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Any error that dns_rdata_additionaldata() can return.
+ */
+
+isc_result_t
+dns_rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig);
+/*%<
+ * Return the noqname proof for this record.
+ *
+ * Requires:
+ *\li 'rdataset' to be valid and #DNS_RDATASETATTR_NOQNAME to be set.
+ *\li 'name' to be valid.
+ *\li 'neg' and 'negsig' to be valid and not associated.
+ */
+
+isc_result_t
+dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name);
+/*%<
+ * Associate a noqname proof with this record.
+ * Sets #DNS_RDATASETATTR_NOQNAME if successful.
+ * Adjusts the 'rdataset->ttl' to minimum of the 'rdataset->ttl' and
+ * the 'nsec'/'nsec3' and 'rrsig(nsec)'/'rrsig(nsec3)' ttl.
+ *
+ * Requires:
+ *\li 'rdataset' to be valid and #DNS_RDATASETATTR_NOQNAME to be set.
+ *\li 'name' to be valid and have NSEC or NSEC3 and associated RRSIG
+ * rdatasets.
+ */
+
+isc_result_t
+dns_rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *nsec, dns_rdataset_t *nsecsig);
+/*%<
+ * Return the closest encloser for this record.
+ *
+ * Requires:
+ *\li 'rdataset' to be valid and #DNS_RDATASETATTR_CLOSEST to be set.
+ *\li 'name' to be valid.
+ *\li 'nsec' and 'nsecsig' to be valid and not associated.
+ */
+
+isc_result_t
+dns_rdataset_addclosest(dns_rdataset_t *rdataset, const dns_name_t *name);
+/*%<
+ * Associate a closest encloset proof with this record.
+ * Sets #DNS_RDATASETATTR_CLOSEST if successful.
+ * Adjusts the 'rdataset->ttl' to minimum of the 'rdataset->ttl' and
+ * the 'nsec' and 'rrsig(nsec)' ttl.
+ *
+ * Requires:
+ *\li 'rdataset' to be valid and #DNS_RDATASETATTR_CLOSEST to be set.
+ *\li 'name' to be valid and have NSEC3 and RRSIG(NSEC3) rdatasets.
+ */
+
+void
+dns_rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust);
+/*%<
+ * Set the trust of the 'rdataset' to trust in any in the backing database.
+ * The local trust level of 'rdataset' is also set.
+ */
+
+void
+dns_rdataset_expire(dns_rdataset_t *rdataset);
+/*%<
+ * Mark the rdataset to be expired in the backing database.
+ */
+
+void
+dns_rdataset_clearprefetch(dns_rdataset_t *rdataset);
+/*%<
+ * Clear the PREFETCH attribute for the given rdataset in the
+ * underlying database.
+ *
+ * In the cache database, this signals that the rdataset is not
+ * eligible to be prefetched when the TTL is close to expiring.
+ * It has no function in other databases.
+ */
+
+void
+dns_rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name);
+/*%<
+ * Store the casing of 'name', the owner name of 'rdataset', into
+ * a bitfield so that the name can be capitalized the same when when
+ * the rdataset is used later. This sets the CASESET attribute.
+ */
+
+void
+dns_rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name);
+/*%<
+ * If the CASESET attribute is set, retrieve the case bitfield that was
+ * previously stored by dns_rdataset_getownername(), and capitalize 'name'
+ * according to it. If CASESET is not set, do nothing.
+ */
+
+isc_result_t
+dns_rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version,
+ dns_message_t *msg);
+/*%<
+ * Add glue records for rdataset to the additional section of message in
+ * 'msg'. 'rdataset' must be of type NS.
+ *
+ * In case a successful result is not returned, the caller should try to
+ * add glue directly to the message by iterating for additional data.
+ *
+ * Requires:
+ * \li 'rdataset' is a valid NS rdataset.
+ * \li 'version' is the DB version.
+ * \li 'msg' is the DNS message to which the glue should be added.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTIMPLEMENTED
+ *\li #ISC_R_FAILURE
+ *\li Any error that dns_rdata_additionaldata() can return.
+ */
+
+void
+dns_rdataset_trimttl(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_rdata_rrsig_t *rrsig, isc_stdtime_t now,
+ bool acceptexpired);
+/*%<
+ * Trim the ttl of 'rdataset' and 'sigrdataset' so that they will expire
+ * at or before 'rrsig->expiretime'. If 'acceptexpired' is true and the
+ * signature has expired or will expire in the next 120 seconds, limit
+ * the ttl to be no more than 120 seconds.
+ *
+ * The ttl is further limited by the original ttl as stored in 'rrsig'
+ * and the original ttl values of 'rdataset' and 'sigrdataset'.
+ *
+ * Requires:
+ * \li 'rdataset' is a valid rdataset.
+ * \li 'sigrdataset' is a valid rdataset.
+ * \li 'rrsig' is non NULL.
+ */
+
+const char *
+dns_trust_totext(dns_trust_t trust);
+/*%<
+ * Display trust in textual form.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_RDATASET_H */
diff --git a/lib/dns/include/dns/rdatasetiter.h b/lib/dns/include/dns/rdatasetiter.h
new file mode 100644
index 0000000..2df4aad
--- /dev/null
+++ b/lib/dns/include/dns/rdatasetiter.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RDATASETITER_H
+#define DNS_RDATASETITER_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/rdatasetiter.h
+ * \brief
+ * The DNS Rdataset Iterator interface allows iteration of all of the
+ * rdatasets at a node.
+ *
+ * The dns_rdatasetiter_t type is like a "virtual class". To actually use
+ * it, an implementation of the class is required. This implementation is
+ * supplied by the database.
+ *
+ * It is the client's responsibility to call dns_rdataset_disassociate()
+ * on all rdatasets returned.
+ *
+ * XXX more XXX
+ *
+ * MP:
+ *\li The iterator itself is not locked. The caller must ensure
+ * synchronization.
+ *
+ *\li The iterator methods ensure appropriate database locking.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li None.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <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
+
+#endif /* DNS_RDATASETITER_H */
diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h
new file mode 100644
index 0000000..5a1c30f
--- /dev/null
+++ b/lib/dns/include/dns/rdataslab.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RDATASLAB_H
+#define DNS_RDATASLAB_H 1
+
+/*! \file dns/rdataslab.h
+ * \brief
+ * Implements storage of rdatasets into slabs of memory.
+ *
+ * MP:
+ *\li Clients of this module must impose any required synchronization.
+ *
+ * Reliability:
+ *\li This module deals with low-level byte streams. Errors in any of
+ * the functions are likely to crash the server or corrupt memory.
+ *
+ *\li If the caller passes invalid memory references, these functions are
+ * likely to crash the server or corrupt memory.
+ *
+ * Resources:
+ *\li None.
+ *
+ * Security:
+ *\li None.
+ *
+ * Standards:
+ *\li None.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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
+
+#endif /* DNS_RDATASLAB_H */
diff --git a/lib/dns/include/dns/rdatatype.h b/lib/dns/include/dns/rdatatype.h
new file mode 100644
index 0000000..008f4da
--- /dev/null
+++ b/lib/dns/include/dns/rdatatype.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RDATATYPE_H
+#define DNS_RDATATYPE_H 1
+
+/*! \file dns/rdatatype.h */
+
+#include <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
+
+#endif /* DNS_RDATATYPE_H */
diff --git a/lib/dns/include/dns/request.h b/lib/dns/include/dns/request.h
new file mode 100644
index 0000000..f745f02
--- /dev/null
+++ b/lib/dns/include/dns/request.h
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_REQUEST_H
+#define DNS_REQUEST_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/request.h
+ *
+ * \brief
+ * The request module provides simple request/response services useful for
+ * sending SOA queries, DNS Notify messages, and dynamic update requests.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ */
+
+#include <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
+#define DNS_REQUESTOPT_SHARE 0x00000008U
+
+typedef struct dns_requestevent {
+ ISC_EVENT_COMMON(struct dns_requestevent);
+ isc_result_t result;
+ dns_request_t *request;
+} dns_requestevent_t;
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_requestmgr_create(isc_mem_t *mctx, isc_timermgr_t *timermgr,
+ isc_socketmgr_t *socketmgr, isc_taskmgr_t *taskmgr,
+ dns_dispatchmgr_t *dispatchmgr,
+ dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6,
+ dns_requestmgr_t **requestmgrp);
+/*%<
+ * Create a request manager.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'timermgr' is a valid timer manager.
+ *
+ *\li 'socketmgr' is a valid socket manager.
+ *
+ *\li 'taskmgr' is a valid task manager.
+ *
+ *\li 'dispatchv4' is a valid dispatcher with an IPv4 UDP socket, or is NULL.
+ *
+ *\li 'dispatchv6' is a valid dispatcher with an IPv6 UDP socket, or is NULL.
+ *
+ *\li requestmgrp != NULL && *requestmgrp == NULL
+ *
+ * Ensures:
+ *
+ *\li On success, *requestmgrp is a valid request manager.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any other result indicates failure.
+ */
+
+void
+dns_requestmgr_whenshutdown(dns_requestmgr_t *requestmgr, isc_task_t *task,
+ isc_event_t **eventp);
+/*%<
+ * Send '*eventp' to 'task' when 'requestmgr' has completed shutdown.
+ *
+ * Notes:
+ *
+ *\li It is not safe to detach the last reference to 'requestmgr' until
+ * shutdown is complete.
+ *
+ * Requires:
+ *
+ *\li 'requestmgr' is a valid request manager.
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li *eventp is a valid event.
+ *
+ * Ensures:
+ *
+ *\li *eventp == NULL.
+ */
+
+void
+dns_requestmgr_shutdown(dns_requestmgr_t *requestmgr);
+/*%<
+ * Start the shutdown process for 'requestmgr'.
+ *
+ * Notes:
+ *
+ *\li This call has no effect if the request manager is already shutting
+ * down.
+ *
+ * Requires:
+ *
+ *\li 'requestmgr' is a valid requestmgr.
+ */
+
+void
+dns_requestmgr_attach(dns_requestmgr_t *source, dns_requestmgr_t **targetp);
+/*%<
+ * Attach to the request manager. dns_requestmgr_shutdown() must not
+ * have been called on 'source' prior to calling dns_requestmgr_attach().
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid requestmgr.
+ *
+ *\li 'targetp' to be non NULL and '*targetp' to be NULL.
+ */
+
+void
+dns_requestmgr_detach(dns_requestmgr_t **requestmgrp);
+/*%<
+ * Detach from the given requestmgr. If this is the final detach
+ * requestmgr will be destroyed. dns_requestmgr_shutdown() must
+ * be called before the final detach.
+ *
+ * Requires:
+ *
+ *\li '*requestmgrp' is a valid requestmgr.
+ *
+ * Ensures:
+ *\li '*requestmgrp' is NULL.
+ */
+
+isc_result_t
+dns_request_create(dns_requestmgr_t *requestmgr, dns_message_t *message,
+ const isc_sockaddr_t *address, unsigned int options,
+ dns_tsigkey_t *key, unsigned int timeout, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_request_t **requestp);
+/*%<
+ * Create and send a request.
+ *
+ * Notes:
+ *
+ *\li 'message' will be rendered and sent to 'address'. If the
+ * #DNS_REQUESTOPT_TCP option is set, TCP will be used,
+ * #DNS_REQUESTOPT_SHARE option is set too, connecting TCP
+ * (vs. connected) will be shared too. The request
+ * will timeout after 'timeout' seconds.
+ *
+ *\li If the #DNS_REQUESTOPT_CASE option is set, use case sensitive
+ * compression.
+ *
+ *\li When the request completes, successfully, due to a timeout, or
+ * because it was canceled, a completion event will be sent to 'task'.
+ *
+ * Requires:
+ *
+ *\li 'message' is a valid DNS message.
+ *
+ *\li 'address' is a valid sockaddr.
+ *
+ *\li 'timeout' > 0
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li requestp != NULL && *requestp == NULL
+ */
+
+isc_result_t
+dns_request_createvia(dns_requestmgr_t *requestmgr, dns_message_t *message,
+ const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, isc_dscp_t dscp,
+ unsigned int options, dns_tsigkey_t *key,
+ unsigned int timeout, unsigned int udptimeout,
+ unsigned int udpretries, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_request_t **requestp);
+/*%<
+ * Create and send a request.
+ *
+ * Notes:
+ *
+ *\li 'message' will be rendered and sent to 'address'. If the
+ * #DNS_REQUESTOPT_TCP option is set, TCP will be used,
+ * #DNS_REQUESTOPT_SHARE option is set too, connecting TCP
+ * (vs. connected) will be shared too. The request
+ * will timeout after 'timeout' seconds. UDP requests will be resent
+ * at 'udptimeout' intervals if non-zero or 'udpretries' is non-zero.
+ *
+ *\li If the #DNS_REQUESTOPT_CASE option is set, use case sensitive
+ * compression.
+ *
+ *\li When the request completes, successfully, due to a timeout, or
+ * because it was canceled, a completion event will be sent to 'task'.
+ *
+ * Requires:
+ *
+ *\li 'message' is a valid DNS message.
+ *
+ *\li 'dstaddr' is a valid sockaddr.
+ *
+ *\li 'srcaddr' is a valid sockaddr or NULL.
+ *
+ *\li 'srcaddr' and 'dstaddr' are the same protocol family.
+ *
+ *\li 'timeout' > 0
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li requestp != NULL && *requestp == NULL
+ */
+
+isc_result_t
+dns_request_createraw(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf,
+ const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, isc_dscp_t dscp,
+ unsigned int options, unsigned int timeout,
+ unsigned int udptimeout, unsigned int udpretries,
+ isc_task_t *task, isc_taskaction_t action, void *arg,
+ dns_request_t **requestp);
+/*!<
+ * \brief Create and send a request.
+ *
+ * Notes:
+ *
+ *\li 'msgbuf' will be sent to 'destaddr' after setting the id. If the
+ * #DNS_REQUESTOPT_TCP option is set, TCP will be used,
+ * #DNS_REQUESTOPT_SHARE option is set too, connecting TCP
+ * (vs. connected) will be shared too. The request
+ * will timeout after 'timeout' seconds. UDP requests will be resent
+ * at 'udptimeout' intervals if non-zero or if 'udpretries' is not zero.
+ *
+ *\li When the request completes, successfully, due to a timeout, or
+ * because it was canceled, a completion event will be sent to 'task'.
+ *
+ * Requires:
+ *
+ *\li 'msgbuf' is a valid DNS message in compressed wire format.
+ *
+ *\li 'destaddr' is a valid sockaddr.
+ *
+ *\li 'srcaddr' is a valid sockaddr or NULL.
+ *
+ *\li 'srcaddr' and 'dstaddr' are the same protocol family.
+ *
+ *\li 'timeout' > 0
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li requestp != NULL && *requestp == NULL
+ */
+
+void
+dns_request_cancel(dns_request_t *request);
+/*%<
+ * Cancel 'request'.
+ *
+ * Requires:
+ *
+ *\li 'request' is a valid request.
+ *
+ * Ensures:
+ *
+ *\li If the completion event for 'request' has not yet been sent, it
+ * will be sent, and the result code will be ISC_R_CANCELED.
+ */
+
+isc_result_t
+dns_request_getresponse(dns_request_t *request, dns_message_t *message,
+ unsigned int options);
+/*%<
+ * Get the response to 'request' by filling in 'message'.
+ *
+ * 'options' is passed to dns_message_parse(). See dns_message_parse()
+ * for more details.
+ *
+ * Requires:
+ *
+ *\li 'request' is a valid request for which the caller has received the
+ * completion event.
+ *
+ *\li The result code of the completion event was #ISC_R_SUCCESS.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS
+ *
+ *\li Any result that dns_message_parse() can return.
+ */
+isc_buffer_t *
+dns_request_getanswer(dns_request_t *request);
+/*
+ * Get the response to 'request' as a buffer.
+ *
+ * Requires:
+ *
+ *\li 'request' is a valid request for which the caller has received the
+ * completion event.
+ *
+ * Returns:
+ *
+ *\li a pointer to the answer buffer.
+ */
+
+bool
+dns_request_usedtcp(dns_request_t *request);
+/*%<
+ * Return whether this query used TCP or not. Setting #DNS_REQUESTOPT_TCP
+ * in the call to dns_request_create() will cause the function to return
+ * #true, otherwise the result is based on the query message size.
+ *
+ * Requires:
+ *\li 'request' is a valid request.
+ *
+ * Returns:
+ *\li true if TCP was used.
+ *\li false if UDP was used.
+ */
+
+void
+dns_request_destroy(dns_request_t **requestp);
+/*%<
+ * Destroy 'request'.
+ *
+ * Requires:
+ *
+ *\li 'request' is a valid request for which the caller has received the
+ * completion event.
+ *
+ * Ensures:
+ *
+ *\li *requestp == NULL
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_REQUEST_H */
diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h
new file mode 100644
index 0000000..22ad9df
--- /dev/null
+++ b/lib/dns/include/dns/resolver.h
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RESOLVER_H
+#define DNS_RESOLVER_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/resolver.h
+ *
+ * \brief
+ * This is the BIND 9 resolver, the module responsible for resolving DNS
+ * requests by iteratively querying authoritative servers and following
+ * referrals. This is a "full resolver", not to be confused with
+ * the stub resolvers most people associate with the word "resolver".
+ * The full resolver is part of the caching name server or resolver
+ * daemon the stub resolver talks to.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li RFCs: 1034, 1035, 2181, TBS
+ *\li Drafts: TBS
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/socket.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 foundname;
+ const isc_sockaddr_t *client;
+ dns_messageid_t id;
+ isc_result_t vresult;
+} dns_fetchevent_t;
+
+/*%
+ * The two quota types (fetches-per-zone and fetches-per-server)
+ */
+typedef enum { dns_quotatype_zone = 0, dns_quotatype_server } dns_quotatype_t;
+
+/*
+ * Options that modify how a 'fetch' is done.
+ */
+#define DNS_FETCHOPT_TCP 0x00000001 /*%< Use TCP. */
+#define DNS_FETCHOPT_UNSHARED 0x00000002 /*%< See below. */
+#define DNS_FETCHOPT_RECURSIVE 0x00000004 /*%< Set RD? */
+#define DNS_FETCHOPT_NOEDNS0 0x00000008 /*%< Do not use EDNS. */
+#define DNS_FETCHOPT_FORWARDONLY 0x00000010 /*%< Only use forwarders. */
+#define DNS_FETCHOPT_NOVALIDATE 0x00000020 /*%< Disable validation. */
+#define DNS_FETCHOPT_EDNS512 \
+ 0x00000040 /*%< Advertise a 512 byte \
+ * UDP buffer. */
+#define DNS_FETCHOPT_WANTNSID 0x00000080 /*%< Request NSID */
+#define DNS_FETCHOPT_PREFETCH 0x00000100 /*%< Do prefetch */
+#define DNS_FETCHOPT_NOCDFLAG 0x00000200 /*%< Don't set CD flag. */
+#define DNS_FETCHOPT_NONTA 0x00000400 /*%< Ignore NTA table. */
+/* RESERVED ECS 0x00000000 */
+/* RESERVED ECS 0x00001000 */
+/* RESERVED ECS 0x00002000 */
+/* RESERVED TCPCLIENT 0x00004000 */
+#define DNS_FETCHOPT_NOCACHED 0x00008000 /*%< Force cache update. */
+#define DNS_FETCHOPT_QMINIMIZE \
+ 0x00010000 /*%< Use qname \
+ * minimization. */
+#define DNS_FETCHOPT_NOFOLLOW \
+ 0x00020000 /*%< Don't follow \
+ * delegations */
+#define DNS_FETCHOPT_QMIN_STRICT \
+ 0x00040000 /*%< Do not work around \
+ * servers that return \
+ * errors on non-empty \
+ * terminals. */
+#define DNS_FETCHOPT_QMIN_USE_A \
+ 0x00080000 /*%< Use A type queries \
+ * instead of NS when \
+ * doing minimization */
+#define DNS_FETCHOPT_QMIN_SKIP_IP6A \
+ 0x00100000 /*%< Skip some labels \
+ * when doing qname \
+ * minimization on \
+ * ip6.arpa. */
+#define DNS_FETCHOPT_NOFORWARD \
+ 0x00200000 /*%< Do not use forwarders \
+ * if possible. */
+
+/* Reserved in use by adb.c 0x00400000 */
+#define DNS_FETCHOPT_EDNSVERSIONSET 0x00800000
+#define DNS_FETCHOPT_EDNSVERSIONMASK 0xff000000
+#define DNS_FETCHOPT_EDNSVERSIONSHIFT 24
+#define DNS_FETCHOPT_TRYSTALE_ONTIMEOUT 0x01000000
+
+/*
+ * Upper bounds of class of query RTT (ms). Corresponds to
+ * dns_resstatscounter_queryrttX statistics counters.
+ */
+#define DNS_RESOLVER_QRYRTTCLASS0 10
+#define DNS_RESOLVER_QRYRTTCLASS0STR "10"
+#define DNS_RESOLVER_QRYRTTCLASS1 100
+#define DNS_RESOLVER_QRYRTTCLASS1STR "100"
+#define DNS_RESOLVER_QRYRTTCLASS2 500
+#define DNS_RESOLVER_QRYRTTCLASS2STR "500"
+#define DNS_RESOLVER_QRYRTTCLASS3 800
+#define DNS_RESOLVER_QRYRTTCLASS3STR "800"
+#define DNS_RESOLVER_QRYRTTCLASS4 1600
+#define DNS_RESOLVER_QRYRTTCLASS4STR "1600"
+
+/*
+ * XXXRTH Should this API be made semi-private? (I.e.
+ * _dns_resolver_create()).
+ */
+
+#define DNS_RESOLVER_CHECKNAMES 0x01
+#define DNS_RESOLVER_CHECKNAMESFAIL 0x02
+
+#define DNS_QMIN_MAXLABELS 7
+#define DNS_QMIN_MAX_NO_DELEGATION 3
+#define DNS_MAX_LABELS 127
+
+isc_result_t
+dns_resolver_create(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ unsigned int ntasks, unsigned int ndisp,
+ isc_socketmgr_t *socketmgr, isc_timermgr_t *timermgr,
+ unsigned int options, dns_dispatchmgr_t *dispatchmgr,
+ dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6,
+ dns_resolver_t **resp);
+
+/*%<
+ * Create a resolver.
+ *
+ * Notes:
+ *
+ *\li Generally, applications should not create a resolver directly, but
+ * should instead call dns_view_createresolver().
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid view.
+ *
+ *\li 'taskmgr' is a valid task manager.
+ *
+ *\li 'ntasks' > 0.
+ *
+ *\li 'socketmgr' is a valid socket manager.
+ *
+ *\li 'timermgr' is a valid timer manager.
+ *
+ *\li 'dispatchv4' is a dispatch with an IPv4 UDP socket, or is NULL.
+ * If not NULL, 'ndisp' clones of it will be created by the resolver.
+ *
+ *\li 'dispatchv6' is a dispatch with an IPv6 UDP socket, or is NULL.
+ * If not NULL, 'ndisp' clones of it will be created by the resolver.
+ *
+ *\li resp != NULL && *resp == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS On success.
+ *
+ *\li Anything else Failure.
+ */
+
+void
+dns_resolver_freeze(dns_resolver_t *res);
+/*%<
+ * Freeze resolver.
+ *
+ * Notes:
+ *
+ *\li Certain configuration changes cannot be made after the resolver
+ * is frozen. Fetches cannot be created until the resolver is frozen.
+ *
+ * Requires:
+ *
+ *\li 'res' is a valid resolver.
+ *
+ * Ensures:
+ *
+ *\li 'res' is frozen.
+ */
+
+void
+dns_resolver_prime(dns_resolver_t *res);
+/*%<
+ * Prime resolver.
+ *
+ * Notes:
+ *
+ *\li Resolvers which have a forwarding policy other than dns_fwdpolicy_only
+ * need to be primed with the root nameservers, otherwise the root
+ * nameserver hints data may be used indefinitely. This function requests
+ * that the resolver start a priming fetch, if it isn't already priming.
+ *
+ * Requires:
+ *
+ *\li 'res' is a valid, frozen resolver.
+ */
+
+void
+dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task,
+ isc_event_t **eventp);
+/*%<
+ * Send '*eventp' to 'task' when 'res' has completed shutdown.
+ *
+ * Notes:
+ *
+ *\li It is not safe to detach the last reference to 'res' until
+ * shutdown is complete.
+ *
+ * Requires:
+ *
+ *\li 'res' is a valid resolver.
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li *eventp is a valid event.
+ *
+ * Ensures:
+ *
+ *\li *eventp == NULL.
+ */
+
+void
+dns_resolver_shutdown(dns_resolver_t *res);
+/*%<
+ * Start the shutdown process for 'res'.
+ *
+ * Notes:
+ *
+ *\li This call has no effect if the resolver is already shutting down.
+ *
+ * Requires:
+ *
+ *\li 'res' is a valid resolver.
+ */
+
+void
+dns_resolver_attach(dns_resolver_t *source, dns_resolver_t **targetp);
+
+void
+dns_resolver_detach(dns_resolver_t **resp);
+
+isc_result_t
+dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
+ dns_rdatatype_t type, const dns_name_t *domain,
+ dns_rdataset_t *nameservers,
+ dns_forwarders_t *forwarders,
+ const isc_sockaddr_t *client, dns_messageid_t id,
+ unsigned int options, unsigned int depth,
+ isc_counter_t *qc, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_fetch_t **fetchp);
+/*%<
+ * Recurse to answer a question.
+ *
+ * Notes:
+ *
+ *\li This call starts a query for 'name', type 'type'.
+ *
+ *\li The 'domain' is a parent domain of 'name' for which
+ * a set of name servers 'nameservers' is known. If no
+ * such name server information is available, set
+ * 'domain' and 'nameservers' to NULL.
+ *
+ *\li 'forwarders' is unimplemented, and subject to change when
+ * we figure out how selective forwarding will work.
+ *
+ *\li When the fetch completes (successfully or otherwise), a
+ * #DNS_EVENT_FETCHDONE event with action 'action' and arg 'arg' will be
+ * posted to 'task'.
+ *
+ *\li The values of 'rdataset' and 'sigrdataset' will be returned in
+ * the FETCHDONE event.
+ *
+ *\li 'client' and 'id' are used for duplicate query detection. '*client'
+ * must remain stable until after 'action' has been called or
+ * dns_resolver_cancelfetch() is called.
+ *
+ * Requires:
+ *
+ *\li 'res' is a valid resolver that has been frozen.
+ *
+ *\li 'name' is a valid name.
+ *
+ *\li 'type' is not a meta type other than ANY.
+ *
+ *\li 'domain' is a valid name or NULL.
+ *
+ *\li 'nameservers' is a valid NS rdataset (whose owner name is 'domain')
+ * iff. 'domain' is not NULL.
+ *
+ *\li 'forwarders' is NULL.
+ *
+ *\li 'client' is a valid sockaddr or NULL.
+ *
+ *\li 'options' contains valid options.
+ *
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ *\li fetchp != NULL && *fetchp == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS Success
+ *\li #DNS_R_DUPLICATE
+ *\li #DNS_R_DROP
+ *
+ *\li Many other values are possible, all of which indicate failure.
+ */
+
+void
+dns_resolver_cancelfetch(dns_fetch_t *fetch);
+/*%<
+ * Cancel 'fetch'.
+ *
+ * Notes:
+ *
+ *\li If 'fetch' has not completed, post its FETCHDONE event with a
+ * result code of #ISC_R_CANCELED.
+ *
+ * Requires:
+ *
+ *\li 'fetch' is a valid fetch.
+ */
+
+void
+dns_resolver_destroyfetch(dns_fetch_t **fetchp);
+/*%<
+ * Destroy 'fetch'.
+ *
+ * Requires:
+ *
+ *\li '*fetchp' is a valid fetch.
+ *
+ *\li The caller has received the FETCHDONE event (either because the
+ * fetch completed or because dns_resolver_cancelfetch() was called).
+ *
+ * Ensures:
+ *
+ *\li *fetchp == NULL.
+ */
+
+void
+dns_resolver_logfetch(dns_fetch_t *fetch, isc_log_t *lctx,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ int level, bool duplicateok);
+/*%<
+ * Dump a log message on internal state at the completion of given 'fetch'.
+ * 'lctx', 'category', 'module', and 'level' are used to write the log message.
+ * By default, only one log message is written even if the corresponding fetch
+ * context serves multiple clients; if 'duplicateok' is true the suppression
+ * is disabled and the message can be written every time this function is
+ * called.
+ *
+ * Requires:
+ *
+ *\li 'fetch' is a valid fetch, and has completed.
+ */
+
+dns_dispatchmgr_t *
+dns_resolver_dispatchmgr(dns_resolver_t *resolver);
+
+dns_dispatch_t *
+dns_resolver_dispatchv4(dns_resolver_t *resolver);
+
+dns_dispatch_t *
+dns_resolver_dispatchv6(dns_resolver_t *resolver);
+
+isc_socketmgr_t *
+dns_resolver_socketmgr(dns_resolver_t *resolver);
+
+isc_taskmgr_t *
+dns_resolver_taskmgr(dns_resolver_t *resolver);
+
+uint32_t
+dns_resolver_getlamettl(dns_resolver_t *resolver);
+/*%<
+ * Get the resolver's lame-ttl. zero => no lame processing.
+ *
+ * Requires:
+ *\li 'resolver' to be valid.
+ */
+
+void
+dns_resolver_setlamettl(dns_resolver_t *resolver, uint32_t lame_ttl);
+/*%<
+ * Set the resolver's lame-ttl. zero => no lame processing.
+ *
+ * Requires:
+ *\li 'resolver' to be valid.
+ */
+
+isc_result_t
+dns_resolver_addalternate(dns_resolver_t *resolver, const isc_sockaddr_t *alt,
+ const dns_name_t *name, in_port_t port);
+/*%<
+ * Add alternate addresses to be tried in the event that the nameservers
+ * for a zone are not available in the address families supported by the
+ * operating system.
+ *
+ * Require:
+ * \li only one of 'name' or 'alt' to be valid.
+ */
+
+void
+dns_resolver_setudpsize(dns_resolver_t *resolver, uint16_t udpsize);
+/*%<
+ * Set the EDNS UDP buffer size advertised by the server.
+ */
+
+uint16_t
+dns_resolver_getudpsize(dns_resolver_t *resolver);
+/*%<
+ * Get the current EDNS UDP buffer size.
+ */
+
+void
+dns_resolver_reset_algorithms(dns_resolver_t *resolver);
+/*%<
+ * Clear the disabled DNSSEC algorithms.
+ */
+
+void
+dns_resolver_reset_ds_digests(dns_resolver_t *resolver);
+/*%<
+ * Clear the disabled DS digest types.
+ */
+
+isc_result_t
+dns_resolver_disable_algorithm(dns_resolver_t *resolver, const dns_name_t *name,
+ unsigned int alg);
+/*%<
+ * Mark the given DNSSEC algorithm as disabled and below 'name'.
+ * Valid algorithms are less than 256.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_RANGE
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_resolver_disable_ds_digest(dns_resolver_t *resolver, const dns_name_t *name,
+ unsigned int digest_type);
+/*%<
+ * Mark the given DS digest type as disabled and below 'name'.
+ * Valid types are less than 256.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_RANGE
+ *\li #ISC_R_NOMEMORY
+ */
+
+bool
+dns_resolver_algorithm_supported(dns_resolver_t *resolver,
+ const dns_name_t *name, unsigned int alg);
+/*%<
+ * Check if the given algorithm is supported by this resolver.
+ * This checks whether the algorithm has been disabled via
+ * dns_resolver_disable_algorithm(), then checks the underlying
+ * crypto libraries if it was not specifically disabled.
+ */
+
+bool
+dns_resolver_ds_digest_supported(dns_resolver_t *resolver,
+ const dns_name_t *name,
+ unsigned int digest_type);
+/*%<
+ * Check if the given digest type is supported by this resolver.
+ * This checks whether the digest type has been disabled via
+ * dns_resolver_disable_ds_digest(), then checks the underlying
+ * crypto libraries if it was not specifically disabled.
+ */
+
+void
+dns_resolver_resetmustbesecure(dns_resolver_t *resolver);
+
+isc_result_t
+dns_resolver_setmustbesecure(dns_resolver_t *resolver, const dns_name_t *name,
+ bool value);
+
+bool
+dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name);
+
+void
+dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout);
+/*%<
+ * Set the length of time the resolver will work on a query, in milliseconds.
+ *
+ * 'timeout' was originally defined in seconds, and later redefined to be in
+ * milliseconds. Values less than or equal to 300 are treated as seconds.
+ *
+ * If timeout is 0, the default timeout will be applied.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+unsigned int
+dns_resolver_gettimeout(dns_resolver_t *resolver);
+/*%<
+ * Get the current length of time the resolver will work on a query,
+ * in milliseconds.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_setclientsperquery(dns_resolver_t *resolver, uint32_t min,
+ uint32_t max);
+void
+dns_resolver_setfetchesperzone(dns_resolver_t *resolver, uint32_t clients);
+
+void
+dns_resolver_getclientsperquery(dns_resolver_t *resolver, uint32_t *cur,
+ uint32_t *min, uint32_t *max);
+
+bool
+dns_resolver_getzeronosoattl(dns_resolver_t *resolver);
+
+void
+dns_resolver_setzeronosoattl(dns_resolver_t *resolver, bool state);
+
+unsigned int
+dns_resolver_getretryinterval(dns_resolver_t *resolver);
+
+void
+dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval);
+/*%<
+ * Sets the amount of time, in milliseconds, that is waited for a reply
+ * to a server before another server is tried. Interacts with the
+ * value of dns_resolver_getnonbackofftries() by trying that number of times
+ * at this interval, before doing exponential backoff and doubling the interval
+ * on each subsequent try, to a maximum of 10 seconds. Defaults to 800 ms;
+ * silently capped at 2000 ms.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ * \li interval > 0.
+ */
+
+unsigned int
+dns_resolver_getnonbackofftries(dns_resolver_t *resolver);
+
+void
+dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries);
+/*%<
+ * Sets the number of failures of getting a reply from remote servers for
+ * a query before backing off by doubling the retry interval for each
+ * subsequent request sent. Defaults to 3.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ * \li tries > 0.
+ */
+
+unsigned int
+dns_resolver_getoptions(dns_resolver_t *resolver);
+/*%<
+ * Get the resolver options.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name,
+ dns_rdatatype_t type, isc_time_t *expire);
+/*%<
+ * Add a entry to the bad cache for <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_setquerydscp4(dns_resolver_t *resolver, isc_dscp_t dscp);
+isc_dscp_t
+dns_resolver_getquerydscp4(dns_resolver_t *resolver);
+
+void
+dns_resolver_setquerydscp6(dns_resolver_t *resolver, isc_dscp_t dscp);
+isc_dscp_t
+dns_resolver_getquerydscp6(dns_resolver_t *resolver);
+/*%
+ * Get and set the DSCP values for the resolver's IPv4 and IPV6 query
+ * sources.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth);
+unsigned int
+dns_resolver_getmaxdepth(dns_resolver_t *resolver);
+/*%
+ * Get and set how many NS indirections will be followed when looking for
+ * nameserver addresses.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_setmaxqueries(dns_resolver_t *resolver, unsigned int queries);
+unsigned int
+dns_resolver_getmaxqueries(dns_resolver_t *resolver);
+/*%
+ * Get and set how many iterative queries will be allowed before
+ * terminating a recursive query.
+ *
+ * Requires:
+ * \li resolver to be valid.
+ */
+
+void
+dns_resolver_setquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which,
+ isc_result_t resp);
+isc_result_t
+dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which);
+/*%
+ * Get and set the result code that will be used when quotas
+ * are exceeded. If 'which' is set to quotatype "zone", then the
+ * result specified in 'resp' will be used when the fetches-per-zone
+ * quota is exceeded by a fetch. If 'which' is set to quotatype "server",
+ * then the result specified in 'resp' will be used when the
+ * fetches-per-server quota has been exceeded for all the
+ * authoritative servers for a zone. Valid choices are
+ * DNS_R_DROP or DNS_R_SERVFAIL.
+ *
+ * Requires:
+ * \li 'resolver' to be valid.
+ * \li 'which' to be dns_quotatype_zone or dns_quotatype_server
+ * \li 'resp' to be DNS_R_DROP or DNS_R_SERVFAIL.
+ */
+
+void
+dns_resolver_dumpfetches(dns_resolver_t *resolver, isc_statsformat_t format,
+ FILE *fp);
+
+#ifdef ENABLE_AFL
+/*%
+ * Enable fuzzing of resolver, changes behaviour and eliminates retries
+ */
+void
+dns_resolver_setfuzzing(void);
+#endif /* ifdef ENABLE_AFL */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_RESOLVER_H */
diff --git a/lib/dns/include/dns/result.h b/lib/dns/include/dns/result.h
new file mode 100644
index 0000000..0ec874b
--- /dev/null
+++ b/lib/dns/include/dns/result.h
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RESULT_H
+#define DNS_RESULT_H 1
+
+/*! \file dns/result.h */
+
+#include <isc/lang.h>
+#include <isc/resultclass.h>
+
+#include <dns/types.h>
+
+/*
+ * Nothing in this file truly depends on <isc/result.h>, but the
+ * DNS result codes are considered to be publicly derived from
+ * the ISC result codes, so including this file buys you the ISC_R_
+ * namespace too.
+ */
+#include <isc/result.h> /* Contractual promise. */
+
+/*
+ * DNS library result codes
+ */
+#define DNS_R_LABELTOOLONG (ISC_RESULTCLASS_DNS + 0)
+#define DNS_R_BADESCAPE (ISC_RESULTCLASS_DNS + 1)
+/*
+ * Since we dropped the support of bitstring labels, deprecate the related
+ * result codes too.
+ *
+ #define DNS_R_BADBITSTRING (ISC_RESULTCLASS_DNS + 2)
+ #define DNS_R_BITSTRINGTOOLONG (ISC_RESULTCLASS_DNS + 3)
+ */
+#define DNS_R_EMPTYLABEL (ISC_RESULTCLASS_DNS + 4)
+#define DNS_R_BADDOTTEDQUAD (ISC_RESULTCLASS_DNS + 5)
+#define DNS_R_INVALIDNS (ISC_RESULTCLASS_DNS + 6)
+#define DNS_R_UNKNOWN (ISC_RESULTCLASS_DNS + 7)
+#define DNS_R_BADLABELTYPE (ISC_RESULTCLASS_DNS + 8)
+#define DNS_R_BADPOINTER (ISC_RESULTCLASS_DNS + 9)
+#define DNS_R_TOOMANYHOPS (ISC_RESULTCLASS_DNS + 10)
+#define DNS_R_DISALLOWED (ISC_RESULTCLASS_DNS + 11)
+#define DNS_R_EXTRATOKEN (ISC_RESULTCLASS_DNS + 12)
+#define DNS_R_EXTRADATA (ISC_RESULTCLASS_DNS + 13)
+#define DNS_R_TEXTTOOLONG (ISC_RESULTCLASS_DNS + 14)
+#define DNS_R_NOTZONETOP (ISC_RESULTCLASS_DNS + 15)
+#define DNS_R_SYNTAX (ISC_RESULTCLASS_DNS + 16)
+#define DNS_R_BADCKSUM (ISC_RESULTCLASS_DNS + 17)
+#define DNS_R_BADAAAA (ISC_RESULTCLASS_DNS + 18)
+#define DNS_R_NOOWNER (ISC_RESULTCLASS_DNS + 19)
+#define DNS_R_NOTTL (ISC_RESULTCLASS_DNS + 20)
+#define DNS_R_BADCLASS (ISC_RESULTCLASS_DNS + 21)
+#define DNS_R_NAMETOOLONG (ISC_RESULTCLASS_DNS + 22)
+#define DNS_R_PARTIALMATCH (ISC_RESULTCLASS_DNS + 23)
+#define DNS_R_NEWORIGIN (ISC_RESULTCLASS_DNS + 24)
+#define DNS_R_UNCHANGED (ISC_RESULTCLASS_DNS + 25)
+#define DNS_R_BADTTL (ISC_RESULTCLASS_DNS + 26)
+#define DNS_R_NOREDATA (ISC_RESULTCLASS_DNS + 27)
+#define DNS_R_CONTINUE (ISC_RESULTCLASS_DNS + 28)
+#define DNS_R_DELEGATION (ISC_RESULTCLASS_DNS + 29)
+#define DNS_R_GLUE (ISC_RESULTCLASS_DNS + 30)
+#define DNS_R_DNAME (ISC_RESULTCLASS_DNS + 31)
+#define DNS_R_CNAME (ISC_RESULTCLASS_DNS + 32)
+#define DNS_R_BADDB (ISC_RESULTCLASS_DNS + 33)
+#define DNS_R_ZONECUT (ISC_RESULTCLASS_DNS + 34)
+#define DNS_R_BADZONE (ISC_RESULTCLASS_DNS + 35)
+#define DNS_R_MOREDATA (ISC_RESULTCLASS_DNS + 36)
+#define DNS_R_UPTODATE (ISC_RESULTCLASS_DNS + 37)
+#define DNS_R_TSIGVERIFYFAILURE (ISC_RESULTCLASS_DNS + 38)
+#define DNS_R_TSIGERRORSET (ISC_RESULTCLASS_DNS + 39)
+#define DNS_R_SIGINVALID (ISC_RESULTCLASS_DNS + 40)
+#define DNS_R_SIGEXPIRED (ISC_RESULTCLASS_DNS + 41)
+#define DNS_R_SIGFUTURE (ISC_RESULTCLASS_DNS + 42)
+#define DNS_R_KEYUNAUTHORIZED (ISC_RESULTCLASS_DNS + 43)
+#define DNS_R_INVALIDTIME (ISC_RESULTCLASS_DNS + 44)
+#define DNS_R_EXPECTEDTSIG (ISC_RESULTCLASS_DNS + 45)
+#define DNS_R_UNEXPECTEDTSIG (ISC_RESULTCLASS_DNS + 46)
+#define DNS_R_INVALIDTKEY (ISC_RESULTCLASS_DNS + 47)
+#define DNS_R_HINT (ISC_RESULTCLASS_DNS + 48)
+#define DNS_R_DROP (ISC_RESULTCLASS_DNS + 49)
+#define DNS_R_NOTLOADED (ISC_RESULTCLASS_DNS + 50)
+#define DNS_R_NCACHENXDOMAIN (ISC_RESULTCLASS_DNS + 51)
+#define DNS_R_NCACHENXRRSET (ISC_RESULTCLASS_DNS + 52)
+#define DNS_R_WAIT (ISC_RESULTCLASS_DNS + 53)
+#define DNS_R_NOTVERIFIEDYET (ISC_RESULTCLASS_DNS + 54)
+#define DNS_R_NOIDENTITY (ISC_RESULTCLASS_DNS + 55)
+#define DNS_R_NOJOURNAL (ISC_RESULTCLASS_DNS + 56)
+#define DNS_R_ALIAS (ISC_RESULTCLASS_DNS + 57)
+#define DNS_R_USETCP (ISC_RESULTCLASS_DNS + 58)
+#define DNS_R_NOVALIDSIG (ISC_RESULTCLASS_DNS + 59)
+#define DNS_R_NOVALIDNSEC (ISC_RESULTCLASS_DNS + 60)
+#define DNS_R_NOTINSECURE (ISC_RESULTCLASS_DNS + 61)
+#define DNS_R_UNKNOWNSERVICE (ISC_RESULTCLASS_DNS + 62)
+#define DNS_R_RECOVERABLE (ISC_RESULTCLASS_DNS + 63)
+#define DNS_R_UNKNOWNOPT (ISC_RESULTCLASS_DNS + 64)
+#define DNS_R_UNEXPECTEDID (ISC_RESULTCLASS_DNS + 65)
+#define DNS_R_SEENINCLUDE (ISC_RESULTCLASS_DNS + 66)
+#define DNS_R_NOTEXACT (ISC_RESULTCLASS_DNS + 67)
+#define DNS_R_BLACKHOLED (ISC_RESULTCLASS_DNS + 68)
+#define DNS_R_BADALG (ISC_RESULTCLASS_DNS + 69)
+#define DNS_R_METATYPE (ISC_RESULTCLASS_DNS + 70)
+#define DNS_R_CNAMEANDOTHER (ISC_RESULTCLASS_DNS + 71)
+#define DNS_R_SINGLETON (ISC_RESULTCLASS_DNS + 72)
+#define DNS_R_HINTNXRRSET (ISC_RESULTCLASS_DNS + 73)
+#define DNS_R_NOMASTERFILE (ISC_RESULTCLASS_DNS + 74)
+#define DNS_R_UNKNOWNPROTO (ISC_RESULTCLASS_DNS + 75)
+#define DNS_R_CLOCKSKEW (ISC_RESULTCLASS_DNS + 76)
+#define DNS_R_BADIXFR (ISC_RESULTCLASS_DNS + 77)
+#define DNS_R_NOTAUTHORITATIVE (ISC_RESULTCLASS_DNS + 78)
+#define DNS_R_NOVALIDKEY (ISC_RESULTCLASS_DNS + 79)
+#define DNS_R_OBSOLETE (ISC_RESULTCLASS_DNS + 80)
+#define DNS_R_FROZEN (ISC_RESULTCLASS_DNS + 81)
+#define DNS_R_UNKNOWNFLAG (ISC_RESULTCLASS_DNS + 82)
+#define DNS_R_EXPECTEDRESPONSE (ISC_RESULTCLASS_DNS + 83)
+#define DNS_R_NOVALIDDS (ISC_RESULTCLASS_DNS + 84)
+#define DNS_R_NSISADDRESS (ISC_RESULTCLASS_DNS + 85)
+#define DNS_R_REMOTEFORMERR (ISC_RESULTCLASS_DNS + 86)
+#define DNS_R_TRUNCATEDTCP (ISC_RESULTCLASS_DNS + 87)
+#define DNS_R_LAME (ISC_RESULTCLASS_DNS + 88)
+#define DNS_R_UNEXPECTEDRCODE (ISC_RESULTCLASS_DNS + 89)
+#define DNS_R_UNEXPECTEDOPCODE (ISC_RESULTCLASS_DNS + 90)
+#define DNS_R_CHASEDSSERVERS (ISC_RESULTCLASS_DNS + 91)
+#define DNS_R_EMPTYNAME (ISC_RESULTCLASS_DNS + 92)
+#define DNS_R_EMPTYWILD (ISC_RESULTCLASS_DNS + 93)
+#define DNS_R_BADBITMAP (ISC_RESULTCLASS_DNS + 94)
+#define DNS_R_FROMWILDCARD (ISC_RESULTCLASS_DNS + 95)
+#define DNS_R_BADOWNERNAME (ISC_RESULTCLASS_DNS + 96)
+#define DNS_R_BADNAME (ISC_RESULTCLASS_DNS + 97)
+#define DNS_R_DYNAMIC (ISC_RESULTCLASS_DNS + 98)
+#define DNS_R_UNKNOWNCOMMAND (ISC_RESULTCLASS_DNS + 99)
+#define DNS_R_MUSTBESECURE (ISC_RESULTCLASS_DNS + 100)
+#define DNS_R_COVERINGNSEC (ISC_RESULTCLASS_DNS + 101)
+#define DNS_R_MXISADDRESS (ISC_RESULTCLASS_DNS + 102)
+#define DNS_R_DUPLICATE (ISC_RESULTCLASS_DNS + 103)
+#define DNS_R_INVALIDNSEC3 (ISC_RESULTCLASS_DNS + 104)
+#define DNS_R_NOTMASTER (ISC_RESULTCLASS_DNS + 105)
+#define DNS_R_BROKENCHAIN (ISC_RESULTCLASS_DNS + 106)
+#define DNS_R_EXPIRED (ISC_RESULTCLASS_DNS + 107)
+#define DNS_R_NOTDYNAMIC (ISC_RESULTCLASS_DNS + 108)
+#define DNS_R_BADEUI (ISC_RESULTCLASS_DNS + 109)
+#define DNS_R_NTACOVERED (ISC_RESULTCLASS_DNS + 110)
+#define DNS_R_BADCDS (ISC_RESULTCLASS_DNS + 111)
+#define DNS_R_BADCDNSKEY (ISC_RESULTCLASS_DNS + 112)
+#define DNS_R_OPTERR (ISC_RESULTCLASS_DNS + 113)
+#define DNS_R_BADDNSTAP (ISC_RESULTCLASS_DNS + 114)
+#define DNS_R_BADTSIG (ISC_RESULTCLASS_DNS + 115)
+#define DNS_R_BADSIG0 (ISC_RESULTCLASS_DNS + 116)
+#define DNS_R_TOOMANYRECORDS (ISC_RESULTCLASS_DNS + 117)
+#define DNS_R_VERIFYFAILURE (ISC_RESULTCLASS_DNS + 118)
+#define DNS_R_ATZONETOP (ISC_RESULTCLASS_DNS + 119)
+#define DNS_R_NOKEYMATCH (ISC_RESULTCLASS_DNS + 120)
+#define DNS_R_TOOMANYKEYS (ISC_RESULTCLASS_DNS + 121)
+#define DNS_R_KEYNOTACTIVE (ISC_RESULTCLASS_DNS + 122)
+#define DNS_R_NSEC3ITERRANGE (ISC_RESULTCLASS_DNS + 123)
+#define DNS_R_NSEC3SALTRANGE (ISC_RESULTCLASS_DNS + 124)
+#define DNS_R_NSEC3BADALG (ISC_RESULTCLASS_DNS + 125)
+#define DNS_R_NSEC3RESALT (ISC_RESULTCLASS_DNS + 126)
+#define DNS_R_INCONSISTENTRR (ISC_RESULTCLASS_DNS + 127)
+
+#define DNS_R_NRESULTS 128 /*%< Number of results */
+
+/*
+ * DNS wire format rcodes.
+ *
+ * By making these their own class we can easily convert them into the
+ * wire-format rcode value simply by masking off the resultclass.
+ */
+#define DNS_R_NOERROR (ISC_RESULTCLASS_DNSRCODE + 0)
+#define DNS_R_FORMERR (ISC_RESULTCLASS_DNSRCODE + 1)
+#define DNS_R_SERVFAIL (ISC_RESULTCLASS_DNSRCODE + 2)
+#define DNS_R_NXDOMAIN (ISC_RESULTCLASS_DNSRCODE + 3)
+#define DNS_R_NOTIMP (ISC_RESULTCLASS_DNSRCODE + 4)
+#define DNS_R_REFUSED (ISC_RESULTCLASS_DNSRCODE + 5)
+#define DNS_R_YXDOMAIN (ISC_RESULTCLASS_DNSRCODE + 6)
+#define DNS_R_YXRRSET (ISC_RESULTCLASS_DNSRCODE + 7)
+#define DNS_R_NXRRSET (ISC_RESULTCLASS_DNSRCODE + 8)
+#define DNS_R_NOTAUTH (ISC_RESULTCLASS_DNSRCODE + 9)
+#define DNS_R_NOTZONE (ISC_RESULTCLASS_DNSRCODE + 10)
+#define DNS_R_RCODE11 (ISC_RESULTCLASS_DNSRCODE + 11)
+#define DNS_R_RCODE12 (ISC_RESULTCLASS_DNSRCODE + 12)
+#define DNS_R_RCODE13 (ISC_RESULTCLASS_DNSRCODE + 13)
+#define DNS_R_RCODE14 (ISC_RESULTCLASS_DNSRCODE + 14)
+#define DNS_R_RCODE15 (ISC_RESULTCLASS_DNSRCODE + 15)
+#define DNS_R_BADVERS (ISC_RESULTCLASS_DNSRCODE + 16)
+
+#define DNS_R_NRCODERESULTS 17 /*%< Number of rcode results */
+
+#define DNS_RESULT_ISRCODE(result) \
+ (ISC_RESULTCLASS_INCLASS(ISC_RESULTCLASS_DNSRCODE, (result)))
+
+ISC_LANG_BEGINDECLS
+
+const char *dns_result_totext(isc_result_t);
+
+void
+dns_result_register(void);
+
+dns_rcode_t
+dns_result_torcode(isc_result_t result);
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_RESULT_H */
diff --git a/lib/dns/include/dns/rootns.h b/lib/dns/include/dns/rootns.h
new file mode 100644
index 0000000..140538b
--- /dev/null
+++ b/lib/dns/include/dns/rootns.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_ROOTNS_H
+#define DNS_ROOTNS_H 1
+
+/*! \file dns/rootns.h */
+
+#include <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
+
+#endif /* DNS_ROOTNS_H */
diff --git a/lib/dns/include/dns/rpz.h b/lib/dns/include/dns/rpz.h
new file mode 100644
index 0000000..f0cc578
--- /dev/null
+++ b/lib/dns/include/dns/rpz.h
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RPZ_H
+#define DNS_RPZ_H 1
+
+#include <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 {
+ isc_refcount_t refs;
+ dns_rpz_num_t num; /* ordinal in list of policy zones */
+ dns_name_t origin; /* Policy zone name */
+ dns_name_t client_ip; /* DNS_RPZ_CLIENT_IP_ZONE.origin. */
+ dns_name_t ip; /* DNS_RPZ_IP_ZONE.origin. */
+ dns_name_t nsdname; /* DNS_RPZ_NSDNAME_ZONE.origin */
+ dns_name_t nsip; /* DNS_RPZ_NSIP_ZONE.origin. */
+ dns_name_t passthru; /* DNS_RPZ_PASSTHRU_NAME. */
+ dns_name_t drop; /* DNS_RPZ_DROP_NAME. */
+ dns_name_t tcp_only; /* DNS_RPZ_TCP_ONLY_NAME. */
+ dns_name_t cname; /* override value for ..._CNAME */
+ dns_ttl_t max_policy_ttl;
+ dns_rpz_policy_t policy; /* DNS_RPZ_POLICY_GIVEN or override */
+
+ uint32_t min_update_interval; /* minimal interval between
+ * updates */
+ isc_ht_t *nodes; /* entries in zone */
+ dns_rpz_zones_t *rpzs; /* owner */
+ isc_time_t lastupdated; /* last time the zone was processed
+ * */
+ bool updatepending; /* there is an update
+ * pending/waiting */
+ bool updaterunning; /* there is an update running */
+ dns_db_t *db; /* zones database */
+ dns_dbversion_t *dbversion; /* version we will be updating to */
+ dns_db_t *updb; /* zones database we're working on */
+ dns_dbversion_t *updbversion; /* version we're currently working
+ * on */
+ dns_dbiterator_t *updbit; /* iterator to use when updating */
+ isc_ht_t *newnodes; /* entries in zone being updated */
+ bool db_registered; /* is the notify event
+ * registered? */
+ bool addsoa; /* add soa to the additional section */
+ isc_timer_t *updatetimer;
+ isc_event_t updateevent;
+};
+
+/*
+ * Radix tree node for response policy IP addresses
+ */
+typedef struct dns_rpz_cidr_node dns_rpz_cidr_node_t;
+
+/*
+ * Bitfields indicating which policy zones have policies of
+ * which type.
+ */
+typedef struct dns_rpz_have dns_rpz_have_t;
+struct dns_rpz_have {
+ dns_rpz_zbits_t client_ipv4;
+ dns_rpz_zbits_t client_ipv6;
+ dns_rpz_zbits_t client_ip;
+ dns_rpz_zbits_t qname;
+ dns_rpz_zbits_t ipv4;
+ dns_rpz_zbits_t ipv6;
+ dns_rpz_zbits_t ip;
+ dns_rpz_zbits_t nsdname;
+ dns_rpz_zbits_t nsipv4;
+ dns_rpz_zbits_t nsipv6;
+ dns_rpz_zbits_t nsip;
+ dns_rpz_zbits_t qname_skip_recurse;
+};
+
+/*
+ * Policy options
+ */
+typedef struct dns_rpz_popt dns_rpz_popt_t;
+struct dns_rpz_popt {
+ dns_rpz_zbits_t no_rd_ok;
+ dns_rpz_zbits_t no_log;
+ dns_rpz_zbits_t nsip_on;
+ dns_rpz_zbits_t nsdname_on;
+ bool dnsrps_enabled;
+ bool break_dnssec;
+ bool qname_wait_recurse;
+ bool nsip_wait_recurse;
+ unsigned int min_ns_labels;
+ dns_rpz_num_t num_zones;
+};
+
+/*
+ * Response policy zones known to a view.
+ */
+struct dns_rpz_zones {
+ dns_rpz_popt_t p;
+ dns_rpz_zone_t *zones[DNS_RPZ_MAX_ZONES];
+ dns_rpz_triggers_t triggers[DNS_RPZ_MAX_ZONES];
+
+ /*
+ * RPZ policy version number.
+ * It is initially 0 and it increases whenever the server is
+ * reconfigured with new zones or policy.
+ */
+ int rpz_ver;
+
+ dns_rpz_zbits_t defined;
+
+ /*
+ * The set of records for a policy zone are in one of these states:
+ * never loaded load_begun=0 have=0
+ * during initial loading load_begun=1 have=0
+ * and rbtdb->rpzsp == rbtdb->load_rpzsp
+ * after good load load_begun=1 have!=0
+ * after failed initial load load_begun=1 have=0
+ * and rbtdb->load_rpzsp == NULL
+ * reloading after failure load_begun=1 have=0
+ * reloading after success
+ * main rpzs load_begun=1 have!=0
+ * load rpzs load_begun=1 have=0
+ */
+ dns_rpz_zbits_t load_begun;
+ dns_rpz_have_t have;
+
+ /*
+ * total_triggers maintains the total number of triggers in all
+ * policy zones in the view. It is only used to print summary
+ * statistics after a zone load of how the trigger counts
+ * changed.
+ */
+ dns_rpz_triggers_t total_triggers;
+
+ isc_mem_t *mctx;
+ isc_taskmgr_t *taskmgr;
+ isc_timermgr_t *timermgr;
+ isc_task_t *updater;
+ isc_refcount_t refs;
+ isc_refcount_t irefs;
+ /*
+ * One lock for short term read-only search that guarantees the
+ * consistency of the pointers.
+ * A second lock for maintenance that guarantees no other thread
+ * is adding or deleting nodes.
+ */
+ isc_rwlock_t search_lock;
+ isc_mutex_t maint_lock;
+
+ dns_rpz_cidr_node_t *cidr;
+ dns_rbt_t *rbt;
+
+ /*
+ * DNSRPZ librpz configuration string and handle on librpz connection
+ */
+ char *rps_cstr;
+ size_t rps_cstr_size;
+ struct librpz_client *rps_client;
+};
+
+/*
+ * context for finding the best policy
+ */
+typedef struct {
+ unsigned int state;
+#define DNS_RPZ_REWRITTEN 0x0001
+#define DNS_RPZ_DONE_CLIENT_IP 0x0002 /* client IP address checked */
+#define DNS_RPZ_DONE_QNAME 0x0004 /* qname checked */
+#define DNS_RPZ_DONE_QNAME_IP 0x0008 /* IP addresses of qname checked */
+#define DNS_RPZ_DONE_NSDNAME 0x0010 /* NS name missed; checking addresses */
+#define DNS_RPZ_DONE_IPv4 0x0020
+#define DNS_RPZ_RECURSING 0x0040
+#define DNS_RPZ_ACTIVE 0x0080
+ /*
+ * Best match so far.
+ */
+ struct {
+ dns_rpz_type_t type;
+ dns_rpz_zone_t *rpz;
+ dns_rpz_prefix_t prefix;
+ dns_rpz_policy_t policy;
+ dns_ttl_t ttl;
+ isc_result_t result;
+ dns_zone_t *zone;
+ dns_db_t *db;
+ dns_dbversion_t *version;
+ dns_dbnode_t *node;
+ dns_rdataset_t *rdataset;
+ } m;
+ /*
+ * State for chasing IP addresses and NS names including recursion.
+ */
+ struct {
+ unsigned int label;
+ dns_db_t *db;
+ dns_rdataset_t *ns_rdataset;
+ dns_rdatatype_t r_type;
+ isc_result_t r_result;
+ dns_rdataset_t *r_rdataset;
+ } r;
+
+ /*
+ * State of real query while recursing for NSIP or NSDNAME.
+ */
+ struct {
+ isc_result_t result;
+ bool is_zone;
+ bool authoritative;
+ dns_zone_t *zone;
+ dns_db_t *db;
+ dns_dbnode_t *node;
+ dns_rdataset_t *rdataset;
+ dns_rdataset_t *sigrdataset;
+ dns_rdatatype_t qtype;
+ } q;
+
+ /*
+ * A copy of the 'have' and 'p' structures and the RPZ
+ * policy version as of the beginning of RPZ processing,
+ * used to avoid problems when policy is updated while
+ * RPZ recursion is ongoing.
+ */
+ dns_rpz_have_t have;
+ dns_rpz_popt_t popt;
+ int rpz_ver;
+
+ /*
+ * Shim db between BIND and DNRPS librpz.
+ */
+ dns_db_t *rpsdb;
+
+ /*
+ * p_name: current policy owner name
+ * r_name: recursing for this name to possible policy triggers
+ * f_name: saved found name from before recursion
+ */
+ dns_name_t *p_name;
+ dns_name_t *r_name;
+ dns_name_t *fname;
+ dns_fixedname_t _p_namef;
+ dns_fixedname_t _r_namef;
+ dns_fixedname_t _fnamef;
+} dns_rpz_st_t;
+
+#define DNS_RPZ_TTL_DEFAULT 5
+#define DNS_RPZ_MAX_TTL_DEFAULT DNS_RPZ_TTL_DEFAULT
+#define DNS_RPZ_MINUPDATEINTERVAL_DEFAULT 60
+
+/*
+ * So various response policy zone messages can be turned up or down.
+ */
+#define DNS_RPZ_ERROR_LEVEL ISC_LOG_WARNING
+#define DNS_RPZ_INFO_LEVEL ISC_LOG_INFO
+#define DNS_RPZ_DEBUG_LEVEL1 ISC_LOG_DEBUG(1)
+#define DNS_RPZ_DEBUG_LEVEL2 ISC_LOG_DEBUG(2)
+#define DNS_RPZ_DEBUG_LEVEL3 ISC_LOG_DEBUG(3)
+#define DNS_RPZ_DEBUG_QUIET (DNS_RPZ_DEBUG_LEVEL3 + 1)
+
+const char *
+dns_rpz_type2str(dns_rpz_type_t type);
+
+dns_rpz_policy_t
+dns_rpz_str2policy(const char *str);
+
+const char *
+dns_rpz_policy2str(dns_rpz_policy_t policy);
+
+dns_rpz_policy_t
+dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset,
+ dns_name_t *selfname);
+
+isc_result_t
+dns_rpz_new_zones(dns_rpz_zones_t **rpzsp, char *rps_cstr, size_t rps_cstr_size,
+ isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr);
+
+isc_result_t
+dns_rpz_new_zone(dns_rpz_zones_t *rpzs, dns_rpz_zone_t **rpzp);
+
+isc_result_t
+dns_rpz_dbupdate_callback(dns_db_t *db, void *fn_arg);
+
+void
+dns_rpz_attach_rpzs(dns_rpz_zones_t *source, dns_rpz_zones_t **target);
+
+void
+dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp);
+
+isc_result_t
+dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp, dns_rpz_zones_t *rpzs,
+ dns_rpz_num_t rpz_num) ISC_DEPRECATED;
+
+isc_result_t
+dns_rpz_ready(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **load_rpzsp,
+ dns_rpz_num_t rpz_num) ISC_DEPRECATED;
+
+isc_result_t
+dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+ const dns_name_t *name);
+
+void
+dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+ const dns_name_t *name);
+
+dns_rpz_num_t
+dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+ dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr,
+ dns_name_t *ip_name, dns_rpz_prefix_t *prefixp);
+
+dns_rpz_zbits_t
+dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+ dns_rpz_zbits_t zbits, dns_name_t *trig_name);
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_RPZ_H */
diff --git a/lib/dns/include/dns/rriterator.h b/lib/dns/include/dns/rriterator.h
new file mode 100644
index 0000000..2294d69
--- /dev/null
+++ b/lib/dns/include/dns/rriterator.h
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RRITERATOR_H
+#define DNS_RRITERATOR_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/rriterator.h
+ * \brief
+ * Functions for "walking" a zone database, visiting each RR or RRset in turn.
+ */
+
+/*****
+***** Imports
+*****/
+
+#include <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
+
+#endif /* DNS_RRITERATOR_H */
diff --git a/lib/dns/include/dns/rrl.h b/lib/dns/include/dns/rrl.h
new file mode 100644
index 0000000..fabefa3
--- /dev/null
+++ b/lib/dns/include/dns/rrl.h
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RRL_H
+#define DNS_RRL_H 1
+
+/*
+ * Rate limit DNS responses.
+ */
+
+#include <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
+
+#endif /* DNS_RRL_H */
diff --git a/lib/dns/include/dns/sdb.h b/lib/dns/include/dns/sdb.h
new file mode 100644
index 0000000..578d7cc
--- /dev/null
+++ b/lib/dns/include/dns/sdb.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_SDB_H
+#define DNS_SDB_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/sdb.h
+ * \brief
+ * Simple database API.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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 master server and 'rname' as
+ * the responsible person mailbox. It is the responsibility of the
+ * driver to increment the serial number between responses if necessary.
+ * All other SOA fields will have reasonable default values.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_SDB_H */
diff --git a/lib/dns/include/dns/sdlz.h b/lib/dns/include/dns/sdlz.h
new file mode 100644
index 0000000..6a06c98
--- /dev/null
+++ b/lib/dns/include/dns/sdlz.h
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
+ * conceived and contributed by Rob Butler.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file dns/sdlz.h */
+
+#ifndef SDLZ_H
+#define SDLZ_H 1
+
+#include <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 master server and
+ * 'rname' as the responsible person mailbox. It is the
+ * responsibility of the driver to increment the serial number between
+ * responses if necessary. All other SOA fields will have reasonable
+ * default values.
+ */
+
+typedef isc_result_t
+dns_sdlz_setdb_t(dns_dlzdb_t *dlzdatabase, dns_rdataclass_t rdclass,
+ const dns_name_t *name, dns_db_t **dbp);
+dns_sdlz_setdb_t dns_sdlz_setdb;
+/*%<
+ * Create the database pointers for a writeable SDLZ zone
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* SDLZ_H */
diff --git a/lib/dns/include/dns/secalg.h b/lib/dns/include/dns/secalg.h
new file mode 100644
index 0000000..6ca61cb
--- /dev/null
+++ b/lib/dns/include/dns/secalg.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_SECALG_H
+#define DNS_SECALG_H 1
+
+/*! \file dns/secalg.h */
+
+#include <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
+
+#endif /* DNS_SECALG_H */
diff --git a/lib/dns/include/dns/secproto.h b/lib/dns/include/dns/secproto.h
new file mode 100644
index 0000000..7eccfab
--- /dev/null
+++ b/lib/dns/include/dns/secproto.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_SECPROTO_H
+#define DNS_SECPROTO_H 1
+
+/*! \file dns/secproto.h */
+
+#include <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
+
+#endif /* DNS_SECPROTO_H */
diff --git a/lib/dns/include/dns/soa.h b/lib/dns/include/dns/soa.h
new file mode 100644
index 0000000..a80a132
--- /dev/null
+++ b/lib/dns/include/dns/soa.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_SOA_H
+#define DNS_SOA_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/soa.h
+ * \brief
+ * SOA utilities.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <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
+
+#endif /* DNS_SOA_H */
diff --git a/lib/dns/include/dns/ssu.h b/lib/dns/include/dns/ssu.h
new file mode 100644
index 0000000..11d40f3
--- /dev/null
+++ b/lib/dns/include/dns/ssu.h
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_SSU_H
+#define DNS_SSU_H 1
+
+/*! \file dns/ssu.h */
+
+#include <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_max = 15, /* max value */
+
+ dns_ssumatchtype_dlz = 16 /* intentionally higher than _max */
+} dns_ssumatchtype_t;
+
+isc_result_t
+dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **table);
+/*%<
+ * Creates a table that will be used to store simple-secure-update rules.
+ * Note: all locking must be provided by the client.
+ *
+ * Requires:
+ *\li 'mctx' is a valid memory context
+ *\li 'table' is not NULL, and '*table' is NULL
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep,
+ dns_dlzdb_t *dlzdatabase);
+/*%<
+ * Create an SSU table that contains a dlzdatabase pointer, and a
+ * single rule with matchtype dns_ssumatchtype_dlz. This type of SSU
+ * table is used by writeable DLZ drivers to offload authorization for
+ * updates to the driver.
+ */
+
+void
+dns_ssutable_attach(dns_ssutable_t *source, dns_ssutable_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *\li 'source' is a valid SSU table
+ *\li 'targetp' points to a NULL dns_ssutable_t *.
+ *
+ * Ensures:
+ *\li *targetp is attached to source.
+ */
+
+void
+dns_ssutable_detach(dns_ssutable_t **tablep);
+/*%<
+ * Detach '*tablep' from its simple-secure-update rule table.
+ *
+ * Requires:
+ *\li 'tablep' points to a valid dns_ssutable_t
+ *
+ * Ensures:
+ *\li *tablep is NULL
+ *\li If '*tablep' is the last reference to the SSU table, all
+ * resources used by the table will be freed.
+ */
+
+isc_result_t
+dns_ssutable_addrule(dns_ssutable_t *table, bool grant,
+ const dns_name_t *identity, dns_ssumatchtype_t matchtype,
+ const dns_name_t *name, unsigned int ntypes,
+ dns_rdatatype_t *types);
+/*%<
+ * Adds a new rule to a simple-secure-update rule table. The rule
+ * either grants or denies update privileges of an identity (or set of
+ * identities) to modify a name (or set of names) or certain types present
+ * at that name.
+ *
+ * Notes:
+ *\li If 'matchtype' is of SELF type, this rule only matches if the
+ * name to be updated matches the signing identity.
+ *
+ *\li If 'ntypes' is 0, this rule applies to all types except
+ * NS, SOA, RRSIG, and NSEC.
+ *
+ *\li If 'types' includes ANY, this rule applies to all types
+ * except NSEC.
+ *
+ * Requires:
+ *\li 'table' is a valid SSU table
+ *\li 'identity' is a valid absolute name
+ *\li 'matchtype' must be one of the defined constants.
+ *\li 'name' is a valid absolute name
+ *\li If 'ntypes' > 0, 'types' must not be NULL
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOMEMORY
+ */
+
+bool
+dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *addr,
+ bool tcp, const dns_aclenv_t *env, dns_rdatatype_t type,
+ const dst_key_t *key);
+/*%<
+ * Checks that the attempted update of (name, type) is allowed according
+ * to the rules specified in the simple-secure-update rule table. If
+ * no rules are matched, access is denied.
+ *
+ * Notes:
+ * In dns_ssutable_checkrules(), 'addr' should only be
+ * set if the request received via TCP. This provides a
+ * weak assurance that the request was not spoofed.
+ * 'addr' is to to validate dns_ssumatchtype_tcpself
+ * and dns_ssumatchtype_6to4self rules.
+ *
+ * In dns_ssutable_checkrules2(), 'addr' can also be passed for
+ * UDP requests and TCP is specified via the 'tcp' parameter.
+ * In addition to dns_ssumatchtype_tcpself and
+ * tcp_ssumatchtype_6to4self rules, the address
+ * also be used to check dns_ssumatchtype_local rules.
+ * If 'addr' is set then 'env' must also be set so that
+ * requests from non-localhost addresses can be rejected.
+ *
+ * For dns_ssumatchtype_tcpself the addresses are mapped to
+ * the standard reverse names under IN-ADDR.ARPA and IP6.ARPA.
+ * RFC 1035, Section 3.5, "IN-ADDR.ARPA domain" and RFC 3596,
+ * Section 2.5, "IP6.ARPA Domain".
+ *
+ * For dns_ssumatchtype_6to4self, IPv4 address are converted
+ * to a 6to4 prefix (48 bits) per the rules in RFC 3056. Only
+ * the top 48 bits of the IPv6 address are mapped to the reverse
+ * name. This is independent of whether the most significant 16
+ * bits match 2002::/16, assigned for 6to4 prefixes, or not.
+ *
+ * Requires:
+ *\li 'table' is a valid SSU table
+ *\li 'signer' is NULL or a valid absolute name
+ *\li 'addr' is NULL or a valid network address.
+ *\li 'aclenv' is NULL or a valid ACL environment.
+ *\li 'name' is a valid absolute name
+ *\li if 'addr' is not NULL, 'env' is not NULL.
+ */
+
+/*% Accessor functions to extract rule components */
+bool
+dns_ssurule_isgrant(const dns_ssurule_t *rule);
+/*% Accessor functions to extract rule components */
+dns_name_t *
+dns_ssurule_identity(const dns_ssurule_t *rule);
+/*% Accessor functions to extract rule components */
+unsigned int
+dns_ssurule_matchtype(const dns_ssurule_t *rule);
+/*% Accessor functions to extract rule components */
+dns_name_t *
+dns_ssurule_name(const dns_ssurule_t *rule);
+/*% Accessor functions to extract rule components */
+unsigned int
+dns_ssurule_types(const dns_ssurule_t *rule, dns_rdatatype_t **types);
+
+isc_result_t
+dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule);
+/*%<
+ * Initiates a rule iterator. There is no need to maintain any state.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE
+ */
+
+isc_result_t
+dns_ssutable_nextrule(dns_ssurule_t *rule, dns_ssurule_t **nextrule);
+/*%<
+ * Returns the next rule in the table.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE
+ */
+
+bool
+dns_ssu_external_match(const dns_name_t *identity, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *tcpaddr,
+ dns_rdatatype_t type, const dst_key_t *key,
+ isc_mem_t *mctx);
+/*%<
+ * Check a policy rule via an external application
+ */
+
+isc_result_t
+dns_ssu_mtypefromstring(const char *str, dns_ssumatchtype_t *mtype);
+/*%<
+ * Set 'mtype' from 'str'
+ *
+ * Requires:
+ *\li 'str' is not NULL.
+ *\li 'mtype' is not NULL,
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTFOUND
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_SSU_H */
diff --git a/lib/dns/include/dns/stats.h b/lib/dns/include/dns/stats.h
new file mode 100644
index 0000000..fd1697e
--- /dev/null
+++ b/lib/dns/include/dns/stats.h
@@ -0,0 +1,826 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_STATS_H
+#define DNS_STATS_H 1
+
+/*! \file dns/stats.h */
+
+#include <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_nextitem = 43,
+ dns_resstatscounter_priming = 44,
+ dns_resstatscounter_max = 45,
+
+ /*
+ * DNSSEC stats.
+ */
+ dns_dnssecstats_asis = 0,
+ dns_dnssecstats_downcase = 1,
+ dns_dnssecstats_wildcard = 2,
+ dns_dnssecstats_fail = 3,
+
+ dns_dnssecstats_max = 4,
+
+ /*%
+ * Zone statistics counters.
+ */
+ dns_zonestatscounter_notifyoutv4 = 0,
+ dns_zonestatscounter_notifyoutv6 = 1,
+ dns_zonestatscounter_notifyinv4 = 2,
+ dns_zonestatscounter_notifyinv6 = 3,
+ dns_zonestatscounter_notifyrej = 4,
+ dns_zonestatscounter_soaoutv4 = 5,
+ dns_zonestatscounter_soaoutv6 = 6,
+ dns_zonestatscounter_axfrreqv4 = 7,
+ dns_zonestatscounter_axfrreqv6 = 8,
+ dns_zonestatscounter_ixfrreqv4 = 9,
+ dns_zonestatscounter_ixfrreqv6 = 10,
+ dns_zonestatscounter_xfrsuccess = 11,
+ dns_zonestatscounter_xfrfail = 12,
+
+ dns_zonestatscounter_max = 13,
+
+ /*
+ * Adb statistics values.
+ */
+ dns_adbstats_nentries = 0,
+ dns_adbstats_entriescnt = 1,
+ dns_adbstats_nnames = 2,
+ dns_adbstats_namescnt = 3,
+
+ dns_adbstats_max = 4,
+
+ /*
+ * Cache statistics values.
+ */
+ dns_cachestatscounter_hits = 1,
+ dns_cachestatscounter_misses = 2,
+ dns_cachestatscounter_queryhits = 3,
+ dns_cachestatscounter_querymisses = 4,
+ dns_cachestatscounter_deletelru = 5,
+ dns_cachestatscounter_deletettl = 6,
+
+ dns_cachestatscounter_max = 7,
+
+ /*%
+ * Query statistics counters (obsolete).
+ */
+ dns_statscounter_success = 0, /*%< Successful lookup */
+ dns_statscounter_referral = 1, /*%< Referral result */
+ dns_statscounter_nxrrset = 2, /*%< NXRRSET result */
+ dns_statscounter_nxdomain = 3, /*%< NXDOMAIN result */
+ dns_statscounter_recursion = 4, /*%< Recursion was used */
+ dns_statscounter_failure = 5, /*%< Some other failure */
+ dns_statscounter_duplicate = 6, /*%< Duplicate query */
+ dns_statscounter_dropped = 7, /*%< Duplicate query (dropped) */
+
+ /*%
+ * DNSTAP statistics counters.
+ */
+ dns_dnstapcounter_success = 0,
+ dns_dnstapcounter_drop = 1,
+ dns_dnstapcounter_max = 2,
+
+ /*
+ * Glue cache statistics counters.
+ */
+ dns_gluecachestatscounter_hits_present = 0,
+ dns_gluecachestatscounter_hits_absent = 1,
+ dns_gluecachestatscounter_inserts_present = 2,
+ dns_gluecachestatscounter_inserts_absent = 3,
+
+ dns_gluecachestatscounter_max = 4,
+};
+
+/*%
+ * Traffic size statistics counters. Used as isc_statscounter_t values.
+ */
+enum {
+ dns_sizecounter_in_0 = 0,
+ dns_sizecounter_in_16 = 1,
+ dns_sizecounter_in_32 = 2,
+ dns_sizecounter_in_48 = 3,
+ dns_sizecounter_in_64 = 4,
+ dns_sizecounter_in_80 = 5,
+ dns_sizecounter_in_96 = 6,
+ dns_sizecounter_in_112 = 7,
+ dns_sizecounter_in_128 = 8,
+ dns_sizecounter_in_144 = 9,
+ dns_sizecounter_in_160 = 10,
+ dns_sizecounter_in_176 = 11,
+ dns_sizecounter_in_192 = 12,
+ dns_sizecounter_in_208 = 13,
+ dns_sizecounter_in_224 = 14,
+ dns_sizecounter_in_240 = 15,
+ dns_sizecounter_in_256 = 16,
+ dns_sizecounter_in_272 = 17,
+ dns_sizecounter_in_288 = 18,
+
+ dns_sizecounter_in_max = 19,
+};
+
+enum {
+ dns_sizecounter_out_0 = 0,
+ dns_sizecounter_out_16 = 1,
+ dns_sizecounter_out_32 = 2,
+ dns_sizecounter_out_48 = 3,
+ dns_sizecounter_out_64 = 4,
+ dns_sizecounter_out_80 = 5,
+ dns_sizecounter_out_96 = 6,
+ dns_sizecounter_out_112 = 7,
+ dns_sizecounter_out_128 = 8,
+ dns_sizecounter_out_144 = 9,
+ dns_sizecounter_out_160 = 10,
+ dns_sizecounter_out_176 = 11,
+ dns_sizecounter_out_192 = 12,
+ dns_sizecounter_out_208 = 13,
+ dns_sizecounter_out_224 = 14,
+ dns_sizecounter_out_240 = 15,
+ dns_sizecounter_out_256 = 16,
+ dns_sizecounter_out_272 = 17,
+ dns_sizecounter_out_288 = 18,
+ dns_sizecounter_out_304 = 19,
+ dns_sizecounter_out_320 = 20,
+ dns_sizecounter_out_336 = 21,
+ dns_sizecounter_out_352 = 22,
+ dns_sizecounter_out_368 = 23,
+ dns_sizecounter_out_384 = 24,
+ dns_sizecounter_out_400 = 25,
+ dns_sizecounter_out_416 = 26,
+ dns_sizecounter_out_432 = 27,
+ dns_sizecounter_out_448 = 28,
+ dns_sizecounter_out_464 = 29,
+ dns_sizecounter_out_480 = 30,
+ dns_sizecounter_out_496 = 31,
+ dns_sizecounter_out_512 = 32,
+ dns_sizecounter_out_528 = 33,
+ dns_sizecounter_out_544 = 34,
+ dns_sizecounter_out_560 = 35,
+ dns_sizecounter_out_576 = 36,
+ dns_sizecounter_out_592 = 37,
+ dns_sizecounter_out_608 = 38,
+ dns_sizecounter_out_624 = 39,
+ dns_sizecounter_out_640 = 40,
+ dns_sizecounter_out_656 = 41,
+ dns_sizecounter_out_672 = 42,
+ dns_sizecounter_out_688 = 43,
+ dns_sizecounter_out_704 = 44,
+ dns_sizecounter_out_720 = 45,
+ dns_sizecounter_out_736 = 46,
+ dns_sizecounter_out_752 = 47,
+ dns_sizecounter_out_768 = 48,
+ dns_sizecounter_out_784 = 49,
+ dns_sizecounter_out_800 = 50,
+ dns_sizecounter_out_816 = 51,
+ dns_sizecounter_out_832 = 52,
+ dns_sizecounter_out_848 = 53,
+ dns_sizecounter_out_864 = 54,
+ dns_sizecounter_out_880 = 55,
+ dns_sizecounter_out_896 = 56,
+ dns_sizecounter_out_912 = 57,
+ dns_sizecounter_out_928 = 58,
+ dns_sizecounter_out_944 = 59,
+ dns_sizecounter_out_960 = 60,
+ dns_sizecounter_out_976 = 61,
+ dns_sizecounter_out_992 = 62,
+ dns_sizecounter_out_1008 = 63,
+ dns_sizecounter_out_1024 = 64,
+ dns_sizecounter_out_1040 = 65,
+ dns_sizecounter_out_1056 = 66,
+ dns_sizecounter_out_1072 = 67,
+ dns_sizecounter_out_1088 = 68,
+ dns_sizecounter_out_1104 = 69,
+ dns_sizecounter_out_1120 = 70,
+ dns_sizecounter_out_1136 = 71,
+ dns_sizecounter_out_1152 = 72,
+ dns_sizecounter_out_1168 = 73,
+ dns_sizecounter_out_1184 = 74,
+ dns_sizecounter_out_1200 = 75,
+ dns_sizecounter_out_1216 = 76,
+ dns_sizecounter_out_1232 = 77,
+ dns_sizecounter_out_1248 = 78,
+ dns_sizecounter_out_1264 = 79,
+ dns_sizecounter_out_1280 = 80,
+ dns_sizecounter_out_1296 = 81,
+ dns_sizecounter_out_1312 = 82,
+ dns_sizecounter_out_1328 = 83,
+ dns_sizecounter_out_1344 = 84,
+ dns_sizecounter_out_1360 = 85,
+ dns_sizecounter_out_1376 = 86,
+ dns_sizecounter_out_1392 = 87,
+ dns_sizecounter_out_1408 = 88,
+ dns_sizecounter_out_1424 = 89,
+ dns_sizecounter_out_1440 = 90,
+ dns_sizecounter_out_1456 = 91,
+ dns_sizecounter_out_1472 = 92,
+ dns_sizecounter_out_1488 = 93,
+ dns_sizecounter_out_1504 = 94,
+ dns_sizecounter_out_1520 = 95,
+ dns_sizecounter_out_1536 = 96,
+ dns_sizecounter_out_1552 = 97,
+ dns_sizecounter_out_1568 = 98,
+ dns_sizecounter_out_1584 = 99,
+ dns_sizecounter_out_1600 = 100,
+ dns_sizecounter_out_1616 = 101,
+ dns_sizecounter_out_1632 = 102,
+ dns_sizecounter_out_1648 = 103,
+ dns_sizecounter_out_1664 = 104,
+ dns_sizecounter_out_1680 = 105,
+ dns_sizecounter_out_1696 = 106,
+ dns_sizecounter_out_1712 = 107,
+ dns_sizecounter_out_1728 = 108,
+ dns_sizecounter_out_1744 = 109,
+ dns_sizecounter_out_1760 = 110,
+ dns_sizecounter_out_1776 = 111,
+ dns_sizecounter_out_1792 = 112,
+ dns_sizecounter_out_1808 = 113,
+ dns_sizecounter_out_1824 = 114,
+ dns_sizecounter_out_1840 = 115,
+ dns_sizecounter_out_1856 = 116,
+ dns_sizecounter_out_1872 = 117,
+ dns_sizecounter_out_1888 = 118,
+ dns_sizecounter_out_1904 = 119,
+ dns_sizecounter_out_1920 = 120,
+ dns_sizecounter_out_1936 = 121,
+ dns_sizecounter_out_1952 = 122,
+ dns_sizecounter_out_1968 = 123,
+ dns_sizecounter_out_1984 = 124,
+ dns_sizecounter_out_2000 = 125,
+ dns_sizecounter_out_2016 = 126,
+ dns_sizecounter_out_2032 = 127,
+ dns_sizecounter_out_2048 = 128,
+ dns_sizecounter_out_2064 = 129,
+ dns_sizecounter_out_2080 = 130,
+ dns_sizecounter_out_2096 = 131,
+ dns_sizecounter_out_2112 = 132,
+ dns_sizecounter_out_2128 = 133,
+ dns_sizecounter_out_2144 = 134,
+ dns_sizecounter_out_2160 = 135,
+ dns_sizecounter_out_2176 = 136,
+ dns_sizecounter_out_2192 = 137,
+ dns_sizecounter_out_2208 = 138,
+ dns_sizecounter_out_2224 = 139,
+ dns_sizecounter_out_2240 = 140,
+ dns_sizecounter_out_2256 = 141,
+ dns_sizecounter_out_2272 = 142,
+ dns_sizecounter_out_2288 = 143,
+ dns_sizecounter_out_2304 = 144,
+ dns_sizecounter_out_2320 = 145,
+ dns_sizecounter_out_2336 = 146,
+ dns_sizecounter_out_2352 = 147,
+ dns_sizecounter_out_2368 = 148,
+ dns_sizecounter_out_2384 = 149,
+ dns_sizecounter_out_2400 = 150,
+ dns_sizecounter_out_2416 = 151,
+ dns_sizecounter_out_2432 = 152,
+ dns_sizecounter_out_2448 = 153,
+ dns_sizecounter_out_2464 = 154,
+ dns_sizecounter_out_2480 = 155,
+ dns_sizecounter_out_2496 = 156,
+ dns_sizecounter_out_2512 = 157,
+ dns_sizecounter_out_2528 = 158,
+ dns_sizecounter_out_2544 = 159,
+ dns_sizecounter_out_2560 = 160,
+ dns_sizecounter_out_2576 = 161,
+ dns_sizecounter_out_2592 = 162,
+ dns_sizecounter_out_2608 = 163,
+ dns_sizecounter_out_2624 = 164,
+ dns_sizecounter_out_2640 = 165,
+ dns_sizecounter_out_2656 = 166,
+ dns_sizecounter_out_2672 = 167,
+ dns_sizecounter_out_2688 = 168,
+ dns_sizecounter_out_2704 = 169,
+ dns_sizecounter_out_2720 = 170,
+ dns_sizecounter_out_2736 = 171,
+ dns_sizecounter_out_2752 = 172,
+ dns_sizecounter_out_2768 = 173,
+ dns_sizecounter_out_2784 = 174,
+ dns_sizecounter_out_2800 = 175,
+ dns_sizecounter_out_2816 = 176,
+ dns_sizecounter_out_2832 = 177,
+ dns_sizecounter_out_2848 = 178,
+ dns_sizecounter_out_2864 = 179,
+ dns_sizecounter_out_2880 = 180,
+ dns_sizecounter_out_2896 = 181,
+ dns_sizecounter_out_2912 = 182,
+ dns_sizecounter_out_2928 = 183,
+ dns_sizecounter_out_2944 = 184,
+ dns_sizecounter_out_2960 = 185,
+ dns_sizecounter_out_2976 = 186,
+ dns_sizecounter_out_2992 = 187,
+ dns_sizecounter_out_3008 = 188,
+ dns_sizecounter_out_3024 = 189,
+ dns_sizecounter_out_3040 = 190,
+ dns_sizecounter_out_3056 = 191,
+ dns_sizecounter_out_3072 = 192,
+ dns_sizecounter_out_3088 = 193,
+ dns_sizecounter_out_3104 = 194,
+ dns_sizecounter_out_3120 = 195,
+ dns_sizecounter_out_3136 = 196,
+ dns_sizecounter_out_3152 = 197,
+ dns_sizecounter_out_3168 = 198,
+ dns_sizecounter_out_3184 = 199,
+ dns_sizecounter_out_3200 = 200,
+ dns_sizecounter_out_3216 = 201,
+ dns_sizecounter_out_3232 = 202,
+ dns_sizecounter_out_3248 = 203,
+ dns_sizecounter_out_3264 = 204,
+ dns_sizecounter_out_3280 = 205,
+ dns_sizecounter_out_3296 = 206,
+ dns_sizecounter_out_3312 = 207,
+ dns_sizecounter_out_3328 = 208,
+ dns_sizecounter_out_3344 = 209,
+ dns_sizecounter_out_3360 = 210,
+ dns_sizecounter_out_3376 = 211,
+ dns_sizecounter_out_3392 = 212,
+ dns_sizecounter_out_3408 = 213,
+ dns_sizecounter_out_3424 = 214,
+ dns_sizecounter_out_3440 = 215,
+ dns_sizecounter_out_3456 = 216,
+ dns_sizecounter_out_3472 = 217,
+ dns_sizecounter_out_3488 = 218,
+ dns_sizecounter_out_3504 = 219,
+ dns_sizecounter_out_3520 = 220,
+ dns_sizecounter_out_3536 = 221,
+ dns_sizecounter_out_3552 = 222,
+ dns_sizecounter_out_3568 = 223,
+ dns_sizecounter_out_3584 = 224,
+ dns_sizecounter_out_3600 = 225,
+ dns_sizecounter_out_3616 = 226,
+ dns_sizecounter_out_3632 = 227,
+ dns_sizecounter_out_3648 = 228,
+ dns_sizecounter_out_3664 = 229,
+ dns_sizecounter_out_3680 = 230,
+ dns_sizecounter_out_3696 = 231,
+ dns_sizecounter_out_3712 = 232,
+ dns_sizecounter_out_3728 = 233,
+ dns_sizecounter_out_3744 = 234,
+ dns_sizecounter_out_3760 = 235,
+ dns_sizecounter_out_3776 = 236,
+ dns_sizecounter_out_3792 = 237,
+ dns_sizecounter_out_3808 = 238,
+ dns_sizecounter_out_3824 = 239,
+ dns_sizecounter_out_3840 = 240,
+ dns_sizecounter_out_3856 = 241,
+ dns_sizecounter_out_3872 = 242,
+ dns_sizecounter_out_3888 = 243,
+ dns_sizecounter_out_3904 = 244,
+ dns_sizecounter_out_3920 = 245,
+ dns_sizecounter_out_3936 = 246,
+ dns_sizecounter_out_3952 = 247,
+ dns_sizecounter_out_3968 = 248,
+ dns_sizecounter_out_3984 = 249,
+ dns_sizecounter_out_4000 = 250,
+ dns_sizecounter_out_4016 = 251,
+ dns_sizecounter_out_4032 = 252,
+ dns_sizecounter_out_4048 = 253,
+ dns_sizecounter_out_4064 = 254,
+ dns_sizecounter_out_4080 = 255,
+ dns_sizecounter_out_4096 = 256,
+
+ dns_sizecounter_out_max = 257
+};
+
+#define DNS_STATS_NCOUNTERS 8
+
+#if 0
+/*%<
+ * Flag(s) for dns_xxxstats_dump(). DNS_STATSDUMP_VERBOSE is obsolete.
+ * ISC_STATSDUMP_VERBOSE should be used instead. These two values are
+ * intentionally defined to be the same value to ensure binary compatibility.
+ */
+#define DNS_STATSDUMP_VERBOSE 0x00000001 /*%< dump 0-value counters */
+#endif /* if 0 */
+
+/*%<
+ * (Obsoleted)
+ */
+LIBDNS_EXTERNAL_DATA extern const char *dns_statscounter_names[];
+
+/*%
+ * Attributes for statistics counters of RRset and Rdatatype types.
+ *
+ * _OTHERTYPE
+ * The rdata type is not explicitly supported and the corresponding counter
+ * is counted for other such types, too. When this attribute is set,
+ * the base type is of no use.
+ *
+ * _NXRRSET
+ * RRset type counters only. Indicates the RRset is non existent.
+ *
+ * _NXDOMAIN
+ * RRset type counters only. Indicates a non existent name. When this
+ * attribute is set, the base type is of no use.
+ *
+ * _STALE
+ * RRset type counters only. This indicates a record that is stale
+ * but may still be served.
+ *
+ * _ANCIENT
+ * RRset type counters only. This indicates a record that is marked for
+ * removal.
+ */
+#define DNS_RDATASTATSTYPE_ATTR_OTHERTYPE 0x0001
+#define DNS_RDATASTATSTYPE_ATTR_NXRRSET 0x0002
+#define DNS_RDATASTATSTYPE_ATTR_NXDOMAIN 0x0004
+#define DNS_RDATASTATSTYPE_ATTR_STALE 0x0008
+#define DNS_RDATASTATSTYPE_ATTR_ANCIENT 0x0010
+
+/*%<
+ * Conversion macros among dns_rdatatype_t, attributes and isc_statscounter_t.
+ */
+#define DNS_RDATASTATSTYPE_BASE(type) ((dns_rdatatype_t)((type)&0xFFFF))
+#define DNS_RDATASTATSTYPE_ATTR(type) ((type) >> 16)
+#define DNS_RDATASTATSTYPE_VALUE(b, a) (((a) << 16) | (b))
+
+/*%
+ * Types of DNSSEC sign statistics operations.
+ */
+typedef enum {
+ dns_dnssecsignstats_sign = 1,
+ dns_dnssecsignstats_refresh = 2
+} dnssecsignstats_type_t;
+
+/*%<
+ * Types of dump callbacks.
+ */
+typedef void (*dns_generalstats_dumper_t)(isc_statscounter_t, uint64_t, void *);
+typedef void (*dns_rdatatypestats_dumper_t)(dns_rdatastatstype_t, uint64_t,
+ void *);
+typedef void (*dns_dnssecsignstats_dumper_t)(dns_keytag_t, uint64_t, void *);
+typedef void (*dns_opcodestats_dumper_t)(dns_opcode_t, uint64_t, void *);
+typedef void (*dns_rcodestats_dumper_t)(dns_rcode_t, uint64_t, void *);
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+dns_generalstats_create(isc_mem_t *mctx, dns_stats_t **statsp, int ncounters);
+/*%<
+ * Create a statistics counter structure of general type. It counts a general
+ * set of counters indexed by an ID between 0 and ncounters -1.
+ * This function is obsolete. A more general function, isc_stats_create(),
+ * should be used.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+isc_result_t
+dns_rdatatypestats_create(isc_mem_t *mctx, dns_stats_t **statsp);
+/*%<
+ * Create a statistics counter structure per rdatatype.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+isc_result_t
+dns_rdatasetstats_create(isc_mem_t *mctx, dns_stats_t **statsp);
+/*%<
+ * Create a statistics counter structure per RRset.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+isc_result_t
+dns_opcodestats_create(isc_mem_t *mctx, dns_stats_t **statsp);
+/*%<
+ * Create a statistics counter structure per opcode.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+isc_result_t
+dns_rcodestats_create(isc_mem_t *mctx, dns_stats_t **statsp);
+/*%<
+ * Create a statistics counter structure per assigned rcode.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+isc_result_t
+dns_dnssecsignstats_create(isc_mem_t *mctx, dns_stats_t **statsp);
+/*%<
+ * Create a statistics counter structure per assigned DNSKEY id.
+ *
+ * Requires:
+ *\li 'mctx' must be a valid memory context.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS -- all ok
+ *
+ *\li anything else -- failure
+ */
+
+void
+dns_stats_attach(dns_stats_t *stats, dns_stats_t **statsp);
+/*%<
+ * Attach to a statistics set.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL
+ */
+
+void
+dns_stats_detach(dns_stats_t **statsp);
+/*%<
+ * Detaches from the statistics set.
+ *
+ * Requires:
+ *\li 'statsp' != NULL and '*statsp' is a valid dns_stats_t.
+ */
+
+void
+dns_generalstats_increment(dns_stats_t *stats, isc_statscounter_t counter);
+/*%<
+ * Increment the counter-th counter of stats. This function is obsolete.
+ * A more general function, isc_stats_increment(), should be used.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ *
+ *\li counter is less than the maximum available ID for the stats specified
+ * on creation.
+ */
+
+void
+dns_rdatatypestats_increment(dns_stats_t *stats, dns_rdatatype_t type);
+/*%<
+ * Increment the statistics counter for 'type'.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_rdatatypestats_create().
+ */
+
+void
+dns_rdatasetstats_increment(dns_stats_t *stats, dns_rdatastatstype_t rrsettype);
+/*%<
+ * Increment the statistics counter for 'rrsettype'.
+ *
+ * Note: if 'rrsettype' has the _STALE attribute set the corresponding
+ * non-stale counter will be decremented.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_rdatasetstats_create().
+ */
+
+void
+dns_rdatasetstats_decrement(dns_stats_t *stats, dns_rdatastatstype_t rrsettype);
+/*%<
+ * Decrement the statistics counter for 'rrsettype'.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_rdatasetstats_create().
+ */
+
+void
+dns_opcodestats_increment(dns_stats_t *stats, dns_opcode_t code);
+/*%<
+ * Increment the statistics counter for 'code'.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_opcodestats_create().
+ */
+
+void
+dns_rcodestats_increment(dns_stats_t *stats, dns_opcode_t code);
+/*%<
+ * Increment the statistics counter for 'code'.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_rcodestats_create().
+ */
+
+void
+dns_dnssecsignstats_increment(dns_stats_t *stats, dns_keytag_t id, uint8_t alg,
+ dnssecsignstats_type_t operation);
+/*%<
+ * Increment the statistics counter for the DNSKEY 'id' with algorithm 'alg'.
+ * The 'operation' determines what counter is incremented.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_dnssecsignstats_create().
+ */
+
+void
+dns_dnssecsignstats_clear(dns_stats_t *stats, dns_keytag_t id, uint8_t alg);
+/*%<
+ * Clear the statistics counter for the DNSKEY 'id' with algorithm 'alg'.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_dnssecsignstats_create().
+ */
+
+void
+dns_generalstats_dump(dns_stats_t *stats, dns_generalstats_dumper_t dump_fn,
+ void *arg, unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with its current value and the given argument
+ * arg. By default counters that have a value of 0 is skipped; if options has
+ * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped.
+ *
+ * This function is obsolete. A more general function, isc_stats_dump(),
+ * should be used.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+void
+dns_rdatatypestats_dump(dns_stats_t *stats, dns_rdatatypestats_dumper_t dump_fn,
+ void *arg, unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with the corresponding type in the form of
+ * dns_rdatastatstype_t, the current counter value and the given argument
+ * arg. By default counters that have a value of 0 is skipped; if options has
+ * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+void
+dns_rdatasetstats_dump(dns_stats_t *stats, dns_rdatatypestats_dumper_t dump_fn,
+ void *arg, unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with the corresponding type in the form of
+ * dns_rdatastatstype_t, the current counter value and the given argument
+ * arg. By default counters that have a value of 0 is skipped; if options has
+ * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+void
+dns_dnssecsignstats_dump(dns_stats_t *stats, dnssecsignstats_type_t operation,
+ dns_dnssecsignstats_dumper_t dump_fn, void *arg,
+ unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with the corresponding type in the form of
+ * dns_rdatastatstype_t, the current counter value and the given argument
+ * arg. By default counters that have a value of 0 is skipped; if options has
+ * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+void
+dns_opcodestats_dump(dns_stats_t *stats, dns_opcodestats_dumper_t dump_fn,
+ void *arg, unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with the corresponding opcode, the current
+ * counter value and the given argument arg. By default counters that have a
+ * value of 0 is skipped; if options has the ISC_STATSDUMP_VERBOSE flag, even
+ * such counters are dumped.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+void
+dns_rcodestats_dump(dns_stats_t *stats, dns_rcodestats_dumper_t dump_fn,
+ void *arg, unsigned int options);
+/*%<
+ * Dump the current statistics counters in a specified way. For each counter
+ * in stats, dump_fn is called with the corresponding rcode, the current
+ * counter value and the given argument arg. By default counters that have a
+ * value of 0 is skipped; if options has the ISC_STATSDUMP_VERBOSE flag, even
+ * such counters are dumped.
+ *
+ * Requires:
+ *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create().
+ */
+
+isc_result_t
+dns_stats_alloccounters(isc_mem_t *mctx, uint64_t **ctrp);
+/*%<
+ * Allocate an array of query statistics counters from the memory
+ * context 'mctx'.
+ *
+ * This function is obsoleted. Use dns_xxxstats_create() instead.
+ */
+
+void
+dns_stats_freecounters(isc_mem_t *mctx, uint64_t **ctrp);
+/*%<
+ * Free an array of query statistics counters allocated from the memory
+ * context 'mctx'.
+ *
+ * This function is obsoleted. Use dns_stats_destroy() instead.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_STATS_H */
diff --git a/lib/dns/include/dns/tcpmsg.h b/lib/dns/include/dns/tcpmsg.h
new file mode 100644
index 0000000..df98cd7
--- /dev/null
+++ b/lib/dns/include/dns/tcpmsg.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_TCPMSG_H
+#define DNS_TCPMSG_H 1
+
+/*! \file dns/tcpmsg.h */
+
+#include <inttypes.h>
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+#include <isc/socket.h>
+
+typedef struct dns_tcpmsg {
+ /* private (don't touch!) */
+ unsigned int magic;
+ uint16_t size;
+ isc_buffer_t buffer;
+ unsigned int maxsize;
+ isc_mem_t *mctx;
+ isc_socket_t *sock;
+ isc_task_t *task;
+ isc_taskaction_t action;
+ void *arg;
+ isc_event_t event;
+ /* public (read-only) */
+ isc_result_t result;
+ isc_sockaddr_t address;
+} dns_tcpmsg_t;
+
+ISC_LANG_BEGINDECLS
+
+void
+dns_tcpmsg_init(isc_mem_t *mctx, isc_socket_t *sock, dns_tcpmsg_t *tcpmsg);
+/*%<
+ * Associate a tcp message state with a given memory context and
+ * TCP socket.
+ *
+ * Requires:
+ *
+ *\li "mctx" and "sock" be non-NULL and valid types.
+ *
+ *\li "sock" be a read/write TCP socket.
+ *
+ *\li "tcpmsg" be non-NULL and an uninitialized or invalidated structure.
+ *
+ * Ensures:
+ *
+ *\li "tcpmsg" is a valid structure.
+ */
+
+void
+dns_tcpmsg_setmaxsize(dns_tcpmsg_t *tcpmsg, unsigned int maxsize);
+/*%<
+ * Set the maximum packet size to "maxsize"
+ *
+ * Requires:
+ *
+ *\li "tcpmsg" be valid.
+ *
+ *\li 512 <= "maxsize" <= 65536
+ */
+
+isc_result_t
+dns_tcpmsg_readmessage(dns_tcpmsg_t *tcpmsg, isc_task_t *task,
+ isc_taskaction_t action, void *arg);
+/*%<
+ * Schedule an event to be delivered when a DNS message is readable, or
+ * when an error occurs on the socket.
+ *
+ * Requires:
+ *
+ *\li "tcpmsg" be valid.
+ *
+ *\li "task", "taskaction", and "arg" be valid.
+ *
+ * Returns:
+ *
+ *\li ISC_R_SUCCESS -- no error
+ *\li Anything that the isc_socket_recv() call can return. XXXMLG
+ *
+ * Notes:
+ *
+ *\li The event delivered is a fully generic event. It will contain no
+ * actual data. The sender will be a pointer to the dns_tcpmsg_t.
+ * The result code inside that structure should be checked to see
+ * what the final result was.
+ */
+
+void
+dns_tcpmsg_cancelread(dns_tcpmsg_t *tcpmsg);
+/*%<
+ * Cancel a readmessage() call. The event will still be posted with a
+ * CANCELED result code.
+ *
+ * Requires:
+ *
+ *\li "tcpmsg" be valid.
+ */
+
+void
+dns_tcpmsg_keepbuffer(dns_tcpmsg_t *tcpmsg, isc_buffer_t *buffer);
+/*%<
+ * If a dns buffer is to be kept between calls, this function marks the
+ * internal state-machine buffer as invalid, and copies all the contents
+ * of the state into "buffer".
+ *
+ * Requires:
+ *
+ *\li "tcpmsg" be valid.
+ *
+ *\li "buffer" be non-NULL.
+ */
+
+void
+dns_tcpmsg_invalidate(dns_tcpmsg_t *tcpmsg);
+/*%<
+ * Clean up all allocated state, and invalidate the structure.
+ *
+ * Requires:
+ *
+ *\li "tcpmsg" be valid.
+ *
+ * Ensures:
+ *
+ *\li "tcpmsg" is invalidated and disassociated with all memory contexts,
+ * sockets, etc.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_TCPMSG_H */
diff --git a/lib/dns/include/dns/time.h b/lib/dns/include/dns/time.h
new file mode 100644
index 0000000..62e0fa0
--- /dev/null
+++ b/lib/dns/include/dns/time.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_TIME_H
+#define DNS_TIME_H 1
+
+/*! \file dns/time.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <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
+
+#endif /* DNS_TIME_H */
diff --git a/lib/dns/include/dns/timer.h b/lib/dns/include/dns/timer.h
new file mode 100644
index 0000000..96fcd3b
--- /dev/null
+++ b/lib/dns/include/dns/timer.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_TIMER_H
+#define DNS_TIMER_H 1
+
+/*! \file dns/timer.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+dns_timer_setidle(isc_timer_t *timer, unsigned int maxtime,
+ unsigned int idletime, bool purge);
+/*%<
+ * Convenience function for setting up simple, one-second-granularity
+ * idle timers as used by zone transfers.
+ * \brief
+ * Set the timer 'timer' to go off after 'idletime' seconds of inactivity,
+ * or after 'maxtime' at the very latest. Events are purged iff
+ * 'purge' is true.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_TIMER_H */
diff --git a/lib/dns/include/dns/tkey.h b/lib/dns/include/dns/tkey.h
new file mode 100644
index 0000000..23d95ae
--- /dev/null
+++ b/lib/dns/include/dns/tkey.h
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_TKEY_H
+#define DNS_TKEY_H 1
+
+/*! \file dns/tkey.h */
+
+#include <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
+
+#endif /* DNS_TKEY_H */
diff --git a/lib/dns/include/dns/tsec.h b/lib/dns/include/dns/tsec.h
new file mode 100644
index 0000000..5ea7b8a
--- /dev/null
+++ b/lib/dns/include/dns/tsec.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_TSEC_H
+#define DNS_TSEC_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ *
+ * \brief
+ * The TSEC (Transaction Security) module is an abstraction layer for managing
+ * DNS transaction mechanisms such as TSIG or SIG(0). A TSEC structure is a
+ * mechanism-independent object containing key information specific to the
+ * mechanism, and is expected to be used as an argument to other modules
+ * that use transaction security in a mechanism-independent manner.
+ *
+ * MP:
+ *\li A TSEC structure is expected to be thread-specific. No inter-thread
+ * synchronization is ensured in multiple access to a single TSEC
+ * structure.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li This module does not handle any low-level data directly, and so no
+ * security issue specific to this module is anticipated.
+ */
+
+#include <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
+
+#endif /* DNS_TSEC_H */
diff --git a/lib/dns/include/dns/tsig.h b/lib/dns/include/dns/tsig.h
new file mode 100644
index 0000000..c908fa1
--- /dev/null
+++ b/lib/dns/include/dns/tsig.h
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_TSIG_H
+#define DNS_TSIG_H 1
+
+/*! \file dns/tsig.h */
+
+#include <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>
+#include <pk11/site.h>
+
+/*
+ * Algorithms.
+ */
+LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacmd5_name;
+#define DNS_TSIG_HMACMD5_NAME dns_tsig_hmacmd5_name
+LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_gssapi_name;
+#define DNS_TSIG_GSSAPI_NAME dns_tsig_gssapi_name
+LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_gssapims_name;
+#define DNS_TSIG_GSSAPIMS_NAME dns_tsig_gssapims_name
+LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacsha1_name;
+#define DNS_TSIG_HMACSHA1_NAME dns_tsig_hmacsha1_name
+LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacsha224_name;
+#define DNS_TSIG_HMACSHA224_NAME dns_tsig_hmacsha224_name
+LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacsha256_name;
+#define DNS_TSIG_HMACSHA256_NAME dns_tsig_hmacsha256_name
+LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacsha384_name;
+#define DNS_TSIG_HMACSHA384_NAME dns_tsig_hmacsha384_name
+LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacsha512_name;
+#define DNS_TSIG_HMACSHA512_NAME dns_tsig_hmacsha512_name
+
+/*%
+ * Default fudge value.
+ */
+#define DNS_TSIG_FUDGE 300
+
+struct dns_tsig_keyring {
+ dns_rbt_t *keys;
+ unsigned int writecount;
+ isc_rwlock_t lock;
+ isc_mem_t *mctx;
+ /*
+ * LRU list of generated key along with a count of the keys on the
+ * list and a maximum size.
+ */
+ unsigned int generated;
+ unsigned int maxgenerated;
+ ISC_LIST(dns_tsigkey_t) lru;
+ isc_refcount_t references;
+};
+
+struct dns_tsigkey {
+ /* Unlocked */
+ unsigned int magic; /*%< Magic number. */
+ isc_mem_t *mctx;
+ dst_key_t *key; /*%< Key */
+ dns_name_t name; /*%< Key name */
+ const dns_name_t *algorithm; /*%< Algorithm name */
+ dns_name_t *creator; /*%< name that created secret */
+ bool generated; /*%< was this generated? */
+ isc_stdtime_t inception; /*%< start of validity period */
+ isc_stdtime_t expire; /*%< end of validity period */
+ dns_tsig_keyring_t *ring; /*%< the enclosing keyring */
+ isc_refcount_t refs; /*%< reference counter */
+ ISC_LINK(dns_tsigkey_t) link;
+};
+
+ISC_LANG_BEGINDECLS
+
+const dns_name_t *
+dns_tsigkey_identity(const dns_tsigkey_t *tsigkey);
+/*%<
+ * Returns the identity of the provided TSIG key.
+ *
+ * Requires:
+ *\li 'tsigkey' is a valid TSIG key or NULL
+ *
+ * Returns:
+ *\li NULL if 'tsigkey' was NULL
+ *\li identity of the provided TSIG key
+ */
+
+isc_result_t
+dns_tsigkey_create(const dns_name_t *name, const dns_name_t *algorithm,
+ unsigned char *secret, int length, bool generated,
+ const dns_name_t *creator, isc_stdtime_t inception,
+ isc_stdtime_t expire, isc_mem_t *mctx,
+ dns_tsig_keyring_t *ring, dns_tsigkey_t **key);
+
+isc_result_t
+dns_tsigkey_createfromkey(const dns_name_t *name, const dns_name_t *algorithm,
+ dst_key_t *dstkey, bool generated,
+ const dns_name_t *creator, isc_stdtime_t inception,
+ isc_stdtime_t expire, isc_mem_t *mctx,
+ dns_tsig_keyring_t *ring, dns_tsigkey_t **key);
+/*%<
+ * Creates a tsig key structure and saves it in the keyring. If key is
+ * not NULL, *key will contain a copy of the key. The keys validity
+ * period is specified by (inception, expire), and will not expire if
+ * inception == expire. If the key was generated, the creating identity,
+ * if there is one, should be in the creator parameter. Specifying an
+ * unimplemented algorithm will cause failure only if dstkey != NULL; this
+ * allows a transient key with an invalid algorithm to exist long enough
+ * to generate a BADKEY response.
+ *
+ * If dns_tsigkey_createfromkey is successful a new reference to 'dstkey'
+ * will have been made.
+ *
+ * Requires:
+ *\li 'name' is a valid dns_name_t
+ *\li 'algorithm' is a valid dns_name_t
+ *\li 'secret' is a valid pointer
+ *\li 'length' is an integer >= 0
+ *\li 'dstkey' is a valid dst key or NULL
+ *\li 'creator' points to a valid dns_name_t or is NULL
+ *\li 'mctx' is a valid memory context
+ *\li 'ring' is a valid TSIG keyring or NULL
+ *\li 'key' or '*key' must be NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_EXISTS - a key with this name already exists
+ *\li #ISC_R_NOTIMPLEMENTED - algorithm is not implemented
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+dns_tsigkey_attach(dns_tsigkey_t *source, dns_tsigkey_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *\li 'key' is a valid TSIG key
+ *
+ * Ensures:
+ *\li *targetp is attached to source.
+ */
+
+void
+dns_tsigkey_detach(dns_tsigkey_t **keyp);
+/*%<
+ * Detaches from the tsig key structure pointed to by '*key'.
+ *
+ * Requires:
+ *\li 'keyp' is not NULL and '*keyp' is a valid TSIG key
+ *
+ * Ensures:
+ *\li 'keyp' points to NULL
+ */
+
+void
+dns_tsigkey_setdeleted(dns_tsigkey_t *key);
+/*%<
+ * Prevents this key from being used again. It will be deleted when
+ * no references exist.
+ *
+ * Requires:
+ *\li 'key' is a valid TSIG key on a keyring
+ */
+
+isc_result_t
+dns_tsig_sign(dns_message_t *msg);
+/*%<
+ * Generates a TSIG record for this message
+ *
+ * Requires:
+ *\li 'msg' is a valid message
+ *\li 'msg->tsigkey' is a valid TSIG key
+ *\li 'msg->tsig' is NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_NOSPACE
+ *\li #DNS_R_EXPECTEDTSIG
+ * - this is a response & msg->querytsig is NULL
+ */
+
+isc_result_t
+dns_tsig_verify(isc_buffer_t *source, dns_message_t *msg,
+ dns_tsig_keyring_t *ring1, dns_tsig_keyring_t *ring2);
+/*%<
+ * Verifies the TSIG record in this message
+ *
+ * Requires:
+ *\li 'source' is a valid buffer containing the unparsed message
+ *\li 'msg' is a valid message
+ *\li 'msg->tsigkey' is a valid TSIG key if this is a response
+ *\li 'msg->tsig' is NULL
+ *\li 'msg->querytsig' is not NULL if this is a response
+ *\li 'ring1' and 'ring2' are each either a valid keyring or NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #DNS_R_EXPECTEDTSIG - A TSIG was expected but not seen
+ *\li #DNS_R_UNEXPECTEDTSIG - A TSIG was seen but not expected
+ *\li #DNS_R_TSIGERRORSET - the TSIG verified but ->error was set
+ * and this is a query
+ *\li #DNS_R_CLOCKSKEW - the TSIG failed to verify because of
+ * the time was out of the allowed range.
+ *\li #DNS_R_TSIGVERIFYFAILURE - the TSIG failed to verify
+ *\li #DNS_R_EXPECTEDRESPONSE - the message was set over TCP and
+ * should have been a response,
+ * but was not.
+ */
+
+isc_result_t
+dns_tsigkey_find(dns_tsigkey_t **tsigkey, const dns_name_t *name,
+ const dns_name_t *algorithm, dns_tsig_keyring_t *ring);
+/*%<
+ * Returns the TSIG key corresponding to this name and (possibly)
+ * algorithm. Also increments the key's reference counter.
+ *
+ * Requires:
+ *\li 'tsigkey' is not NULL
+ *\li '*tsigkey' is NULL
+ *\li 'name' is a valid dns_name_t
+ *\li 'algorithm' is a valid dns_name_t or NULL
+ *\li 'ring' is a valid keyring
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTFOUND
+ */
+
+isc_result_t
+dns_tsigkeyring_create(isc_mem_t *mctx, dns_tsig_keyring_t **ringp);
+/*%<
+ * Create an empty TSIG key ring.
+ *
+ * Requires:
+ *\li 'mctx' is not NULL
+ *\li 'ringp' is not NULL, and '*ringp' is NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_tsigkeyring_add(dns_tsig_keyring_t *ring, const dns_name_t *name,
+ dns_tsigkey_t *tkey);
+/*%<
+ * Place a TSIG key onto a key ring.
+ *
+ * Requires:
+ *\li 'ring', 'name' and 'tkey' are not NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li Any other value indicates failure.
+ */
+
+void
+dns_tsigkeyring_attach(dns_tsig_keyring_t *source, dns_tsig_keyring_t **target);
+
+void
+dns_tsigkeyring_detach(dns_tsig_keyring_t **ringp);
+
+isc_result_t
+dns_tsigkeyring_dumpanddetach(dns_tsig_keyring_t **ringp, FILE *fp);
+
+/*%<
+ * Destroy a TSIG key ring.
+ *
+ * Requires:
+ *\li 'ringp' is not NULL
+ */
+
+void
+dns_keyring_restore(dns_tsig_keyring_t *ring, FILE *fp);
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_TSIG_H */
diff --git a/lib/dns/include/dns/ttl.h b/lib/dns/include/dns/ttl.h
new file mode 100644
index 0000000..2d718f4
--- /dev/null
+++ b/lib/dns/include/dns/ttl.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_TTL_H
+#define DNS_TTL_H 1
+
+/*! \file dns/ttl.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <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
+
+#endif /* DNS_TTL_H */
diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h
new file mode 100644
index 0000000..641d81f
--- /dev/null
+++ b/lib/dns/include/dns/types.h
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_TYPES_H
+#define DNS_TYPES_H 1
+
+/*! \file dns/types.h
+ * \brief
+ * Including this file gives you type declarations suitable for use in
+ * .h files, which lets us avoid circular type reference problems.
+ * \brief
+ * To actually use a type or get declarations of its methods, you must
+ * include the appropriate .h file too.
+ */
+
+#include <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_zone dns_catz_zone_t;
+typedef struct dns_catz_changed dns_catz_changed_t;
+typedef struct dns_catz_zones dns_catz_zones_t;
+typedef struct dns_client dns_client_t;
+typedef void dns_clientrestrans_t;
+typedef void dns_clientreqtrans_t;
+typedef void dns_clientupdatetrans_t;
+typedef struct dns_cache dns_cache_t;
+typedef uint16_t dns_cert_t;
+typedef struct dns_compress dns_compress_t;
+typedef struct dns_db dns_db_t;
+typedef struct dns_dbimplementation dns_dbimplementation_t;
+typedef struct dns_dbiterator dns_dbiterator_t;
+typedef void dns_dbload_t;
+typedef void dns_dbnode_t;
+typedef struct dns_dbonupdatelistener dns_dbonupdatelistener_t;
+typedef struct dns_dbtable dns_dbtable_t;
+typedef void dns_dbversion_t;
+typedef struct dns_dlzimplementation dns_dlzimplementation_t;
+typedef struct dns_dlzdb dns_dlzdb_t;
+typedef ISC_LIST(dns_dlzdb_t) dns_dlzdblist_t;
+typedef struct dns_dyndbctx dns_dyndbctx_t;
+typedef struct dns_sdlzimplementation dns_sdlzimplementation_t;
+typedef struct dns_decompress dns_decompress_t;
+typedef struct dns_dispatch dns_dispatch_t;
+typedef struct dns_dispatchevent dns_dispatchevent_t;
+typedef struct dns_dispatchlist dns_dispatchlist_t;
+typedef struct dns_dispatchset dns_dispatchset_t;
+typedef struct dns_dispatchmgr dns_dispatchmgr_t;
+typedef struct dns_dispentry dns_dispentry_t;
+typedef struct dns_dns64 dns_dns64_t;
+typedef ISC_LIST(dns_dns64_t) dns_dns64list_t;
+typedef struct dns_dnsseckey dns_dnsseckey_t;
+typedef ISC_LIST(dns_dnsseckey_t) dns_dnsseckeylist_t;
+typedef uint8_t dns_dsdigest_t;
+typedef struct dns_dtdata dns_dtdata_t;
+typedef struct dns_dtenv dns_dtenv_t;
+typedef struct dns_dtmsg dns_dtmsg_t;
+typedef uint16_t dns_dtmsgtype_t;
+typedef struct dns_dumpctx dns_dumpctx_t;
+typedef struct dns_ecs dns_ecs_t;
+typedef struct dns_ednsopt dns_ednsopt_t;
+typedef struct dns_fetch dns_fetch_t;
+typedef struct dns_fixedname dns_fixedname_t;
+typedef struct dns_forwarders dns_forwarders_t;
+typedef struct dns_forwarder dns_forwarder_t;
+typedef struct dns_fwdtable dns_fwdtable_t;
+typedef struct dns_geoip_databases dns_geoip_databases_t;
+typedef struct dns_iptable dns_iptable_t;
+typedef uint32_t dns_iterations_t;
+typedef struct dns_kasp dns_kasp_t;
+typedef ISC_LIST(dns_kasp_t) dns_kasplist_t;
+typedef struct dns_kasp_key dns_kasp_key_t;
+typedef ISC_LIST(dns_kasp_key_t) dns_kasp_keylist_t;
+typedef struct dns_kasp_nsec3param dns_kasp_nsec3param_t;
+typedef uint16_t dns_keyflags_t;
+typedef struct dns_keynode dns_keynode_t;
+typedef ISC_LIST(dns_keynode_t) dns_keynodelist_t;
+typedef struct dns_keytable dns_keytable_t;
+typedef uint16_t dns_keytag_t;
+typedef struct dns_loadctx dns_loadctx_t;
+typedef struct dns_loadmgr dns_loadmgr_t;
+typedef struct dns_masterrawheader dns_masterrawheader_t;
+typedef uint64_t dns_masterstyle_flags_t;
+typedef struct dns_message dns_message_t;
+typedef uint16_t dns_messageid_t;
+typedef isc_region_t dns_label_t;
+typedef struct dns_lookup dns_lookup_t;
+typedef struct dns_name dns_name_t;
+typedef ISC_LIST(dns_name_t) dns_namelist_t;
+typedef struct dns_nta dns_nta_t;
+typedef struct dns_ntatable dns_ntatable_t;
+typedef uint16_t dns_opcode_t;
+typedef unsigned char dns_offsets_t[128];
+typedef struct dns_order dns_order_t;
+typedef struct dns_peer dns_peer_t;
+typedef struct dns_peerlist dns_peerlist_t;
+typedef struct dns_portlist dns_portlist_t;
+typedef struct dns_rbt dns_rbt_t;
+typedef uint16_t dns_rcode_t;
+typedef struct dns_rdata dns_rdata_t;
+typedef struct dns_rdatacallbacks dns_rdatacallbacks_t;
+typedef uint16_t dns_rdataclass_t;
+typedef struct dns_rdatalist dns_rdatalist_t;
+typedef struct dns_rdataset dns_rdataset_t;
+typedef ISC_LIST(dns_rdataset_t) dns_rdatasetlist_t;
+typedef struct dns_rdatasetiter dns_rdatasetiter_t;
+typedef uint16_t dns_rdatatype_t;
+typedef struct dns_request dns_request_t;
+typedef struct dns_requestmgr dns_requestmgr_t;
+typedef struct dns_resolver dns_resolver_t;
+typedef struct dns_sdbimplementation dns_sdbimplementation_t;
+typedef uint8_t dns_secalg_t;
+typedef uint8_t dns_secproto_t;
+typedef struct dns_signature dns_signature_t;
+typedef struct dns_sortlist_arg dns_sortlist_arg_t;
+typedef struct dns_ssurule dns_ssurule_t;
+typedef struct dns_ssutable dns_ssutable_t;
+typedef struct dns_stats dns_stats_t;
+typedef uint32_t dns_rdatastatstype_t;
+typedef struct dns_tkeyctx dns_tkeyctx_t;
+typedef uint16_t dns_trust_t;
+typedef struct dns_tsec dns_tsec_t;
+typedef struct dns_tsig_keyring dns_tsig_keyring_t;
+typedef struct dns_tsigkey dns_tsigkey_t;
+typedef uint32_t dns_ttl_t;
+typedef struct dns_update_state dns_update_state_t;
+typedef struct dns_validator dns_validator_t;
+typedef struct dns_view dns_view_t;
+typedef ISC_LIST(dns_view_t) dns_viewlist_t;
+typedef struct dns_zone dns_zone_t;
+typedef ISC_LIST(dns_zone_t) dns_zonelist_t;
+typedef struct dns_zonemgr dns_zonemgr_t;
+typedef struct dns_zt dns_zt_t;
+typedef struct dns_ipkeylist dns_ipkeylist_t;
+
+/*
+ * If we are not using GSSAPI, define the types we use as opaque types here.
+ */
+#ifndef GSSAPI
+typedef struct not_defined_gss_cred_id *gss_cred_id_t;
+typedef struct not_defined_gss_ctx *gss_ctx_id_t;
+#endif /* ifndef GSSAPI */
+typedef struct dst_gssapi_signverifyctx dst_gssapi_signverifyctx_t;
+
+typedef enum { dns_hash_sha1 = 1 } dns_hash_t;
+
+typedef enum {
+ dns_fwdpolicy_none = 0,
+ dns_fwdpolicy_first = 1,
+ dns_fwdpolicy_only = 2
+} dns_fwdpolicy_t;
+
+typedef enum {
+ dns_namereln_none = 0,
+ dns_namereln_contains = 1,
+ dns_namereln_subdomain = 2,
+ dns_namereln_equal = 3,
+ dns_namereln_commonancestor = 4
+} dns_namereln_t;
+
+typedef enum { dns_one_answer, dns_many_answers } dns_transfer_format_t;
+
+typedef enum {
+ dns_dbtype_zone = 0,
+ dns_dbtype_cache = 1,
+ dns_dbtype_stub = 3
+} dns_dbtype_t;
+
+typedef enum {
+ dns_notifytype_no = 0,
+ dns_notifytype_yes = 1,
+ dns_notifytype_explicit = 2,
+ dns_notifytype_masteronly = 3
+} dns_notifytype_t;
+
+typedef enum {
+ dns_minimal_no = 0,
+ dns_minimal_yes = 1,
+ dns_minimal_noauth = 2,
+ dns_minimal_noauthrec = 3
+} dns_minimaltype_t;
+
+typedef enum {
+ dns_dialuptype_no = 0,
+ dns_dialuptype_yes = 1,
+ dns_dialuptype_notify = 2,
+ dns_dialuptype_notifypassive = 3,
+ dns_dialuptype_refresh = 4,
+ dns_dialuptype_passive = 5
+} dns_dialuptype_t;
+
+typedef enum {
+ dns_masterformat_none = 0,
+ dns_masterformat_text = 1,
+ dns_masterformat_raw = 2,
+ dns_masterformat_map = 3
+} dns_masterformat_t;
+
+/*
+ * These are generated by gen.c.
+ */
+#include <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);
+
+typedef isc_result_t (*dns_digestfunc_t)(void *, isc_region_t *);
+
+typedef void (*dns_xfrindone_t)(dns_zone_t *, isc_result_t);
+
+typedef void (*dns_updatecallback_t)(void *, isc_result_t, dns_message_t *);
+
+typedef int (*dns_rdatasetorderfunc_t)(const dns_rdata_t *, const void *);
+
+typedef bool (*dns_checkmxfunc_t)(dns_zone_t *, const dns_name_t *,
+ const dns_name_t *);
+
+typedef bool (*dns_checksrvfunc_t)(dns_zone_t *, const dns_name_t *,
+ const dns_name_t *);
+
+typedef bool (*dns_checknsfunc_t)(dns_zone_t *, const dns_name_t *,
+ const dns_name_t *, dns_rdataset_t *,
+ dns_rdataset_t *);
+
+typedef bool (*dns_isselffunc_t)(dns_view_t *, dns_tsigkey_t *,
+ const isc_sockaddr_t *, const isc_sockaddr_t *,
+ dns_rdataclass_t, void *);
+
+typedef isc_result_t (*dns_deserializefunc_t)(void *, FILE *, off_t);
+
+typedef void (*dns_nseclog_t)(void *val, int, const char *, ...);
+
+#endif /* DNS_TYPES_H */
diff --git a/lib/dns/include/dns/update.h b/lib/dns/include/dns/update.h
new file mode 100644
index 0000000..aca9e71
--- /dev/null
+++ b/lib/dns/include/dns/update.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_UPDATE_H
+#define DNS_UPDATE_H 1
+
+/*! \file dns/update.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <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
+
+#endif /* DNS_UPDATE_H */
diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h
new file mode 100644
index 0000000..b435cd6
--- /dev/null
+++ b/lib/dns/include/dns/validator.h
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_VALIDATOR_H
+#define DNS_VALIDATOR_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/validator.h
+ *
+ * \brief
+ * DNS Validator
+ * This is the BIND 9 validator, the module responsible for validating the
+ * rdatasets and negative responses (messages). It makes use of zones in
+ * the view and may fetch RRset to complete trust chains. It implements
+ * DNSSEC as specified in RFC 4033, 4034 and 4035.
+ *
+ * Correct operation is critical to preventing spoofed answers from secure
+ * zones being accepted.
+ *
+ * MP:
+ *\li The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li RFCs: 1034, 1035, 2181, 4033, 4034, 4035.
+ */
+
+#include <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
+
+#endif /* DNS_VALIDATOR_H */
diff --git a/lib/dns/include/dns/version.h b/lib/dns/include/dns/version.h
new file mode 100644
index 0000000..0f5ec7e
--- /dev/null
+++ b/lib/dns/include/dns/version.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file dns/version.h */
+
+#ifndef DNS_VERSION_H
+#define DNS_VERSION_H 1
+
+#include <isc/platform.h>
+
+LIBDNS_EXTERNAL_DATA extern const char dns_version[];
+LIBDNS_EXTERNAL_DATA extern const char dns_major[];
+LIBDNS_EXTERNAL_DATA extern const char dns_mapapi[];
+
+#endif /* DNS_VERSION_H */
diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h
new file mode 100644
index 0000000..d6e7f10
--- /dev/null
+++ b/lib/dns/include/dns/view.h
@@ -0,0 +1,1359 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_VIEW_H
+#define DNS_VIEW_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/view.h
+ * \brief
+ * DNS View
+ *
+ * A "view" is a DNS namespace, together with an optional resolver and a
+ * forwarding policy. A "DNS namespace" is a (possibly empty) set of
+ * authoritative zones together with an optional cache and optional
+ * "hints" information.
+ *
+ * Views start out "unfrozen". In this state, core attributes like
+ * the cache, set of zones, and forwarding policy may be set. While
+ * "unfrozen", the caller (e.g. nameserver configuration loading
+ * code), must ensure exclusive access to the view. When the view is
+ * "frozen", the core attributes become immutable, and the view module
+ * will ensure synchronization. Freezing allows the view's core attributes
+ * to be accessed without locking.
+ *
+ * MP:
+ *\li Before the view is frozen, the caller must ensure synchronization.
+ *
+ *\li After the view is frozen, the module guarantees appropriate
+ * synchronization of any data structures it creates and manipulates.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li TBS
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li None.
+ */
+
+#include <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/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_tsig_keyring_t *statickeys;
+ dns_tsig_keyring_t *dynamickeys;
+ dns_peerlist_t *peers;
+ dns_order_t *order;
+ dns_fwdtable_t *fwdtable;
+ bool recursion;
+ bool qminimization;
+ bool qmin_strict;
+ bool auth_nxdomain;
+ bool use_glue_cache;
+ bool minimal_any;
+ dns_minimaltype_t minimalresponses;
+ bool enablevalidation;
+ bool acceptexpired;
+ bool requireservercookie;
+ bool synthfromdnssec;
+ bool trust_anchor_telemetry;
+ bool root_key_sentinel;
+ dns_transfer_format_t transfer_format;
+ dns_acl_t *cacheacl;
+ dns_acl_t *cacheonacl;
+ dns_acl_t *queryacl;
+ dns_acl_t *queryonacl;
+ dns_acl_t *recursionacl;
+ dns_acl_t *recursiononacl;
+ dns_acl_t *sortlist;
+ dns_acl_t *notifyacl;
+ dns_acl_t *transferacl;
+ dns_acl_t *updateacl;
+ dns_acl_t *upfwdacl;
+ dns_acl_t *denyansweracl;
+ dns_acl_t *nocasecompress;
+ bool msgcompression;
+ dns_rbt_t *answeracl_exclude;
+ dns_rbt_t *denyanswernames;
+ dns_rbt_t *answernames_exclude;
+ dns_rrl_t *rrl;
+ bool provideixfr;
+ bool requestnsid;
+ bool sendcookie;
+ dns_ttl_t maxcachettl;
+ dns_ttl_t maxncachettl;
+ dns_ttl_t mincachettl;
+ dns_ttl_t minncachettl;
+ uint32_t nta_lifetime;
+ uint32_t nta_recheck;
+ char *nta_file;
+ dns_ttl_t prefetch_trigger;
+ dns_ttl_t prefetch_eligible;
+ in_port_t dstport;
+ dns_aclenv_t aclenv;
+ dns_rdatatype_t preferred_glue;
+ bool flush;
+ dns_namelist_t *delonly;
+ bool rootdelonly;
+ dns_namelist_t *rootexclude;
+ bool checknames;
+ uint16_t maxudp;
+ dns_ttl_t staleanswerttl;
+ dns_stale_answer_t staleanswersok; /* rndc setting */
+ bool staleanswersenable; /* named.conf setting
+ * */
+ uint32_t staleanswerclienttimeout;
+ uint16_t nocookieudp;
+ uint16_t padding;
+ dns_acl_t *pad_acl;
+ unsigned int maxbits;
+ dns_dns64list_t dns64;
+ unsigned int dns64cnt;
+ dns_rpz_zones_t *rpzs;
+ dns_catz_zones_t *catzs;
+ dns_dlzdblist_t dlz_searched;
+ dns_dlzdblist_t dlz_unsearched;
+ uint32_t fail_ttl;
+ dns_badcache_t *failcache;
+
+ /*
+ * Configurable data for server use only,
+ * locked by server configuration lock.
+ */
+ dns_acl_t *matchclients;
+ dns_acl_t *matchdestinations;
+ bool matchrecursiveonly;
+
+ /* Locked by themselves. */
+ isc_refcount_t references;
+ isc_refcount_t weakrefs;
+ atomic_uint_fast32_t attributes;
+
+ /* Under owner's locking control. */
+ ISC_LINK(struct dns_view) link;
+ dns_viewlist_t *viewlist;
+
+ dns_zone_t *managed_keys;
+ dns_zone_t *redirect;
+ dns_name_t *redirectzone; /* points to
+ * redirectfixed
+ * when valid */
+ dns_fixedname_t redirectfixed;
+
+ /*
+ * File and configuration data for zones added at runtime
+ * (only used in BIND9).
+ *
+ * XXX: This should be a pointer to an opaque type that
+ * named implements.
+ */
+ char *new_zone_dir;
+ char *new_zone_file;
+ char *new_zone_db;
+ void *new_zone_dbenv;
+ uint64_t new_zone_mapsize;
+ void *new_zone_config;
+ void (*cfg_destroy)(void **);
+ isc_mutex_t new_zone_lock;
+
+ unsigned char secret[32]; /* Client secret */
+ unsigned int v6bias;
+
+ dns_dtenv_t *dtenv; /* Dnstap environment */
+ dns_dtmsgtype_t dttypes; /* Dnstap message types
+ * to log */
+
+ /* Registered module instances */
+ void *plugins;
+ void (*plugins_free)(isc_mem_t *, void **);
+
+ /* Hook table */
+ void *hooktable; /* ns_hooktable */
+ void (*hooktable_free)(isc_mem_t *, void **);
+};
+
+#define DNS_VIEW_MAGIC ISC_MAGIC('V', 'i', 'e', 'w')
+#define DNS_VIEW_VALID(view) ISC_MAGIC_VALID(view, DNS_VIEW_MAGIC)
+
+#define DNS_VIEWATTR_RESSHUTDOWN 0x01
+#define DNS_VIEWATTR_ADBSHUTDOWN 0x02
+#define DNS_VIEWATTR_REQSHUTDOWN 0x04
+
+isc_result_t
+dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, const char *name,
+ dns_view_t **viewp);
+/*%<
+ * Create a view.
+ *
+ * Notes:
+ *
+ *\li The newly created view has no cache, no resolver, and an empty
+ * zone table. The view is not frozen.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'rdclass' is a valid class.
+ *
+ *\li 'name' is a valid C string.
+ *
+ *\li viewp != NULL && *viewp == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+void
+dns_view_attach(dns_view_t *source, dns_view_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid, frozen view.
+ *
+ *\li 'targetp' points to a NULL dns_view_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ *
+ *\li While *targetp is attached, the view will not shut down.
+ */
+
+void
+dns_view_detach(dns_view_t **viewp);
+/*%<
+ * Detach '*viewp' from its view.
+ *
+ * Requires:
+ *
+ *\li 'viewp' points to a valid dns_view_t *
+ *
+ * Ensures:
+ *
+ *\li *viewp is NULL.
+ */
+
+void
+dns_view_flushanddetach(dns_view_t **viewp);
+/*%<
+ * Detach '*viewp' from its view. If this was the last reference
+ * uncommitted changed in zones will be flushed to disk.
+ *
+ * Requires:
+ *
+ *\li 'viewp' points to a valid dns_view_t *
+ *
+ * Ensures:
+ *
+ *\li *viewp is NULL.
+ */
+
+void
+dns_view_weakattach(dns_view_t *source, dns_view_t **targetp);
+/*%<
+ * Weakly attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid, frozen view.
+ *
+ *\li 'targetp' points to a NULL dns_view_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ *
+ * \li While *targetp is attached, the view will not be freed.
+ */
+
+void
+dns_view_weakdetach(dns_view_t **targetp);
+/*%<
+ * Detach '*viewp' from its view.
+ *
+ * Requires:
+ *
+ *\li 'viewp' points to a valid dns_view_t *.
+ *
+ * Ensures:
+ *
+ *\li *viewp is NULL.
+ */
+
+isc_result_t
+dns_view_createzonetable(dns_view_t *view);
+/*%<
+ * Create a zonetable for the view.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view.
+ *
+ *\li 'view' does not have a zonetable already.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Any error that dns_zt_create() can return.
+ */
+
+isc_result_t
+dns_view_createresolver(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ unsigned int ntasks, unsigned int ndisp,
+ isc_socketmgr_t *socketmgr, isc_timermgr_t *timermgr,
+ unsigned int options, dns_dispatchmgr_t *dispatchmgr,
+ dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6);
+/*%<
+ * Create a resolver and address database for the view.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view.
+ *
+ *\li 'view' does not have a resolver already.
+ *
+ *\li The requirements of dns_resolver_create() apply to 'taskmgr',
+ * 'ntasks', 'socketmgr', 'timermgr', 'options', 'dispatchv4', and
+ * 'dispatchv6'.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *
+ *\li Any error that dns_resolver_create() can return.
+ */
+
+void
+dns_view_setcache(dns_view_t *view, dns_cache_t *cache, bool shared);
+/*%<
+ * Set the view's cache database. If 'shared' is true, this means the cache
+ * is created by another view and is shared with that view. dns_view_setcache()
+ * is a backward compatible version equivalent to setcache2(..., false).
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view.
+ *
+ *\li 'cache' is a valid cache.
+ *
+ * Ensures:
+ *
+ * \li The cache of 'view' is 'cached.
+ *
+ *\li If this is not the first call to dns_view_setcache() for this
+ * view, then previously set cache is detached.
+ */
+
+void
+dns_view_sethints(dns_view_t *view, dns_db_t *hints);
+/*%<
+ * Set the view's hints database.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view, whose hints database has not been
+ * set.
+ *
+ *\li 'hints' is a valid zone database.
+ *
+ * Ensures:
+ *
+ * \li The hints database of 'view' is 'hints'.
+ */
+
+void
+dns_view_setkeyring(dns_view_t *view, dns_tsig_keyring_t *ring);
+void
+dns_view_setdynamickeyring(dns_view_t *view, dns_tsig_keyring_t *ring);
+/*%<
+ * Set the view's static TSIG keys
+ *
+ * Requires:
+ *
+ * \li 'view' is a valid, unfrozen view, whose static TSIG keyring has not
+ * been set.
+ *
+ *\li 'ring' is a valid TSIG keyring
+ *
+ * Ensures:
+ *
+ *\li The static TSIG keyring of 'view' is 'ring'.
+ */
+
+void
+dns_view_getdynamickeyring(dns_view_t *view, dns_tsig_keyring_t **ringp);
+/*%<
+ * Return the views dynamic keys.
+ *
+ * \li 'view' is a valid, unfrozen view.
+ * \li 'ringp' != NULL && ringp == NULL.
+ */
+
+void
+dns_view_setdstport(dns_view_t *view, in_port_t dstport);
+/*%<
+ * Set the view's destination port. This is the port to
+ * which outgoing queries are sent. The default is 53,
+ * the standard DNS port.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid view.
+ *
+ *\li 'dstport' is a valid TCP/UDP port number.
+ *
+ * Ensures:
+ *\li External name servers will be assumed to be listening
+ * on 'dstport'. For servers whose address has already
+ * obtained obtained at the time of the call, the view may
+ * continue to use the previously set port until the address
+ * times out from the view's address database.
+ */
+
+isc_result_t
+dns_view_addzone(dns_view_t *view, dns_zone_t *zone);
+/*%<
+ * Add zone 'zone' to 'view'.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view.
+ *
+ *\li 'zone' is a valid zone.
+ */
+
+void
+dns_view_freeze(dns_view_t *view);
+/*%<
+ * Freeze view. No changes can be made to view configuration while frozen.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, unfrozen view.
+ *
+ * Ensures:
+ *
+ *\li 'view' is frozen.
+ */
+
+void
+dns_view_thaw(dns_view_t *view);
+/*%<
+ * Thaw view. This allows zones to be added or removed at runtime. This is
+ * NOT thread-safe; the caller MUST have run isc_task_exclusive() prior to
+ * thawing the view.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, frozen view.
+ *
+ * Ensures:
+ *
+ *\li 'view' is no longer frozen.
+ */
+
+isc_result_t
+dns_view_find(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type,
+ isc_stdtime_t now, unsigned int options, bool use_hints,
+ bool use_static_stub, dns_db_t **dbp, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset);
+/*%<
+ * Find an rdataset whose owner name is 'name', and whose type is
+ * 'type'.
+ * In general, this function first searches view's zone and cache DBs for the
+ * best match data against 'name'. If nothing found there, and if 'use_hints'
+ * is true, the view's hint DB (if configured) is searched.
+ * If the view is configured with a static-stub zone which gives the longest
+ * match for 'name' among the zones, however, the cache DB is not consulted
+ * unless 'use_static_stub' is false (see below about this argument).
+ *
+ * dns_view_find() is a backward compatible version equivalent to
+ * dns_view_find2() with use_static_stub argument being false.
+ *
+ * Notes:
+ *
+ *\li See the description of dns_db_find() for information about 'options'.
+ * If the caller sets #DNS_DBFIND_GLUEOK, it must ensure that 'name'
+ * and 'type' are appropriate for glue retrieval.
+ *
+ *\li If 'now' is zero, then the current time will be used.
+ *
+ *\li If 'use_hints' is true, and the view has a hints database, then
+ * it will be searched last. If the answer is found in the hints
+ * database, the result code will be DNS_R_HINT. If the name is found
+ * in the hints database but not the type, the result code will be
+ * #DNS_R_HINTNXRRSET.
+ *
+ *\li If 'use_static_stub' is false and the longest match zone for 'name'
+ * is a static-stub zone, it's ignored and the cache and/or hints will be
+ * searched. In the majority of the cases this argument should be
+ * false. The only known usage of this argument being true is
+ * if this search is for a "bailiwick" glue A or AAAA RRset that may
+ * best match a static-stub zone. Consider the following example:
+ * this view is configured with a static-stub zone "example.com",
+ * and an attempt of recursive resolution needs to send a query for the
+ * zone. In this case it's quite likely that the resolver is trying to
+ * find A/AAAA RRs for the apex name "example.com". And, to honor the
+ * static-stub configuration it needs to return the glue RRs in the
+ * static-stub zone even if that exact RRs coming from the authoritative
+ * zone has been cached.
+ * In other general cases, the requested data is better to be
+ * authoritative, either locally configured or retrieved from an external
+ * server, and the data in the static-stub zone should better be ignored.
+ *
+ *\li 'foundname' must meet the requirements of dns_db_find().
+ *
+ *\li If 'sigrdataset' is not NULL, and there is a SIG rdataset which
+ * covers 'type', then 'sigrdataset' will be bound to it.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, frozen view.
+ *
+ *\li 'name' is valid name.
+ *
+ *\li 'type' is a valid dns_rdatatype_t, and is not a meta query type
+ * except dns_rdatatype_any.
+ *
+ *\li dbp == NULL || *dbp == NULL
+ *
+ *\li nodep == NULL || *nodep == NULL. If nodep != NULL, dbp != NULL.
+ *
+ *\li 'foundname' is a valid name with a dedicated buffer or NULL.
+ *
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ * Ensures:
+ *
+ *\li In successful cases, 'rdataset', and possibly 'sigrdataset', are
+ * bound to the found data.
+ *
+ *\li If dbp != NULL, it points to the database containing the data.
+ *
+ *\li If nodep != NULL, it points to the database node containing the data.
+ *
+ *\li If foundname != NULL, it contains the full name of the found data.
+ *
+ * Returns:
+ *
+ *\li Any result that dns_db_find() can return, with the exception of
+ * #DNS_R_DELEGATION.
+ */
+
+isc_result_t
+dns_view_simplefind(dns_view_t *view, const dns_name_t *name,
+ dns_rdatatype_t type, isc_stdtime_t now,
+ unsigned int options, bool use_hints,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find an rdataset whose owner name is 'name', and whose type is
+ * 'type'.
+ *
+ * Notes:
+ *
+ *\li This routine is appropriate for simple, exact-match queries of the
+ * view. 'name' must be a canonical name; there is no DNAME or CNAME
+ * processing.
+ *
+ *\li See the description of dns_db_find() for information about 'options'.
+ * If the caller sets DNS_DBFIND_GLUEOK, it must ensure that 'name'
+ * and 'type' are appropriate for glue retrieval.
+ *
+ *\li If 'now' is zero, then the current time will be used.
+ *
+ *\li If 'use_hints' is true, and the view has a hints database, then
+ * it will be searched last. If the answer is found in the hints
+ * database, the result code will be DNS_R_HINT. If the name is found
+ * in the hints database but not the type, the result code will be
+ * DNS_R_HINTNXRRSET.
+ *
+ *\li If 'sigrdataset' is not NULL, and there is a SIG rdataset which
+ * covers 'type', then 'sigrdataset' will be bound to it.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, frozen view.
+ *
+ *\li 'name' is valid name.
+ *
+ *\li 'type' is a valid dns_rdatatype_t, and is not a meta query type
+ * (e.g. dns_rdatatype_any), or dns_rdatatype_rrsig.
+ *
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ * Ensures:
+ *
+ *\li In successful cases, 'rdataset', and possibly 'sigrdataset', are
+ * bound to the found data.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS Success; result is desired type.
+ *\li DNS_R_GLUE Success; result is glue.
+ *\li DNS_R_HINT Success; result is a hint.
+ *\li DNS_R_NCACHENXDOMAIN Success; result is a ncache entry.
+ *\li DNS_R_NCACHENXRRSET Success; result is a ncache entry.
+ *\li DNS_R_NXDOMAIN The name does not exist.
+ *\li DNS_R_NXRRSET The rrset does not exist.
+ *\li #ISC_R_NOTFOUND No matching data found,
+ * or an error occurred.
+ */
+
+isc_result_t
+dns_view_findzonecut(dns_view_t *view, const dns_name_t *name,
+ dns_name_t *fname, dns_name_t *dcname, isc_stdtime_t now,
+ unsigned int options, bool use_hints, bool use_cache,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find the best known zonecut containing 'name'.
+ *
+ * This uses local authority, cache, and optionally hints data.
+ * No external queries are performed.
+ *
+ * Notes:
+ *
+ *\li If 'now' is zero, then the current time will be used.
+ *
+ *\li If 'use_hints' is true, and the view has a hints database, then
+ * it will be searched last.
+ *
+ *\li If 'use_cache' is true, and the view has a cache, then it will be
+ * searched.
+ *
+ *\li If 'sigrdataset' is not NULL, and there is a SIG rdataset which
+ * covers 'type', then 'sigrdataset' will be bound to it.
+ *
+ *\li If the DNS_DBFIND_NOEXACT option is set, then the zonecut returned
+ * (if any) will be the deepest known ancestor of 'name'.
+ *
+ *\li If dcname is not NULL the deepest cached name is copied to it.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid, frozen view.
+ *
+ *\li 'name' is valid name.
+ *
+ *\li 'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS Success.
+ *
+ *\li Many other results are possible.
+ */
+
+isc_result_t
+dns_viewlist_find(dns_viewlist_t *list, const char *name,
+ dns_rdataclass_t rdclass, dns_view_t **viewp);
+/*%<
+ * Search for a view with name 'name' and class 'rdclass' in 'list'.
+ * If found, '*viewp' is (strongly) attached to it.
+ *
+ * Requires:
+ *
+ *\li 'viewp' points to a NULL dns_view_t *.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS A matching view was found.
+ *\li #ISC_R_NOTFOUND No matching view was found.
+ */
+
+isc_result_t
+dns_viewlist_findzone(dns_viewlist_t *list, const dns_name_t *name,
+ bool allclasses, dns_rdataclass_t rdclass,
+ dns_zone_t **zonep);
+
+/*%<
+ * Search zone with 'name' in view with 'rdclass' in viewlist 'list'
+ * If found, zone is returned in *zonep. If allclasses is set rdclass is ignored
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS A matching zone was found.
+ *\li #ISC_R_NOTFOUND No matching zone was found.
+ *\li #ISC_R_MULTIPLE Multiple zones with the same name were found.
+ */
+
+isc_result_t
+dns_view_findzone(dns_view_t *view, const dns_name_t *name, dns_zone_t **zonep);
+/*%<
+ * Search for the zone 'name' in the zone table of 'view'.
+ * If found, 'zonep' is (strongly) attached to it. There
+ * are no partial matches.
+ *
+ * Requires:
+ *
+ *\li 'zonep' points to a NULL dns_zone_t *.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS A matching zone was found.
+ *\li #ISC_R_NOTFOUND No matching zone was found.
+ *\li others An error occurred.
+ */
+
+isc_result_t
+dns_view_load(dns_view_t *view, bool stop, bool newonly);
+
+isc_result_t
+dns_view_asyncload(dns_view_t *view, bool newonly, dns_zt_allloaded_t callback,
+ void *arg);
+/*%<
+ * Load zones attached to this view. dns_view_load() loads
+ * all zones whose master file has changed since the last
+ * load
+ *
+ * dns_view_asyncload() loads zones asynchronously. When all zones
+ * in the view have finished loading, 'callback' is called with argument
+ * 'arg' to inform the caller.
+ *
+ * If 'stop' is true, stop on the first error and return it.
+ * If 'stop' is false (or we are loading asynchronously), ignore errors.
+ *
+ * If 'newonly' is true load only zones that were never loaded.
+ *
+ * Requires:
+ *
+ *\li 'view' is valid.
+ */
+
+isc_result_t
+dns_view_gettsig(dns_view_t *view, const dns_name_t *keyname,
+ dns_tsigkey_t **keyp);
+/*%<
+ * Find the TSIG key configured in 'view' with name 'keyname',
+ * if any.
+ *
+ * Requires:
+ *\li keyp points to a NULL dns_tsigkey_t *.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS A key was found and '*keyp' now points to it.
+ *\li #ISC_R_NOTFOUND No key was found.
+ *\li others An error occurred.
+ */
+
+isc_result_t
+dns_view_getpeertsig(dns_view_t *view, const isc_netaddr_t *peeraddr,
+ dns_tsigkey_t **keyp);
+/*%<
+ * Find the TSIG key configured in 'view' for the server whose
+ * address is 'peeraddr', if any.
+ *
+ * Requires:
+ * keyp points to a NULL dns_tsigkey_t *.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS A key was found and '*keyp' now points to it.
+ *\li #ISC_R_NOTFOUND No key was found.
+ *\li others An error occurred.
+ */
+
+isc_result_t
+dns_view_checksig(dns_view_t *view, isc_buffer_t *source, dns_message_t *msg);
+/*%<
+ * Verifies the signature of a message.
+ *
+ * Requires:
+ *
+ *\li 'view' is a valid view.
+ *\li 'source' is a valid buffer containing the message
+ *\li 'msg' is a valid message
+ *
+ * Returns:
+ *\li see dns_tsig_verify()
+ */
+
+void
+dns_view_dialup(dns_view_t *view);
+/*%<
+ * Perform dialup-time maintenance on the zones of 'view'.
+ */
+
+isc_result_t
+dns_view_dumpdbtostream(dns_view_t *view, FILE *fp);
+/*%<
+ * Dump the current state of the view 'view' to the stream 'fp'
+ * for purposes of analysis or debugging.
+ *
+ * Currently the dumped state includes the view's cache; in the future
+ * it may also include other state such as the address database.
+ * It will not not include authoritative data since it is voluminous and
+ * easily obtainable by other means.
+ *
+ * Requires:
+ *
+ *\li 'view' is valid.
+ *
+ *\li 'fp' refers to a file open for writing.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS The cache was successfully dumped.
+ * \li others An error occurred (see dns_master_dump)
+ */
+
+isc_result_t
+dns_view_flushcache(dns_view_t *view, bool fixuponly);
+/*%<
+ * Flush the view's cache (and ADB). If 'fixuponly' is true, it only updates
+ * the internal reference to the cache DB with omitting actual flush operation.
+ * 'fixuponly' is intended to be used for a view that shares a cache with
+ * a different view. dns_view_flushcache() is a backward compatible version
+ * that always sets fixuponly to false.
+ *
+ * Requires:
+ * 'view' is valid.
+ *
+ * No other tasks are executing.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_view_flushnode(dns_view_t *view, const dns_name_t *name, bool tree);
+/*%<
+ * Flush the given name from the view's cache (and optionally ADB/badcache).
+ *
+ * Flush the given name from the cache, ADB, and bad cache. If 'tree'
+ * is true, also flush all subdomains of 'name'.
+ *
+ * Requires:
+ *\li 'view' is valid.
+ *\li 'name' is valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ * other returns are failures.
+ */
+
+isc_result_t
+dns_view_flushname(dns_view_t *view, const dns_name_t *name);
+/*%<
+ * Flush the given name from the view's cache, ADB and badcache.
+ * Equivalent to dns_view_flushnode(view, name, false).
+ *
+ *
+ * Requires:
+ *\li 'view' is valid.
+ *\li 'name' is valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ * other returns are failures.
+ */
+
+isc_result_t
+dns_view_adddelegationonly(dns_view_t *view, const dns_name_t *name);
+/*%<
+ * Add the given name to the delegation only table.
+ *
+ * Requires:
+ *\li 'view' is valid.
+ *\li 'name' is valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_view_excludedelegationonly(dns_view_t *view, const dns_name_t *name);
+/*%<
+ * Add the given name to be excluded from the root-delegation-only.
+ *
+ *
+ * Requires:
+ *\li 'view' is valid.
+ *\li 'name' is valid.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+bool
+dns_view_isdelegationonly(dns_view_t *view, const dns_name_t *name);
+/*%<
+ * Check if 'name' is in the delegation only table or if
+ * rootdelonly is set that name is not being excluded.
+ *
+ * Requires:
+ *\li 'view' is valid.
+ *\li 'name' is valid.
+ *
+ * Returns:
+ *\li #true if the name is the table.
+ *\li #false otherwise.
+ */
+
+void
+dns_view_setrootdelonly(dns_view_t *view, bool value);
+/*%<
+ * Set the root delegation only flag.
+ *
+ * Requires:
+ *\li 'view' is valid.
+ */
+
+bool
+dns_view_getrootdelonly(dns_view_t *view);
+/*%<
+ * Get the root delegation only flag.
+ *
+ * Requires:
+ *\li 'view' is valid.
+ */
+
+isc_result_t
+dns_view_freezezones(dns_view_t *view, bool freeze);
+/*%<
+ * Freeze/thaw updates to master zones.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ */
+
+void
+dns_view_setadbstats(dns_view_t *view, isc_stats_t *stats);
+/*%<
+ * Set a adb statistics set 'stats' for 'view'.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li stats is a valid statistics supporting adb statistics
+ * (see dns/stats.h).
+ */
+
+void
+dns_view_getadbstats(dns_view_t *view, isc_stats_t **statsp);
+/*%<
+ * Get the adb statistics counter set for 'view'. If a statistics set is
+ * set '*statsp' will be attached to the set; otherwise, '*statsp' will be
+ * untouched.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li 'statsp' != NULL && '*statsp' != NULL
+ */
+
+void
+dns_view_setresstats(dns_view_t *view, isc_stats_t *stats);
+/*%<
+ * Set a general resolver statistics counter set 'stats' for 'view'.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li stats is a valid statistics supporting resolver statistics counters
+ * (see dns/stats.h).
+ */
+
+void
+dns_view_getresstats(dns_view_t *view, isc_stats_t **statsp);
+/*%<
+ * Get the general statistics counter set for 'view'. If a statistics set is
+ * set '*statsp' will be attached to the set; otherwise, '*statsp' will be
+ * untouched.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li 'statsp' != NULL && '*statsp' != NULL
+ */
+
+void
+dns_view_setresquerystats(dns_view_t *view, dns_stats_t *stats);
+/*%<
+ * Set a statistics counter set of rdata type, 'stats', for 'view'. Once the
+ * statistic set is installed, view's resolver will count outgoing queries
+ * per rdata type.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li stats is a valid statistics created by dns_rdatatypestats_create().
+ */
+
+void
+dns_view_getresquerystats(dns_view_t *view, dns_stats_t **statsp);
+/*%<
+ * Get the rdatatype statistics counter set for 'view'. If a statistics set is
+ * set '*statsp' will be attached to the set; otherwise, '*statsp' will be
+ * untouched.
+ *
+ * Requires:
+ * \li 'view' is valid and is not frozen.
+ *
+ *\li 'statsp' != NULL && '*statsp' != NULL
+ */
+
+bool
+dns_view_iscacheshared(dns_view_t *view);
+/*%<
+ * Check if the view shares the cache created by another view.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ *\li #true if the cache is shared.
+ *\li #false otherwise.
+ */
+
+isc_result_t
+dns_view_initntatable(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr);
+/*%<
+ * Initialize the negative trust anchor table for the view.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li Any other result indicates failure
+ */
+
+isc_result_t
+dns_view_getntatable(dns_view_t *view, dns_ntatable_t **ntp);
+/*%<
+ * Get the negative trust anchor table for this view. Returns
+ * ISC_R_NOTFOUND if the table not been initialized for the view.
+ *
+ * '*ntp' is attached on success; the caller is responsible for
+ * detaching it with dns_ntatable_detach().
+ *
+ * Requires:
+ * \li 'view' is valid.
+ * \li 'nta' is not NULL and '*nta' is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTFOUND
+ */
+
+isc_result_t
+dns_view_initsecroots(dns_view_t *view, isc_mem_t *mctx);
+/*%<
+ * Initialize security roots for the view, detaching any previously
+ * existing security roots first. (Note that secroots_priv is
+ * NULL until this function is called, so any function using
+ * security roots must check that they have been initialized first.
+ * One way to do this is use dns_view_getsecroots() and check its
+ * return value.)
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li Any other result indicates failure
+ */
+
+isc_result_t
+dns_view_getsecroots(dns_view_t *view, dns_keytable_t **ktp);
+/*%<
+ * Get the security roots for this view. Returns ISC_R_NOTFOUND if
+ * the security roots keytable has not been initialized for the view.
+ *
+ * '*ktp' is attached on success; the caller is responsible for
+ * detaching it with dns_keytable_detach().
+ *
+ * Requires:
+ * \li 'view' is valid.
+ * \li 'ktp' is not NULL and '*ktp' is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOTFOUND
+ */
+
+isc_result_t
+dns_view_issecuredomain(dns_view_t *view, const dns_name_t *name,
+ isc_stdtime_t now, bool checknta, bool *ntap,
+ bool *secure_domain);
+/*%<
+ * Is 'name' at or beneath a trusted key, and not covered by a valid
+ * negative trust anchor? Put answer in '*secure_domain'.
+ *
+ * If 'checknta' is false, ignore the NTA table in determining
+ * whether this is a secure domain. If 'checknta' is not false, and if
+ * 'ntap' is non-NULL, then '*ntap' will be updated with true if the
+ * name is covered by an NTA.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li Any other value indicates failure
+ */
+
+bool
+dns_view_ntacovers(dns_view_t *view, isc_stdtime_t now, const dns_name_t *name,
+ const dns_name_t *anchor);
+/*%<
+ * Is there a current negative trust anchor above 'name' and below 'anchor'?
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ *\li ISC_R_TRUE
+ *\li ISC_R_FALSE
+ */
+
+void
+dns_view_untrust(dns_view_t *view, const dns_name_t *keyname,
+ const dns_rdata_dnskey_t *dnskey);
+/*%<
+ * Remove keys that match 'keyname' and 'dnskey' from the views trust
+ * anchors.
+ *
+ * (NOTE: If the configuration specifies that there should be a
+ * trust anchor at 'keyname', but no keys are left after this
+ * operation, that is an error. We fail closed, inserting a NULL
+ * key so as to prevent validation until a legimitate key has been
+ * provided.)
+ *
+ * Requires:
+ * \li 'view' is valid.
+ * \li 'keyname' is valid.
+ * \li 'dnskey' is valid.
+ */
+
+bool
+dns_view_istrusted(dns_view_t *view, const dns_name_t *keyname,
+ const dns_rdata_dnskey_t *dnskey);
+/*%<
+ * Determine if the key defined by 'keyname' and 'dnskey' is
+ * trusted by 'view'.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ * \li 'keyname' is valid.
+ * \li 'dnskey' is valid.
+ */
+
+isc_result_t
+dns_view_setnewzones(dns_view_t *view, bool allow, void *cfgctx,
+ void (*cfg_destroy)(void **), uint64_t mapsize);
+/*%<
+ * Set whether or not to allow zones to be created or deleted at runtime.
+ *
+ * If 'allow' is true, determines the filename into which new zone
+ * configuration will be written. Preserves the configuration context
+ * (a pointer to which is passed in 'cfgctx') for use when parsing new
+ * zone configuration. 'cfg_destroy' points to a callback routine to
+ * destroy the configuration context when the view is destroyed. (This
+ * roundabout method is used in order to avoid libdns having a dependency
+ * on libisccfg and libbind9.)
+ *
+ * If 'allow' is false, removes any existing references to
+ * configuration context and frees any memory.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOSPACE
+ */
+
+void
+dns_view_setnewzonedir(dns_view_t *view, const char *dir);
+const char *
+dns_view_getnewzonedir(dns_view_t *view);
+/*%<
+ * Set/get the path to the directory in which NZF or NZD files should
+ * be stored. If the path was previously set to a non-NULL value,
+ * the previous value is freed.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ */
+
+void
+dns_view_restorekeyring(dns_view_t *view);
+
+isc_result_t
+dns_view_searchdlz(dns_view_t *view, const dns_name_t *name,
+ unsigned int minlabels, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo, dns_db_t **dbp);
+
+/*%<
+ * Search through the DLZ database(s) in view->dlz_searched to find
+ * one that can answer a query for 'name', using the DLZ driver's
+ * findzone method. If successful, '*dbp' is set to point to the
+ * DLZ database.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOTFOUND
+ *
+ * Requires:
+ * \li 'view' is valid.
+ * \li 'name' is not NULL.
+ * \li 'dbp' is not NULL and *dbp is NULL.
+ */
+
+uint32_t
+dns_view_getfailttl(dns_view_t *view);
+/*%<
+ * Get the view's servfail-ttl. zero => no servfail caching.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+void
+dns_view_setfailttl(dns_view_t *view, uint32_t failttl);
+/*%<
+ * Set the view's servfail-ttl. zero => no servfail caching.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+isc_result_t
+dns_view_saventa(dns_view_t *view);
+/*%<
+ * Save NTA for names in this view to a file.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+isc_result_t
+dns_view_loadnta(dns_view_t *view);
+/*%<
+ * Loads NTA for names in this view from a file.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+void
+dns_view_setviewcommit(dns_view_t *view);
+/*%<
+ * Commit dns_zone_setview() calls previously made for all zones in this
+ * view.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+void
+dns_view_setviewrevert(dns_view_t *view);
+/*%<
+ * Revert dns_zone_setview() calls previously made for all zones in this
+ * view.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+bool
+dns_view_staleanswerenabled(dns_view_t *view);
+/*%<
+ * Check if stale answers are enabled for this view.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_VIEW_H */
diff --git a/lib/dns/include/dns/xfrin.h b/lib/dns/include/dns/xfrin.h
new file mode 100644
index 0000000..2c0f530
--- /dev/null
+++ b/lib/dns/include/dns/xfrin.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_XFRIN_H
+#define DNS_XFRIN_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file dns/xfrin.h
+ * \brief
+ * Incoming zone transfers (AXFR + IXFR).
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <isc/lang.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 *masteraddr,
+ const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp,
+ dns_tsigkey_t *tsigkey, isc_mem_t *mctx,
+ isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr,
+ isc_task_t *task, dns_xfrindone_t done,
+ dns_xfrin_ctx_t **xfrp);
+/*%<
+ * Attempt to start an incoming zone transfer of 'zone'
+ * from 'masteraddr', creating a dns_xfrin_ctx_t object to
+ * manage it. Attach '*xfrp' to the newly created object.
+ *
+ * Iff ISC_R_SUCCESS is returned, '*done' is guaranteed to be
+ * called in the context of 'task', with 'zone' and a result
+ * code as arguments when the transfer finishes.
+ *
+ * Requires:
+ *\li 'xfrtype' is dns_rdatatype_axfr, dns_rdatatype_ixfr
+ * or dns_rdatatype_soa (soa query followed by axfr if
+ * serial is greater than current serial).
+ *
+ *\li If 'xfrtype' is dns_rdatatype_ixfr or dns_rdatatype_soa,
+ * the zone has a database.
+ */
+
+void
+dns_xfrin_shutdown(dns_xfrin_ctx_t *xfr);
+/*%<
+ * If the zone transfer 'xfr' has already finished,
+ * do nothing. Otherwise, abort it and cause it to call
+ * its done callback with a status of ISC_R_CANCELED.
+ */
+
+void
+dns_xfrin_detach(dns_xfrin_ctx_t **xfrp);
+/*%<
+ * Detach a reference to a zone transfer object.
+ * Caller to maintain external locking if required.
+ */
+
+void
+dns_xfrin_attach(dns_xfrin_ctx_t *source, dns_xfrin_ctx_t **target);
+/*%<
+ * Caller to maintain external locking if required.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_XFRIN_H */
diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h
new file mode 100644
index 0000000..4bdc936
--- /dev/null
+++ b/lib/dns/include/dns/zone.h
@@ -0,0 +1,2726 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_ZONE_H
+#define DNS_ZONE_H 1
+
+/*! \file dns/zone.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/formatcheck.h>
+#include <isc/lang.h>
+#include <isc/rwlock.h>
+
+#include <dns/catz.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.
+ */
+
+void
+dns_zone_setmaxrecords(dns_zone_t *zone, uint32_t records);
+/*%<
+ * Sets the maximum number of records permitted in a zone.
+ * 0 implies unlimited.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ *\li void
+ */
+
+uint32_t
+dns_zone_getmaxrecords(dns_zone_t *zone);
+/*%<
+ * Gets the maximum number of records permitted in a zone.
+ * 0 implies unlimited.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ *\li uint32_t maxrecords.
+ */
+
+void
+dns_zone_setmaxttl(dns_zone_t *zone, uint32_t maxttl);
+/*%<
+ * Sets the max ttl of the zone.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ *\li void
+ */
+
+dns_ttl_t
+dns_zone_getmaxttl(dns_zone_t *zone);
+/*%<
+ * Gets the max ttl of the zone.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ *\li dns_ttl_t maxttl.
+ */
+
+void
+dns_zone_lock_keyfiles(dns_zone_t *zone);
+/*%<
+ * Lock associated keyfiles for this zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_unlock_keyfiles(dns_zone_t *zone);
+/*%<
+ * Unlock associated keyfiles for this zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_load(dns_zone_t *zone, bool newonly);
+
+isc_result_t
+dns_zone_loadandthaw(dns_zone_t *zone);
+
+/*%<
+ * Cause the database to be loaded from its backing store.
+ * Confirm that the minimum requirements for the zone type are
+ * met, otherwise DNS_R_BADZONE is returned.
+ *
+ * If newonly is set dns_zone_load() only loads new zones.
+ * dns_zone_loadandthaw() is similar to dns_zone_load() but will
+ * also re-enable DNS UPDATEs when the load completes.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_UNEXPECTED
+ *\li #ISC_R_SUCCESS
+ *\li DNS_R_CONTINUE Incremental load has been queued.
+ *\li DNS_R_UPTODATE The zone has already been loaded based on
+ * file system timestamps.
+ *\li DNS_R_BADZONE
+ *\li Any result value from dns_db_load().
+ */
+
+isc_result_t
+dns_zone_asyncload(dns_zone_t *zone, bool newonly, dns_zt_zoneloaded_t done,
+ void *arg);
+/*%<
+ * Cause the database to be loaded from its backing store asynchronously.
+ * Other zone maintenance functions are suspended until this is complete.
+ * When finished, 'done' is called to inform the caller, with 'arg' as
+ * its first argument and 'zone' as its second. (Normally, 'arg' is
+ * expected to point to the zone table but is left undefined for testing
+ * purposes.)
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_ALREADYRUNNING
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_FAILURE
+ *\li #ISC_R_NOMEMORY
+ */
+
+bool
+dns__zone_loadpending(dns_zone_t *zone);
+/*%<
+ * Indicates whether the zone is waiting to be loaded asynchronously.
+ * (Not currently intended for use outside of this module and associated
+ * tests.)
+ */
+
+void
+dns_zone_attach(dns_zone_t *source, dns_zone_t **target);
+/*%<
+ * Attach '*target' to 'source' incrementing its external
+ * reference count.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_zone_detach(dns_zone_t **zonep);
+/*%<
+ * Detach from a zone decrementing its external reference count.
+ * If this was the last external reference to the zone it will be
+ * shut down and eventually freed.
+ *
+ * Require:
+ *\li 'zonep' to point to a valid zone.
+ */
+
+void
+dns_zone_iattach(dns_zone_t *source, dns_zone_t **target);
+/*%<
+ * Attach '*target' to 'source' incrementing its internal
+ * reference count. This is intended for use by operations
+ * such as zone transfers that need to prevent the zone
+ * object from being freed but not from shutting down.
+ *
+ * Require:
+ *\li The caller is running in the context of the zone's task.
+ *\li 'zone' to be a valid zone.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_zone_idetach(dns_zone_t **zonep);
+/*%<
+ * Detach from a zone decrementing its internal reference count.
+ * If there are no more internal or external references to the
+ * zone, it will be freed.
+ *
+ * Require:
+ *\li The caller is running in the context of the zone's task.
+ *\li 'zonep' to point to a valid zone.
+ */
+
+isc_result_t
+dns_zone_getdb(dns_zone_t *zone, dns_db_t **dbp);
+/*%<
+ * Attach '*dbp' to the database to if it exists otherwise
+ * return DNS_R_NOTLOADED.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'dbp' to be != NULL && '*dbp' == NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li DNS_R_NOTLOADED
+ */
+
+void
+dns_zone_setdb(dns_zone_t *zone, dns_db_t *db);
+/*%<
+ * Sets the zone database to 'db'.
+ *
+ * This function is expected to be used to configure a zone with a
+ * database which is not loaded from a file or zone transfer.
+ * It can be used for a general purpose zone, but right now its use
+ * is limited to static-stub zones to avoid possible undiscovered
+ * problems in the general cases.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone of static-stub.
+ *\li zone doesn't have a database.
+ */
+
+void
+dns_zone_setdbtype(dns_zone_t *zone, unsigned int dbargc,
+ const char *const *dbargv);
+/*%<
+ * Sets the database type to dbargv[0] and database arguments
+ * to subsequent dbargv elements.
+ * 'db_type' is not checked to see if it is a valid database type.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'database' to be non NULL.
+ *\li 'dbargc' to be >= 1
+ *\li 'dbargv' to point to dbargc NULL-terminated strings
+ */
+
+isc_result_t
+dns_zone_getdbtype(dns_zone_t *zone, char ***argv, isc_mem_t *mctx);
+/*%<
+ * Returns the current dbtype. isc_mem_free() should be used
+ * to free 'argv' after use.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'argv' to be non NULL and *argv to be NULL.
+ *\li 'mctx' to be valid.
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_SUCCESS
+ */
+
+void
+dns_zone_markdirty(dns_zone_t *zone);
+/*%<
+ * Mark a zone as 'dirty'.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_expire(dns_zone_t *zone);
+/*%<
+ * Mark the zone as expired. If the zone requires dumping cause it to
+ * be initiated. Set the refresh and retry intervals to there default
+ * values and unload the zone.
+ *
+ * Require
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_refresh(dns_zone_t *zone);
+/*%<
+ * Initiate zone up to date checks. The zone must already be being
+ * managed.
+ *
+ * Require
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_flush(dns_zone_t *zone);
+/*%<
+ * Write the zone to database if there are uncommitted changes.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_dump(dns_zone_t *zone);
+/*%<
+ * Write the zone to database.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_dumptostream(dns_zone_t *zone, FILE *fd, dns_masterformat_t format,
+ const dns_master_style_t *style,
+ const uint32_t rawversion);
+/*%<
+ * Write the zone to stream 'fd' in the specified 'format'.
+ * If the 'format' is dns_masterformat_text (RFC1035), 'style' also
+ * specifies the file style (e.g., &dns_master_style_default).
+ *
+ * dns_zone_dumptostream() is a backward-compatible form of
+ * dns_zone_dumptostream2(), which always uses the dns_masterformat_text
+ * format and the dns_master_style_default style.
+ *
+ * dns_zone_dumptostream2() is a backward-compatible form of
+ * dns_zone_dumptostream3(), which always uses the current
+ * default raw file format version.
+ *
+ * Note that dns_zone_dumptostream3() is the most flexible form. It
+ * can also provide the functionality of dns_zone_fulldumptostream().
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'fd' to be a stream open for writing.
+ */
+
+void
+dns_zone_maintenance(dns_zone_t *zone);
+/*%<
+ * Perform regular maintenance on the zone. This is called as a
+ * result of a zone being managed.
+ *
+ * Require
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setprimaries(dns_zone_t *zone, const isc_sockaddr_t *primaries,
+ uint32_t count);
+isc_result_t
+dns_zone_setprimarieswithkeys(dns_zone_t *zone, const isc_sockaddr_t *primaries,
+ dns_name_t **keynames, uint32_t count);
+/*%<
+ * Set the list of master servers for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'primaries' array of isc_sockaddr_t with port set or NULL.
+ *\li 'count' the number of primaries.
+ *\li 'keynames' array of dns_name_t's for tsig keys or NULL.
+ *
+ *\li If 'primaries' is NULL then 'count' must be zero.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Any result dns_name_dup() can return, if keynames!=NULL
+ */
+
+isc_result_t
+dns_zone_setparentals(dns_zone_t *zone, const isc_sockaddr_t *parentals,
+ dns_name_t **keynames, uint32_t count);
+/*%<
+ * Set the list of parental agents for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'parentals' array of isc_sockaddr_t with port set or NULL.
+ *\li 'count' the number of primaries.
+ *\li 'keynames' array of dns_name_t's for tsig keys or NULL.
+ *
+ *\li If 'parentals' is NULL then 'count' must be zero.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Any result dns_name_dup() can return, if keynames!=NULL
+ */
+
+isc_result_t
+dns_zone_setalsonotify(dns_zone_t *zone, const isc_sockaddr_t *notify,
+ uint32_t count);
+isc_result_t
+dns_zone_setalsonotifywithkeys(dns_zone_t *zone, const isc_sockaddr_t *notify,
+ dns_name_t **keynames, uint32_t count);
+isc_result_t
+dns_zone_setalsonotifydscpkeys(dns_zone_t *zone, const isc_sockaddr_t *notify,
+ const isc_dscp_t *dscps, dns_name_t **keynames,
+ uint32_t count);
+/*%<
+ * Set the list of additional servers to be notified when
+ * a zone changes. To clear the list use 'count = 0'.
+ *
+ * dns_zone_alsonotifywithkeys() allows each notify address to
+ * be associated with a TSIG key.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'notify' to be non-NULL if count != 0.
+ *\li 'count' to be the number of notifiees.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+dns_zone_unload(dns_zone_t *zone);
+/*%<
+ * detach the database from the zone structure.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+dns_kasp_t *
+dns_zone_getkasp(dns_zone_t *zone);
+/*%<
+ * Returns the current kasp.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setkasp(dns_zone_t *zone, dns_kasp_t *kasp);
+/*%<
+ * Set kasp for zone. If a kasp is already set, it will be detached.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option, bool value);
+/*%<
+ * Set the given options on ('value' == true) or off
+ * ('value' == #false).
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+dns_zoneopt_t
+dns_zone_getoptions(dns_zone_t *zone);
+/*%<
+ * Returns the current zone options.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setkeyopt(dns_zone_t *zone, unsigned int option, bool value);
+/*%<
+ * Set key options on ('value' == true) or off ('value' ==
+ * #false).
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+unsigned int
+dns_zone_getkeyopts(dns_zone_t *zone);
+/*%<
+ * Returns the current zone key options.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setminrefreshtime(dns_zone_t *zone, uint32_t val);
+/*%<
+ * Set the minimum refresh time.
+ *
+ * Requires:
+ *\li 'zone' is valid.
+ *\li val > 0.
+ */
+
+void
+dns_zone_setmaxrefreshtime(dns_zone_t *zone, uint32_t val);
+/*%<
+ * Set the maximum refresh time.
+ *
+ * Requires:
+ *\li 'zone' is valid.
+ *\li val > 0.
+ */
+
+void
+dns_zone_setminretrytime(dns_zone_t *zone, uint32_t val);
+/*%<
+ * Set the minimum retry time.
+ *
+ * Requires:
+ *\li 'zone' is valid.
+ *\li val > 0.
+ */
+
+void
+dns_zone_setmaxretrytime(dns_zone_t *zone, uint32_t val);
+/*%<
+ * Set the maximum retry time.
+ *
+ * Requires:
+ *\li 'zone' is valid.
+ * val > 0.
+ */
+
+isc_result_t
+dns_zone_setxfrsource4(dns_zone_t *zone, const isc_sockaddr_t *xfrsource);
+isc_result_t
+dns_zone_setaltxfrsource4(dns_zone_t *zone, const isc_sockaddr_t *xfrsource);
+/*%<
+ * Set the source address to be used in IPv4 zone transfers.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'xfrsource' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getxfrsource4(dns_zone_t *zone);
+isc_sockaddr_t *
+dns_zone_getaltxfrsource4(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setxfrsource4
+ * call, or the default of inaddr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setxfrsource4dscp(dns_zone_t *zone, isc_dscp_t dscp);
+isc_result_t
+dns_zone_setaltxfrsource4dscp(dns_zone_t *zone, isc_dscp_t dscp);
+/*%<
+ * Set the DSCP value associated with the transfer/alt-transfer source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_dscp_t
+dns_zone_getxfrsource4dscp(dns_zone_t *zone);
+isc_dscp_t
+dns_zone_getaltxfrsource4dscp(dns_zone_t *zone);
+/*%/
+ * Get the DSCP value associated with the transfer/alt-transfer source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setxfrsource6(dns_zone_t *zone, const isc_sockaddr_t *xfrsource);
+isc_result_t
+dns_zone_setaltxfrsource6(dns_zone_t *zone, const isc_sockaddr_t *xfrsource);
+/*%<
+ * Set the source address to be used in IPv6 zone transfers.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'xfrsource' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getxfrsource6(dns_zone_t *zone);
+isc_sockaddr_t *
+dns_zone_getaltxfrsource6(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setxfrsource6
+ * call, or the default of in6addr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_dscp_t
+dns_zone_getxfrsource6dscp(dns_zone_t *zone);
+isc_dscp_t
+dns_zone_getaltxfrsource6dscp(dns_zone_t *zone);
+/*%/
+ * Get the DSCP value associated with the transfer/alt-transfer source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setxfrsource6dscp(dns_zone_t *zone, isc_dscp_t dscp);
+isc_result_t
+dns_zone_setaltxfrsource6dscp(dns_zone_t *zone, isc_dscp_t dscp);
+/*%<
+ * Set the DSCP value associated with the transfer/alt-transfer source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_result_t
+dns_zone_setparentalsrc4(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc);
+/*%<
+ * Set the source address to be used with IPv4 parental DS queries.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'parentalsrc' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getparentalsrc4(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setparentalsrc4
+ * call, or the default of inaddr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_dscp_t
+dns_zone_getparentalsrc4dscp(dns_zone_t *zone);
+/*%/
+ * Get the DSCP value associated with the IPv4 parental source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setparentalsrc4dscp(dns_zone_t *zone, isc_dscp_t dscp);
+/*%<
+ * Set the DSCP value associated with the IPv4 parental source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_result_t
+dns_zone_setparentalsrc6(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc);
+/*%<
+ * Set the source address to be used with IPv6 parental DS queries.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'parentalsrc' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getparentalsrc6(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setparentalsrc6
+ * call, or the default of in6addr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_dscp_t
+dns_zone_getparentalsrc6dscp(dns_zone_t *zone);
+/*%/
+ * Get the DSCP value associated with the IPv6 parental source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setparentalsrc6dscp(dns_zone_t *zone, isc_dscp_t dscp);
+/*%<
+ * Set the DSCP value associated with the IPv6 parental source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_result_t
+dns_zone_setnotifysrc4(dns_zone_t *zone, const isc_sockaddr_t *notifysrc);
+/*%<
+ * Set the source address to be used with IPv4 NOTIFY messages.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'notifysrc' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getnotifysrc4(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setnotifysrc4
+ * call, or the default of inaddr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_dscp_t
+dns_zone_getnotifysrc4dscp(dns_zone_t *zone);
+/*%/
+ * Get the DSCP value associated with the IPv4 notify source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setnotifysrc4dscp(dns_zone_t *zone, isc_dscp_t dscp);
+/*%<
+ * Set the DSCP value associated with the IPv4 notify source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_result_t
+dns_zone_setnotifysrc6(dns_zone_t *zone, const isc_sockaddr_t *notifysrc);
+/*%<
+ * Set the source address to be used with IPv6 NOTIFY messages.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'notifysrc' to contain the address.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+isc_sockaddr_t *
+dns_zone_getnotifysrc6(dns_zone_t *zone);
+/*%<
+ * Returns the source address set by a previous dns_zone_setnotifysrc6
+ * call, or the default of in6addr_any, port 0.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_dscp_t
+dns_zone_getnotifysrc6dscp(dns_zone_t *zone);
+/*%/
+ * Get the DSCP value associated with the IPv6 notify source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setnotifysrc6dscp(dns_zone_t *zone, isc_dscp_t dscp);
+/*%<
+ * Set the DSCP value associated with the IPv6 notify source.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ */
+
+void
+dns_zone_setnotifyacl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the notify acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be a valid acl.
+ */
+
+void
+dns_zone_setqueryacl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the query acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be a valid acl.
+ */
+
+void
+dns_zone_setqueryonacl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the query-on acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be a valid acl.
+ */
+
+void
+dns_zone_setupdateacl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the update acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be valid acl.
+ */
+
+void
+dns_zone_setforwardacl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the forward unsigned updates acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be valid acl.
+ */
+
+void
+dns_zone_setxfracl(dns_zone_t *zone, dns_acl_t *acl);
+/*%<
+ * Sets the transfer acl list for the zone.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'acl' to be valid acl.
+ */
+
+dns_acl_t *
+dns_zone_getnotifyacl(dns_zone_t *zone);
+/*%<
+ * Returns the current notify acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+dns_acl_t *
+dns_zone_getqueryacl(dns_zone_t *zone);
+/*%<
+ * Returns the current query acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+dns_acl_t *
+dns_zone_getqueryonacl(dns_zone_t *zone);
+/*%<
+ * Returns the current query-on acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+dns_acl_t *
+dns_zone_getupdateacl(dns_zone_t *zone);
+/*%<
+ * Returns the current update acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+dns_acl_t *
+dns_zone_getforwardacl(dns_zone_t *zone);
+/*%<
+ * Returns the current forward unsigned updates acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+dns_acl_t *
+dns_zone_getxfracl(dns_zone_t *zone);
+/*%<
+ * Returns the current transfer acl or NULL.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li acl a pointer to the acl.
+ *\li NULL
+ */
+
+void
+dns_zone_clearupdateacl(dns_zone_t *zone);
+/*%<
+ * Clear the current update acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_clearforwardacl(dns_zone_t *zone);
+/*%<
+ * Clear the current forward unsigned updates acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_clearnotifyacl(dns_zone_t *zone);
+/*%<
+ * Clear the current notify acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_clearqueryacl(dns_zone_t *zone);
+/*%<
+ * Clear the current query acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_clearqueryonacl(dns_zone_t *zone);
+/*%<
+ * Clear the current query-on acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_clearxfracl(dns_zone_t *zone);
+/*%<
+ * Clear the current transfer acl.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+bool
+dns_zone_getupdatedisabled(dns_zone_t *zone);
+/*%<
+ * Return update disabled.
+ * Transient unless called when running in isc_task_exclusive() mode.
+ */
+
+void
+dns_zone_setupdatedisabled(dns_zone_t *zone, bool state);
+/*%<
+ * Set update disabled.
+ * Should only be called only when running in isc_task_exclusive() mode.
+ * Failure to do so may result in updates being committed after the
+ * call has been made.
+ */
+
+bool
+dns_zone_getzeronosoattl(dns_zone_t *zone);
+/*%<
+ * Return zero-no-soa-ttl status.
+ */
+
+void
+dns_zone_setzeronosoattl(dns_zone_t *zone, bool state);
+/*%<
+ * Set zero-no-soa-ttl status.
+ */
+
+void
+dns_zone_setchecknames(dns_zone_t *zone, dns_severity_t severity);
+/*%<
+ * Set the severity of name checking when loading a zone.
+ *
+ * Require:
+ * \li 'zone' to be a valid zone.
+ */
+
+dns_severity_t
+dns_zone_getchecknames(dns_zone_t *zone);
+/*%<
+ * Return the current severity of name checking.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setjournalsize(dns_zone_t *zone, int32_t size);
+/*%<
+ * Sets the journal size for the zone.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+int32_t
+dns_zone_getjournalsize(dns_zone_t *zone);
+/*%<
+ * Return the journal size as set with a previous call to
+ * dns_zone_setjournalsize().
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from,
+ isc_sockaddr_t *to, dns_message_t *msg);
+/*%<
+ * Tell the zone that it has received a NOTIFY message from another
+ * server. This may cause some zone maintenance activity to occur.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ *\li '*from' to contain the address of the server from which 'msg'
+ * was received.
+ *\li 'msg' a message with opcode NOTIFY and qr clear.
+ *
+ * Returns:
+ *\li DNS_R_REFUSED
+ *\li DNS_R_NOTIMP
+ *\li DNS_R_FORMERR
+ *\li DNS_R_SUCCESS
+ */
+
+void
+dns_zone_setmaxxfrin(dns_zone_t *zone, uint32_t maxxfrin);
+/*%<
+ * Set the maximum time (in seconds) that a zone transfer in (AXFR/IXFR)
+ * of this zone will use before being aborted.
+ *
+ * Requires:
+ * \li 'zone' to be valid initialised zone.
+ */
+
+uint32_t
+dns_zone_getmaxxfrin(dns_zone_t *zone);
+/*%<
+ * Returns the maximum transfer time for this zone. This will be
+ * either the value set by the last call to dns_zone_setmaxxfrin() or
+ * the default value of 1 hour.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ */
+
+void
+dns_zone_setmaxxfrout(dns_zone_t *zone, uint32_t maxxfrout);
+/*%<
+ * Set the maximum time (in seconds) that a zone transfer out (AXFR/IXFR)
+ * of this zone will use before being aborted.
+ *
+ * Requires:
+ * \li 'zone' to be valid initialised zone.
+ */
+
+uint32_t
+dns_zone_getmaxxfrout(dns_zone_t *zone);
+/*%<
+ * Returns the maximum transfer time for this zone. This will be
+ * either the value set by the last call to dns_zone_setmaxxfrout() or
+ * the default value of 1 hour.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ */
+
+isc_result_t
+dns_zone_setjournal(dns_zone_t *zone, const char *myjournal);
+/*%<
+ * Sets the filename used for journaling updates / IXFR transfers.
+ * The default journal name is set by dns_zone_setfile() to be
+ * "file.jnl". If 'myjournal' is NULL, the zone will have no
+ * journal name.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+char *
+dns_zone_getjournal(dns_zone_t *zone);
+/*%<
+ * Returns the journal name associated with this zone.
+ * If no journal has been set this will be NULL.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ */
+
+dns_zonetype_t
+dns_zone_gettype(dns_zone_t *zone);
+/*%<
+ * Returns the type of the zone (master/slave/etc.)
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ */
+
+dns_zonetype_t
+dns_zone_getredirecttype(dns_zone_t *zone);
+/*%<
+ * Returns whether the redirect zone is configured as a master or a
+ * slave zone.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *\li 'zone' to be a redirect zone.
+ *
+ * Returns:
+ *\li 'dns_zone_primary'
+ *\li 'dns_zone_secondary'
+ */
+
+void
+dns_zone_settask(dns_zone_t *zone, isc_task_t *task);
+/*%<
+ * Give a zone a task to work with. Any current task will be detached.
+ *
+ * Requires:
+ *\li 'zone' to be valid.
+ *\li 'task' to be valid.
+ */
+
+void
+dns_zone_gettask(dns_zone_t *zone, isc_task_t **target);
+/*%<
+ * Attach '*target' to the zone's task.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *\li 'zone' to have a task.
+ *\li 'target' to be != NULL && '*target' == NULL.
+ */
+
+void
+dns_zone_notify(dns_zone_t *zone);
+/*%<
+ * Generate notify events for this zone.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump);
+/*%<
+ * Replace the database of "zone" with a new database "db".
+ *
+ * If "dump" is true, then the new zone contents are dumped
+ * into to the zone's master file for persistence. When replacing
+ * a zone database by one just loaded from a master file, set
+ * "dump" to false to avoid a redundant redump of the data just
+ * loaded. Otherwise, it should be set to true.
+ *
+ * If the "diff-on-reload" option is enabled in the configuration file,
+ * the differences between the old and the new database are added to the
+ * journal file, and the master file dump is postponed.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ * Returns:
+ * \li DNS_R_SUCCESS
+ * \li DNS_R_BADZONE zone failed basic consistency checks:
+ * * a single SOA must exist
+ * * some NS records must exist.
+ * Others
+ */
+
+uint32_t
+dns_zone_getidlein(dns_zone_t *zone);
+/*%<
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ * Returns:
+ * \li number of seconds of idle time before we abort the transfer in.
+ */
+
+void
+dns_zone_setidlein(dns_zone_t *zone, uint32_t idlein);
+/*%<
+ * \li Set the idle timeout for transfer the.
+ * \li Zero set the default value, 1 hour.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+uint32_t
+dns_zone_getidleout(dns_zone_t *zone);
+/*%<
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ * Returns:
+ * \li number of seconds of idle time before we abort a transfer out.
+ */
+
+void
+dns_zone_setidleout(dns_zone_t *zone, uint32_t idleout);
+/*%<
+ * \li Set the idle timeout for transfers out.
+ * \li Zero set the default value, 1 hour.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_getssutable(dns_zone_t *zone, dns_ssutable_t **table);
+/*%<
+ * Get the simple-secure-update policy table.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setssutable(dns_zone_t *zone, dns_ssutable_t *table);
+/*%<
+ * Set / clear the simple-secure-update policy table.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+isc_mem_t *
+dns_zone_getmctx(dns_zone_t *zone);
+/*%<
+ * Get the memory context of a zone.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+dns_zonemgr_t *
+dns_zone_getmgr(dns_zone_t *zone);
+/*%<
+ * If 'zone' is managed return the zone manager otherwise NULL.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setsigvalidityinterval(dns_zone_t *zone, uint32_t interval);
+/*%<
+ * Set the zone's general signature validity interval. This is the length
+ * of time for which DNSSEC signatures created as a result of dynamic
+ * updates to secure zones will remain valid, in seconds.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+uint32_t
+dns_zone_getsigvalidityinterval(dns_zone_t *zone);
+/*%<
+ * Get the zone's general signature validity interval.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setkeyvalidityinterval(dns_zone_t *zone, uint32_t interval);
+/*%<
+ * Set the zone's DNSKEY signature validity interval. This is the length
+ * of time for which DNSSEC signatures created for DNSKEY records
+ * will remain valid, in seconds.
+ *
+ * If this value is set to zero, then the regular signature validity
+ * interval (see dns_zone_setsigvalidityinterval(), above) is used
+ * for all RRSIGs. However, if this value is nonzero, then it is used
+ * as the validity interval for RRSIGs covering DNSKEY and CDNSKEY
+ * RRsets.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+uint32_t
+dns_zone_getkeyvalidityinterval(dns_zone_t *zone);
+/*%<
+ * Get the zone's DNSKEY signature validity interval.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setsigresigninginterval(dns_zone_t *zone, uint32_t interval);
+/*%<
+ * Set the zone's RRSIG re-signing interval. A dynamic zone's RRSIG's
+ * will be re-signed 'interval' amount of time before they expire.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+uint32_t
+dns_zone_getsigresigninginterval(dns_zone_t *zone);
+/*%<
+ * Get the zone's RRSIG re-signing interval.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setnotifytype(dns_zone_t *zone, dns_notifytype_t notifytype);
+/*%<
+ * Sets zone notify method to "notifytype"
+ */
+
+isc_result_t
+dns_zone_forwardupdate(dns_zone_t *zone, dns_message_t *msg,
+ dns_updatecallback_t callback, void *callback_arg);
+/*%<
+ * Forward 'msg' to each master in turn until we get an answer or we
+ * have exhausted the list of primaries. 'callback' will be called with
+ * ISC_R_SUCCESS if we get an answer and the returned message will be
+ * passed as 'answer_message', otherwise a non ISC_R_SUCCESS result code
+ * will be passed and answer_message will be NULL. The callback function
+ * is responsible for destroying 'answer_message'.
+ * (callback)(callback_arg, result, answer_message);
+ *
+ * Require:
+ *\li 'zone' to be valid
+ *\li 'msg' to be valid.
+ *\li 'callback' to be non NULL.
+ * Returns:
+ *\li #ISC_R_SUCCESS if the message has been forwarded,
+ *\li #ISC_R_NOMEMORY
+ *\li Others
+ */
+
+isc_result_t
+dns_zone_next(dns_zone_t *zone, dns_zone_t **next);
+/*%<
+ * Find the next zone in the list of managed zones.
+ *
+ * Requires:
+ *\li 'zone' to be valid
+ *\li The zone manager for the indicated zone MUST be locked
+ * by the caller. This is not checked.
+ *\li 'next' be non-NULL, and '*next' be NULL.
+ *
+ * Ensures:
+ *\li 'next' points to a valid zone (result ISC_R_SUCCESS) or to NULL
+ * (result ISC_R_NOMORE).
+ */
+
+isc_result_t
+dns_zone_first(dns_zonemgr_t *zmgr, dns_zone_t **first);
+/*%<
+ * Find the first zone in the list of managed zones.
+ *
+ * Requires:
+ *\li 'zonemgr' to be valid
+ *\li The zone manager for the indicated zone MUST be locked
+ * by the caller. This is not checked.
+ *\li 'first' be non-NULL, and '*first' be NULL
+ *
+ * Ensures:
+ *\li 'first' points to a valid zone (result ISC_R_SUCCESS) or to NULL
+ * (result ISC_R_NOMORE).
+ */
+
+isc_result_t
+dns_zone_setkeydirectory(dns_zone_t *zone, const char *directory);
+/*%<
+ * Sets the name of the directory where private keys used for
+ * online signing of dynamic zones are found.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_SUCCESS
+ */
+
+const char *
+dns_zone_getkeydirectory(dns_zone_t *zone);
+/*%<
+ * Gets the name of the directory where private keys used for
+ * online signing of dynamic zones are found.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *
+ * Returns:
+ * Pointer to null-terminated file name, or NULL.
+ */
+
+isc_result_t
+dns_zone_getdnsseckeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ isc_stdtime_t now, dns_dnsseckeylist_t *keys);
+/*%
+ * Find DNSSEC keys used for signing with dnssec-policy. Load these keys
+ * into 'keys'.
+ *
+ * Requires:
+ *\li 'zone' to be valid initialised zone.
+ *\li 'keys' to be an initialised DNSSEC keylist.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li Error
+ */
+
+isc_result_t
+dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr,
+ dns_zonemgr_t **zmgrp);
+/*%<
+ * Create a zone manager. Note: the zone manager will not be able to
+ * manage any zones until dns_zonemgr_setsize() has been run.
+ *
+ * Requires:
+ *\li 'mctx' to be a valid memory context.
+ *\li 'taskmgr' to be a valid task manager.
+ *\li 'timermgr' to be a valid timer manager.
+ *\li 'zmgrp' to point to a NULL pointer.
+ */
+
+isc_result_t
+dns_zonemgr_setsize(dns_zonemgr_t *zmgr, int num_zones);
+/*%<
+ * Set the size of the zone manager task pool. This must be run
+ * before zmgr can be used for managing zones. Currently, it can only
+ * be run once; the task pool cannot be resized.
+ *
+ * Requires:
+ *\li zmgr is a valid zone manager.
+ *\li zmgr->zonetasks has been initialized.
+ */
+
+isc_result_t
+dns_zonemgr_createzone(dns_zonemgr_t *zmgr, dns_zone_t **zonep);
+/*%<
+ * Allocate a new zone using a memory context from the
+ * zone manager's memory context pool.
+ *
+ * Require:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'zonep' != NULL and '*zonep' == NULL.
+ */
+
+isc_result_t
+dns_zonemgr_managezone(dns_zonemgr_t *zmgr, dns_zone_t *zone);
+/*%<
+ * Bring the zone under control of a zone manager.
+ *
+ * Require:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zonemgr_forcemaint(dns_zonemgr_t *zmgr);
+/*%<
+ * Force zone maintenance of all loaded zones managed by 'zmgr'
+ * to take place at the system's earliest convenience.
+ */
+
+void
+dns__zonemgr_run(isc_task_t *task, isc_event_t *event);
+/*%<
+ * Event handler to call dns_zonemgr_forcemaint(); used to start
+ * zone operations from a unit test. Not intended for use outside
+ * libdns or related tests.
+ */
+
+void
+dns_zonemgr_resumexfrs(dns_zonemgr_t *zmgr);
+/*%<
+ * Attempt to start any stalled zone transfers.
+ */
+
+void
+dns_zonemgr_shutdown(dns_zonemgr_t *zmgr);
+/*%<
+ * Shut down the zone manager.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+void
+dns_zonemgr_attach(dns_zonemgr_t *source, dns_zonemgr_t **target);
+/*%<
+ * Attach '*target' to 'source' incrementing its external
+ * reference count.
+ *
+ * Require:
+ *\li 'zone' to be a valid zone.
+ *\li 'target' to be non NULL and '*target' to be NULL.
+ */
+
+void
+dns_zonemgr_detach(dns_zonemgr_t **zmgrp);
+/*%<
+ * Detach from a zone manager.
+ *
+ * Requires:
+ *\li '*zmgrp' is a valid, non-NULL zone manager pointer.
+ *
+ * Ensures:
+ *\li '*zmgrp' is NULL.
+ */
+
+void
+dns_zonemgr_releasezone(dns_zonemgr_t *zmgr, dns_zone_t *zone);
+/*%<
+ * Release 'zone' from the managed by 'zmgr'. 'zmgr' is implicitly
+ * detached from 'zone'.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'zone' to be a valid zone.
+ *\li 'zmgr' == 'zone->zmgr'
+ *
+ * Ensures:
+ *\li 'zone->zmgr' == NULL;
+ */
+
+isc_taskmgr_t *
+dns_zonemgr_gettaskmgr(dns_zonemgr_t *zmgr);
+/*%
+ * Get the tasmkgr object attached to 'zmgr'.
+ */
+
+void
+dns_zonemgr_settransfersin(dns_zonemgr_t *zmgr, uint32_t value);
+/*%<
+ * Set the maximum number of simultaneous transfers in allowed by
+ * the zone manager.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+uint32_t
+dns_zonemgr_getttransfersin(dns_zonemgr_t *zmgr);
+/*%<
+ * Return the maximum number of simultaneous transfers in allowed.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+void
+dns_zonemgr_settransfersperns(dns_zonemgr_t *zmgr, uint32_t value);
+/*%<
+ * Set the number of zone transfers allowed per nameserver.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager
+ */
+
+uint32_t
+dns_zonemgr_getttransfersperns(dns_zonemgr_t *zmgr);
+/*%<
+ * Return the number of transfers allowed per nameserver.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+void
+dns_zonemgr_setiolimit(dns_zonemgr_t *zmgr, uint32_t iolimit);
+/*%<
+ * Set the number of simultaneous file descriptors available for
+ * reading and writing masterfiles.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'iolimit' to be positive.
+ */
+
+uint32_t
+dns_zonemgr_getiolimit(dns_zonemgr_t *zmgr);
+/*%<
+ * Get the number of simultaneous file descriptors available for
+ * reading and writing masterfiles.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+void
+dns_zonemgr_setcheckdsrate(dns_zonemgr_t *zmgr, unsigned int value);
+/*%<
+ * Set the number of parental DS queries sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager
+ */
+
+void
+dns_zonemgr_setnotifyrate(dns_zonemgr_t *zmgr, unsigned int value);
+/*%<
+ * Set the number of NOTIFY requests sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager
+ */
+
+void
+dns_zonemgr_setstartupnotifyrate(dns_zonemgr_t *zmgr, unsigned int value);
+/*%<
+ * Set the number of startup NOTIFY requests sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager
+ */
+
+void
+dns_zonemgr_setserialqueryrate(dns_zonemgr_t *zmgr, unsigned int value);
+/*%<
+ * Set the number of SOA queries sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager
+ */
+
+unsigned int
+dns_zonemgr_getnotifyrate(dns_zonemgr_t *zmgr);
+/*%<
+ * Return the number of NOTIFY requests sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+unsigned int
+dns_zonemgr_getstartupnotifyrate(dns_zonemgr_t *zmgr);
+/*%<
+ * Return the number of startup NOTIFY requests sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+unsigned int
+dns_zonemgr_getserialqueryrate(dns_zonemgr_t *zmgr);
+/*%<
+ * Return the number of SOA queries sent per second.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ */
+
+unsigned int
+dns_zonemgr_getcount(dns_zonemgr_t *zmgr, int state);
+/*%<
+ * Returns the number of zones in the specified state.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'state' to be a valid DNS_ZONESTATE_ constant.
+ */
+
+void
+dns_zonemgr_unreachableadd(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
+ isc_sockaddr_t *local, isc_time_t *now);
+/*%<
+ * Add the pair of addresses to the unreachable cache.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'remote' to be a valid sockaddr.
+ *\li 'local' to be a valid sockaddr.
+ */
+
+bool
+dns_zonemgr_unreachable(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
+ isc_sockaddr_t *local, isc_time_t *now);
+/*%<
+ * Returns true if the given local/remote address pair
+ * is found in the zone maanger's unreachable cache.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'remote' to be a valid sockaddr.
+ *\li 'local' to be a valid sockaddr.
+ *\li 'now' != NULL
+ */
+
+void
+dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
+ isc_sockaddr_t *local);
+/*%<
+ * Remove the pair of addresses from the unreachable cache.
+ *
+ * Requires:
+ *\li 'zmgr' to be a valid zone manager.
+ *\li 'remote' to be a valid sockaddr.
+ *\li 'local' to be a valid sockaddr.
+ */
+
+void
+dns_zone_forcereload(dns_zone_t *zone);
+/*%<
+ * Force a reload of specified zone.
+ *
+ * Requires:
+ *\li 'zone' to be a valid zone.
+ */
+
+bool
+dns_zone_isforced(dns_zone_t *zone);
+/*%<
+ * Check if the zone is waiting a forced reload.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ */
+
+isc_result_t
+dns_zone_setstatistics(dns_zone_t *zone, bool on);
+/*%<
+ * This function is obsoleted by dns_zone_setrequeststats().
+ */
+
+uint64_t *
+dns_zone_getstatscounters(dns_zone_t *zone);
+/*%<
+ * This function is obsoleted by dns_zone_getrequeststats().
+ */
+
+void
+dns_zone_setstats(dns_zone_t *zone, isc_stats_t *stats);
+/*%<
+ * Set a general zone-maintenance statistics set 'stats' for 'zone'. This
+ * function is expected to be called only on zone creation (when necessary).
+ * Once installed, it cannot be removed or replaced. Also, there is no
+ * interface to get the installed stats from the zone; the caller must keep the
+ * stats to reference (e.g. dump) it later.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone and does not have a statistics set already
+ * installed.
+ *
+ *\li stats is a valid statistics supporting zone statistics counters
+ * (see dns/stats.h).
+ */
+
+void
+dns_zone_setrequeststats(dns_zone_t *zone, isc_stats_t *stats);
+
+void
+dns_zone_setrcvquerystats(dns_zone_t *zone, dns_stats_t *stats);
+
+void
+dns_zone_setdnssecsignstats(dns_zone_t *zone, dns_stats_t *stats);
+/*%<
+ * Set additional statistics sets to zone. These are attached to the zone
+ * but are not counted in the zone module; only the caller updates the
+ * counters.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ *\li stats is a valid statistics.
+ */
+
+isc_stats_t *
+dns_zone_getrequeststats(dns_zone_t *zone);
+
+dns_stats_t *
+dns_zone_getrcvquerystats(dns_zone_t *zone);
+
+dns_stats_t *
+dns_zone_getdnssecsignstats(dns_zone_t *zone);
+/*%<
+ * Get the additional statistics for zone, if one is installed.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ * Returns:
+ * \li when available, a pointer to the statistics set installed in zone;
+ * otherwise NULL.
+ */
+
+void
+dns_zone_dialup(dns_zone_t *zone);
+/*%<
+ * Perform dialup-time maintenance on 'zone'.
+ */
+
+void
+dns_zone_setdialup(dns_zone_t *zone, dns_dialuptype_t dialup);
+/*%<
+ * Set the dialup type of 'zone' to 'dialup'.
+ *
+ * Requires:
+ * \li 'zone' to be valid initialised zone.
+ *\li 'dialup' to be a valid dialup type.
+ */
+
+void
+dns_zone_logv(dns_zone_t *zone, isc_logcategory_t *category, int level,
+ const char *prefix, const char *msg, va_list ap);
+/*%<
+ * Log the message 'msg...' at 'level' using log category 'category', including
+ * text that identifies the message as applying to 'zone'. If the (optional)
+ * 'prefix' is not NULL, it will be placed at the start of the entire log line.
+ */
+
+void
+dns_zone_log(dns_zone_t *zone, int level, const char *msg, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+/*%<
+ * Log the message 'msg...' at 'level', including text that identifies
+ * the message as applying to 'zone'.
+ */
+
+void
+dns_zone_logc(dns_zone_t *zone, isc_logcategory_t *category, int level,
+ const char *msg, ...) ISC_FORMAT_PRINTF(4, 5);
+/*%<
+ * Log the message 'msg...' at 'level', including text that identifies
+ * the message as applying to 'zone'.
+ */
+
+void
+dns_zone_name(dns_zone_t *zone, char *buf, size_t len);
+/*%<
+ * Return the name of the zone with class and view.
+ *
+ * Requires:
+ *\li 'zone' to be valid.
+ *\li 'buf' to be non NULL.
+ */
+
+void
+dns_zone_nameonly(dns_zone_t *zone, char *buf, size_t len);
+/*%<
+ * Return the name of the zone only.
+ *
+ * Requires:
+ *\li 'zone' to be valid.
+ *\li 'buf' to be non NULL.
+ */
+
+isc_result_t
+dns_zone_checknames(dns_zone_t *zone, const dns_name_t *name,
+ dns_rdata_t *rdata);
+/*%<
+ * Check if this record meets the check-names policy.
+ *
+ * Requires:
+ * 'zone' to be valid.
+ * 'name' to be valid.
+ * 'rdata' to be valid.
+ *
+ * Returns:
+ * DNS_R_SUCCESS passed checks.
+ * DNS_R_BADOWNERNAME failed ownername checks.
+ * DNS_R_BADNAME failed rdata checks.
+ */
+
+void
+dns_zone_setcheckmx(dns_zone_t *zone, dns_checkmxfunc_t checkmx);
+/*%<
+ * Set the post load integrity callback function 'checkmx'.
+ * 'checkmx' will be called if the MX TARGET is not within the zone.
+ *
+ * Require:
+ * 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setchecksrv(dns_zone_t *zone, dns_checkmxfunc_t checksrv);
+/*%<
+ * Set the post load integrity callback function 'checksrv'.
+ * 'checksrv' will be called if the SRV TARGET is not within the zone.
+ *
+ * Require:
+ * 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setcheckns(dns_zone_t *zone, dns_checknsfunc_t checkns);
+/*%<
+ * Set the post load integrity callback function 'checkns'.
+ * 'checkns' will be called if the NS TARGET is not within the zone.
+ *
+ * Require:
+ * 'zone' to be a valid zone.
+ */
+
+void
+dns_zone_setnotifydelay(dns_zone_t *zone, uint32_t delay);
+/*%<
+ * Set the minimum delay between sets of notify messages.
+ *
+ * Requires:
+ * 'zone' to be valid.
+ */
+
+uint32_t
+dns_zone_getnotifydelay(dns_zone_t *zone);
+/*%<
+ * Get the minimum delay between sets of notify messages.
+ *
+ * Requires:
+ * 'zone' to be valid.
+ */
+
+void
+dns_zone_setisself(dns_zone_t *zone, dns_isselffunc_t isself, void *arg);
+/*%<
+ * Set the isself callback function and argument.
+ *
+ * bool
+ * isself(dns_view_t *myview, dns_tsigkey_t *mykey,
+ * const isc_netaddr_t *srcaddr, const isc_netaddr_t *destaddr,
+ * dns_rdataclass_t rdclass, void *arg);
+ *
+ * 'isself' returns true if a non-recursive query from 'srcaddr' to
+ * 'destaddr' with optional key 'mykey' for class 'rdclass' would be
+ * delivered to 'myview'.
+ */
+
+void
+dns_zone_setnodes(dns_zone_t *zone, uint32_t nodes);
+/*%<
+ * Set the number of nodes that will be checked per quantum.
+ */
+
+void
+dns_zone_setsignatures(dns_zone_t *zone, uint32_t signatures);
+/*%<
+ * Set the number of signatures that will be generated per quantum.
+ */
+
+uint32_t
+dns_zone_getsignatures(dns_zone_t *zone);
+/*%<
+ * Get the number of signatures that will be generated per quantum.
+ */
+
+isc_result_t
+dns_zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid,
+ bool deleteit);
+/*%<
+ * Initiate/resume signing of the entire zone with the zone DNSKEY(s)
+ * that match the given algorithm and keyid.
+ */
+
+isc_result_t
+dns_zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param);
+/*%<
+ * Incrementally add a NSEC3 chain that corresponds to 'nsec3param'.
+ */
+
+void
+dns_zone_setprivatetype(dns_zone_t *zone, dns_rdatatype_t type);
+dns_rdatatype_t
+dns_zone_getprivatetype(dns_zone_t *zone);
+/*
+ * Get/Set the private record type. It is expected that these interfaces
+ * will not be permanent.
+ */
+
+void
+dns_zone_rekey(dns_zone_t *zone, bool fullsign);
+/*%<
+ * Update the zone's DNSKEY set from the key repository.
+ *
+ * If 'fullsign' is true, trigger an immediate full signing of
+ * the zone with the new key. Otherwise, if there are no keys or
+ * if the new keys are for algorithms that have already signed the
+ * zone, then the zone can be re-signed incrementally.
+ */
+
+isc_result_t
+dns_zone_nscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
+ unsigned int *errors);
+/*%
+ * Check if the name servers for the zone are sane (have address, don't
+ * refer to CNAMEs/DNAMEs. The number of constiancy errors detected in
+ * returned in '*errors'
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ * \li 'db' to be valid.
+ * \li 'version' to be valid or NULL.
+ * \li 'errors' to be non NULL.
+ *
+ * Returns:
+ * ISC_R_SUCCESS if there were no errors examining the zone contents.
+ */
+
+isc_result_t
+dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version);
+/*%
+ * Check if CSD, CDNSKEY and DNSKEY are consistent.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ * \li 'db' to be valid.
+ * \li 'version' to be valid or NULL.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #DNS_R_BADCDS
+ *\li #DNS_R_BADCDNSKEY
+ * Others
+ */
+
+void
+dns_zone_setadded(dns_zone_t *zone, bool added);
+/*%
+ * Sets the value of zone->added, which should be true for
+ * zones that were originally added by "rndc addzone".
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+bool
+dns_zone_getadded(dns_zone_t *zone);
+/*%
+ * Returns true if the zone was originally added at runtime
+ * using "rndc addzone".
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setautomatic(dns_zone_t *zone, bool automatic);
+/*%
+ * Sets the value of zone->automatic, which should be true for
+ * zones that were automatically added by named.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+bool
+dns_zone_getautomatic(dns_zone_t *zone);
+/*%
+ * Returns true if the zone was added automatically by named.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+isc_result_t
+dns_zone_dlzpostload(dns_zone_t *zone, dns_db_t *db);
+/*%
+ * Load the origin names for a writeable DLZ database.
+ */
+
+bool
+dns_zone_isdynamic(dns_zone_t *zone, bool ignore_freeze);
+/*%
+ * Return true iff the zone is "dynamic", in the sense that the zone's
+ * master file (if any) is written by the server, rather than being
+ * updated manually and read by the server.
+ *
+ * This is true for slave zones, stub zones, key zones, and zones that
+ * allow dynamic updates either by having an update policy ("ssutable")
+ * or an "allow-update" ACL with a value other than exactly "{ none; }".
+ *
+ * If 'ignore_freeze' is true, then the zone which has had updates disabled
+ * will still report itself to be dynamic.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+isc_result_t
+dns_zone_setrefreshkeyinterval(dns_zone_t *zone, uint32_t interval);
+/*%
+ * Sets the frequency, in minutes, with which the key repository will be
+ * checked to see if the keys for this zone have been updated. Any value
+ * higher than 1440 minutes (24 hours) will be silently reduced. A
+ * value of zero will return an out-of-range error.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+bool
+dns_zone_getrequestexpire(dns_zone_t *zone);
+/*%
+ * Returns the true/false value of the request-expire option in the zone.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setrequestexpire(dns_zone_t *zone, bool flag);
+/*%
+ * Sets the request-expire option for the zone. Either true or false. The
+ * default value is determined by the setting of this option in the view.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+bool
+dns_zone_getrequestixfr(dns_zone_t *zone);
+/*%
+ * Returns the true/false value of the request-ixfr option in the zone.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setrequestixfr(dns_zone_t *zone, bool flag);
+/*%
+ * Sets the request-ixfr option for the zone. Either true or false. The
+ * default value is determined by the setting of this option in the view.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+uint32_t
+dns_zone_getixfrratio(dns_zone_t *zone);
+/*%
+ * Returns the zone's current IXFR ratio.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setixfrratio(dns_zone_t *zone, uint32_t ratio);
+/*%
+ * Sets the ratio of IXFR size to zone size above which we use an AXFR
+ * response, expressed as a percentage. Cannot exceed 100.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setserialupdatemethod(dns_zone_t *zone, dns_updatemethod_t method);
+/*%
+ * Sets the update method to use when incrementing the zone serial number
+ * due to a DDNS update. Valid options are dns_updatemethod_increment
+ * and dns_updatemethod_unixtime.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+dns_updatemethod_t
+dns_zone_getserialupdatemethod(dns_zone_t *zone);
+/*%
+ * Returns the update method to be used when incrementing the zone serial
+ * number due to a DDNS update.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+isc_result_t
+dns_zone_link(dns_zone_t *zone, dns_zone_t *raw);
+
+void
+dns_zone_getraw(dns_zone_t *zone, dns_zone_t **raw);
+
+isc_result_t
+dns_zone_keydone(dns_zone_t *zone, const char *data);
+
+isc_result_t
+dns_zone_setnsec3param(dns_zone_t *zone, uint8_t hash, uint8_t flags,
+ uint16_t iter, uint8_t saltlen, unsigned char *salt,
+ bool replace, bool resalt);
+/*%
+ * Set the NSEC3 parameters for the zone.
+ *
+ * If 'replace' is true, then the existing NSEC3 chain, if any, will
+ * be replaced with the new one. If 'hash' is zero, then the replacement
+ * chain will be NSEC rather than NSEC3. If 'resalt' is true, or if 'salt'
+ * is NULL, generate a new salt with the given salt length.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ */
+
+void
+dns_zone_setrawdata(dns_zone_t *zone, dns_masterrawheader_t *header);
+/*%
+ * Set the data to be included in the header when the zone is dumped in
+ * binary format.
+ */
+
+isc_result_t
+dns_zone_synckeyzone(dns_zone_t *zone);
+/*%
+ * Force the managed key zone to synchronize, and start the key
+ * maintenance timer.
+ */
+
+isc_result_t
+dns_zone_getloadtime(dns_zone_t *zone, isc_time_t *loadtime);
+/*%
+ * Return the time when the zone was last loaded.
+ */
+
+isc_result_t
+dns_zone_getrefreshtime(dns_zone_t *zone, isc_time_t *refreshtime);
+/*%
+ * Return the time when the (slave) zone will need to be refreshed.
+ */
+
+isc_result_t
+dns_zone_getexpiretime(dns_zone_t *zone, isc_time_t *expiretime);
+/*%
+ * Return the time when the (slave) zone will expire.
+ */
+
+isc_result_t
+dns_zone_getrefreshkeytime(dns_zone_t *zone, isc_time_t *refreshkeytime);
+/*%
+ * Return the time of the next scheduled DNSSEC key event.
+ */
+
+unsigned int
+dns_zone_getincludes(dns_zone_t *zone, char ***includesp);
+/*%
+ * Return the number include files that were encountered
+ * during load. If the number is greater than zero, 'includesp'
+ * will point to an array containing the filenames.
+ *
+ * The array and its contents need to be freed using isc_mem_free.
+ */
+
+isc_result_t
+dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs,
+ dns_rpz_num_t rpz_num);
+/*%
+ * Set the response policy associated with a zone.
+ */
+
+void
+dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db);
+/*%
+ * If a zone is a response policy zone, mark its new database.
+ */
+
+dns_rpz_num_t
+dns_zone_get_rpz_num(dns_zone_t *zone);
+
+void
+dns_zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs);
+/*%<
+ * Enable zone as catalog zone.
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ * \li 'catzs' is not NULL
+ * \li prior to calling, zone->catzs is NULL or is equal to 'catzs'
+ */
+
+void
+dns_zone_catz_disable(dns_zone_t *zone);
+/*%<
+ * Disable zone as catalog zone, if it is one. Also disables any
+ * registered callbacks for the catalog zone.
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ */
+
+bool
+dns_zone_catz_is_enabled(dns_zone_t *zone);
+/*%<
+ * Return a boolean indicating whether the zone is enabled as catalog zone.
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ */
+
+void
+dns_zone_catz_enable_db(dns_zone_t *zone, dns_db_t *db);
+/*%<
+ * If 'zone' is a catalog zone, then set up a notify-on-update trigger
+ * in its database. (If not a catalog zone, this function has no effect.)
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ * \li 'db' is not NULL
+ */
+void
+dns_zone_set_parentcatz(dns_zone_t *zone, dns_catz_zone_t *catz);
+/*%<
+ * Set parent catalog zone for this zone
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ * \li 'catz' is not NULL
+ */
+
+dns_catz_zone_t *
+dns_zone_get_parentcatz(const dns_zone_t *zone);
+/*%<
+ * Get parent catalog zone for this zone
+ *
+ * Requires:
+ *
+ * \li 'zone' is a valid zone object
+ */
+
+void
+dns_zone_setstatlevel(dns_zone_t *zone, dns_zonestat_level_t level);
+
+dns_zonestat_level_t
+dns_zone_getstatlevel(dns_zone_t *zone);
+/*%
+ * Set and get the statistics reporting level for the zone;
+ * full, terse, or none.
+ */
+
+isc_result_t
+dns_zone_setserial(dns_zone_t *zone, uint32_t serial);
+/*%
+ * Set the zone's serial to 'serial'.
+ */
+ISC_LANG_ENDDECLS
+
+isc_stats_t *
+dns_zone_getgluecachestats(dns_zone_t *zone);
+/*%<
+ * Get the glue cache statistics for zone.
+ *
+ * Requires:
+ * \li 'zone' to be a valid zone.
+ *
+ * Returns:
+ * \li if present, a pointer to the statistics set installed in zone;
+ * otherwise NULL.
+ */
+
+bool
+dns_zone_isloaded(dns_zone_t *zone);
+/*%<
+ * Return true if 'zone' was loaded and has not expired yet, return
+ * false otherwise.
+ */
+
+isc_result_t
+dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver);
+/*%<
+ * If 'zone' is a mirror zone, perform DNSSEC validation of version 'ver' of
+ * its database, 'db'. Ensure that the DNSKEY RRset at zone apex is signed by
+ * at least one trust anchor specified for the view that 'zone' is assigned to.
+ * If 'ver' is NULL, use the current version of 'db'.
+ *
+ * If 'zone' is not a mirror zone, return ISC_R_SUCCESS immediately.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS either 'zone' is not a mirror zone or 'zone' is
+ * a mirror zone and all DNSSEC checks succeeded
+ * and the DNSKEY RRset at zone apex is signed by
+ * a trusted key
+ *
+ * \li #DNS_R_VERIFYFAILURE any other case
+ */
+
+const char *
+dns_zonetype_name(dns_zonetype_t type);
+/*%<
+ * Return the name of the zone type 'type'.
+ */
+
+#endif /* DNS_ZONE_H */
diff --git a/lib/dns/include/dns/zonekey.h b/lib/dns/include/dns/zonekey.h
new file mode 100644
index 0000000..f03f0a5
--- /dev/null
+++ b/lib/dns/include/dns/zonekey.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_ZONEKEY_H
+#define DNS_ZONEKEY_H 1
+
+/*! \file dns/zonekey.h */
+
+#include <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
+
+#endif /* DNS_ZONEKEY_H */
diff --git a/lib/dns/include/dns/zoneverify.h b/lib/dns/include/dns/zoneverify.h
new file mode 100644
index 0000000..22a680a
--- /dev/null
+++ b/lib/dns/include/dns/zoneverify.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/zoneverify.h */
+
+#include <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..2964fc9
--- /dev/null
+++ b/lib/dns/include/dns/zt.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_ZT_H
+#define DNS_ZT_H 1
+
+/*! \file dns/zt.h */
+
+#include <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_flushanddetach(dns_zt_t **ztp);
+/*%<
+ * Detach the given zonetable, if the reference count goes to zero the
+ * zonetable will be flushed and then freed. In either case 'ztp' is
+ * set to NULL.
+ *
+ * Requires:
+ * \li '*ztp' to be valid
+ */
+
+void
+dns_zt_attach(dns_zt_t *zt, dns_zt_t **ztp);
+/*%<
+ * Attach 'zt' to '*ztp'.
+ *
+ * Requires:
+ * \li 'zt' to be valid
+ * \li '*ztp' to be NULL
+ */
+
+isc_result_t
+dns_zt_load(dns_zt_t *zt, bool stop, bool newonly);
+
+isc_result_t
+dns_zt_asyncload(dns_zt_t *zt, bool newonly, dns_zt_allloaded_t alldone,
+ void *arg);
+/*%<
+ * Load all zones in the table. If 'stop' is true,
+ * stop on the first error and return it. If 'stop'
+ * is false, ignore errors.
+ *
+ * if newonly is set only zones that were never loaded are loaded.
+ * dns_zt_asyncload() loads zones asynchronously; when all
+ * zones in the zone table have finished loaded (or failed due
+ * to errors), the caller is informed by calling 'alldone'
+ * with an argument of 'arg'.
+ *
+ * Requires:
+ * \li 'zt' to be valid
+ */
+
+isc_result_t
+dns_zt_freezezones(dns_zt_t *zt, dns_view_t *view, bool freeze);
+/*%<
+ * Freeze/thaw updates to master zones.
+ * Any pending updates will be flushed.
+ * Zones will be reloaded on thaw.
+ */
+
+isc_result_t
+dns_zt_apply(dns_zt_t *zt, isc_rwlocktype_t lock, bool stop, isc_result_t *sub,
+ isc_result_t (*action)(dns_zone_t *, void *), void *uap);
+/*%<
+ * Apply a given 'action' to all zone zones in the table.
+ * If 'stop' is 'true' then walking the zone tree will stop if
+ * 'action' does not return ISC_R_SUCCESS.
+ *
+ * Requires:
+ * \li 'zt' to be valid.
+ * \li 'action' to be non NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS if action was applied to all nodes. If 'stop' is
+ * false and 'sub' is non NULL then the first error (if any)
+ * reported by 'action' is returned in '*sub';
+ * any error code from 'action'.
+ */
+
+bool
+dns_zt_loadspending(dns_zt_t *zt);
+/*%<
+ * Returns true if and only if there are zones still waiting to
+ * be loaded in zone table 'zt'.
+ *
+ * Requires:
+ * \li 'zt' to be valid.
+ */
+
+void
+dns_zt_setviewcommit(dns_zt_t *zt);
+/*%<
+ * Commit dns_zone_setview() calls previously made for all zones in this
+ * zone table.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+void
+dns_zt_setviewrevert(dns_zt_t *zt);
+/*%<
+ * Revert dns_zone_setview() calls previously made for all zones in this
+ * zone table.
+ *
+ * Requires:
+ *\li 'view' to be valid.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_ZT_H */
diff --git a/lib/dns/include/dst/Makefile.in b/lib/dns/include/dst/Makefile.in
new file mode 100644
index 0000000..e4dad59
--- /dev/null
+++ b/lib/dns/include/dst/Makefile.in
@@ -0,0 +1,36 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+HEADERS = dst.h gssapi.h result.h
+
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/dst
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/dst || exit 1; \
+ done
+
+uninstall::
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/dst/$$i || exit 1; \
+ done
diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h
new file mode 100644
index 0000000..3185e9f
--- /dev/null
+++ b/lib/dns/include/dst/dst.h
@@ -0,0 +1,1227 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DST_DST_H
+#define DST_DST_H 1
+
+/*! \file dst/dst.h */
+
+#include <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 */
+#define DST_ALG_UNKNOWN 0
+#define DST_ALG_RSA 1 /* Used for parsing RSASHA1, RSASHA256 and RSASHA512 */
+#define DST_ALG_RSAMD5 1
+#define DST_ALG_DH 2
+#define DST_ALG_DSA 3
+#define DST_ALG_ECC 4
+#define DST_ALG_RSASHA1 5
+#define DST_ALG_NSEC3DSA 6
+#define DST_ALG_NSEC3RSASHA1 7
+#define DST_ALG_RSASHA256 8
+#define DST_ALG_RSASHA512 10
+#define DST_ALG_ECCGOST 12
+#define DST_ALG_ECDSA256 13
+#define DST_ALG_ECDSA384 14
+#define DST_ALG_ED25519 15
+#define DST_ALG_ED448 16
+#define DST_ALG_HMACMD5 157
+#define DST_ALG_GSSAPI 160
+#define DST_ALG_HMACSHA1 161 /* XXXMPA */
+#define DST_ALG_HMACSHA224 162 /* XXXMPA */
+#define DST_ALG_HMACSHA256 163 /* XXXMPA */
+#define DST_ALG_HMACSHA384 164 /* XXXMPA */
+#define DST_ALG_HMACSHA512 165 /* XXXMPA */
+#define DST_ALG_INDIRECT 252
+#define DST_ALG_PRIVATE 254
+#define DST_MAX_ALGS 256
+
+/*% A buffer of this size is large enough to hold any key */
+#define DST_KEY_MAXSIZE 1280
+
+/*%
+ * A buffer of this size is large enough to hold the textual representation
+ * of any key
+ */
+#define DST_KEY_MAXTEXTSIZE 2048
+
+/*% 'Type' for dst_read_key() */
+#define DST_TYPE_KEY 0x1000000 /* KEY key */
+#define DST_TYPE_PRIVATE 0x2000000
+#define DST_TYPE_PUBLIC 0x4000000
+#define DST_TYPE_STATE 0x8000000
+
+/* Key timing metadata definitions */
+#define DST_TIME_CREATED 0
+#define DST_TIME_PUBLISH 1
+#define DST_TIME_ACTIVATE 2
+#define DST_TIME_REVOKE 3
+#define DST_TIME_INACTIVE 4
+#define DST_TIME_DELETE 5
+#define DST_TIME_DSPUBLISH 6
+#define DST_TIME_SYNCPUBLISH 7
+#define DST_TIME_SYNCDELETE 8
+#define DST_TIME_DNSKEY 9
+#define DST_TIME_ZRRSIG 10
+#define DST_TIME_KRRSIG 11
+#define DST_TIME_DS 12
+#define DST_TIME_DSDELETE 13
+#define DST_MAX_TIMES 13
+
+/* Numeric metadata definitions */
+#define DST_NUM_PREDECESSOR 0
+#define DST_NUM_SUCCESSOR 1
+#define DST_NUM_MAXTTL 2
+#define DST_NUM_ROLLPERIOD 3
+#define DST_NUM_LIFETIME 4
+#define DST_NUM_DSPUBCOUNT 5
+#define DST_NUM_DSDELCOUNT 6
+#define DST_MAX_NUMERIC 6
+
+/* Boolean metadata definitions */
+#define DST_BOOL_KSK 0
+#define DST_BOOL_ZSK 1
+#define DST_MAX_BOOLEAN 1
+
+/* Key state metadata definitions */
+#define DST_KEY_DNSKEY 0
+#define DST_KEY_ZRRSIG 1
+#define DST_KEY_KRRSIG 2
+#define DST_KEY_DS 3
+#define DST_KEY_GOAL 4
+#define DST_MAX_KEYSTATES 4
+
+/*
+ * Current format version number of the private key parser.
+ *
+ * When parsing a key file with the same major number but a higher minor
+ * number, the key parser will ignore any fields it does not recognize.
+ * Thus, DST_MINOR_VERSION should be incremented whenever new
+ * fields are added to the private key file (such as new metadata).
+ *
+ * When rewriting these keys, those fields will be dropped, and the
+ * format version set back to the current one..
+ *
+ * When a key is seen with a higher major number, the key parser will
+ * reject it as invalid. Thus, DST_MAJOR_VERSION should be incremented
+ * and DST_MINOR_VERSION set to zero whenever there is a format change
+ * which is not backward compatible to previous versions of the dst_key
+ * parser, such as change in the syntax of an existing field, the removal
+ * of a currently mandatory field, or a new field added which would
+ * alter the functioning of the key if it were absent.
+ */
+#define DST_MAJOR_VERSION 1
+#define DST_MINOR_VERSION 3
+
+/***
+ *** Functions
+ ***/
+isc_result_t
+dst_lib_init(isc_mem_t *mctx, const char *engine);
+/*%<
+ * Initializes the DST subsystem.
+ *
+ * Requires:
+ * \li "mctx" is a valid memory context
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOMEMORY
+ * \li DST_R_NOENGINE
+ *
+ * Ensures:
+ * \li DST is properly initialized.
+ */
+
+void
+dst_lib_destroy(void);
+/*%<
+ * Releases all resources allocated by DST.
+ */
+
+bool
+dst_algorithm_supported(unsigned int alg);
+/*%<
+ * Checks that a given algorithm is supported by DST.
+ *
+ * Returns:
+ * \li true
+ * \li false
+ */
+
+bool
+dst_ds_digest_supported(unsigned int digest_type);
+/*%<
+ * Checks that a given digest algorithm is supported by DST.
+ *
+ * Returns:
+ * \li true
+ * \li false
+ */
+
+isc_result_t
+dst_context_create(dst_key_t *key, isc_mem_t *mctx, isc_logcategory_t *category,
+ bool useforsigning, int maxbits, dst_context_t **dctxp);
+/*%<
+ * Creates a context to be used for a sign or verify operation.
+ *
+ * Requires:
+ * \li "key" is a valid key.
+ * \li "mctx" is a valid memory context.
+ * \li dctxp != NULL && *dctxp == NULL
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOMEMORY
+ *
+ * Ensures:
+ * \li *dctxp will contain a usable context.
+ */
+
+void
+dst_context_destroy(dst_context_t **dctxp);
+/*%<
+ * Destroys all memory associated with a context.
+ *
+ * Requires:
+ * \li *dctxp != NULL && *dctxp == NULL
+ *
+ * Ensures:
+ * \li *dctxp == NULL
+ */
+
+isc_result_t
+dst_context_adddata(dst_context_t *dctx, const isc_region_t *data);
+/*%<
+ * Incrementally adds data to the context to be used in a sign or verify
+ * operation.
+ *
+ * Requires:
+ * \li "dctx" is a valid context
+ * \li "data" is a valid region
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li DST_R_SIGNFAILURE
+ * \li all other errors indicate failure
+ */
+
+isc_result_t
+dst_context_sign(dst_context_t *dctx, isc_buffer_t *sig);
+/*%<
+ * Computes a signature using the data and key stored in the context.
+ *
+ * Requires:
+ * \li "dctx" is a valid context.
+ * \li "sig" is a valid buffer.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li DST_R_VERIFYFAILURE
+ * \li all other errors indicate failure
+ *
+ * Ensures:
+ * \li "sig" will contain the signature
+ */
+
+isc_result_t
+dst_context_verify(dst_context_t *dctx, isc_region_t *sig);
+
+isc_result_t
+dst_context_verify2(dst_context_t *dctx, unsigned int maxbits,
+ isc_region_t *sig);
+/*%<
+ * Verifies the signature using the data and key stored in the context.
+ *
+ * 'maxbits' specifies the maximum number of bits permitted in the RSA
+ * exponent.
+ *
+ * Requires:
+ * \li "dctx" is a valid context.
+ * \li "sig" is a valid region.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li all other errors indicate failure
+ *
+ * Ensures:
+ * \li "sig" will contain the signature
+ */
+
+isc_result_t
+dst_key_computesecret(const dst_key_t *pub, const dst_key_t *priv,
+ isc_buffer_t *secret);
+/*%<
+ * Computes a shared secret from two (Diffie-Hellman) keys.
+ *
+ * Requires:
+ * \li "pub" is a valid key that can be used to derive a shared secret
+ * \li "priv" is a valid private key that can be used to derive a shared secret
+ * \li "secret" is a valid buffer
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, secret will contain the derived shared secret.
+ */
+
+isc_result_t
+dst_key_getfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg,
+ int type, const char *directory, isc_mem_t *mctx,
+ isc_buffer_t *buf);
+/*%<
+ * Generates a key filename for the name, algorithm, and
+ * id, and places it in the buffer 'buf'. If directory is NULL, the
+ * current directory is assumed.
+ *
+ * Requires:
+ * \li "name" is a valid absolute dns name.
+ * \li "id" is a valid key tag identifier.
+ * \li "alg" is a supported key algorithm.
+ * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union.
+ * DST_TYPE_KEY look for a KEY record otherwise DNSKEY
+ * \li "mctx" is a valid memory context.
+ * \li "buf" is not NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ */
+
+isc_result_t
+dst_key_fromfile(dns_name_t *name, dns_keytag_t id, unsigned int alg, int type,
+ const char *directory, isc_mem_t *mctx, dst_key_t **keyp);
+/*%<
+ * Reads a key from permanent storage. The key can either be a public or
+ * private key, or a key state. It specified by name, algorithm, and id. If
+ * a private key or key state is specified, the public key must also be
+ * present. If directory is NULL, the current directory is assumed.
+ *
+ * Requires:
+ * \li "name" is a valid absolute dns name.
+ * \li "id" is a valid key tag identifier.
+ * \li "alg" is a supported key algorithm.
+ * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE or the bitwise union.
+ * DST_TYPE_KEY look for a KEY record otherwise DNSKEY.
+ * DST_TYPE_STATE to also read the key state.
+ * \li "mctx" is a valid memory context.
+ * \li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, *keyp will contain a valid key.
+ */
+
+isc_result_t
+dst_key_fromnamedfile(const char *filename, const char *dirname, int type,
+ isc_mem_t *mctx, dst_key_t **keyp);
+/*%<
+ * Reads a key from permanent storage. The key can either be a public or
+ * private key, or a key state. It is specified by filename. If a private key
+ * or key state is specified, the public key must also be present.
+ *
+ * If 'dirname' is not NULL, and 'filename' is a relative path,
+ * then the file is looked up relative to the given directory.
+ * If 'filename' is an absolute path, 'dirname' is ignored.
+ *
+ * Requires:
+ * \li "filename" is not NULL
+ * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union.
+ * DST_TYPE_KEY look for a KEY record otherwise DNSKEY.
+ * DST_TYPE_STATE to also read the key state.
+ * \li "mctx" is a valid memory context
+ * \li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, *keyp will contain a valid key.
+ */
+
+isc_result_t
+dst_key_read_public(const char *filename, int type, isc_mem_t *mctx,
+ dst_key_t **keyp);
+/*%<
+ * Reads a public key from permanent storage. The key must be a public key.
+ *
+ * Requires:
+ * \li "filename" is not NULL.
+ * \li "type" is DST_TYPE_KEY look for a KEY record otherwise DNSKEY.
+ * \li "mctx" is a valid memory context.
+ * \li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li DST_R_BADKEYTYPE if the key type is not the expected one
+ * \li ISC_R_UNEXPECTEDTOKEN if the file can not be parsed as a public key
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, *keyp will contain a valid key.
+ */
+
+isc_result_t
+dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp);
+/*%<
+ * Reads a key state from permanent storage.
+ *
+ * Requires:
+ * \li "filename" is not NULL.
+ * \li "mctx" is a valid memory context.
+ * \li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_UNEXPECTEDTOKEN if the file can not be parsed as a public key
+ * \li any other result indicates failure
+ */
+
+isc_result_t
+dst_key_tofile(const dst_key_t *key, int type, const char *directory);
+/*%<
+ * Writes a key to permanent storage. The key can either be a public or
+ * private key. Public keys are written in DNS format and private keys
+ * are written as a set of base64 encoded values. If directory is NULL,
+ * the current directory is assumed.
+ *
+ * Requires:
+ * \li "key" is a valid key.
+ * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ */
+
+isc_result_t
+dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
+/*%<
+ * Converts a DNS KEY record into a DST key.
+ *
+ * Requires:
+ * \li "name" is a valid absolute dns name.
+ * \li "source" is a valid buffer. There must be at least 4 bytes available.
+ * \li "mctx" is a valid memory context.
+ * \li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, *keyp will contain a valid key, and the consumed
+ * pointer in data will be advanced.
+ */
+
+isc_result_t
+dst_key_todns(const dst_key_t *key, isc_buffer_t *target);
+/*%<
+ * Converts a DST key into a DNS KEY record.
+ *
+ * Requires:
+ * \li "key" is a valid key.
+ * \li "target" is a valid buffer. There must be at least 4 bytes unused.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ * \li If successful, the used pointer in 'target' is advanced by at least 4.
+ */
+
+isc_result_t
+dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
+/*%<
+ * Converts a buffer containing DNS KEY RDATA into a DST key.
+ *
+ * Requires:
+ *\li "name" is a valid absolute dns name.
+ *\li "alg" is a supported key algorithm.
+ *\li "source" is a valid buffer.
+ *\li "mctx" is a valid memory context.
+ *\li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ *\li If successful, *keyp will contain a valid key, and the consumed
+ * pointer in source will be advanced.
+ */
+
+isc_result_t
+dst_key_tobuffer(const dst_key_t *key, isc_buffer_t *target);
+/*%<
+ * Converts a DST key into DNS KEY RDATA format.
+ *
+ * Requires:
+ *\li "key" is a valid key.
+ *\li "target" is a valid buffer.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ *\li If successful, the used pointer in 'target' is advanced.
+ */
+
+isc_result_t
+dst_key_privatefrombuffer(dst_key_t *key, isc_buffer_t *buffer);
+/*%<
+ * Converts a public key into a private key, reading the private key
+ * information from the buffer. The buffer should contain the same data
+ * as the .private key file would.
+ *
+ * Requires:
+ *\li "key" is a valid public key.
+ *\li "buffer" is not NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ *\li If successful, key will contain a valid private key.
+ */
+
+dns_gss_ctx_id_t
+dst_key_getgssctx(const dst_key_t *key);
+/*%<
+ * Returns the opaque key data.
+ * Be cautions when using this value unless you know what you are doing.
+ *
+ * Requires:
+ *\li "key" is not NULL.
+ *
+ * Returns:
+ *\li gssctx key data, possibly NULL.
+ */
+
+isc_result_t
+dst_key_fromgssapi(const dns_name_t *name, dns_gss_ctx_id_t gssctx,
+ isc_mem_t *mctx, dst_key_t **keyp, isc_region_t *intoken);
+/*%<
+ * Converts a GSSAPI opaque context id into a DST key.
+ *
+ * Requires:
+ *\li "name" is a valid absolute dns name.
+ *\li "gssctx" is a GSSAPI context id.
+ *\li "mctx" is a valid memory context.
+ *\li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ *\li If successful, *keyp will contain a valid key and be responsible for
+ * the context id.
+ */
+
+#ifdef DST_KEY_INTERNAL
+isc_result_t
+dst_key_buildinternal(const dns_name_t *name, unsigned int alg,
+ unsigned int bits, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ void *data, isc_mem_t *mctx, dst_key_t **keyp);
+#endif /* ifdef DST_KEY_INTERNAL */
+
+isc_result_t
+dst_key_fromlabel(const dns_name_t *name, int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ const char *engine, const char *label, const char *pin,
+ isc_mem_t *mctx, dst_key_t **keyp);
+
+isc_result_t
+dst_key_generate(const dns_name_t *name, unsigned int alg, unsigned int bits,
+ unsigned int param, unsigned int flags, unsigned int protocol,
+ dns_rdataclass_t rdclass, isc_mem_t *mctx, dst_key_t **keyp,
+ void (*callback)(int));
+
+/*%<
+ * Generate a DST key (or keypair) with the supplied parameters. The
+ * interpretation of the "param" field depends on the algorithm:
+ * \code
+ * RSA: exponent
+ * 0 use exponent 3
+ * !0 use Fermat4 (2^16 + 1)
+ * DH: generator
+ * 0 default - use well known prime if bits == 768 or 1024,
+ * otherwise use 2 as the generator.
+ * !0 use this value as the generator.
+ * DSA: unused
+ * HMACMD5: entropy
+ * 0 default - require good entropy
+ * !0 lack of good entropy is ok
+ *\endcode
+ *
+ * Requires:
+ *\li "name" is a valid absolute dns name.
+ *\li "keyp" is not NULL and "*keyp" is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ * \li any other result indicates failure
+ *
+ * Ensures:
+ *\li If successful, *keyp will contain a valid key.
+ */
+
+bool
+dst_key_compare(const dst_key_t *key1, const dst_key_t *key2);
+/*%<
+ * Compares two DST keys. Returns true if they match, false otherwise.
+ *
+ * Keys ARE NOT considered to match if one of them is the revoked version
+ * of the other.
+ *
+ * Requires:
+ *\li "key1" is a valid key.
+ *\li "key2" is a valid key.
+ *
+ * Returns:
+ *\li true
+ * \li false
+ */
+
+bool
+dst_key_pubcompare(const dst_key_t *key1, const dst_key_t *key2,
+ bool match_revoked_key);
+/*%<
+ * Compares only the public portions of two DST keys. Returns true
+ * if they match, false otherwise. This allows us, for example, to
+ * determine whether a public key found in a zone matches up with a
+ * key pair found on disk.
+ *
+ * If match_revoked_key is TRUE, then keys ARE considered to match if one
+ * of them is the revoked version of the other. Otherwise, they are not.
+ *
+ * Requires:
+ *\li "key1" is a valid key.
+ *\li "key2" is a valid key.
+ *
+ * Returns:
+ *\li true
+ * \li false
+ */
+
+bool
+dst_key_paramcompare(const dst_key_t *key1, const dst_key_t *key2);
+/*%<
+ * Compares the parameters of two DST keys. This is used to determine if
+ * two (Diffie-Hellman) keys can be used to derive a shared secret.
+ *
+ * Requires:
+ *\li "key1" is a valid key.
+ *\li "key2" is a valid key.
+ *
+ * Returns:
+ *\li true
+ * \li false
+ */
+
+void
+dst_key_attach(dst_key_t *source, dst_key_t **target);
+/*
+ * Attach to a existing key increasing the reference count.
+ *
+ * Requires:
+ *\li 'source' to be a valid key.
+ *\li 'target' to be non-NULL and '*target' to be NULL.
+ */
+
+void
+dst_key_free(dst_key_t **keyp);
+/*%<
+ * Decrement the key's reference counter and, when it reaches zero,
+ * release all memory associated with the key.
+ *
+ * Requires:
+ *\li "keyp" is not NULL and "*keyp" is a valid key.
+ *\li reference counter greater than zero.
+ *
+ * Ensures:
+ *\li All memory associated with "*keyp" will be freed.
+ *\li *keyp == NULL
+ */
+
+/*%<
+ * Accessor functions to obtain key fields.
+ *
+ * Require:
+ *\li "key" is a valid key.
+ */
+dns_name_t *
+dst_key_name(const dst_key_t *key);
+
+unsigned int
+dst_key_size(const dst_key_t *key);
+
+unsigned int
+dst_key_proto(const dst_key_t *key);
+
+unsigned int
+dst_key_alg(const dst_key_t *key);
+
+uint32_t
+dst_key_flags(const dst_key_t *key);
+
+dns_keytag_t
+dst_key_id(const dst_key_t *key);
+
+dns_keytag_t
+dst_key_rid(const dst_key_t *key);
+
+dns_rdataclass_t
+dst_key_class(const dst_key_t *key);
+
+bool
+dst_key_isprivate(const dst_key_t *key);
+
+bool
+dst_key_iszonekey(const dst_key_t *key);
+
+bool
+dst_key_isnullkey(const dst_key_t *key);
+
+isc_result_t
+dst_key_buildfilename(const dst_key_t *key, int type, const char *directory,
+ isc_buffer_t *out);
+/*%<
+ * Generates the filename used by dst to store the specified key.
+ * If directory is NULL, the current directory is assumed.
+ *
+ * Requires:
+ *\li "key" is a valid key
+ *\li "type" is either DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or 0 for no suffix.
+ *\li "out" is a valid buffer
+ *
+ * Ensures:
+ *\li the file name will be written to "out", and the used pointer will
+ * be advanced.
+ */
+
+isc_result_t
+dst_key_sigsize(const dst_key_t *key, unsigned int *n);
+/*%<
+ * Computes the size of a signature generated by the given key.
+ *
+ * Requires:
+ *\li "key" is a valid key.
+ *\li "n" is not NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li DST_R_UNSUPPORTEDALG
+ *
+ * Ensures:
+ *\li "n" stores the size of a generated signature
+ */
+
+isc_result_t
+dst_key_secretsize(const dst_key_t *key, unsigned int *n);
+/*%<
+ * Computes the size of a shared secret generated by the given key.
+ *
+ * Requires:
+ *\li "key" is a valid key.
+ *\li "n" is not NULL
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li DST_R_UNSUPPORTEDALG
+ *
+ * Ensures:
+ *\li "n" stores the size of a generated shared secret
+ */
+
+uint16_t
+dst_region_computeid(const isc_region_t *source);
+uint16_t
+dst_region_computerid(const isc_region_t *source);
+/*%<
+ * Computes the (revoked) key id of the key stored in the provided
+ * region.
+ *
+ * Requires:
+ *\li "source" contains a valid, non-NULL region.
+ *
+ * Returns:
+ *\li the key id
+ */
+
+uint16_t
+dst_key_getbits(const dst_key_t *key);
+/*%<
+ * Get the number of digest bits required (0 == MAX).
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+void
+dst_key_setbits(dst_key_t *key, uint16_t bits);
+/*%<
+ * Set the number of digest bits required (0 == MAX).
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+void
+dst_key_setttl(dst_key_t *key, dns_ttl_t ttl);
+/*%<
+ * Set the default TTL to use when converting the key
+ * to a KEY or DNSKEY RR.
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+dns_ttl_t
+dst_key_getttl(const dst_key_t *key);
+/*%<
+ * Get the default TTL to use when converting the key
+ * to a KEY or DNSKEY RR.
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+isc_result_t
+dst_key_setflags(dst_key_t *key, uint32_t flags);
+/*
+ * Set the key flags, and recompute the key ID.
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+isc_result_t
+dst_key_getbool(const dst_key_t *key, int type, bool *valuep);
+/*%<
+ * Get a member of the boolean metadata array and place it in '*valuep'.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_BOOLEAN
+ * "valuep" is not null.
+ */
+
+void
+dst_key_setbool(dst_key_t *key, int type, bool value);
+/*%<
+ * Set a member of the boolean metadata array.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_BOOLEAN
+ */
+
+void
+dst_key_unsetbool(dst_key_t *key, int type);
+/*%<
+ * Flag a member of the boolean metadata array as "not set".
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_BOOLEAN
+ */
+
+isc_result_t
+dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep);
+/*%<
+ * Get a member of the numeric metadata array and place it in '*valuep'.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_NUMERIC
+ * "valuep" is not null.
+ */
+
+void
+dst_key_setnum(dst_key_t *key, int type, uint32_t value);
+/*%<
+ * Set a member of the numeric metadata array.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_NUMERIC
+ */
+
+void
+dst_key_unsetnum(dst_key_t *key, int type);
+/*%<
+ * Flag a member of the numeric metadata array as "not set".
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_NUMERIC
+ */
+
+isc_result_t
+dst_key_gettime(const dst_key_t *key, int type, isc_stdtime_t *timep);
+/*%<
+ * Get a member of the timing metadata array and place it in '*timep'.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_TIMES
+ * "timep" is not null.
+ */
+
+void
+dst_key_settime(dst_key_t *key, int type, isc_stdtime_t when);
+/*%<
+ * Set a member of the timing metadata array.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_TIMES
+ */
+
+void
+dst_key_unsettime(dst_key_t *key, int type);
+/*%<
+ * Flag a member of the timing metadata array as "not set".
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_TIMES
+ */
+
+isc_result_t
+dst_key_getstate(const dst_key_t *key, int type, dst_key_state_t *statep);
+/*%<
+ * Get a member of the keystate metadata array and place it in '*statep'.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_KEYSTATES
+ * "statep" is not null.
+ */
+
+void
+dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state);
+/*%<
+ * Set a member of the keystate metadata array.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "state" is a valid state.
+ * "type" is no larger than DST_MAX_KEYSTATES
+ */
+
+void
+dst_key_unsetstate(dst_key_t *key, int type);
+/*%<
+ * Flag a member of the keystate metadata array as "not set".
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "type" is no larger than DST_MAX_KEYSTATES
+ */
+
+isc_result_t
+dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp);
+/*%<
+ * Get the private key format version number. (If the key does not have
+ * a private key associated with it, the version will be 0.0.) The major
+ * version number is placed in '*majorp', and the minor version number in
+ * '*minorp'.
+ *
+ * Requires:
+ * "key" is a valid key.
+ * "majorp" is not NULL.
+ * "minorp" is not NULL.
+ */
+
+void
+dst_key_setprivateformat(dst_key_t *key, int major, int minor);
+/*%<
+ * Set the private key format version number.
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+#define DST_KEY_FORMATSIZE (DNS_NAME_FORMATSIZE + DNS_SECALG_FORMATSIZE + 7)
+
+void
+dst_key_format(const dst_key_t *key, char *cp, unsigned int size);
+/*%<
+ * Write the uniquely identifying information about the key (name,
+ * algorithm, key ID) into a string 'cp' of size 'size'.
+ */
+
+isc_buffer_t *
+dst_key_tkeytoken(const dst_key_t *key);
+/*%<
+ * Return the token from the TKEY request, if any. If this key was
+ * not negotiated via TKEY, return NULL.
+ *
+ * Requires:
+ * "key" is a valid key.
+ */
+
+isc_result_t
+dst_key_dump(dst_key_t *key, isc_mem_t *mctx, char **buffer, int *length);
+/*%<
+ * Allocate 'buffer' and dump the key into it in base64 format. The buffer
+ * is not NUL terminated. The length of the buffer is returned in *length.
+ *
+ * 'buffer' needs to be freed using isc_mem_put(mctx, buffer, length);
+ *
+ * Requires:
+ * 'buffer' to be non NULL and *buffer to be NULL.
+ * 'length' to be non NULL and *length to be zero.
+ *
+ * Returns:
+ * ISC_R_SUCCESS
+ * ISC_R_NOMEMORY
+ * ISC_R_NOTIMPLEMENTED
+ * others.
+ */
+
+isc_result_t
+dst_key_restore(dns_name_t *name, unsigned int alg, unsigned int flags,
+ unsigned int protocol, dns_rdataclass_t rdclass,
+ isc_mem_t *mctx, const char *keystr, dst_key_t **keyp);
+
+bool
+dst_key_inactive(const dst_key_t *key);
+/*%<
+ * Determines if the private key is missing due the key being deemed inactive.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+void
+dst_key_setinactive(dst_key_t *key, bool inactive);
+/*%<
+ * Set key inactive state.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+void
+dst_key_setexternal(dst_key_t *key, bool value);
+/*%<
+ * Set key external state.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_isexternal(dst_key_t *key);
+/*%<
+ * Check if this is an external key.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+void
+dst_key_setmodified(dst_key_t *key, bool value);
+/*%<
+ * If 'value' is true, this marks the key to indicate that key file metadata
+ * has been modified. If 'value' is false, this resets the value, for example
+ * after you have written the key to file.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_ismodified(const dst_key_t *key);
+/*%<
+ * Check if the key file has been modified.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_haskasp(dst_key_t *key);
+/*%<
+ * Check if this key has state (and thus uses KASP).
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_unused(dst_key_t *key);
+/*%<
+ * Check if this key is unused.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_published(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *publish);
+/*%<
+ * Check if it is safe to publish this key (e.g. put the DNSKEY in the zone).
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_active(dst_key_t *key, isc_stdtime_t now);
+/*%<
+ * Check if this key is active. This means that it is creating RRSIG records
+ * (ZSK), or that it is used to create a chain of trust (KSK), or both (CSK).
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_signing(dst_key_t *key, int role, isc_stdtime_t now,
+ isc_stdtime_t *active);
+/*%<
+ * Check if it is safe to use this key for signing, given the role.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_revoked(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *revoke);
+/*%<
+ * Check if this key is revoked.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+bool
+dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove);
+/*%<
+ * Check if this key is removed from the zone (e.g. the DNSKEY record should
+ * no longer be in the zone).
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+dst_key_state_t
+dst_key_goal(dst_key_t *key);
+/*%<
+ * Get the key goal. Should be OMNIPRESENT or HIDDEN.
+ * This can be used to determine if the key is being introduced or
+ * is on its way out.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+isc_result_t
+dst_key_role(dst_key_t *key, bool *ksk, bool *zsk);
+/*%<
+ * Get the key role. A key can have the KSK or the ZSK role, or both.
+ *
+ * Requires:
+ * 'key' to be valid.
+ */
+
+void
+dst_key_copy_metadata(dst_key_t *to, dst_key_t *from);
+/*%<
+ * Copy key metadata from one key to another.
+ *
+ * Requires:
+ * 'to' and 'from' to be valid.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DST_DST_H */
diff --git a/lib/dns/include/dst/gssapi.h b/lib/dns/include/dst/gssapi.h
new file mode 100644
index 0000000..e783f20
--- /dev/null
+++ b/lib/dns/include/dst/gssapi.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DST_GSSAPI_H
+#define DST_GSSAPI_H 1
+
+/*! \file dst/gssapi.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/formatcheck.h>
+#include <isc/lang.h>
+#include <isc/platform.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 GSS_C_NO_CONTEXT)
+ *
+ * Returns:
+ * ISC_R_SUCCESS msg was successfully updated to include the
+ * query to be sent
+ * other an error occurred while building the message
+ * *err_message optional error message
+ */
+
+isc_result_t
+dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
+ isc_region_t *intoken, isc_buffer_t **outtoken,
+ dns_gss_ctx_id_t *context, dns_name_t *principal,
+ isc_mem_t *mctx);
+/*
+ * Accepts a GSS context.
+ *
+ * Requires:
+ * 'mctx' is a valid memory context
+ * 'cred' is the acceptor's valid GSS credential handle
+ * 'intoken' is a token received from the initiator
+ * 'outtoken' is a pointer a buffer pointer used to return the token
+ * generated by gss_accept_sec_context() to be sent to the
+ * initiator
+ * 'context' is a valid pointer to receive the generated context handle.
+ * On the initial call, it should be a pointer to NULL, which
+ * will be allocated as a dns_gss_ctx_id_t. Subsequent calls
+ * should pass in the handle generated on the first call.
+ * Call dst_gssapi_releasecred to delete the context and free
+ * the memory.
+ *
+ * Requires:
+ * 'outtoken' to != NULL && *outtoken == NULL.
+ *
+ * Returns:
+ * ISC_R_SUCCESS msg was successfully updated to include the
+ * query to be sent
+ * DNS_R_CONTINUE transaction still in progress
+ * other an error occurred while building the message
+ */
+
+isc_result_t
+dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx);
+/*
+ * Destroys a GSS context. This function deletes the context from the GSS
+ * provider and then frees the memory used by the context pointer.
+ *
+ * Requires:
+ * 'mctx' is a valid memory context
+ * 'context' is a valid GSS context
+ *
+ * Returns:
+ * ISC_R_SUCCESS
+ */
+
+void
+gss_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3);
+/*
+ * Logging function for GSS.
+ *
+ * Requires
+ * 'level' is the log level to be used, as an integer
+ * 'fmt' is a printf format specifier
+ */
+
+char *
+gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen);
+/*
+ * Render a GSS major status/minor status pair into a string
+ *
+ * Requires:
+ * 'major' is a GSS major status code
+ * 'minor' is a GSS minor status code
+ *
+ * Returns:
+ * A string containing the text representation of the error codes.
+ * Users should copy the string if they wish to keep it.
+ */
+
+bool
+dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain);
+/*
+ * Compare a "signer" (in the format of a Kerberos-format Kerberos5
+ * principal: host/example.com@EXAMPLE.COM) to the realm name stored
+ * in "name" (which represents the realm name).
+ *
+ */
+
+bool
+dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
+ const dns_name_t *name,
+ const dns_name_t *realm, bool subdomain);
+/*
+ * Compare a "signer" (in the format of a Kerberos-format Kerberos5
+ * principal: host/example.com@EXAMPLE.COM) to the realm name stored
+ * in "name" (which represents the realm name).
+ *
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DST_GSSAPI_H */
diff --git a/lib/dns/include/dst/result.h b/lib/dns/include/dst/result.h
new file mode 100644
index 0000000..b909f55
--- /dev/null
+++ b/lib/dns/include/dst/result.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DST_RESULT_H
+#define DST_RESULT_H 1
+
+/*! \file dst/result.h */
+
+#include <isc/lang.h>
+#include <isc/resultclass.h>
+
+/*
+ * Nothing in this file truly depends on <isc/result.h>, but the
+ * DST result codes are considered to be publicly derived from
+ * the ISC result codes, so including this file buys you the ISC_R_
+ * namespace too.
+ */
+#include <isc/result.h> /* Contractual promise. */
+
+#define DST_R_UNSUPPORTEDALG (ISC_RESULTCLASS_DST + 0)
+#define DST_R_CRYPTOFAILURE (ISC_RESULTCLASS_DST + 1)
+/* compat */
+#define DST_R_OPENSSLFAILURE DST_R_CRYPTOFAILURE
+#define DST_R_NOCRYPTO (ISC_RESULTCLASS_DST + 2)
+#define DST_R_NULLKEY (ISC_RESULTCLASS_DST + 3)
+#define DST_R_INVALIDPUBLICKEY (ISC_RESULTCLASS_DST + 4)
+#define DST_R_INVALIDPRIVATEKEY (ISC_RESULTCLASS_DST + 5)
+/* 6 is unused */
+#define DST_R_WRITEERROR (ISC_RESULTCLASS_DST + 7)
+#define DST_R_INVALIDPARAM (ISC_RESULTCLASS_DST + 8)
+/* 9 is unused */
+/* 10 is unused */
+#define DST_R_SIGNFAILURE (ISC_RESULTCLASS_DST + 11)
+/* 12 is unused */
+/* 13 is unused */
+#define DST_R_VERIFYFAILURE (ISC_RESULTCLASS_DST + 14)
+#define DST_R_NOTPUBLICKEY (ISC_RESULTCLASS_DST + 15)
+#define DST_R_NOTPRIVATEKEY (ISC_RESULTCLASS_DST + 16)
+#define DST_R_KEYCANNOTCOMPUTESECRET (ISC_RESULTCLASS_DST + 17)
+#define DST_R_COMPUTESECRETFAILURE (ISC_RESULTCLASS_DST + 18)
+#define DST_R_NORANDOMNESS (ISC_RESULTCLASS_DST + 19)
+#define DST_R_BADKEYTYPE (ISC_RESULTCLASS_DST + 20)
+#define DST_R_NOENGINE (ISC_RESULTCLASS_DST + 21)
+#define DST_R_EXTERNALKEY (ISC_RESULTCLASS_DST + 22)
+
+#define DST_R_NRESULTS 23 /* Number of results */
+
+ISC_LANG_BEGINDECLS
+
+const char *dst_result_totext(isc_result_t);
+
+void
+dst_result_register(void);
+
+ISC_LANG_ENDDECLS
+
+#endif /* DST_RESULT_H */
diff --git a/lib/dns/ipkeylist.c b/lib/dns/ipkeylist.c
new file mode 100644
index 0000000..07d2c12
--- /dev/null
+++ b/lib/dns/ipkeylist.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <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->dscps = NULL;
+ ipkl->keys = NULL;
+ ipkl->labels = NULL;
+}
+
+void
+dns_ipkeylist_clear(isc_mem_t *mctx, dns_ipkeylist_t *ipkl) {
+ uint32_t i;
+
+ REQUIRE(ipkl != NULL);
+
+ if (ipkl->allocated == 0) {
+ return;
+ }
+
+ if (ipkl->addrs != NULL) {
+ isc_mem_put(mctx, ipkl->addrs,
+ ipkl->allocated * sizeof(isc_sockaddr_t));
+ }
+
+ if (ipkl->dscps != NULL) {
+ isc_mem_put(mctx, ipkl->dscps,
+ ipkl->allocated * sizeof(isc_dscp_t));
+ }
+
+ if (ipkl->keys != NULL) {
+ for (i = 0; i < ipkl->allocated; i++) {
+ if (ipkl->keys[i] == NULL) {
+ continue;
+ }
+ if (dns_name_dynamic(ipkl->keys[i])) {
+ dns_name_free(ipkl->keys[i], mctx);
+ }
+ isc_mem_put(mctx, ipkl->keys[i], sizeof(dns_name_t));
+ }
+ isc_mem_put(mctx, ipkl->keys,
+ ipkl->allocated * sizeof(dns_name_t *));
+ }
+
+ if (ipkl->labels != NULL) {
+ for (i = 0; i < ipkl->allocated; i++) {
+ if (ipkl->labels[i] == NULL) {
+ continue;
+ }
+ if (dns_name_dynamic(ipkl->labels[i])) {
+ dns_name_free(ipkl->labels[i], mctx);
+ }
+ isc_mem_put(mctx, ipkl->labels[i], sizeof(dns_name_t));
+ }
+ isc_mem_put(mctx, ipkl->labels,
+ ipkl->allocated * sizeof(dns_name_t *));
+ }
+
+ dns_ipkeylist_init(ipkl);
+}
+
+isc_result_t
+dns_ipkeylist_copy(isc_mem_t *mctx, const dns_ipkeylist_t *src,
+ dns_ipkeylist_t *dst) {
+ isc_result_t result = ISC_R_SUCCESS;
+ uint32_t i;
+
+ REQUIRE(dst != NULL);
+ /* dst might be preallocated, we don't care, but it must be empty */
+ REQUIRE(dst->count == 0);
+
+ if (src->count == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_ipkeylist_resize(mctx, dst, src->count);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ memmove(dst->addrs, src->addrs, src->count * sizeof(isc_sockaddr_t));
+
+ if (src->dscps != NULL) {
+ memmove(dst->dscps, src->dscps,
+ src->count * sizeof(isc_dscp_t));
+ }
+
+ if (src->keys != NULL) {
+ for (i = 0; i < src->count; i++) {
+ if (src->keys[i] != NULL) {
+ dst->keys[i] = isc_mem_get(mctx,
+ sizeof(dns_name_t));
+ dns_name_init(dst->keys[i], NULL);
+ dns_name_dup(src->keys[i], mctx, dst->keys[i]);
+ } else {
+ dst->keys[i] = NULL;
+ }
+ }
+ }
+
+ if (src->labels != NULL) {
+ for (i = 0; i < src->count; i++) {
+ if (src->labels[i] != NULL) {
+ dst->labels[i] =
+ isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(dst->labels[i], NULL);
+ dns_name_dup(src->labels[i], mctx,
+ dst->labels[i]);
+ } else {
+ dst->labels[i] = NULL;
+ }
+ }
+ }
+ dst->count = src->count;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_ipkeylist_resize(isc_mem_t *mctx, dns_ipkeylist_t *ipkl, unsigned int n) {
+ isc_sockaddr_t *addrs = NULL;
+ isc_dscp_t *dscps = NULL;
+ dns_name_t **keys = NULL;
+ dns_name_t **labels = NULL;
+
+ REQUIRE(ipkl != NULL);
+ REQUIRE(n > ipkl->count);
+
+ if (n <= ipkl->allocated) {
+ return (ISC_R_SUCCESS);
+ }
+
+ addrs = isc_mem_get(mctx, n * sizeof(isc_sockaddr_t));
+ dscps = isc_mem_get(mctx, n * sizeof(isc_dscp_t));
+ keys = isc_mem_get(mctx, n * sizeof(dns_name_t *));
+ labels = isc_mem_get(mctx, n * sizeof(dns_name_t *));
+
+ if (ipkl->addrs != NULL) {
+ memmove(addrs, ipkl->addrs,
+ ipkl->allocated * sizeof(isc_sockaddr_t));
+ isc_mem_put(mctx, ipkl->addrs,
+ ipkl->allocated * sizeof(isc_sockaddr_t));
+ }
+ ipkl->addrs = addrs;
+ memset(&ipkl->addrs[ipkl->allocated], 0,
+ (n - ipkl->allocated) * sizeof(isc_sockaddr_t));
+
+ if (ipkl->dscps != NULL) {
+ memmove(dscps, ipkl->dscps,
+ ipkl->allocated * sizeof(isc_dscp_t));
+ isc_mem_put(mctx, ipkl->dscps,
+ ipkl->allocated * sizeof(isc_dscp_t));
+ }
+ ipkl->dscps = dscps;
+ memset(&ipkl->dscps[ipkl->allocated], 0,
+ (n - ipkl->allocated) * sizeof(isc_dscp_t));
+
+ if (ipkl->keys) {
+ memmove(keys, ipkl->keys,
+ ipkl->allocated * sizeof(dns_name_t *));
+ isc_mem_put(mctx, ipkl->keys,
+ ipkl->allocated * sizeof(dns_name_t *));
+ }
+ ipkl->keys = keys;
+ memset(&ipkl->keys[ipkl->allocated], 0,
+ (n - ipkl->allocated) * sizeof(dns_name_t *));
+
+ if (ipkl->labels != NULL) {
+ memmove(labels, ipkl->labels,
+ ipkl->allocated * sizeof(dns_name_t *));
+ isc_mem_put(mctx, ipkl->labels,
+ ipkl->allocated * sizeof(dns_name_t *));
+ }
+ ipkl->labels = labels;
+ memset(&ipkl->labels[ipkl->allocated], 0,
+ (n - ipkl->allocated) * sizeof(dns_name_t *));
+
+ ipkl->allocated = n;
+ return (ISC_R_SUCCESS);
+
+ isc_mem_put(mctx, addrs, n * sizeof(isc_sockaddr_t));
+ isc_mem_put(mctx, dscps, n * sizeof(isc_dscp_t));
+ isc_mem_put(mctx, keys, n * sizeof(dns_name_t *));
+ isc_mem_put(mctx, labels, n * sizeof(dns_name_t *));
+
+ return (ISC_R_NOMEMORY);
+}
diff --git a/lib/dns/iptable.c b/lib/dns/iptable.c
new file mode 100644
index 0000000..f479d71
--- /dev/null
+++ b/lib/dns/iptable.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <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..b586528
--- /dev/null
+++ b/lib/dns/journal.c
@@ -0,0 +1,2856 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <isc/file.h>
+#include <isc/mem.h>
+#include <isc/print.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/result.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_copynf(dns_db_origin(db), zonename);
+
+ node = NULL;
+ result = dns_db_findnode(db, zonename, false, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto nonode;
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_soa, 0,
+ (isc_stdtime_t)0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto freenode;
+ }
+
+ result = dns_rdataset_first(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto freenode;
+ }
+
+ dns_rdataset_current(&rdataset, &rdata);
+ dns_rdataset_getownercase(&rdataset, zonename);
+
+ result = dns_difftuple_create(mctx, op, zonename, rdataset.ttl, &rdata,
+ tp);
+
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(db, &node);
+ return (result);
+
+freenode:
+ dns_db_detachnode(db, &node);
+nonode:
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "missing SOA");
+ return (result);
+}
+
+/* Journaling */
+
+/*%
+ * On-disk representation of a "pointer" to a journal entry.
+ * These are used in the journal header to locate the beginning
+ * and end of the journal, and in the journal index to locate
+ * other transactions.
+ */
+typedef struct {
+ unsigned char serial[4]; /*%< SOA serial before update. */
+ /*
+ * XXXRTH Should offset be 8 bytes?
+ * XXXDCL ... probably, since isc_offset_t is 8 bytes on many OSs.
+ * XXXAG ... but we will not be able to seek >2G anyway on many
+ * platforms as long as we are using fseek() rather
+ * than lseek().
+ */
+ unsigned char offset[4]; /*%< Offset from beginning of file. */
+} journal_rawpos_t;
+
+/*%
+ * The header is of a fixed size, with some spare room for future
+ * extensions.
+ */
+#define JOURNAL_HEADER_SIZE 64 /* Bytes. */
+
+typedef enum {
+ XHDR_VERSION1 = 1,
+ XHDR_VERSION2 = 2,
+} xhdr_version_t;
+
+/*%
+ * The on-disk representation of the journal header.
+ * All numbers are stored in big-endian order.
+ */
+typedef union {
+ struct {
+ /*% File format version ID. */
+ unsigned char format[16];
+ /*% Position of the first addressable transaction */
+ journal_rawpos_t begin;
+ /*% Position of the next (yet nonexistent) transaction. */
+ journal_rawpos_t end;
+ /*% Number of index entries following the header. */
+ unsigned char index_size[4];
+ /*% Source serial number. */
+ unsigned char sourceserial[4];
+ unsigned char flags;
+ } h;
+ /* Pad the header to a fixed size. */
+ unsigned char pad[JOURNAL_HEADER_SIZE];
+} journal_rawheader_t;
+
+/*%
+ * The on-disk representation of the transaction header, version 2.
+ * There is one of these at the beginning of each transaction.
+ */
+typedef struct {
+ unsigned char size[4]; /*%< In bytes, excluding header. */
+ unsigned char count[4]; /*%< Number of records in transaction */
+ unsigned char serial0[4]; /*%< SOA serial before update. */
+ unsigned char serial1[4]; /*%< SOA serial after update. */
+} journal_rawxhdr_t;
+
+/*%
+ * Old-style raw transaction header, version 1, used for backward
+ * compatibility mode.
+ */
+typedef struct {
+ unsigned char size[4];
+ unsigned char serial0[4];
+ unsigned char serial1[4];
+} journal_rawxhdr_ver1_t;
+
+/*%
+ * The on-disk representation of the RR header.
+ * There is one of these at the beginning of each RR.
+ */
+typedef struct {
+ unsigned char size[4]; /*%< In bytes, excluding header. */
+} journal_rawrrhdr_t;
+
+/*%
+ * The in-core representation of the journal header.
+ */
+typedef struct {
+ uint32_t serial;
+ isc_offset_t offset;
+} journal_pos_t;
+
+#define POS_VALID(pos) ((pos).offset != 0)
+#define POS_INVALIDATE(pos) ((pos).offset = 0, (pos).serial = 0)
+
+typedef struct {
+ unsigned char format[16];
+ journal_pos_t begin;
+ journal_pos_t end;
+ uint32_t index_size;
+ uint32_t sourceserial;
+ bool serialset;
+} journal_header_t;
+
+/*%
+ * The in-core representation of the transaction header.
+ */
+typedef struct {
+ uint32_t size;
+ uint32_t count;
+ uint32_t serial0;
+ uint32_t serial1;
+} journal_xhdr_t;
+
+/*%
+ * The in-core representation of the RR header.
+ */
+typedef struct {
+ uint32_t size;
+} journal_rrhdr_t;
+
+/*%
+ * Initial contents to store in the header of a newly created
+ * journal file.
+ *
+ * The header starts with the magic string ";BIND LOG V9.2\n"
+ * to identify the file as a BIND 9 journal file. An ASCII
+ * identification string is used rather than a binary magic
+ * number to be consistent with BIND 8 (BIND 8 journal files
+ * are ASCII text files).
+ */
+
+static journal_header_t journal_header_ver1 = {
+ ";BIND LOG V9\n", { 0, 0 }, { 0, 0 }, 0, 0, 0
+};
+static journal_header_t initial_journal_header = {
+ ";BIND LOG V9.2\n", { 0, 0 }, { 0, 0 }, 0, 0, 0
+};
+
+#define JOURNAL_EMPTY(h) ((h)->begin.offset == (h)->end.offset)
+
+typedef enum {
+ JOURNAL_STATE_INVALID,
+ JOURNAL_STATE_READ,
+ JOURNAL_STATE_WRITE,
+ JOURNAL_STATE_TRANSACTION,
+ JOURNAL_STATE_INLINE
+} journal_state_t;
+
+struct dns_journal {
+ unsigned int magic; /*%< JOUR */
+ isc_mem_t *mctx; /*%< Memory context */
+ journal_state_t state;
+ xhdr_version_t xhdr_version; /*%< Expected transaction header version */
+ bool header_ver1; /*%< Transaction header compatibility
+ * mode is allowed */
+ bool recovered; /*%< A recoverable error was found
+ * while reading the journal */
+ char *filename; /*%< Journal file name */
+ FILE *fp; /*%< File handle */
+ isc_offset_t offset; /*%< Current file offset */
+ journal_xhdr_t curxhdr; /*%< Current transaction header */
+ journal_header_t header; /*%< In-core journal header */
+ unsigned char *rawindex; /*%< In-core buffer for journal index
+ * in on-disk format */
+ journal_pos_t *index; /*%< In-core journal index */
+
+ /*% Current transaction state (when writing). */
+ struct {
+ unsigned int n_soa; /*%< Number of SOAs seen */
+ unsigned int n_rr; /*%< Number of RRs to write */
+ journal_pos_t pos[2]; /*%< Begin/end position */
+ } x;
+
+ /*% Iteration state (when reading). */
+ struct {
+ /* These define the part of the journal we iterate over. */
+ journal_pos_t bpos; /*%< Position before first, */
+ journal_pos_t cpos; /*%< before current, */
+ journal_pos_t epos; /*%< and after last transaction */
+ /* The rest is iterator state. */
+ uint32_t current_serial; /*%< Current SOA serial */
+ isc_buffer_t source; /*%< Data from disk */
+ isc_buffer_t target; /*%< Data from _fromwire check */
+ dns_decompress_t dctx; /*%< Dummy decompression ctx */
+ dns_name_t name; /*%< Current domain name */
+ dns_rdata_t rdata; /*%< Current rdata */
+ uint32_t ttl; /*%< Current TTL */
+ unsigned int xsize; /*%< Size of transaction data */
+ unsigned int xpos; /*%< Current position in it */
+ isc_result_t result; /*%< Result of last call */
+ } it;
+};
+
+#define DNS_JOURNAL_MAGIC ISC_MAGIC('J', 'O', 'U', 'R')
+#define DNS_JOURNAL_VALID(t) ISC_MAGIC_VALID(t, DNS_JOURNAL_MAGIC)
+
+static void
+journal_pos_decode(journal_rawpos_t *raw, journal_pos_t *cooked) {
+ cooked->serial = decode_uint32(raw->serial);
+ cooked->offset = decode_uint32(raw->offset);
+}
+
+static void
+journal_pos_encode(journal_rawpos_t *raw, journal_pos_t *cooked) {
+ encode_uint32(cooked->serial, raw->serial);
+ encode_uint32(cooked->offset, raw->offset);
+}
+
+static void
+journal_header_decode(journal_rawheader_t *raw, journal_header_t *cooked) {
+ INSIST(sizeof(cooked->format) == sizeof(raw->h.format));
+
+ memmove(cooked->format, raw->h.format, sizeof(cooked->format));
+ journal_pos_decode(&raw->h.begin, &cooked->begin);
+ journal_pos_decode(&raw->h.end, &cooked->end);
+ cooked->index_size = decode_uint32(raw->h.index_size);
+ cooked->sourceserial = decode_uint32(raw->h.sourceserial);
+ cooked->serialset = ((raw->h.flags & JOURNAL_SERIALSET) != 0);
+}
+
+static void
+journal_header_encode(journal_header_t *cooked, journal_rawheader_t *raw) {
+ unsigned char flags = 0;
+
+ INSIST(sizeof(cooked->format) == sizeof(raw->h.format));
+
+ memset(raw->pad, 0, sizeof(raw->pad));
+ memmove(raw->h.format, cooked->format, sizeof(raw->h.format));
+ journal_pos_encode(&raw->h.begin, &cooked->begin);
+ journal_pos_encode(&raw->h.end, &cooked->end);
+ encode_uint32(cooked->index_size, raw->h.index_size);
+ encode_uint32(cooked->sourceserial, raw->h.sourceserial);
+ if (cooked->serialset) {
+ flags |= JOURNAL_SERIALSET;
+ }
+ raw->h.flags = flags;
+}
+
+/*
+ * Journal file I/O subroutines, with error checking and reporting.
+ */
+static isc_result_t
+journal_seek(dns_journal_t *j, uint32_t offset) {
+ isc_result_t result;
+
+ result = isc_stdio_seek(j->fp, (off_t)offset, SEEK_SET);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: seek: %s", j->filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+ j->offset = offset;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+journal_read(dns_journal_t *j, void *mem, size_t nbytes) {
+ isc_result_t result;
+
+ result = isc_stdio_read(mem, 1, nbytes, j->fp, NULL);
+ if (result != ISC_R_SUCCESS) {
+ if (result == ISC_R_EOF) {
+ return (ISC_R_NOMORE);
+ }
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: read: %s", j->filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+ j->offset += (isc_offset_t)nbytes;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+journal_write(dns_journal_t *j, void *mem, size_t nbytes) {
+ isc_result_t result;
+
+ result = isc_stdio_write(mem, 1, nbytes, j->fp, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: write: %s", j->filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+ j->offset += (isc_offset_t)nbytes;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+journal_fsync(dns_journal_t *j) {
+ isc_result_t result;
+
+ result = isc_stdio_flush(j->fp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: flush: %s", j->filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+ result = isc_stdio_sync(j->fp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: fsync: %s", j->filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Read/write a transaction header at the current file position.
+ */
+static isc_result_t
+journal_read_xhdr(dns_journal_t *j, journal_xhdr_t *xhdr) {
+ isc_result_t result;
+
+ j->it.cpos.offset = j->offset;
+
+ switch (j->xhdr_version) {
+ case XHDR_VERSION1: {
+ journal_rawxhdr_ver1_t raw;
+ result = journal_read(j, &raw, sizeof(raw));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ xhdr->size = decode_uint32(raw.size);
+ xhdr->count = 0;
+ xhdr->serial0 = decode_uint32(raw.serial0);
+ xhdr->serial1 = decode_uint32(raw.serial1);
+ j->curxhdr = *xhdr;
+ return (ISC_R_SUCCESS);
+ }
+
+ case XHDR_VERSION2: {
+ journal_rawxhdr_t raw;
+ result = journal_read(j, &raw, sizeof(raw));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ xhdr->size = decode_uint32(raw.size);
+ xhdr->count = decode_uint32(raw.count);
+ xhdr->serial0 = decode_uint32(raw.serial0);
+ xhdr->serial1 = decode_uint32(raw.serial1);
+ j->curxhdr = *xhdr;
+ return (ISC_R_SUCCESS);
+ }
+
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+}
+
+static isc_result_t
+journal_write_xhdr(dns_journal_t *j, uint32_t size, uint32_t count,
+ uint32_t serial0, uint32_t serial1) {
+ if (j->header_ver1) {
+ journal_rawxhdr_ver1_t raw;
+ encode_uint32(size, raw.size);
+ encode_uint32(serial0, raw.serial0);
+ encode_uint32(serial1, raw.serial1);
+ return (journal_write(j, &raw, sizeof(raw)));
+ } else {
+ journal_rawxhdr_t raw;
+ encode_uint32(size, raw.size);
+ encode_uint32(count, raw.count);
+ encode_uint32(serial0, raw.serial0);
+ encode_uint32(serial1, raw.serial1);
+ return (journal_write(j, &raw, sizeof(raw)));
+ }
+}
+
+/*
+ * Read an RR header at the current file position.
+ */
+
+static isc_result_t
+journal_read_rrhdr(dns_journal_t *j, journal_rrhdr_t *rrhdr) {
+ journal_rawrrhdr_t raw;
+ isc_result_t result;
+
+ result = journal_read(j, &raw, sizeof(raw));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ rrhdr->size = decode_uint32(raw.size);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+journal_file_create(isc_mem_t *mctx, bool downgrade, const char *filename) {
+ FILE *fp = NULL;
+ isc_result_t result;
+ journal_header_t header;
+ journal_rawheader_t rawheader;
+ int index_size = 56; /* XXX configurable */
+ int size;
+ void *mem = NULL; /* Memory for temporary index image. */
+
+ INSIST(sizeof(journal_rawheader_t) == JOURNAL_HEADER_SIZE);
+
+ result = isc_stdio_open(filename, "wb", &fp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: create: %s", filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+
+ if (downgrade) {
+ header = journal_header_ver1;
+ } else {
+ header = initial_journal_header;
+ }
+ header.index_size = index_size;
+ journal_header_encode(&header, &rawheader);
+
+ size = sizeof(journal_rawheader_t) +
+ index_size * sizeof(journal_rawpos_t);
+
+ mem = isc_mem_get(mctx, size);
+ memset(mem, 0, size);
+ memmove(mem, &rawheader, sizeof(rawheader));
+
+ result = isc_stdio_write(mem, 1, (size_t)size, fp, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: write: %s", filename,
+ isc_result_totext(result));
+ (void)isc_stdio_close(fp);
+ (void)isc_file_remove(filename);
+ isc_mem_put(mctx, mem, size);
+ return (ISC_R_UNEXPECTED);
+ }
+ isc_mem_put(mctx, mem, size);
+
+ result = isc_stdio_close(fp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: close: %s", filename,
+ isc_result_totext(result));
+ (void)isc_file_remove(filename);
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+journal_open(isc_mem_t *mctx, const char *filename, bool writable, bool create,
+ bool downgrade, dns_journal_t **journalp) {
+ FILE *fp = NULL;
+ isc_result_t result;
+ journal_rawheader_t rawheader;
+ dns_journal_t *j;
+
+ REQUIRE(journalp != NULL && *journalp == NULL);
+
+ j = isc_mem_get(mctx, sizeof(*j));
+ *j = (dns_journal_t){ .state = JOURNAL_STATE_INVALID,
+ .filename = isc_mem_strdup(mctx, filename),
+ .xhdr_version = XHDR_VERSION2 };
+ isc_mem_attach(mctx, &j->mctx);
+
+ result = isc_stdio_open(j->filename, writable ? "rb+" : "rb", &fp);
+ if (result == ISC_R_FILENOTFOUND) {
+ if (create) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(1),
+ "journal file %s does not exist, "
+ "creating it",
+ j->filename);
+ CHECK(journal_file_create(mctx, downgrade, filename));
+ /*
+ * Retry.
+ */
+ result = isc_stdio_open(j->filename, "rb+", &fp);
+ } else {
+ FAIL(ISC_R_NOTFOUND);
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: open: %s", j->filename,
+ isc_result_totext(result));
+ FAIL(ISC_R_UNEXPECTED);
+ }
+
+ j->fp = fp;
+
+ /*
+ * Set magic early so that seek/read can succeed.
+ */
+ j->magic = DNS_JOURNAL_MAGIC;
+
+ CHECK(journal_seek(j, 0));
+ CHECK(journal_read(j, &rawheader, sizeof(rawheader)));
+
+ if (memcmp(rawheader.h.format, journal_header_ver1.format,
+ sizeof(journal_header_ver1.format)) == 0)
+ {
+ /*
+ * The file header says it's the old format, but it
+ * still might have the new xhdr format because we
+ * forgot to change the format string when we introduced
+ * the new xhdr. When we first try to read it, we assume
+ * it uses the new xhdr format. If that fails, we'll be
+ * called a second time with compat set to true, in which
+ * case we can lower xhdr_version to 1 if we find a
+ * corrupt transaction.
+ */
+ j->header_ver1 = true;
+ } else if (memcmp(rawheader.h.format, initial_journal_header.format,
+ sizeof(initial_journal_header.format)) == 0)
+ {
+ /*
+ * File header says this is format version 2; all
+ * transactions have to match.
+ */
+ j->header_ver1 = false;
+ } else {
+ isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "%s: journal format not recognized", j->filename);
+ FAIL(ISC_R_UNEXPECTED);
+ }
+ journal_header_decode(&rawheader, &j->header);
+
+ /*
+ * If there is an index, read the raw index into a dynamically
+ * allocated buffer and then convert it into a cooked index.
+ */
+ if (j->header.index_size != 0) {
+ unsigned int i;
+ unsigned int rawbytes;
+ unsigned char *p;
+
+ rawbytes = j->header.index_size * sizeof(journal_rawpos_t);
+ j->rawindex = isc_mem_get(mctx, rawbytes);
+
+ CHECK(journal_read(j, j->rawindex, rawbytes));
+
+ j->index = isc_mem_get(mctx, j->header.index_size *
+ sizeof(journal_pos_t));
+
+ p = j->rawindex;
+ for (i = 0; i < j->header.index_size; i++) {
+ j->index[i].serial = decode_uint32(p);
+ p += 4;
+ j->index[i].offset = decode_uint32(p);
+ p += 4;
+ }
+ INSIST(p == j->rawindex + rawbytes);
+ }
+ j->offset = -1; /* Invalid, must seek explicitly. */
+
+ /*
+ * Initialize the iterator.
+ */
+ dns_name_init(&j->it.name, NULL);
+ dns_rdata_init(&j->it.rdata);
+
+ /*
+ * Set up empty initial buffers for unchecked and checked
+ * wire format RR data. They will be reallocated
+ * later.
+ */
+ isc_buffer_init(&j->it.source, NULL, 0);
+ isc_buffer_init(&j->it.target, NULL, 0);
+ dns_decompress_init(&j->it.dctx, -1, DNS_DECOMPRESS_NONE);
+
+ j->state = writable ? JOURNAL_STATE_WRITE : JOURNAL_STATE_READ;
+
+ *journalp = j;
+ return (ISC_R_SUCCESS);
+
+failure:
+ j->magic = 0;
+ if (j->rawindex != NULL) {
+ isc_mem_put(j->mctx, j->rawindex,
+ j->header.index_size * sizeof(journal_rawpos_t));
+ }
+ if (j->index != NULL) {
+ isc_mem_put(j->mctx, j->index,
+ j->header.index_size * sizeof(journal_pos_t));
+ }
+ isc_mem_free(j->mctx, j->filename);
+ if (j->fp != NULL) {
+ (void)isc_stdio_close(j->fp);
+ }
+ isc_mem_putanddetach(&j->mctx, j, sizeof(*j));
+ return (result);
+}
+
+isc_result_t
+dns_journal_open(isc_mem_t *mctx, const char *filename, unsigned int mode,
+ dns_journal_t **journalp) {
+ isc_result_t result;
+ size_t namelen;
+ char backup[1024];
+ bool writable, create;
+
+ create = ((mode & DNS_JOURNAL_CREATE) != 0);
+ writable = ((mode & (DNS_JOURNAL_WRITE | DNS_JOURNAL_CREATE)) != 0);
+
+ result = journal_open(mctx, filename, writable, create, false,
+ journalp);
+ if (result == ISC_R_NOTFOUND) {
+ namelen = strlen(filename);
+ if (namelen > 4U && strcmp(filename + namelen - 4, ".jnl") == 0)
+ {
+ namelen -= 4;
+ }
+
+ result = snprintf(backup, sizeof(backup), "%.*s.jbk",
+ (int)namelen, filename);
+ if (result >= sizeof(backup)) {
+ return (ISC_R_NOSPACE);
+ }
+ result = journal_open(mctx, backup, writable, writable, false,
+ journalp);
+ }
+ return (result);
+}
+
+/*
+ * A comparison function defining the sorting order for
+ * entries in the IXFR-style journal file.
+ *
+ * The IXFR format requires that deletions are sorted before
+ * additions, and within either one, SOA records are sorted
+ * before others.
+ *
+ * Also sort the non-SOA records by type as a courtesy to the
+ * server receiving the IXFR - it may help reduce the amount of
+ * rdataset merging it has to do.
+ */
+static int
+ixfr_order(const void *av, const void *bv) {
+ dns_difftuple_t const *const *ap = av;
+ dns_difftuple_t const *const *bp = bv;
+ dns_difftuple_t const *a = *ap;
+ dns_difftuple_t const *b = *bp;
+ int r;
+ int bop = 0, aop = 0;
+
+ switch (a->op) {
+ case DNS_DIFFOP_DEL:
+ case DNS_DIFFOP_DELRESIGN:
+ aop = 1;
+ break;
+ case DNS_DIFFOP_ADD:
+ case DNS_DIFFOP_ADDRESIGN:
+ aop = 0;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ switch (b->op) {
+ case DNS_DIFFOP_DEL:
+ case DNS_DIFFOP_DELRESIGN:
+ bop = 1;
+ break;
+ case DNS_DIFFOP_ADD:
+ case DNS_DIFFOP_ADDRESIGN:
+ bop = 0;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ r = bop - aop;
+ if (r != 0) {
+ return (r);
+ }
+
+ r = (b->rdata.type == dns_rdatatype_soa) -
+ (a->rdata.type == dns_rdatatype_soa);
+ if (r != 0) {
+ return (r);
+ }
+
+ r = (a->rdata.type - b->rdata.type);
+ return (r);
+}
+
+static isc_result_t
+maybe_fixup_xhdr(dns_journal_t *j, journal_xhdr_t *xhdr, uint32_t serial,
+ isc_offset_t offset) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ /*
+ * Handle mixture of version 1 and version 2
+ * transaction headers in a version 1 journal.
+ */
+ if ((xhdr->serial0 != serial ||
+ isc_serial_le(xhdr->serial1, xhdr->serial0)))
+ {
+ if (j->xhdr_version == XHDR_VERSION1 && xhdr->serial1 == serial)
+ {
+ isc_log_write(
+ JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(3),
+ "%s: XHDR_VERSION1 -> XHDR_VERSION2 at %u",
+ j->filename, serial);
+ j->xhdr_version = XHDR_VERSION2;
+ CHECK(journal_seek(j, offset));
+ CHECK(journal_read_xhdr(j, xhdr));
+ j->recovered = true;
+ } else if (j->xhdr_version == XHDR_VERSION2 &&
+ xhdr->count == serial)
+ {
+ isc_log_write(
+ JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(3),
+ "%s: XHDR_VERSION2 -> XHDR_VERSION1 at %u",
+ j->filename, serial);
+ j->xhdr_version = XHDR_VERSION1;
+ CHECK(journal_seek(j, offset));
+ CHECK(journal_read_xhdr(j, xhdr));
+ j->recovered = true;
+ }
+ }
+
+ /*
+ * Handle <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 (this is
+ * necessary on WIN32).
+ */
+ dns_journal_destroy(&j1);
+ dns_journal_destroy(&j2);
+
+ /*
+ * With a UFS file system this should just succeed and be atomic.
+ * Any IXFR outs will just continue and the old journal will be
+ * removed on final close.
+ *
+ * With MSDOS / NTFS we need to do a two stage rename, triggered
+ * by EEXIST. (If any IXFR's are running in other threads, however,
+ * this will fail, and the journal will not be compacted. But
+ * if so, hopefully they'll be finished by the next time we
+ * compact.)
+ */
+ if (rename(newname, filename) == -1) {
+ if (errno == EEXIST && !is_backup) {
+ result = isc_file_remove(backup);
+ if (result != ISC_R_SUCCESS &&
+ result != ISC_R_FILENOTFOUND)
+ {
+ goto failure;
+ }
+ if (rename(filename, backup) == -1) {
+ goto maperrno;
+ }
+ if (rename(newname, filename) == -1) {
+ goto maperrno;
+ }
+ (void)isc_file_remove(backup);
+ } else {
+ maperrno:
+ result = ISC_R_FAILURE;
+ goto failure;
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ (void)isc_file_remove(newname);
+ if (buf != NULL) {
+ isc_mem_put(mctx, buf, size);
+ }
+ if (j1 != NULL) {
+ dns_journal_destroy(&j1);
+ }
+ if (j2 != NULL) {
+ dns_journal_destroy(&j2);
+ }
+ return (result);
+}
+
+static isc_result_t
+index_to_disk(dns_journal_t *j) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ if (j->header.index_size != 0) {
+ unsigned int i;
+ unsigned char *p;
+ unsigned int rawbytes;
+
+ rawbytes = j->header.index_size * sizeof(journal_rawpos_t);
+
+ p = j->rawindex;
+ for (i = 0; i < j->header.index_size; i++) {
+ encode_uint32(j->index[i].serial, p);
+ p += 4;
+ encode_uint32(j->index[i].offset, p);
+ p += 4;
+ }
+ INSIST(p == j->rawindex + rawbytes);
+
+ CHECK(journal_seek(j, sizeof(journal_rawheader_t)));
+ CHECK(journal_write(j, j->rawindex, rawbytes));
+ }
+failure:
+ return (result);
+}
diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c
new file mode 100644
index 0000000..09c6810
--- /dev/null
+++ b/lib/dns/kasp.c
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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>
+
+isc_result_t
+dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp) {
+ dns_kasp_t *kasp;
+
+ REQUIRE(name != NULL);
+ REQUIRE(kaspp != NULL && *kaspp == NULL);
+
+ kasp = isc_mem_get(mctx, sizeof(*kasp));
+ kasp->mctx = NULL;
+ isc_mem_attach(mctx, &kasp->mctx);
+
+ kasp->name = isc_mem_strdup(mctx, name);
+ isc_mutex_init(&kasp->lock);
+ kasp->frozen = false;
+
+ isc_refcount_init(&kasp->references, 1);
+
+ ISC_LINK_INIT(kasp, link);
+
+ kasp->signatures_refresh = DNS_KASP_SIG_REFRESH;
+ kasp->signatures_validity = DNS_KASP_SIG_VALIDITY;
+ kasp->signatures_validity_dnskey = DNS_KASP_SIG_VALIDITY_DNSKEY;
+
+ ISC_LIST_INIT(kasp->keys);
+
+ kasp->dnskey_ttl = DNS_KASP_KEY_TTL;
+ kasp->publish_safety = DNS_KASP_PUBLISH_SAFETY;
+ kasp->retire_safety = DNS_KASP_RETIRE_SAFETY;
+ kasp->purge_keys = DNS_KASP_PURGE_KEYS;
+
+ kasp->zone_max_ttl = DNS_KASP_ZONE_MAXTTL;
+ kasp->zone_propagation_delay = DNS_KASP_ZONE_PROPDELAY;
+
+ kasp->parent_ds_ttl = DNS_KASP_DS_TTL;
+ kasp->parent_propagation_delay = DNS_KASP_PARENT_PROPDELAY;
+
+ kasp->nsec3 = false;
+
+ kasp->magic = DNS_KASP_MAGIC;
+ *kaspp = kasp;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp) {
+ REQUIRE(DNS_KASP_VALID(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+ *targetp = source;
+}
+
+static void
+destroy(dns_kasp_t *kasp) {
+ dns_kasp_key_t *key;
+ dns_kasp_key_t *key_next;
+
+ REQUIRE(!ISC_LINK_LINKED(kasp, link));
+
+ for (key = ISC_LIST_HEAD(kasp->keys); key != NULL; key = key_next) {
+ key_next = ISC_LIST_NEXT(key, link);
+ ISC_LIST_UNLINK(kasp->keys, key, link);
+ dns_kasp_key_destroy(key);
+ }
+ INSIST(ISC_LIST_EMPTY(kasp->keys));
+
+ isc_mutex_destroy(&kasp->lock);
+ isc_mem_free(kasp->mctx, kasp->name);
+ isc_mem_putanddetach(&kasp->mctx, kasp, sizeof(*kasp));
+}
+
+void
+dns_kasp_detach(dns_kasp_t **kaspp) {
+ REQUIRE(kaspp != NULL && DNS_KASP_VALID(*kaspp));
+
+ dns_kasp_t *kasp = *kaspp;
+ *kaspp = NULL;
+
+ if (isc_refcount_decrement(&kasp->references) == 1) {
+ destroy(kasp);
+ }
+}
+
+const char *
+dns_kasp_getname(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+
+ return (kasp->name);
+}
+
+void
+dns_kasp_freeze(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->frozen = true;
+}
+
+void
+dns_kasp_thaw(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ kasp->frozen = false;
+}
+
+uint32_t
+dns_kasp_signdelay(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->signatures_validity - kasp->signatures_refresh);
+}
+
+uint32_t
+dns_kasp_sigrefresh(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->signatures_refresh);
+}
+
+void
+dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->signatures_refresh = value;
+}
+
+uint32_t
+dns_kasp_sigvalidity(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->signatures_validity);
+}
+
+void
+dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->signatures_validity = value;
+}
+
+uint32_t
+dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->signatures_validity_dnskey);
+}
+
+void
+dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->signatures_validity_dnskey = value;
+}
+
+dns_ttl_t
+dns_kasp_dnskeyttl(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->dnskey_ttl);
+}
+
+void
+dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->dnskey_ttl = ttl;
+}
+
+uint32_t
+dns_kasp_purgekeys(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->purge_keys);
+}
+
+void
+dns_kasp_setpurgekeys(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->purge_keys = value;
+}
+
+uint32_t
+dns_kasp_publishsafety(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->publish_safety);
+}
+
+void
+dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->publish_safety = value;
+}
+
+uint32_t
+dns_kasp_retiresafety(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->retire_safety);
+}
+
+void
+dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->retire_safety = value;
+}
+
+dns_ttl_t
+dns_kasp_zonemaxttl(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->zone_max_ttl);
+}
+
+void
+dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->zone_max_ttl = ttl;
+}
+
+uint32_t
+dns_kasp_zonepropagationdelay(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->zone_propagation_delay);
+}
+
+void
+dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->zone_propagation_delay = value;
+}
+
+dns_ttl_t
+dns_kasp_dsttl(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->parent_ds_ttl);
+}
+
+void
+dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->parent_ds_ttl = ttl;
+}
+
+uint32_t
+dns_kasp_parentpropagationdelay(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->parent_propagation_delay);
+}
+
+void
+dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+
+ kasp->parent_propagation_delay = value;
+}
+
+isc_result_t
+dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) {
+ dns_kasp_t *kasp = NULL;
+
+ REQUIRE(kaspp != NULL && *kaspp == NULL);
+
+ if (list == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ for (kasp = ISC_LIST_HEAD(*list); kasp != NULL;
+ kasp = ISC_LIST_NEXT(kasp, link))
+ {
+ if (strcmp(kasp->name, name) == 0) {
+ break;
+ }
+ }
+
+ if (kasp == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ dns_kasp_attach(kasp, kaspp);
+ return (ISC_R_SUCCESS);
+}
+
+dns_kasp_keylist_t
+dns_kasp_keys(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(kasp->frozen);
+
+ return (kasp->keys);
+}
+
+bool
+dns_kasp_keylist_empty(dns_kasp_t *kasp) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+
+ return (ISC_LIST_EMPTY(kasp->keys));
+}
+
+void
+dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key) {
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(!kasp->frozen);
+ REQUIRE(key != NULL);
+
+ ISC_LIST_APPEND(kasp->keys, key, link);
+}
+
+isc_result_t
+dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp) {
+ dns_kasp_key_t *key;
+
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(keyp != NULL && *keyp == NULL);
+
+ key = isc_mem_get(kasp->mctx, sizeof(*key));
+ key->mctx = NULL;
+ isc_mem_attach(kasp->mctx, &key->mctx);
+
+ ISC_LINK_INIT(key, link);
+
+ key->lifetime = 0;
+ key->algorithm = 0;
+ key->length = -1;
+ key->role = 0;
+ *keyp = key;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_kasp_key_destroy(dns_kasp_key_t *key) {
+ REQUIRE(key != NULL);
+
+ isc_mem_putanddetach(&key->mctx, key, sizeof(*key));
+}
+
+uint32_t
+dns_kasp_key_algorithm(dns_kasp_key_t *key) {
+ REQUIRE(key != NULL);
+
+ return (key->algorithm);
+}
+
+unsigned int
+dns_kasp_key_size(dns_kasp_key_t *key) {
+ unsigned int size = 0;
+ unsigned int min = 0;
+
+ REQUIRE(key != NULL);
+
+ switch (key->algorithm) {
+ case DNS_KEYALG_RSASHA1:
+ case DNS_KEYALG_NSEC3RSASHA1:
+ case DNS_KEYALG_RSASHA256:
+ case DNS_KEYALG_RSASHA512:
+ min = (key->algorithm == DNS_KEYALG_RSASHA512) ? 1024 : 512;
+ if (key->length > -1) {
+ size = (unsigned int)key->length;
+ if (size < min) {
+ size = min;
+ }
+ if (size > 4096) {
+ size = 4096;
+ }
+ } else {
+ size = 2048;
+ }
+ break;
+ case DNS_KEYALG_ECDSA256:
+ size = 256;
+ break;
+ case DNS_KEYALG_ECDSA384:
+ size = 384;
+ break;
+ case DNS_KEYALG_ED25519:
+ size = 256;
+ break;
+ case DNS_KEYALG_ED448:
+ size = 456;
+ break;
+ default:
+ /* unsupported */
+ break;
+ }
+ return (size);
+}
+
+uint32_t
+dns_kasp_key_lifetime(dns_kasp_key_t *key) {
+ REQUIRE(key != NULL);
+
+ return (key->lifetime);
+}
+
+bool
+dns_kasp_key_ksk(dns_kasp_key_t *key) {
+ REQUIRE(key != NULL);
+
+ return (key->role & DNS_KASP_KEY_ROLE_KSK);
+}
+
+bool
+dns_kasp_key_zsk(dns_kasp_key_t *key) {
+ REQUIRE(key != NULL);
+
+ return (key->role & DNS_KASP_KEY_ROLE_ZSK);
+}
+
+uint8_t
+dns_kasp_nsec3iter(dns_kasp_t *kasp) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(kasp->frozen);
+ REQUIRE(kasp->nsec3);
+
+ return (kasp->nsec3param.iterations);
+}
+
+uint8_t
+dns_kasp_nsec3flags(dns_kasp_t *kasp) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(kasp->frozen);
+ REQUIRE(kasp->nsec3);
+
+ if (kasp->nsec3param.optout) {
+ return (0x01);
+ }
+ return (0x00);
+}
+
+uint8_t
+dns_kasp_nsec3saltlen(dns_kasp_t *kasp) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(kasp->frozen);
+ REQUIRE(kasp->nsec3);
+
+ return (kasp->nsec3param.saltlen);
+}
+
+bool
+dns_kasp_nsec3(dns_kasp_t *kasp) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(kasp->frozen);
+
+ return kasp->nsec3;
+}
+
+void
+dns_kasp_setnsec3(dns_kasp_t *kasp, bool nsec3) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(!kasp->frozen);
+
+ kasp->nsec3 = nsec3;
+}
+
+void
+dns_kasp_setnsec3param(dns_kasp_t *kasp, uint8_t iter, bool optout,
+ uint8_t saltlen) {
+ REQUIRE(kasp != NULL);
+ REQUIRE(!kasp->frozen);
+ REQUIRE(kasp->nsec3);
+
+ kasp->nsec3param.iterations = iter;
+ kasp->nsec3param.optout = optout;
+ kasp->nsec3param.saltlen = saltlen;
+}
diff --git a/lib/dns/key.c b/lib/dns/key.c
new file mode 100644
index 0000000..7d4f378
--- /dev/null
+++ b/lib/dns/key.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <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..fe0af0e
--- /dev/null
+++ b/lib/dns/keymgr.c
@@ -0,0 +1,2628 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/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 <dns/result.h>
+
+#include <dst/dst.h>
+#include <dst/result.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; \
+ if (dst_key_getstate((key), (state), &s) == ISC_R_NOTFOUND) { \
+ dst_key_setstate((key), (state), (target)); \
+ dst_key_settime((key), (timing), time); \
+ } \
+ } while (0)
+
+/* Shorter keywords for better readability. */
+#define HIDDEN DST_KEY_STATE_HIDDEN
+#define RUMOURED DST_KEY_STATE_RUMOURED
+#define OMNIPRESENT DST_KEY_STATE_OMNIPRESENT
+#define UNRETENTIVE DST_KEY_STATE_UNRETENTIVE
+#define NA DST_KEY_STATE_NA
+
+/* Quickly get key state timing metadata. */
+#define NUM_KEYSTATES (DST_MAX_KEYSTATES)
+static int keystatetimes[NUM_KEYSTATES] = { DST_TIME_DNSKEY, DST_TIME_ZRRSIG,
+ DST_TIME_KRRSIG, DST_TIME_DS };
+/* Readable key state types and values. */
+static const char *keystatetags[NUM_KEYSTATES] = { "DNSKEY", "ZRRSIG", "KRRSIG",
+ "DS" };
+static const char *keystatestrings[4] = { "HIDDEN", "RUMOURED", "OMNIPRESENT",
+ "UNRETENTIVE" };
+
+/*
+ * Print key role.
+ *
+ */
+static const char *
+keymgr_keyrole(dst_key_t *key) {
+ bool ksk = false, zsk = false;
+ isc_result_t ret;
+ ret = dst_key_getbool(key, DST_BOOL_KSK, &ksk);
+ if (ret != ISC_R_SUCCESS) {
+ return ("UNKNOWN");
+ }
+ ret = dst_key_getbool(key, DST_BOOL_ZSK, &zsk);
+ if (ret != ISC_R_SUCCESS) {
+ return ("UNKNOWN");
+ }
+ if (ksk && zsk) {
+ return ("CSK");
+ } else if (ksk) {
+ return ("KSK");
+ } else if (zsk) {
+ return ("ZSK");
+ }
+ return ("NOSIGN");
+}
+
+/*
+ * Set the remove time on key given its retire time.
+ *
+ */
+static void
+keymgr_settime_remove(dns_dnsseckey_t *key, dns_kasp_t *kasp) {
+ isc_stdtime_t retire = 0, remove = 0, ksk_remove = 0, zsk_remove = 0;
+ bool zsk = false, ksk = false;
+ isc_result_t ret;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key != NULL);
+
+ ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
+ if (ret != ISC_R_SUCCESS) {
+ return;
+ }
+
+ ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
+ if (ret == ISC_R_SUCCESS && zsk) {
+ /* ZSK: Iret = Dsgn + Dprp + TTLsig */
+ zsk_remove = retire + dns_kasp_zonemaxttl(kasp) +
+ dns_kasp_zonepropagationdelay(kasp) +
+ dns_kasp_retiresafety(kasp) +
+ dns_kasp_signdelay(kasp);
+ }
+ ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
+ if (ret == ISC_R_SUCCESS && ksk) {
+ /* KSK: Iret = DprpP + TTLds */
+ ksk_remove = retire + dns_kasp_dsttl(kasp) +
+ dns_kasp_parentpropagationdelay(kasp) +
+ dns_kasp_retiresafety(kasp);
+ }
+
+ remove = ksk_remove > zsk_remove ? ksk_remove : zsk_remove;
+ dst_key_settime(key->key, DST_TIME_DELETE, remove);
+}
+
+/*
+ * Set the SyncPublish time (when the DS may be submitted to the parent)
+ *
+ */
+static void
+keymgr_settime_syncpublish(dns_dnsseckey_t *key, dns_kasp_t *kasp, bool first) {
+ isc_stdtime_t published, syncpublish;
+ bool ksk = false;
+ isc_result_t ret;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key != NULL);
+
+ ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &published);
+ if (ret != ISC_R_SUCCESS) {
+ return;
+ }
+
+ ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
+ if (ret != ISC_R_SUCCESS || !ksk) {
+ return;
+ }
+
+ syncpublish = published + dst_key_getttl(key->key) +
+ dns_kasp_zonepropagationdelay(kasp) +
+ dns_kasp_publishsafety(kasp);
+ if (first) {
+ /* Also need to wait until the signatures are omnipresent. */
+ isc_stdtime_t zrrsig_present;
+ zrrsig_present = published + dns_kasp_zonemaxttl(kasp) +
+ dns_kasp_zonepropagationdelay(kasp) +
+ dns_kasp_publishsafety(kasp);
+ if (zrrsig_present > syncpublish) {
+ syncpublish = zrrsig_present;
+ }
+ }
+ dst_key_settime(key->key, DST_TIME_SYNCPUBLISH, syncpublish);
+}
+
+/*
+ * Calculate prepublication time of a successor key of 'key'.
+ * This function can have side effects:
+ * 1. If there is no active time set, which would be super weird, set it now.
+ * 2. If there is no published time set, also super weird, set it now.
+ * 3. If there is no syncpublished time set, set it now.
+ * 4. If the lifetime is not set, it will be set now.
+ * 5. If there should be a retire time and it is not set, it will be set now.
+ * 6. The removed time is adjusted accordingly.
+ *
+ * This returns when the successor key needs to be published in the zone.
+ * A special value of 0 means there is no need for a successor.
+ *
+ */
+static isc_stdtime_t
+keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
+ uint32_t lifetime, isc_stdtime_t now) {
+ isc_result_t ret;
+ isc_stdtime_t active, retire, pub, prepub;
+ bool zsk = false, ksk = false;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key != NULL);
+
+ active = 0;
+ pub = 0;
+ retire = 0;
+
+ /*
+ * An active key must have publish and activate timing
+ * metadata.
+ */
+ ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active);
+ if (ret != ISC_R_SUCCESS) {
+ /* Super weird, but if it happens, set it to now. */
+ dst_key_settime(key->key, DST_TIME_ACTIVATE, now);
+ active = now;
+ }
+ ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub);
+ if (ret != ISC_R_SUCCESS) {
+ /* Super weird, but if it happens, set it to now. */
+ dst_key_settime(key->key, DST_TIME_PUBLISH, now);
+ pub = now;
+ }
+
+ /*
+ * Calculate prepublication time.
+ */
+ prepub = dst_key_getttl(key->key) + dns_kasp_publishsafety(kasp) +
+ dns_kasp_zonepropagationdelay(kasp);
+ ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
+ if (ret == ISC_R_SUCCESS && ksk) {
+ isc_stdtime_t syncpub;
+
+ /*
+ * Set PublishCDS if not set.
+ */
+ ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub);
+ if (ret != ISC_R_SUCCESS) {
+ uint32_t tag;
+ isc_stdtime_t syncpub1, syncpub2;
+
+ syncpub1 = pub + prepub;
+ syncpub2 = 0;
+ ret = dst_key_getnum(key->key, DST_NUM_PREDECESSOR,
+ &tag);
+ if (ret != ISC_R_SUCCESS) {
+ /*
+ * No predecessor, wait for zone to be
+ * completely signed.
+ */
+ syncpub2 = pub + dns_kasp_zonemaxttl(kasp) +
+ dns_kasp_publishsafety(kasp) +
+ dns_kasp_zonepropagationdelay(kasp);
+ }
+
+ syncpub = syncpub1 > syncpub2 ? syncpub1 : syncpub2;
+ dst_key_settime(key->key, DST_TIME_SYNCPUBLISH,
+ syncpub);
+ }
+ }
+
+ /*
+ * Not sure what to do when dst_key_getbool() fails here. Extending
+ * the prepublication time anyway is arguably the safest thing to do,
+ * so ignore the result code.
+ */
+ (void)dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
+
+ ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
+ if (ret != ISC_R_SUCCESS) {
+ uint32_t klifetime = 0;
+
+ ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime);
+ if (ret != ISC_R_SUCCESS) {
+ dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime);
+ klifetime = lifetime;
+ }
+ if (klifetime == 0) {
+ /*
+ * No inactive time and no lifetime,
+ * so no need to start a rollover.
+ */
+ return (0);
+ }
+
+ retire = active + klifetime;
+ dst_key_settime(key->key, DST_TIME_INACTIVE, retire);
+ }
+
+ /*
+ * Update remove time.
+ */
+ keymgr_settime_remove(key, kasp);
+
+ /*
+ * Publish successor 'prepub' time before the 'retire' time of 'key'.
+ */
+ if (prepub > retire) {
+ /* We should have already prepublished the new key. */
+ return (now);
+ }
+ return (retire - prepub);
+}
+
+static void
+keymgr_key_retire(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now) {
+ char keystr[DST_KEY_FORMATSIZE];
+ isc_result_t ret;
+ isc_stdtime_t retire;
+ dst_key_state_t s;
+ bool ksk = false, zsk = false;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key != NULL);
+
+ /* This key wants to retire and hide in a corner. */
+ ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
+ if (ret != ISC_R_SUCCESS || (retire > now)) {
+ dst_key_settime(key->key, DST_TIME_INACTIVE, now);
+ }
+ dst_key_setstate(key->key, DST_KEY_GOAL, HIDDEN);
+ keymgr_settime_remove(key, kasp);
+
+ /* This key may not have key states set yet. Pretend as if they are
+ * in the OMNIPRESENT state.
+ */
+ if (dst_key_getstate(key->key, DST_KEY_DNSKEY, &s) != ISC_R_SUCCESS) {
+ dst_key_setstate(key->key, DST_KEY_DNSKEY, OMNIPRESENT);
+ dst_key_settime(key->key, DST_TIME_DNSKEY, now);
+ }
+
+ ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
+ if (ret == ISC_R_SUCCESS && ksk) {
+ if (dst_key_getstate(key->key, DST_KEY_KRRSIG, &s) !=
+ ISC_R_SUCCESS)
+ {
+ dst_key_setstate(key->key, DST_KEY_KRRSIG, OMNIPRESENT);
+ dst_key_settime(key->key, DST_TIME_KRRSIG, now);
+ }
+ if (dst_key_getstate(key->key, DST_KEY_DS, &s) != ISC_R_SUCCESS)
+ {
+ dst_key_setstate(key->key, DST_KEY_DS, OMNIPRESENT);
+ dst_key_settime(key->key, DST_TIME_DS, now);
+ }
+ }
+ ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
+ if (ret == ISC_R_SUCCESS && zsk) {
+ if (dst_key_getstate(key->key, DST_KEY_ZRRSIG, &s) !=
+ ISC_R_SUCCESS)
+ {
+ dst_key_setstate(key->key, DST_KEY_ZRRSIG, OMNIPRESENT);
+ dst_key_settime(key->key, DST_TIME_ZRRSIG, now);
+ }
+ }
+
+ dst_key_format(key->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO, "keymgr: retire DNSKEY %s (%s)", keystr,
+ keymgr_keyrole(key->key));
+}
+
+/*
+ * Check if a dnsseckey matches kasp key configuration. A dnsseckey matches
+ * if it has the same algorithm and size, and if it has the same role as the
+ * kasp key configuration.
+ *
+ */
+static bool
+keymgr_dnsseckey_kaspkey_match(dns_dnsseckey_t *dkey, dns_kasp_key_t *kkey) {
+ dst_key_t *key;
+ isc_result_t ret;
+ bool role = false;
+
+ REQUIRE(dkey != NULL);
+ REQUIRE(kkey != NULL);
+
+ key = dkey->key;
+
+ /* Matching algorithms? */
+ if (dst_key_alg(key) != dns_kasp_key_algorithm(kkey)) {
+ return (false);
+ }
+ /* Matching length? */
+ if (dst_key_size(key) != dns_kasp_key_size(kkey)) {
+ return (false);
+ }
+ /* Matching role? */
+ ret = dst_key_getbool(key, DST_BOOL_KSK, &role);
+ if (ret != ISC_R_SUCCESS || role != dns_kasp_key_ksk(kkey)) {
+ return (false);
+ }
+ ret = dst_key_getbool(key, DST_BOOL_ZSK, &role);
+ if (ret != ISC_R_SUCCESS || role != dns_kasp_key_zsk(kkey)) {
+ return (false);
+ }
+
+ /* Found a match. */
+ return (true);
+}
+
+static bool
+keymgr_keyid_conflict(dst_key_t *newkey, dns_dnsseckeylist_t *keys) {
+ uint16_t id = dst_key_id(newkey);
+ uint32_t rid = dst_key_rid(newkey);
+ uint32_t alg = dst_key_alg(newkey);
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keys); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (dst_key_alg(dkey->key) != alg) {
+ continue;
+ }
+ if (dst_key_id(dkey->key) == id ||
+ dst_key_rid(dkey->key) == id ||
+ dst_key_id(dkey->key) == rid ||
+ dst_key_rid(dkey->key) == rid)
+ {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/*
+ * Create a new key for 'origin' given the kasp key configuration 'kkey'.
+ * This will check for key id collisions with keys in 'keylist'.
+ * The created key will be stored in 'dst_key'.
+ *
+ */
+static isc_result_t
+keymgr_createkey(dns_kasp_key_t *kkey, const dns_name_t *origin,
+ dns_rdataclass_t rdclass, isc_mem_t *mctx,
+ dns_dnsseckeylist_t *keylist, dns_dnsseckeylist_t *newkeys,
+ dst_key_t **dst_key) {
+ bool conflict = false;
+ int keyflags = DNS_KEYOWNER_ZONE;
+ isc_result_t result = ISC_R_SUCCESS;
+ dst_key_t *newkey = NULL;
+
+ do {
+ uint32_t algo = dns_kasp_key_algorithm(kkey);
+ int size = dns_kasp_key_size(kkey);
+
+ if (dns_kasp_key_ksk(kkey)) {
+ keyflags |= DNS_KEYFLAG_KSK;
+ }
+ RETERR(dst_key_generate(origin, algo, size, 0, keyflags,
+ DNS_KEYPROTO_DNSSEC, rdclass, mctx,
+ &newkey, NULL));
+
+ /* Key collision? */
+ conflict = keymgr_keyid_conflict(newkey, keylist);
+ if (!conflict) {
+ conflict = keymgr_keyid_conflict(newkey, newkeys);
+ }
+ if (conflict) {
+ /* Try again. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "keymgr: key collision id %d",
+ dst_key_id(newkey));
+ dst_key_free(&newkey);
+ }
+ } while (conflict);
+
+ INSIST(!conflict);
+ dst_key_setnum(newkey, DST_NUM_LIFETIME, dns_kasp_key_lifetime(kkey));
+ dst_key_setbool(newkey, DST_BOOL_KSK, dns_kasp_key_ksk(kkey));
+ dst_key_setbool(newkey, DST_BOOL_ZSK, dns_kasp_key_zsk(kkey));
+ *dst_key = newkey;
+ return (ISC_R_SUCCESS);
+
+failure:
+ return (result);
+}
+
+/*
+ * Return the desired state for this record 'type'. The desired state depends
+ * on whether the key wants to be active, or wants to retire. This implements
+ * the edges of our state machine:
+ *
+ * ----> OMNIPRESENT ----
+ * | |
+ * | \|/
+ *
+ * RUMOURED <----> UNRETENTIVE
+ *
+ * /|\ |
+ * | |
+ * ---- HIDDEN <----
+ *
+ * A key that wants to be active eventually wants to have its record types
+ * in the OMNIPRESENT state (that is, all resolvers that know about these
+ * type of records know about these records specifically).
+ *
+ * A key that wants to be retired eventually wants to have its record types
+ * in the HIDDEN state (that is, all resolvers that know about these type
+ * of records specifically don't know about these records).
+ *
+ */
+static dst_key_state_t
+keymgr_desiredstate(dns_dnsseckey_t *key, dst_key_state_t state) {
+ dst_key_state_t goal;
+
+ if (dst_key_getstate(key->key, DST_KEY_GOAL, &goal) != ISC_R_SUCCESS) {
+ /* No goal? No movement. */
+ return (state);
+ }
+
+ if (goal == HIDDEN) {
+ switch (state) {
+ case RUMOURED:
+ case OMNIPRESENT:
+ return (UNRETENTIVE);
+ case HIDDEN:
+ case UNRETENTIVE:
+ return (HIDDEN);
+ default:
+ return (state);
+ }
+ } else if (goal == OMNIPRESENT) {
+ switch (state) {
+ case RUMOURED:
+ case OMNIPRESENT:
+ return (OMNIPRESENT);
+ case HIDDEN:
+ case UNRETENTIVE:
+ return (RUMOURED);
+ default:
+ return (state);
+ }
+ }
+
+ /* Unknown goal. */
+ return (state);
+}
+
+/*
+ * Check if 'key' matches specific 'states'.
+ * A state in 'states' that is NA matches any state.
+ * A state in 'states' that is HIDDEN also matches if the state is not set.
+ * If 'next_state' is set (not NA), we are pretending as if record 'type' of
+ * 'subject' key already transitioned to the 'next state'.
+ *
+ */
+static bool
+keymgr_key_match_state(dst_key_t *key, dst_key_t *subject, int type,
+ dst_key_state_t next_state,
+ dst_key_state_t states[NUM_KEYSTATES]) {
+ REQUIRE(key != NULL);
+
+ for (int i = 0; i < NUM_KEYSTATES; i++) {
+ dst_key_state_t state;
+ if (states[i] == NA) {
+ continue;
+ }
+ if (next_state != NA && i == type &&
+ dst_key_id(key) == dst_key_id(subject))
+ {
+ /* Check next state rather than current state. */
+ state = next_state;
+ } else if (dst_key_getstate(key, i, &state) != ISC_R_SUCCESS) {
+ /* This is fine only if expected state is HIDDEN. */
+ if (states[i] != HIDDEN) {
+ return (false);
+ }
+ continue;
+ }
+ if (state != states[i]) {
+ return (false);
+ }
+ }
+ /* Match. */
+ return (true);
+}
+
+/*
+ * Key d directly depends on k if d is the direct predecessor of k.
+ */
+static bool
+keymgr_direct_dep(dst_key_t *d, dst_key_t *k) {
+ uint32_t s, p;
+
+ if (dst_key_getnum(d, DST_NUM_SUCCESSOR, &s) != ISC_R_SUCCESS) {
+ return (false);
+ }
+ if (dst_key_getnum(k, DST_NUM_PREDECESSOR, &p) != ISC_R_SUCCESS) {
+ return (false);
+ }
+ return (dst_key_id(d) == p && dst_key_id(k) == s);
+}
+
+/*
+ * Determine which key (if any) has a dependency on k.
+ */
+static bool
+keymgr_dep(dst_key_t *k, dns_dnsseckeylist_t *keyring, uint32_t *dep) {
+ for (dns_dnsseckey_t *d = ISC_LIST_HEAD(*keyring); d != NULL;
+ d = ISC_LIST_NEXT(d, link))
+ {
+ /*
+ * Check if k is a direct successor of d, e.g. d depends on k.
+ */
+ if (keymgr_direct_dep(d->key, k)) {
+ if (dep != NULL) {
+ *dep = dst_key_id(d->key);
+ }
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/*
+ * Check if a 'z' is a successor of 'x'.
+ * This implements Equation(2) of "Flexible and Robust Key Rollover".
+ */
+static bool
+keymgr_key_is_successor(dst_key_t *x, dst_key_t *z, dst_key_t *key, int type,
+ dst_key_state_t next_state,
+ dns_dnsseckeylist_t *keyring) {
+ uint32_t dep_x;
+ uint32_t dep_z;
+
+ /*
+ * The successor relation requires that the predecessor key must not
+ * have any other keys relying on it. In other words, there must be
+ * nothing depending on x.
+ */
+ if (keymgr_dep(x, keyring, &dep_x)) {
+ return (false);
+ }
+
+ /*
+ * If there is no keys relying on key z, then z is not a successor.
+ */
+ if (!keymgr_dep(z, keyring, &dep_z)) {
+ return (false);
+ }
+
+ /*
+ * x depends on z, thus key z is a direct successor of key x.
+ */
+ if (dst_key_id(x) == dep_z) {
+ return (true);
+ }
+
+ /*
+ * It is possible to roll keys faster than the time required to finish
+ * the rollover procedure. For example, consider the keys x, y, z.
+ * Key x is currently published and is going to be replaced by y. The
+ * DNSKEY for x is removed from the zone and at the same moment the
+ * DNSKEY for y is introduced. Key y is a direct dependency for key x
+ * and is therefore the successor of x. However, before the new DNSKEY
+ * has been propagated, key z will replace key y. The DNSKEY for y is
+ * removed and moves into the same state as key x. Key y now directly
+ * depends on key z, and key z will be a new successor key for x.
+ */
+ dst_key_state_t zst[NUM_KEYSTATES] = { NA, NA, NA, NA };
+ for (int i = 0; i < NUM_KEYSTATES; i++) {
+ dst_key_state_t state;
+ if (dst_key_getstate(z, i, &state) != ISC_R_SUCCESS) {
+ continue;
+ }
+ zst[i] = state;
+ }
+
+ for (dns_dnsseckey_t *y = ISC_LIST_HEAD(*keyring); y != NULL;
+ y = ISC_LIST_NEXT(y, link))
+ {
+ if (dst_key_id(y->key) == dst_key_id(z)) {
+ continue;
+ }
+
+ if (dst_key_id(y->key) != dep_z) {
+ continue;
+ }
+ /*
+ * This is another key y, that depends on key z. It may be
+ * part of the successor relation if the key states match
+ * those of key z.
+ */
+
+ if (keymgr_key_match_state(y->key, key, type, next_state, zst))
+ {
+ /*
+ * If y is a successor of x, then z is also a
+ * successor of x.
+ */
+ return (keymgr_key_is_successor(x, y->key, key, type,
+ next_state, keyring));
+ }
+ }
+
+ return (false);
+}
+
+/*
+ * Check if a key exists in 'keyring' that matches 'states'.
+ *
+ * If 'match_algorithms', the key must also match the algorithm of 'key'.
+ * If 'next_state' is not NA, we are actually looking for a key as if
+ * 'key' already transitioned to the next state.
+ * If 'check_successor', we also want to make sure there is a successor
+ * relationship with the found key that matches 'states2'.
+ */
+static bool
+keymgr_key_exists_with_state(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
+ int type, dst_key_state_t next_state,
+ dst_key_state_t states[NUM_KEYSTATES],
+ dst_key_state_t states2[NUM_KEYSTATES],
+ bool check_successor, bool match_algorithms) {
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (match_algorithms &&
+ (dst_key_alg(dkey->key) != dst_key_alg(key->key)))
+ {
+ continue;
+ }
+
+ if (!keymgr_key_match_state(dkey->key, key->key, type,
+ next_state, states))
+ {
+ continue;
+ }
+
+ /* Found a match. */
+ if (!check_successor) {
+ return (true);
+ }
+
+ /*
+ * We have to make sure that the key we are checking, also
+ * has a successor relationship with another key.
+ */
+ for (dns_dnsseckey_t *skey = ISC_LIST_HEAD(*keyring);
+ skey != NULL; skey = ISC_LIST_NEXT(skey, link))
+ {
+ if (skey == dkey) {
+ continue;
+ }
+
+ if (!keymgr_key_match_state(skey->key, key->key, type,
+ next_state, states2))
+ {
+ continue;
+ }
+
+ /*
+ * Found a possible successor, check.
+ */
+ if (keymgr_key_is_successor(dkey->key, skey->key,
+ key->key, type, next_state,
+ keyring))
+ {
+ return (true);
+ }
+ }
+ }
+ /* No match. */
+ return (false);
+}
+
+/*
+ * Check if a key has a successor.
+ */
+static bool
+keymgr_key_has_successor(dns_dnsseckey_t *predecessor,
+ dns_dnsseckeylist_t *keyring) {
+ for (dns_dnsseckey_t *successor = ISC_LIST_HEAD(*keyring);
+ successor != NULL; successor = ISC_LIST_NEXT(successor, link))
+ {
+ if (keymgr_direct_dep(predecessor->key, successor->key)) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/*
+ * Check if all keys have their DS hidden. If not, then there must be at
+ * least one key with an OMNIPRESENT DNSKEY.
+ *
+ * If 'next_state' is not NA, we are actually looking for a key as if
+ * 'key' already transitioned to the next state.
+ * If 'match_algorithms', only consider keys with same algorithm of 'key'.
+ *
+ */
+static bool
+keymgr_ds_hidden_or_chained(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
+ int type, dst_key_state_t next_state,
+ bool match_algorithms, bool must_be_hidden) {
+ /* (3e) */
+ dst_key_state_t dnskey_chained[NUM_KEYSTATES] = { OMNIPRESENT, NA,
+ OMNIPRESENT, NA };
+ dst_key_state_t ds_hidden[NUM_KEYSTATES] = { NA, NA, NA, HIDDEN };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (match_algorithms &&
+ (dst_key_alg(dkey->key) != dst_key_alg(key->key)))
+ {
+ continue;
+ }
+
+ if (keymgr_key_match_state(dkey->key, key->key, type,
+ next_state, ds_hidden))
+ {
+ /* This key has its DS hidden. */
+ continue;
+ }
+
+ if (must_be_hidden) {
+ return (false);
+ }
+
+ /*
+ * This key does not have its DS hidden. There must be at
+ * least one key with the same algorithm that provides a
+ * chain of trust (can be this key).
+ */
+ if (keymgr_key_match_state(dkey->key, key->key, type,
+ next_state, dnskey_chained))
+ {
+ /* This DNSKEY and KRRSIG are OMNIPRESENT. */
+ continue;
+ }
+
+ /*
+ * Perhaps another key provides a chain of trust.
+ */
+ dnskey_chained[DST_KEY_DS] = OMNIPRESENT;
+ if (!keymgr_key_exists_with_state(keyring, key, type,
+ next_state, dnskey_chained,
+ na, false, match_algorithms))
+ {
+ /* There is no chain of trust. */
+ return (false);
+ }
+ }
+ /* All good. */
+ return (true);
+}
+
+/*
+ * Check if all keys have their DNSKEY hidden. If not, then there must be at
+ * least one key with an OMNIPRESENT ZRRSIG.
+ *
+ * If 'next_state' is not NA, we are actually looking for a key as if
+ * 'key' already transitioned to the next state.
+ * If 'match_algorithms', only consider keys with same algorithm of 'key'.
+ *
+ */
+static bool
+keymgr_dnskey_hidden_or_chained(dns_dnsseckeylist_t *keyring,
+ dns_dnsseckey_t *key, int type,
+ dst_key_state_t next_state,
+ bool match_algorithms) {
+ /* (3i) */
+ dst_key_state_t rrsig_chained[NUM_KEYSTATES] = { OMNIPRESENT,
+ OMNIPRESENT, NA, NA };
+ dst_key_state_t dnskey_hidden[NUM_KEYSTATES] = { HIDDEN, NA, NA, NA };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (match_algorithms &&
+ (dst_key_alg(dkey->key) != dst_key_alg(key->key)))
+ {
+ continue;
+ }
+
+ if (keymgr_key_match_state(dkey->key, key->key, type,
+ next_state, dnskey_hidden))
+ {
+ /* This key has its DNSKEY hidden. */
+ continue;
+ }
+
+ /*
+ * This key does not have its DNSKEY hidden. There must be at
+ * least one key with the same algorithm that has its RRSIG
+ * records OMNIPRESENT.
+ */
+ (void)dst_key_getstate(dkey->key, DST_KEY_DNSKEY,
+ &rrsig_chained[DST_KEY_DNSKEY]);
+ if (!keymgr_key_exists_with_state(keyring, key, type,
+ next_state, rrsig_chained, na,
+ false, match_algorithms))
+ {
+ /* There is no chain of trust. */
+ return (false);
+ }
+ }
+ /* All good. */
+ return (true);
+}
+
+/*
+ * Check for existence of DS.
+ *
+ */
+static bool
+keymgr_have_ds(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
+ dst_key_state_t next_state, bool secure_to_insecure) {
+ /* (3a) */
+ dst_key_state_t states[2][NUM_KEYSTATES] = {
+ /* DNSKEY, ZRRSIG, KRRSIG, DS */
+ { NA, NA, NA, OMNIPRESENT }, /* DS present */
+ { NA, NA, NA, RUMOURED } /* DS introducing */
+ };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ /*
+ * Equation (3a):
+ * There is a key with the DS in either RUMOURD or OMNIPRESENT state.
+ */
+ return (keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[0], na, false, false) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[1], na, false, false) ||
+ (secure_to_insecure &&
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ na, na, false, false)));
+}
+
+/*
+ * Check for existence of DNSKEY, or at least a good DNSKEY state.
+ * See equations what are good DNSKEY states.
+ *
+ */
+static bool
+keymgr_have_dnskey(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
+ dst_key_state_t next_state) {
+ dst_key_state_t states[9][NUM_KEYSTATES] = {
+ /* DNSKEY, ZRRSIG, KRRSIG, DS */
+ { OMNIPRESENT, NA, OMNIPRESENT, OMNIPRESENT }, /* (3b) */
+
+ { OMNIPRESENT, NA, OMNIPRESENT, UNRETENTIVE }, /* (3c)p */
+ { OMNIPRESENT, NA, OMNIPRESENT, RUMOURED }, /* (3c)s */
+
+ { UNRETENTIVE, NA, UNRETENTIVE, OMNIPRESENT }, /* (3d)p */
+ { OMNIPRESENT, NA, UNRETENTIVE, OMNIPRESENT }, /* (3d)p */
+ { UNRETENTIVE, NA, OMNIPRESENT, OMNIPRESENT }, /* (3d)p */
+ { RUMOURED, NA, RUMOURED, OMNIPRESENT }, /* (3d)s */
+ { OMNIPRESENT, NA, RUMOURED, OMNIPRESENT }, /* (3d)s */
+ { RUMOURED, NA, OMNIPRESENT, OMNIPRESENT }, /* (3d)s */
+ };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ return (
+ /*
+ * Equation (3b):
+ * There is a key with the same algorithm with its DNSKEY,
+ * KRRSIG and DS records in OMNIPRESENT state.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[0], na, false, true) ||
+ /*
+ * Equation (3c):
+ * There are two or more keys with an OMNIPRESENT DNSKEY and
+ * the DS records get swapped. These keys must be in a
+ * successor relation.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[1], states[2], true,
+ true) ||
+ /*
+ * Equation (3d):
+ * There are two or more keys with an OMNIPRESENT DS and
+ * the DNSKEY records and its KRRSIG records get swapped.
+ * These keys must be in a successor relation. Since the
+ * state for DNSKEY and KRRSIG move independently, we have
+ * to check all combinations for DNSKEY and KRRSIG in
+ * OMNIPRESENT/UNRETENTIVE state for the predecessor, and
+ * OMNIPRESENT/RUMOURED state for the successor.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[3], states[6], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[3], states[7], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[3], states[8], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[4], states[6], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[4], states[7], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[4], states[8], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[5], states[6], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[5], states[7], true,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[5], states[8], true,
+ true) ||
+ /*
+ * Equation (3e):
+ * The key may be in any state as long as all keys have their
+ * DS HIDDEN, or when their DS is not HIDDEN, there must be a
+ * key with its DS in the same state and its DNSKEY omnipresent.
+ * In other words, if a DS record for the same algorithm is
+ * is still available to some validators, there must be a
+ * chain of trust for those validators.
+ */
+ keymgr_ds_hidden_or_chained(keyring, key, type, next_state,
+ true, false));
+}
+
+/*
+ * Check for existence of RRSIG (zsk), or a good RRSIG state.
+ * See equations what are good RRSIG states.
+ *
+ */
+static bool
+keymgr_have_rrsig(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
+ dst_key_state_t next_state) {
+ dst_key_state_t states[11][NUM_KEYSTATES] = {
+ /* DNSKEY, ZRRSIG, KRRSIG, DS */
+ { OMNIPRESENT, OMNIPRESENT, NA, NA }, /* (3f) */
+ { UNRETENTIVE, OMNIPRESENT, NA, NA }, /* (3g)p */
+ { RUMOURED, OMNIPRESENT, NA, NA }, /* (3g)s */
+ { OMNIPRESENT, UNRETENTIVE, NA, NA }, /* (3h)p */
+ { OMNIPRESENT, RUMOURED, NA, NA }, /* (3h)s */
+ };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ return (
+ /*
+ * If all DS records are hidden than this rule can be ignored.
+ */
+ keymgr_ds_hidden_or_chained(keyring, key, type, next_state,
+ true, true) ||
+ /*
+ * Equation (3f):
+ * There is a key with the same algorithm with its DNSKEY and
+ * ZRRSIG records in OMNIPRESENT state.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[0], na, false, true) ||
+ /*
+ * Equation (3g):
+ * There are two or more keys with OMNIPRESENT ZRRSIG
+ * records and the DNSKEY records get swapped. These keys
+ * must be in a successor relation.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[1], states[2], true,
+ true) ||
+ /*
+ * Equation (3h):
+ * There are two or more keys with an OMNIPRESENT DNSKEY
+ * and the ZRRSIG records get swapped. These keys must be in
+ * a successor relation.
+ */
+ keymgr_key_exists_with_state(keyring, key, type, next_state,
+ states[3], states[4], true,
+ true) ||
+ /*
+ * Equation (3i):
+ * If no DNSKEYs are published, the state of the signatures is
+ * irrelevant. In case a DNSKEY is published however, there
+ * must be a path that can be validated from there.
+ */
+ keymgr_dnskey_hidden_or_chained(keyring, key, type, next_state,
+ true));
+}
+
+/*
+ * Check if a transition in the state machine is allowed by the policy.
+ * This means when we do rollovers, we want to follow the rules of the
+ * 1. Pre-publish rollover method (in case of a ZSK)
+ * - First introduce the DNSKEY record.
+ * - Only if the DNSKEY record is OMNIPRESENT, introduce ZRRSIG records.
+ *
+ * 2. Double-KSK rollover method (in case of a KSK)
+ * - First introduce the DNSKEY record, as well as the KRRSIG records.
+ * - Only if the DNSKEY record is OMNIPRESENT, suggest to introduce the DS.
+ */
+static bool
+keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
+ int type, dst_key_state_t next) {
+ dst_key_state_t dnskeystate = HIDDEN;
+ dst_key_state_t ksk_present[NUM_KEYSTATES] = { OMNIPRESENT, NA,
+ OMNIPRESENT,
+ OMNIPRESENT };
+ dst_key_state_t ds_rumoured[NUM_KEYSTATES] = { OMNIPRESENT, NA,
+ OMNIPRESENT, RUMOURED };
+ dst_key_state_t ds_retired[NUM_KEYSTATES] = { OMNIPRESENT, NA,
+ OMNIPRESENT,
+ UNRETENTIVE };
+ dst_key_state_t ksk_rumoured[NUM_KEYSTATES] = { RUMOURED, NA, NA,
+ OMNIPRESENT };
+ dst_key_state_t ksk_retired[NUM_KEYSTATES] = { UNRETENTIVE, NA, NA,
+ OMNIPRESENT };
+ /* successor n/a */
+ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA };
+
+ if (next != RUMOURED) {
+ /*
+ * Local policy only adds an extra barrier on transitions to
+ * the RUMOURED state.
+ */
+ return (true);
+ }
+
+ switch (type) {
+ case DST_KEY_DNSKEY:
+ /* No restrictions. */
+ return (true);
+ case DST_KEY_ZRRSIG:
+ /* Make sure the DNSKEY record is OMNIPRESENT. */
+ (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
+ if (dnskeystate == OMNIPRESENT) {
+ return (true);
+ }
+ /*
+ * Or are we introducing a new key for this algorithm? Because
+ * in that case allow publishing the RRSIG records before the
+ * DNSKEY.
+ */
+ return (!(keymgr_key_exists_with_state(keyring, key, type, next,
+ ksk_present, na, false,
+ true) ||
+ keymgr_key_exists_with_state(keyring, key, type, next,
+ ds_retired, ds_rumoured,
+ true, true) ||
+ keymgr_key_exists_with_state(
+ keyring, key, type, next, ksk_retired,
+ ksk_rumoured, true, true)));
+ case DST_KEY_KRRSIG:
+ /* Only introduce if the DNSKEY is also introduced. */
+ (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
+ return (dnskeystate != HIDDEN);
+ case DST_KEY_DS:
+ /* Make sure the DNSKEY record is OMNIPRESENT. */
+ (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
+ return (dnskeystate == OMNIPRESENT);
+ default:
+ return (false);
+ }
+}
+
+/*
+ * Check if a transition in the state machine is DNSSEC safe.
+ * This implements Equation(1) of "Flexible and Robust Key Rollover".
+ *
+ */
+static bool
+keymgr_transition_allowed(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
+ int type, dst_key_state_t next_state,
+ bool secure_to_insecure) {
+ /* Debug logging. */
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ bool rule1a, rule1b, rule2a, rule2b, rule3a, rule3b;
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key->key, keystr, sizeof(keystr));
+ rule1a = keymgr_have_ds(keyring, key, type, NA,
+ secure_to_insecure);
+ rule1b = keymgr_have_ds(keyring, key, type, next_state,
+ secure_to_insecure);
+ rule2a = keymgr_have_dnskey(keyring, key, type, NA);
+ rule2b = keymgr_have_dnskey(keyring, key, type, next_state);
+ rule3a = keymgr_have_rrsig(keyring, key, type, NA);
+ rule3b = keymgr_have_rrsig(keyring, key, type, next_state);
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_DEBUG(1),
+ "keymgr: dnssec evaluation of %s %s record %s: "
+ "rule1=(~%s or %s) rule2=(~%s or %s) "
+ "rule3=(~%s or %s)",
+ keymgr_keyrole(key->key), keystr, keystatetags[type],
+ rule1a ? "true" : "false", rule1b ? "true" : "false",
+ rule2a ? "true" : "false", rule2b ? "true" : "false",
+ rule3a ? "true" : "false", rule3b ? "true" : "false");
+ }
+
+ return (
+ /*
+ * Rule 1: There must be a DS at all times.
+ * First check the current situation: if the rule check fails,
+ * we allow the transition to attempt to move us out of the
+ * invalid state. If the rule check passes, also check if
+ * the next state is also still a valid situation.
+ */
+ (!keymgr_have_ds(keyring, key, type, NA, secure_to_insecure) ||
+ keymgr_have_ds(keyring, key, type, next_state,
+ secure_to_insecure)) &&
+ /*
+ * Rule 2: There must be a DNSKEY at all times. Again, first
+ * check the current situation, then assess the next state.
+ */
+ (!keymgr_have_dnskey(keyring, key, type, NA) ||
+ keymgr_have_dnskey(keyring, key, type, next_state)) &&
+ /*
+ * Rule 3: There must be RRSIG records at all times. Again,
+ * first check the current situation, then assess the next
+ * state.
+ */
+ (!keymgr_have_rrsig(keyring, key, type, NA) ||
+ keymgr_have_rrsig(keyring, key, type, next_state)));
+}
+
+/*
+ * Calculate the time when it is safe to do the next transition.
+ *
+ */
+static void
+keymgr_transition_time(dns_dnsseckey_t *key, int type,
+ dst_key_state_t next_state, dns_kasp_t *kasp,
+ isc_stdtime_t now, isc_stdtime_t *when) {
+ isc_result_t ret;
+ isc_stdtime_t lastchange, dstime, nexttime = now;
+
+ /*
+ * No need to wait if we move things into an uncertain state.
+ */
+ if (next_state == RUMOURED || next_state == UNRETENTIVE) {
+ *when = now;
+ return;
+ }
+
+ ret = dst_key_gettime(key->key, keystatetimes[type], &lastchange);
+ if (ret != ISC_R_SUCCESS) {
+ /* No last change, for safety purposes let's set it to now. */
+ dst_key_settime(key->key, keystatetimes[type], now);
+ lastchange = now;
+ }
+
+ switch (type) {
+ case DST_KEY_DNSKEY:
+ case DST_KEY_KRRSIG:
+ switch (next_state) {
+ case OMNIPRESENT:
+ /*
+ * RFC 7583: The publication interval (Ipub) is the
+ * amount of time that must elapse after the
+ * publication of a DNSKEY (plus RRSIG (KSK)) before
+ * it can be assumed that any resolvers that have the
+ * relevant RRset cached have a copy of the new
+ * information. This is the sum of the propagation
+ * delay (Dprp) and the DNSKEY TTL (TTLkey). This
+ * translates to zone-propagation-delay + dnskey-ttl.
+ * We will also add the publish-safety interval.
+ */
+ nexttime = lastchange + dst_key_getttl(key->key) +
+ dns_kasp_zonepropagationdelay(kasp) +
+ dns_kasp_publishsafety(kasp);
+ break;
+ case HIDDEN:
+ /*
+ * Same as OMNIPRESENT but without the publish-safety
+ * interval.
+ */
+ nexttime = lastchange + dst_key_getttl(key->key) +
+ dns_kasp_zonepropagationdelay(kasp);
+ break;
+ default:
+ nexttime = now;
+ break;
+ }
+ break;
+ case DST_KEY_ZRRSIG:
+ switch (next_state) {
+ case OMNIPRESENT:
+ case HIDDEN:
+ /*
+ * RFC 7583: The retire interval (Iret) is the amount
+ * of time that must elapse after a DNSKEY or
+ * associated data enters the retire state for any
+ * dependent information (RRSIG ZSK) to be purged from
+ * validating resolver caches. This is defined as:
+ *
+ * Iret = Dsgn + Dprp + TTLsig
+ *
+ * Where Dsgn is the Dsgn is the delay needed to
+ * ensure that all existing RRsets have been re-signed
+ * with the new key, Dprp is the propagation delay and
+ * TTLsig is the maximum TTL of all zone RRSIG
+ * records. This translates to:
+ *
+ * Dsgn + zone-propagation-delay + max-zone-ttl.
+ *
+ * We will also add the retire-safety interval.
+ */
+ nexttime = lastchange + dns_kasp_zonemaxttl(kasp) +
+ dns_kasp_zonepropagationdelay(kasp) +
+ dns_kasp_retiresafety(kasp);
+ /*
+ * Only add the sign delay Dsgn if there is an actual
+ * predecessor or successor key.
+ */
+ uint32_t tag;
+ ret = dst_key_getnum(key->key, DST_NUM_PREDECESSOR,
+ &tag);
+ if (ret != ISC_R_SUCCESS) {
+ ret = dst_key_getnum(key->key,
+ DST_NUM_SUCCESSOR, &tag);
+ }
+ if (ret == ISC_R_SUCCESS) {
+ nexttime += dns_kasp_signdelay(kasp);
+ }
+ break;
+ default:
+ nexttime = now;
+ break;
+ }
+ break;
+ case DST_KEY_DS:
+ switch (next_state) {
+ /*
+ * RFC 7583: The successor DS record is published in
+ * the parent zone and after the registration delay
+ * (Dreg), the time taken after the DS record has been
+ * submitted to the parent zone manager for it to be
+ * placed in the zone. Key N (the predecessor) must
+ * remain in the zone until any caches that contain a
+ * copy of the DS RRset have a copy containing the new
+ * DS record. This interval is the retire interval
+ * (Iret), given by:
+ *
+ * Iret = DprpP + TTLds
+ *
+ * This translates to:
+ *
+ * parent-propagation-delay + parent-ds-ttl.
+ *
+ * We will also add the retire-safety interval.
+ */
+ case OMNIPRESENT:
+ /* Make sure DS has been seen in the parent. */
+ ret = dst_key_gettime(key->key, DST_TIME_DSPUBLISH,
+ &dstime);
+ if (ret != ISC_R_SUCCESS || dstime > now) {
+ /* Not yet, try again in an hour. */
+ nexttime = now + 3600;
+ } else {
+ nexttime =
+ dstime + dns_kasp_dsttl(kasp) +
+ dns_kasp_parentpropagationdelay(kasp) +
+ dns_kasp_retiresafety(kasp);
+ }
+ break;
+ case HIDDEN:
+ /* Make sure DS has been withdrawn from the parent. */
+ ret = dst_key_gettime(key->key, DST_TIME_DSDELETE,
+ &dstime);
+ if (ret != ISC_R_SUCCESS || dstime > now) {
+ /* Not yet, try again in an hour. */
+ nexttime = now + 3600;
+ } else {
+ nexttime =
+ dstime + dns_kasp_dsttl(kasp) +
+ dns_kasp_parentpropagationdelay(kasp) +
+ dns_kasp_retiresafety(kasp);
+ }
+ break;
+ default:
+ nexttime = now;
+ break;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+
+ *when = nexttime;
+}
+
+/*
+ * Update keys.
+ * This implements Algorithm (1) of "Flexible and Robust Key Rollover".
+ *
+ */
+static isc_result_t
+keymgr_update(dns_dnsseckeylist_t *keyring, dns_kasp_t *kasp, isc_stdtime_t now,
+ isc_stdtime_t *nexttime, bool secure_to_insecure) {
+ bool changed;
+
+ /* Repeat until nothing changed. */
+transition:
+ changed = false;
+
+ /* For all keys in the zone. */
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(dkey->key, keystr, sizeof(keystr));
+
+ /* For all records related to this key. */
+ for (int i = 0; i < NUM_KEYSTATES; i++) {
+ isc_result_t ret;
+ isc_stdtime_t when;
+ dst_key_state_t state, next_state;
+
+ ret = dst_key_getstate(dkey->key, i, &state);
+ if (ret == ISC_R_NOTFOUND) {
+ /*
+ * This record type is not applicable for this
+ * key, continue to the next record type.
+ */
+ continue;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: examine %s %s type %s "
+ "in state %s",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state]);
+
+ /* Get the desired next state. */
+ next_state = keymgr_desiredstate(dkey, state);
+ if (state == next_state) {
+ /*
+ * This record is in a stable state.
+ * No change needed, continue with the next
+ * record type.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_DEBUG(1),
+ "keymgr: %s %s type %s in "
+ "stable state %s",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i],
+ keystatestrings[state]);
+ continue;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: can we transition %s %s type %s "
+ "state %s to state %s?",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state],
+ keystatestrings[next_state]);
+
+ /* Is the transition allowed according to policy? */
+ if (!keymgr_policy_approval(keyring, dkey, i,
+ next_state))
+ {
+ /* No, please respect rollover methods. */
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: policy says no to %s %s type "
+ "%s "
+ "state %s to state %s",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state],
+ keystatestrings[next_state]);
+
+ continue;
+ }
+
+ /* Is the transition DNSSEC safe? */
+ if (!keymgr_transition_allowed(keyring, dkey, i,
+ next_state,
+ secure_to_insecure))
+ {
+ /* No, this would make the zone bogus. */
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: dnssec says no to %s %s type "
+ "%s "
+ "state %s to state %s",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state],
+ keystatestrings[next_state]);
+ continue;
+ }
+
+ /* Is it time to make the transition? */
+ when = now;
+ keymgr_transition_time(dkey, i, next_state, kasp, now,
+ &when);
+ if (when > now) {
+ /* Not yet. */
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: time says no to %s %s type %s "
+ "state %s to state %s (wait %u "
+ "seconds)",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state],
+ keystatestrings[next_state],
+ when - now);
+ if (*nexttime == 0 || *nexttime > when) {
+ *nexttime = when;
+ }
+ continue;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: transition %s %s type %s "
+ "state %s to state %s!",
+ keymgr_keyrole(dkey->key), keystr,
+ keystatetags[i], keystatestrings[state],
+ keystatestrings[next_state]);
+
+ /* It is safe to make the transition. */
+ dst_key_setstate(dkey->key, i, next_state);
+ dst_key_settime(dkey->key, keystatetimes[i], now);
+ INSIST(dst_key_ismodified(dkey->key));
+ changed = true;
+ }
+ }
+
+ /* We changed something, continue processing. */
+ if (changed) {
+ goto transition;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * See if this key needs to be initialized with properties. A key created
+ * and derived from a dnssec-policy will have the required metadata available,
+ * otherwise these may be missing and need to be initialized. The key states
+ * will be initialized according to existing timing metadata.
+ *
+ */
+static void
+keymgr_key_init(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now,
+ bool csk) {
+ bool ksk, zsk;
+ isc_result_t ret;
+ isc_stdtime_t active = 0, pub = 0, syncpub = 0, retire = 0, remove = 0;
+ dst_key_state_t dnskey_state = HIDDEN;
+ dst_key_state_t ds_state = HIDDEN;
+ dst_key_state_t zrrsig_state = HIDDEN;
+ dst_key_state_t goal_state = HIDDEN;
+
+ REQUIRE(key != NULL);
+ REQUIRE(key->key != NULL);
+
+ /* Initialize role. */
+ ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
+ if (ret != ISC_R_SUCCESS) {
+ ksk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) != 0);
+ dst_key_setbool(key->key, DST_BOOL_KSK, (ksk || csk));
+ }
+ ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
+ if (ret != ISC_R_SUCCESS) {
+ zsk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) == 0);
+ dst_key_setbool(key->key, DST_BOOL_ZSK, (zsk || csk));
+ }
+
+ /* Get time metadata. */
+ ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active);
+ if (active <= now && ret == ISC_R_SUCCESS) {
+ dns_ttl_t zone_ttl = dns_kasp_zonemaxttl(kasp);
+ zone_ttl += dns_kasp_zonepropagationdelay(kasp);
+ if ((active + zone_ttl) <= now) {
+ zrrsig_state = OMNIPRESENT;
+ } else {
+ zrrsig_state = RUMOURED;
+ }
+ goal_state = OMNIPRESENT;
+ }
+ ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub);
+ if (pub <= now && ret == ISC_R_SUCCESS) {
+ dns_ttl_t key_ttl = dst_key_getttl(key->key);
+ key_ttl += dns_kasp_zonepropagationdelay(kasp);
+ if ((pub + key_ttl) <= now) {
+ dnskey_state = OMNIPRESENT;
+ } else {
+ dnskey_state = RUMOURED;
+ }
+ goal_state = OMNIPRESENT;
+ }
+ ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub);
+ if (syncpub <= now && ret == ISC_R_SUCCESS) {
+ dns_ttl_t ds_ttl = dns_kasp_dsttl(kasp);
+ ds_ttl += dns_kasp_parentpropagationdelay(kasp);
+ if ((syncpub + ds_ttl) <= now) {
+ ds_state = OMNIPRESENT;
+ } else {
+ ds_state = RUMOURED;
+ }
+ goal_state = OMNIPRESENT;
+ }
+ ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
+ if (retire <= now && ret == ISC_R_SUCCESS) {
+ dns_ttl_t zone_ttl = dns_kasp_zonemaxttl(kasp);
+ zone_ttl += dns_kasp_zonepropagationdelay(kasp);
+ if ((retire + zone_ttl) <= now) {
+ zrrsig_state = HIDDEN;
+ } else {
+ zrrsig_state = UNRETENTIVE;
+ }
+ ds_state = UNRETENTIVE;
+ goal_state = HIDDEN;
+ }
+ ret = dst_key_gettime(key->key, DST_TIME_DELETE, &remove);
+ if (remove <= now && ret == ISC_R_SUCCESS) {
+ dns_ttl_t key_ttl = dst_key_getttl(key->key);
+ key_ttl += dns_kasp_zonepropagationdelay(kasp);
+ if ((remove + key_ttl) <= now) {
+ dnskey_state = HIDDEN;
+ } else {
+ dnskey_state = UNRETENTIVE;
+ }
+ zrrsig_state = HIDDEN;
+ ds_state = HIDDEN;
+ goal_state = HIDDEN;
+ }
+
+ /* Set goal if not already set. */
+ if (dst_key_getstate(key->key, DST_KEY_GOAL, &goal_state) !=
+ ISC_R_SUCCESS)
+ {
+ dst_key_setstate(key->key, DST_KEY_GOAL, goal_state);
+ }
+
+ /* Set key states for all keys that do not have them. */
+ INITIALIZE_STATE(key->key, DST_KEY_DNSKEY, DST_TIME_DNSKEY,
+ dnskey_state, now);
+ if (ksk || csk) {
+ INITIALIZE_STATE(key->key, DST_KEY_KRRSIG, DST_TIME_KRRSIG,
+ dnskey_state, now);
+ INITIALIZE_STATE(key->key, DST_KEY_DS, DST_TIME_DS, ds_state,
+ now);
+ }
+ if (zsk || csk) {
+ INITIALIZE_STATE(key->key, DST_KEY_ZRRSIG, DST_TIME_ZRRSIG,
+ zrrsig_state, now);
+ }
+}
+
+static isc_result_t
+keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key,
+ dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *newkeys,
+ const dns_name_t *origin, dns_rdataclass_t rdclass,
+ dns_kasp_t *kasp, uint32_t lifetime, bool rollover,
+ isc_stdtime_t now, isc_stdtime_t *nexttime,
+ isc_mem_t *mctx) {
+ char keystr[DST_KEY_FORMATSIZE];
+ isc_stdtime_t retire = 0, active = 0, prepub = 0;
+ dns_dnsseckey_t *new_key = NULL;
+ dns_dnsseckey_t *candidate = NULL;
+ dst_key_t *dst_key = NULL;
+
+ /* Do we need to create a successor for the active key? */
+ if (active_key != NULL) {
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ dst_key_format(active_key->key, keystr, sizeof(keystr));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: DNSKEY %s (%s) is active in policy %s",
+ keystr, keymgr_keyrole(active_key->key),
+ dns_kasp_getname(kasp));
+ }
+
+ /*
+ * Calculate when the successor needs to be published
+ * in the zone.
+ */
+ prepub = keymgr_prepublication_time(active_key, kasp, lifetime,
+ now);
+ if (prepub == 0 || prepub > now) {
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ dst_key_format(active_key->key, keystr,
+ sizeof(keystr));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: new successor needed for "
+ "DNSKEY %s (%s) (policy %s) in %u "
+ "seconds",
+ keystr, keymgr_keyrole(active_key->key),
+ dns_kasp_getname(kasp), (prepub - now));
+ }
+
+ /* No need to start rollover now. */
+ if (*nexttime == 0 || prepub < *nexttime) {
+ *nexttime = prepub;
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ if (keymgr_key_has_successor(active_key, keyring)) {
+ /* Key already has successor. */
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ dst_key_format(active_key->key, keystr,
+ sizeof(keystr));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: key DNSKEY %s (%s) (policy "
+ "%s) already has successor",
+ keystr, keymgr_keyrole(active_key->key),
+ dns_kasp_getname(kasp));
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ dst_key_format(active_key->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: need successor for DNSKEY %s "
+ "(%s) (policy %s)",
+ keystr, keymgr_keyrole(active_key->key),
+ dns_kasp_getname(kasp));
+ }
+
+ /*
+ * If rollover is not allowed, warn.
+ */
+ if (!rollover) {
+ dst_key_format(active_key->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "keymgr: DNSKEY %s (%s) is offline in "
+ "policy %s, cannot start rollover",
+ keystr, keymgr_keyrole(active_key->key),
+ dns_kasp_getname(kasp));
+ return (ISC_R_SUCCESS);
+ }
+ } else if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ char namestr[DNS_NAME_FORMATSIZE];
+ dns_name_format(origin, namestr, sizeof(namestr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: no active key found for %s (policy %s)",
+ namestr, dns_kasp_getname(kasp));
+ }
+
+ /* It is time to do key rollover, we need a new key. */
+
+ /*
+ * Check if there is a key available in pool because keys
+ * may have been pregenerated with dnssec-keygen.
+ */
+ for (candidate = ISC_LIST_HEAD(*keyring); candidate != NULL;
+ candidate = ISC_LIST_NEXT(candidate, link))
+ {
+ if (keymgr_dnsseckey_kaspkey_match(candidate, kaspkey) &&
+ dst_key_is_unused(candidate->key))
+ {
+ /* Found a candidate in keyring. */
+ break;
+ }
+ }
+
+ if (candidate == NULL) {
+ /* No key available in keyring, create a new one. */
+ bool csk = (dns_kasp_key_ksk(kaspkey) &&
+ dns_kasp_key_zsk(kaspkey));
+
+ isc_result_t result = keymgr_createkey(kaspkey, origin, rdclass,
+ mctx, keyring, newkeys,
+ &dst_key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dst_key_setttl(dst_key, dns_kasp_dnskeyttl(kasp));
+ dst_key_settime(dst_key, DST_TIME_CREATED, now);
+ result = dns_dnsseckey_create(mctx, &dst_key, &new_key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ keymgr_key_init(new_key, kasp, now, csk);
+ } else {
+ new_key = candidate;
+ }
+ dst_key_setnum(new_key->key, DST_NUM_LIFETIME, lifetime);
+
+ /* Got a key. */
+ if (active_key == NULL) {
+ /*
+ * If there is no active key found yet for this kasp
+ * key configuration, immediately make this key active.
+ */
+ dst_key_settime(new_key->key, DST_TIME_PUBLISH, now);
+ dst_key_settime(new_key->key, DST_TIME_ACTIVATE, now);
+ keymgr_settime_syncpublish(new_key, kasp, true);
+ active = now;
+ } else {
+ /*
+ * This is a successor. Mark the relationship.
+ */
+ isc_stdtime_t created;
+ (void)dst_key_gettime(new_key->key, DST_TIME_CREATED, &created);
+
+ dst_key_setnum(new_key->key, DST_NUM_PREDECESSOR,
+ dst_key_id(active_key->key));
+ dst_key_setnum(active_key->key, DST_NUM_SUCCESSOR,
+ dst_key_id(new_key->key));
+ (void)dst_key_gettime(active_key->key, DST_TIME_INACTIVE,
+ &retire);
+ active = retire;
+
+ /*
+ * If prepublication time and/or retire time are
+ * in the past (before the new key was created), use
+ * creation time as published and active time,
+ * effectively immediately making the key active.
+ */
+ if (prepub < created) {
+ active += (created - prepub);
+ prepub = created;
+ }
+ if (active < created) {
+ active = created;
+ }
+ dst_key_settime(new_key->key, DST_TIME_PUBLISH, prepub);
+ dst_key_settime(new_key->key, DST_TIME_ACTIVATE, active);
+ keymgr_settime_syncpublish(new_key, kasp, false);
+
+ /*
+ * Retire predecessor.
+ */
+ dst_key_setstate(active_key->key, DST_KEY_GOAL, HIDDEN);
+ }
+
+ /* This key wants to be present. */
+ dst_key_setstate(new_key->key, DST_KEY_GOAL, OMNIPRESENT);
+
+ /* Do we need to set retire time? */
+ if (lifetime > 0) {
+ dst_key_settime(new_key->key, DST_TIME_INACTIVE,
+ (active + lifetime));
+ keymgr_settime_remove(new_key, kasp);
+ }
+
+ /* Append dnsseckey to list of new keys. */
+ dns_dnssec_get_hints(new_key, now);
+ new_key->source = dns_keysource_repository;
+ INSIST(!new_key->legacy);
+ if (candidate == NULL) {
+ ISC_LIST_APPEND(*newkeys, new_key, link);
+ }
+
+ /* Logging. */
+ dst_key_format(new_key->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_INFO, "keymgr: DNSKEY %s (%s) %s for policy %s",
+ keystr, keymgr_keyrole(new_key->key),
+ (candidate != NULL) ? "selected" : "created",
+ dns_kasp_getname(kasp));
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+keymgr_key_may_be_purged(dst_key_t *key, uint32_t after, isc_stdtime_t now) {
+ bool ksk = false;
+ bool zsk = false;
+ dst_key_state_t hidden[NUM_KEYSTATES] = { HIDDEN, NA, NA, NA };
+ isc_stdtime_t lastchange = 0;
+
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key, keystr, sizeof(keystr));
+
+ /* If 'purge-keys' is disabled, always retain keys. */
+ if (after == 0) {
+ return (false);
+ }
+
+ /* Don't purge keys with goal OMNIPRESENT */
+ if (dst_key_goal(key) == OMNIPRESENT) {
+ return (false);
+ }
+
+ /* Don't purge unused keys. */
+ if (dst_key_is_unused(key)) {
+ return (false);
+ }
+
+ /* If this key is completely HIDDEN it may be purged. */
+ (void)dst_key_getbool(key, DST_BOOL_KSK, &ksk);
+ (void)dst_key_getbool(key, DST_BOOL_ZSK, &zsk);
+ if (ksk) {
+ hidden[DST_KEY_KRRSIG] = HIDDEN;
+ hidden[DST_KEY_DS] = HIDDEN;
+ }
+ if (zsk) {
+ hidden[DST_KEY_ZRRSIG] = HIDDEN;
+ }
+ if (!keymgr_key_match_state(key, key, 0, NA, hidden)) {
+ return (false);
+ }
+
+ /*
+ * Check 'purge-keys' interval. If the interval has passed since
+ * the last key change, it may be purged.
+ */
+ for (int i = 0; i < NUM_KEYSTATES; i++) {
+ isc_stdtime_t change = 0;
+ (void)dst_key_gettime(key, keystatetimes[i], &change);
+ if (change > lastchange) {
+ lastchange = change;
+ }
+ }
+
+ return ((lastchange + after) < now);
+}
+
+static void
+keymgr_purge_keyfile(dst_key_t *key, const char *dir, int type) {
+ isc_result_t ret;
+ isc_buffer_t fileb;
+ char filename[NAME_MAX];
+
+ /*
+ * Make the filename.
+ */
+ isc_buffer_init(&fileb, filename, sizeof(filename));
+ ret = dst_key_buildfilename(key, type, dir, &fileb);
+ if (ret != ISC_R_SUCCESS) {
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "keymgr: failed to purge DNSKEY %s (%s): cannot "
+ "build filename (%s)",
+ keystr, keymgr_keyrole(key),
+ isc_result_totext(ret));
+ return;
+ }
+
+ if (unlink(filename) < 0) {
+ char keystr[DST_KEY_FORMATSIZE];
+ dst_key_format(key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING,
+ "keymgr: failed to purge DNSKEY %s (%s): unlink "
+ "'%s' failed",
+ keystr, keymgr_keyrole(key), filename);
+ }
+}
+
+/*
+ * Examine 'keys' and match 'kasp' policy.
+ *
+ */
+isc_result_t
+dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
+ const char *directory, isc_mem_t *mctx,
+ dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *dnskeys,
+ dns_kasp_t *kasp, isc_stdtime_t now, isc_stdtime_t *nexttime) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_dnsseckeylist_t newkeys;
+ dns_kasp_key_t *kkey;
+ dns_dnsseckey_t *newkey = NULL;
+ isc_dir_t dir;
+ bool dir_open = false;
+ bool secure_to_insecure = false;
+ int numkeys = 0;
+ int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE);
+ char keystr[DST_KEY_FORMATSIZE];
+
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(keyring != NULL);
+
+ ISC_LIST_INIT(newkeys);
+
+ isc_dir_init(&dir);
+ if (directory == NULL) {
+ directory = ".";
+ }
+
+ RETERR(isc_dir_open(&dir, directory));
+ dir_open = true;
+
+ *nexttime = 0;
+
+ /* Debug logging: what keys are available in the keyring? */
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ if (ISC_LIST_EMPTY(*keyring)) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(origin, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: keyring empty (zone %s policy "
+ "%s)",
+ namebuf, dns_kasp_getname(kasp));
+ }
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring);
+ dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ dst_key_format(dkey->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: keyring: %s (policy %s)", keystr,
+ dns_kasp_getname(kasp));
+ }
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*dnskeys);
+ dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ dst_key_format(dkey->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
+ "keymgr: dnskeys: %s (policy %s)", keystr,
+ dns_kasp_getname(kasp));
+ }
+ }
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*dnskeys); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ numkeys++;
+ }
+
+ /* Do we need to remove keys? */
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ bool found_match = false;
+
+ keymgr_key_init(dkey, kasp, now, (numkeys == 1));
+
+ for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
+ kkey = ISC_LIST_NEXT(kkey, link))
+ {
+ if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) {
+ found_match = true;
+ break;
+ }
+ }
+
+ /* No match, so retire unwanted retire key. */
+ if (!found_match) {
+ keymgr_key_retire(dkey, kasp, now);
+ }
+
+ /* Check purge-keys interval. */
+ if (keymgr_key_may_be_purged(dkey->key,
+ dns_kasp_purgekeys(kasp), now))
+ {
+ dst_key_format(dkey->key, keystr, sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
+ "keymgr: purge DNSKEY %s (%s) according "
+ "to policy %s",
+ keystr, keymgr_keyrole(dkey->key),
+ dns_kasp_getname(kasp));
+
+ keymgr_purge_keyfile(dkey->key, directory,
+ DST_TYPE_PUBLIC);
+ keymgr_purge_keyfile(dkey->key, directory,
+ DST_TYPE_PRIVATE);
+ keymgr_purge_keyfile(dkey->key, directory,
+ DST_TYPE_STATE);
+
+ dkey->purge = true;
+ }
+ }
+
+ /* Create keys according to the policy, if come in short. */
+ for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
+ kkey = ISC_LIST_NEXT(kkey, link))
+ {
+ uint32_t lifetime = dns_kasp_key_lifetime(kkey);
+ dns_dnsseckey_t *active_key = NULL;
+ bool rollover_allowed = true;
+
+ /* Do we have keys available for this kasp key? */
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring);
+ dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) {
+ /* Found a match. */
+ dst_key_format(dkey->key, keystr,
+ sizeof(keystr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_DEBUG(1),
+ "keymgr: DNSKEY %s (%s) matches "
+ "policy %s",
+ keystr, keymgr_keyrole(dkey->key),
+ dns_kasp_getname(kasp));
+
+ /* Initialize lifetime if not set. */
+ uint32_t l;
+ if (dst_key_getnum(dkey->key, DST_NUM_LIFETIME,
+ &l) != ISC_R_SUCCESS)
+ {
+ dst_key_setnum(dkey->key,
+ DST_NUM_LIFETIME,
+ lifetime);
+ }
+
+ if (active_key) {
+ /* We already have an active key that
+ * matches the kasp policy.
+ */
+ if (!dst_key_is_unused(dkey->key) &&
+ (dst_key_goal(dkey->key) ==
+ OMNIPRESENT) &&
+ !keymgr_dep(dkey->key, keyring,
+ NULL) &&
+ !keymgr_dep(active_key->key,
+ keyring, NULL))
+ {
+ /*
+ * Multiple signing keys match
+ * the kasp key configuration.
+ * Retire excess keys in use.
+ */
+ keymgr_key_retire(dkey, kasp,
+ now);
+ }
+ continue;
+ }
+
+ /*
+ * Save the matched key only if it is active
+ * or desires to be active.
+ */
+ if (dst_key_goal(dkey->key) == OMNIPRESENT ||
+ dst_key_is_active(dkey->key, now))
+ {
+ active_key = dkey;
+ }
+ }
+ }
+
+ if (active_key == NULL) {
+ /*
+ * We didn't found an active key, perhaps the .private
+ * key file is offline. If so, we don't want to create
+ * a successor key. Check if we have an appropriate
+ * state file.
+ */
+ for (dns_dnsseckey_t *dnskey = ISC_LIST_HEAD(*dnskeys);
+ dnskey != NULL;
+ dnskey = ISC_LIST_NEXT(dnskey, link))
+ {
+ if (keymgr_dnsseckey_kaspkey_match(dnskey,
+ kkey))
+ {
+ /* Found a match. */
+ dst_key_format(dnskey->key, keystr,
+ sizeof(keystr));
+ isc_log_write(
+ dns_lctx,
+ DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC,
+ ISC_LOG_DEBUG(1),
+ "keymgr: DNSKEY %s (%s) "
+ "offline, policy %s",
+ keystr,
+ keymgr_keyrole(dnskey->key),
+ dns_kasp_getname(kasp));
+ rollover_allowed = false;
+ active_key = dnskey;
+ break;
+ }
+ }
+ }
+
+ /* See if this key requires a rollover. */
+ RETERR(keymgr_key_rollover(
+ kkey, active_key, keyring, &newkeys, origin, rdclass,
+ kasp, lifetime, rollover_allowed, now, nexttime, mctx));
+ }
+
+ /* Walked all kasp key configurations. Append new keys. */
+ if (!ISC_LIST_EMPTY(newkeys)) {
+ ISC_LIST_APPENDLIST(*keyring, newkeys, link);
+ }
+
+ /*
+ * If the policy has an empty key list, this means the zone is going
+ * back to unsigned.
+ */
+ secure_to_insecure = dns_kasp_keylist_empty(kasp);
+
+ /* Read to update key states. */
+ keymgr_update(keyring, kasp, now, nexttime, secure_to_insecure);
+
+ /* Store key states and update hints. */
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (dst_key_ismodified(dkey->key) && !dkey->purge) {
+ dns_dnssec_get_hints(dkey, now);
+ RETERR(dst_key_tofile(dkey->key, options, directory));
+ dst_key_setmodified(dkey->key, false);
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (dir_open) {
+ isc_dir_close(&dir);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ while ((newkey = ISC_LIST_HEAD(newkeys)) != NULL) {
+ ISC_LIST_UNLINK(newkeys, newkey, link);
+ INSIST(newkey->key != NULL);
+ dst_key_free(&newkey->key);
+ dns_dnsseckey_destroy(mctx, &newkey);
+ }
+ }
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(origin, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(3),
+ "keymgr: %s done", namebuf);
+ }
+ return (result);
+}
+
+static isc_result_t
+keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now, isc_stdtime_t when,
+ bool dspublish, dns_keytag_t id, unsigned int alg,
+ bool check_id) {
+ int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE);
+ isc_dir_t dir;
+ isc_result_t result;
+ dns_dnsseckey_t *ksk_key = NULL;
+
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(keyring != NULL);
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ isc_result_t ret;
+ bool ksk = false;
+
+ ret = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk);
+ if (ret == ISC_R_SUCCESS && ksk) {
+ if (check_id && dst_key_id(dkey->key) != id) {
+ continue;
+ }
+ if (alg > 0 && dst_key_alg(dkey->key) != alg) {
+ continue;
+ }
+
+ if (ksk_key != NULL) {
+ /*
+ * Only checkds for one key at a time.
+ */
+ return (DNS_R_TOOMANYKEYS);
+ }
+
+ ksk_key = dkey;
+ }
+ }
+
+ if (ksk_key == NULL) {
+ return (DNS_R_NOKEYMATCH);
+ }
+
+ if (dspublish) {
+ dst_key_state_t s;
+ dst_key_settime(ksk_key->key, DST_TIME_DSPUBLISH, when);
+ result = dst_key_getstate(ksk_key->key, DST_KEY_DS, &s);
+ if (result != ISC_R_SUCCESS || s != RUMOURED) {
+ dst_key_setstate(ksk_key->key, DST_KEY_DS, RUMOURED);
+ }
+ } else {
+ dst_key_state_t s;
+ dst_key_settime(ksk_key->key, DST_TIME_DSDELETE, when);
+ result = dst_key_getstate(ksk_key->key, DST_KEY_DS, &s);
+ if (result != ISC_R_SUCCESS || s != UNRETENTIVE) {
+ dst_key_setstate(ksk_key->key, DST_KEY_DS, UNRETENTIVE);
+ }
+ }
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_NOTICE)) {
+ char keystr[DST_KEY_FORMATSIZE];
+ char timestr[26]; /* Minimal buf as per ctime_r() spec. */
+
+ dst_key_format(ksk_key->key, keystr, sizeof(keystr));
+ isc_stdtime_tostring(when, timestr, sizeof(timestr));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_DNSSEC, ISC_LOG_NOTICE,
+ "keymgr: checkds DS for key %s seen %s at %s",
+ keystr, dspublish ? "published" : "withdrawn",
+ timestr);
+ }
+
+ /* Store key state and update hints. */
+ isc_dir_init(&dir);
+ if (directory == NULL) {
+ directory = ".";
+ }
+ result = isc_dir_open(&dir, directory);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_dnssec_get_hints(ksk_key, now);
+ result = dst_key_tofile(ksk_key->key, options, directory);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_setmodified(ksk_key->key, false);
+ }
+ isc_dir_close(&dir);
+
+ return (result);
+}
+
+isc_result_t
+dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now, isc_stdtime_t when,
+ bool dspublish) {
+ return (keymgr_checkds(kasp, keyring, directory, now, when, dspublish,
+ 0, 0, false));
+}
+
+isc_result_t
+dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now,
+ isc_stdtime_t when, bool dspublish, dns_keytag_t id,
+ unsigned int alg) {
+ return (keymgr_checkds(kasp, keyring, directory, now, when, dspublish,
+ id, alg, true));
+}
+
+static void
+keytime_status(dst_key_t *key, isc_stdtime_t now, isc_buffer_t *buf,
+ const char *pre, int ks, int kt) {
+ char timestr[26]; /* Minimal buf as per ctime_r() spec. */
+ isc_result_t ret;
+ isc_stdtime_t when = 0;
+ dst_key_state_t state = NA;
+
+ isc_buffer_printf(buf, "%s", pre);
+ (void)dst_key_getstate(key, ks, &state);
+ ret = dst_key_gettime(key, kt, &when);
+ if (state == RUMOURED || state == OMNIPRESENT) {
+ isc_buffer_printf(buf, "yes - since ");
+ } else if (now < when) {
+ isc_buffer_printf(buf, "no - scheduled ");
+ } else {
+ isc_buffer_printf(buf, "no\n");
+ return;
+ }
+ if (ret == ISC_R_SUCCESS) {
+ isc_stdtime_tostring(when, timestr, sizeof(timestr));
+ isc_buffer_printf(buf, "%s\n", timestr);
+ }
+}
+
+static void
+rollover_status(dns_dnsseckey_t *dkey, dns_kasp_t *kasp, isc_stdtime_t now,
+ isc_buffer_t *buf, bool zsk) {
+ char timestr[26]; /* Minimal buf as per ctime_r() spec. */
+ isc_result_t ret = ISC_R_SUCCESS;
+ isc_stdtime_t active_time = 0;
+ dst_key_state_t state = NA, goal = NA;
+ int rrsig, active, retire;
+ dst_key_t *key = dkey->key;
+
+ if (zsk) {
+ rrsig = DST_KEY_ZRRSIG;
+ active = DST_TIME_ACTIVATE;
+ retire = DST_TIME_INACTIVE;
+ } else {
+ rrsig = DST_KEY_KRRSIG;
+ active = DST_TIME_PUBLISH;
+ retire = DST_TIME_DELETE;
+ }
+
+ isc_buffer_printf(buf, "\n");
+
+ (void)dst_key_getstate(key, DST_KEY_GOAL, &goal);
+ (void)dst_key_getstate(key, rrsig, &state);
+ (void)dst_key_gettime(key, active, &active_time);
+ if (active_time == 0) {
+ // only interested in keys that were once active.
+ return;
+ }
+
+ if (goal == HIDDEN && (state == UNRETENTIVE || state == HIDDEN)) {
+ isc_stdtime_t remove_time = 0;
+ // is the key removed yet?
+ state = NA;
+ (void)dst_key_getstate(key, DST_KEY_DNSKEY, &state);
+ if (state == RUMOURED || state == OMNIPRESENT) {
+ ret = dst_key_gettime(key, DST_TIME_DELETE,
+ &remove_time);
+ if (ret == ISC_R_SUCCESS) {
+ isc_buffer_printf(buf, " Key is retired, will "
+ "be removed on ");
+ isc_stdtime_tostring(remove_time, timestr,
+ sizeof(timestr));
+ isc_buffer_printf(buf, "%s", timestr);
+ }
+ } else {
+ isc_buffer_printf(
+ buf, " Key has been removed from the zone");
+ }
+ } else {
+ isc_stdtime_t retire_time = 0;
+ uint32_t lifetime = 0;
+ (void)dst_key_getnum(key, DST_NUM_LIFETIME, &lifetime);
+ ret = dst_key_gettime(key, retire, &retire_time);
+ if (ret == ISC_R_SUCCESS) {
+ if (now < retire_time) {
+ if (goal == OMNIPRESENT) {
+ isc_buffer_printf(buf,
+ " Next rollover "
+ "scheduled on ");
+ retire_time = keymgr_prepublication_time(
+ dkey, kasp, lifetime, now);
+ } else {
+ isc_buffer_printf(
+ buf, " Key will retire on ");
+ }
+ } else {
+ isc_buffer_printf(buf,
+ " Rollover is due since ");
+ }
+ isc_stdtime_tostring(retire_time, timestr,
+ sizeof(timestr));
+ isc_buffer_printf(buf, "%s", timestr);
+ } else {
+ isc_buffer_printf(buf, " No rollover scheduled");
+ }
+ }
+ isc_buffer_printf(buf, "\n");
+}
+
+static void
+keystate_status(dst_key_t *key, isc_buffer_t *buf, const char *pre, int ks) {
+ dst_key_state_t state = NA;
+
+ (void)dst_key_getstate(key, ks, &state);
+ switch (state) {
+ case HIDDEN:
+ isc_buffer_printf(buf, " - %shidden\n", pre);
+ break;
+ case RUMOURED:
+ isc_buffer_printf(buf, " - %srumoured\n", pre);
+ break;
+ case OMNIPRESENT:
+ isc_buffer_printf(buf, " - %somnipresent\n", pre);
+ break;
+ case UNRETENTIVE:
+ isc_buffer_printf(buf, " - %sunretentive\n", pre);
+ break;
+ case NA:
+ default:
+ /* print nothing */
+ break;
+ }
+}
+
+void
+dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ isc_stdtime_t now, char *out, size_t out_len) {
+ isc_buffer_t buf;
+ char timestr[26]; /* Minimal buf as per ctime_r() spec. */
+
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(keyring != NULL);
+ REQUIRE(out != NULL);
+
+ isc_buffer_init(&buf, out, out_len);
+
+ // policy name
+ isc_buffer_printf(&buf, "dnssec-policy: %s\n", dns_kasp_getname(kasp));
+ isc_buffer_printf(&buf, "current time: ");
+ isc_stdtime_tostring(now, timestr, sizeof(timestr));
+ isc_buffer_printf(&buf, "%s\n", timestr);
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ char algstr[DNS_NAME_FORMATSIZE];
+ bool ksk = false, zsk = false;
+ isc_result_t ret;
+
+ if (dst_key_is_unused(dkey->key)) {
+ continue;
+ }
+
+ // key data
+ dns_secalg_format((dns_secalg_t)dst_key_alg(dkey->key), algstr,
+ sizeof(algstr));
+ isc_buffer_printf(&buf, "\nkey: %d (%s), %s\n",
+ dst_key_id(dkey->key), algstr,
+ keymgr_keyrole(dkey->key));
+
+ // publish status
+ keytime_status(dkey->key, now, &buf,
+ " published: ", DST_KEY_DNSKEY,
+ DST_TIME_PUBLISH);
+
+ // signing status
+ ret = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk);
+ if (ret == ISC_R_SUCCESS && ksk) {
+ keytime_status(dkey->key, now, &buf,
+ " key signing: ", DST_KEY_KRRSIG,
+ DST_TIME_PUBLISH);
+ }
+ ret = dst_key_getbool(dkey->key, DST_BOOL_ZSK, &zsk);
+ if (ret == ISC_R_SUCCESS && zsk) {
+ keytime_status(dkey->key, now, &buf,
+ " zone signing: ", DST_KEY_ZRRSIG,
+ DST_TIME_ACTIVATE);
+ }
+
+ // rollover status
+ rollover_status(dkey, kasp, now, &buf, zsk);
+
+ // key states
+ keystate_status(dkey->key, &buf,
+ "goal: ", DST_KEY_GOAL);
+ keystate_status(dkey->key, &buf,
+ "dnskey: ", DST_KEY_DNSKEY);
+ keystate_status(dkey->key, &buf,
+ "ds: ", DST_KEY_DS);
+ keystate_status(dkey->key, &buf,
+ "zone rrsig: ", DST_KEY_ZRRSIG);
+ keystate_status(dkey->key, &buf,
+ "key rrsig: ", DST_KEY_KRRSIG);
+ }
+}
+
+isc_result_t
+dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring,
+ const char *directory, isc_stdtime_t now,
+ isc_stdtime_t when, dns_keytag_t id,
+ unsigned int algorithm) {
+ int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE);
+ isc_dir_t dir;
+ isc_result_t result;
+ dns_dnsseckey_t *key = NULL;
+ isc_stdtime_t active, retire, prepub;
+
+ REQUIRE(DNS_KASP_VALID(kasp));
+ REQUIRE(keyring != NULL);
+
+ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
+ dkey = ISC_LIST_NEXT(dkey, link))
+ {
+ if (dst_key_id(dkey->key) != id) {
+ continue;
+ }
+ if (algorithm > 0 && dst_key_alg(dkey->key) != algorithm) {
+ continue;
+ }
+ if (key != NULL) {
+ /*
+ * Only rollover for one key at a time.
+ */
+ return (DNS_R_TOOMANYKEYS);
+ }
+ key = dkey;
+ }
+
+ if (key == NULL) {
+ return (DNS_R_NOKEYMATCH);
+ }
+
+ result = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active);
+ if (result != ISC_R_SUCCESS || active > now) {
+ return (DNS_R_KEYNOTACTIVE);
+ }
+
+ result = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
+ if (result != ISC_R_SUCCESS) {
+ /**
+ * Default to as if this key was not scheduled to
+ * become retired, as if it had unlimited lifetime.
+ */
+ retire = 0;
+ }
+
+ /**
+ * Usually when is set to now, which is before the scheduled
+ * prepublication time, meaning we reduce the lifetime of the
+ * key. But in some cases, the lifetime can also be extended.
+ * We accept it, but we can return an error here if that
+ * turns out to be unintuitive behavior.
+ */
+ prepub = dst_key_getttl(key->key) + dns_kasp_publishsafety(kasp) +
+ dns_kasp_zonepropagationdelay(kasp);
+ retire = when + prepub;
+
+ dst_key_settime(key->key, DST_TIME_INACTIVE, retire);
+ dst_key_setnum(key->key, DST_NUM_LIFETIME, (retire - active));
+
+ /* Store key state and update hints. */
+ isc_dir_init(&dir);
+ if (directory == NULL) {
+ directory = ".";
+ }
+ result = isc_dir_open(&dir, directory);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_dnssec_get_hints(key, now);
+ result = dst_key_tofile(key->key, options, directory);
+ if (result == ISC_R_SUCCESS) {
+ dst_key_setmodified(key->key, false);
+ }
+ isc_dir_close(&dir);
+
+ return (result);
+}
diff --git a/lib/dns/keytable.c b/lib/dns/keytable.c
new file mode 100644
index 0000000..e5786f1
--- /dev/null
+++ b/lib/dns/keytable.c
@@ -0,0 +1,943 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/refcount.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>
+#include <dns/result.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_rbtnode_t *node = NULL;
+ isc_result_t result;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_write);
+
+ result = dns_rbt_addnode(keytable->table, keyname, &node);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * There was no node for "keyname" in "keytable" yet, so one
+ * was created. Create a new key node for the supplied
+ * trust anchor (or a null key node if "ds" is NULL)
+ * and attach it to the created node.
+ */
+ node->data = new_keynode(ds, keytable, managed, initial);
+ } else if (result == ISC_R_EXISTS) {
+ /*
+ * A node already exists for "keyname" in "keytable".
+ */
+ if (ds != NULL) {
+ dns_keynode_t *knode = node->data;
+ if (knode == NULL) {
+ node->data = new_keynode(ds, keytable, managed,
+ initial);
+ } else {
+ add_ds(knode, ds, keytable->mctx);
+ }
+ }
+ result = ISC_R_SUCCESS;
+ }
+
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+isc_result_t
+dns_keytable_add(dns_keytable_t *keytable, bool managed, bool initial,
+ dns_name_t *name, dns_rdata_ds_t *ds) {
+ REQUIRE(ds != NULL);
+ REQUIRE(!initial || managed);
+
+ return (insert(keytable, managed, initial, name, ds));
+}
+
+isc_result_t
+dns_keytable_marksecure(dns_keytable_t *keytable, const dns_name_t *name) {
+ return (insert(keytable, true, false, name, NULL));
+}
+
+isc_result_t
+dns_keytable_delete(dns_keytable_t *keytable, const dns_name_t *keyname) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(keyname != NULL);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_write);
+ result = dns_rbt_findnode(keytable->table, keyname, NULL, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ if (node->data != NULL) {
+ result = dns_rbt_deletenode(keytable->table, node,
+ false);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ } else if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+isc_result_t
+dns_keytable_deletekey(dns_keytable_t *keytable, const dns_name_t *keyname,
+ dns_rdata_dnskey_t *dnskey) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+ dns_keynode_t *knode = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned char data[4096], digest[DNS_DS_BUFFERSIZE];
+ dns_rdata_ds_t ds;
+ isc_buffer_t b;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(dnskey != NULL);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_write);
+ result = dns_rbt_findnode(keytable->table, keyname, NULL, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+
+ if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto finish;
+ }
+
+ if (node->data == NULL) {
+ result = ISC_R_NOTFOUND;
+ goto finish;
+ }
+
+ knode = node->data;
+
+ RWLOCK(&knode->rwlock, isc_rwlocktype_read);
+ if (knode->dslist == NULL) {
+ RWUNLOCK(&knode->rwlock, isc_rwlocktype_read);
+ result = DNS_R_PARTIALMATCH;
+ goto finish;
+ }
+ RWUNLOCK(&knode->rwlock, isc_rwlocktype_read);
+
+ isc_buffer_init(&b, data, sizeof(data));
+ result = dns_rdata_fromstruct(&rdata, dnskey->common.rdclass,
+ dns_rdatatype_dnskey, dnskey, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto finish;
+ }
+
+ result = dns_ds_fromkeyrdata(keyname, &rdata, DNS_DSDIGEST_SHA256,
+ digest, &ds);
+ if (result != ISC_R_SUCCESS) {
+ goto finish;
+ }
+
+ result = delete_ds(keytable, node, &ds);
+
+finish:
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write);
+ return (result);
+}
+
+isc_result_t
+dns_keytable_find(dns_keytable_t *keytable, const dns_name_t *keyname,
+ dns_keynode_t **keynodep) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(keyname != NULL);
+ REQUIRE(keynodep != NULL && *keynodep == NULL);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_read);
+ result = dns_rbt_findnode(keytable->table, keyname, NULL, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ if (node->data != NULL) {
+ keynode_attach(node->data, keynodep);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ } else if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+isc_result_t
+dns_keytable_finddeepestmatch(dns_keytable_t *keytable, const dns_name_t *name,
+ dns_name_t *foundname) {
+ isc_result_t result;
+ void *data;
+
+ /*
+ * Search for the deepest match in 'keytable'.
+ */
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(foundname != NULL);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_read);
+
+ data = NULL;
+ result = dns_rbt_findname(keytable->table, name, 0, foundname, &data);
+
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_SUCCESS;
+ }
+
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+void
+dns_keytable_detachkeynode(dns_keytable_t *keytable, dns_keynode_t **keynodep) {
+ /*
+ * Give back a keynode found via dns_keytable_findkeynode().
+ */
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(keynodep != NULL && VALID_KEYNODE(*keynodep));
+
+ keynode_detach(keytable->mctx, keynodep);
+}
+
+isc_result_t
+dns_keytable_issecuredomain(dns_keytable_t *keytable, const dns_name_t *name,
+ dns_name_t *foundname, bool *wantdnssecp) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ /*
+ * Is 'name' at or beneath a trusted key?
+ */
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(wantdnssecp != NULL);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_read);
+
+ result = dns_rbt_findnode(keytable->table, name, foundname, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ INSIST(node->data != NULL);
+ *wantdnssecp = true;
+ result = ISC_R_SUCCESS;
+ } else if (result == ISC_R_NOTFOUND) {
+ *wantdnssecp = false;
+ result = ISC_R_SUCCESS;
+ }
+
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+static isc_result_t
+putstr(isc_buffer_t **b, const char *str) {
+ isc_result_t result;
+
+ result = isc_buffer_reserve(b, strlen(str));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_putstr(*b, str);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_keytable_dump(dns_keytable_t *keytable, FILE *fp) {
+ isc_result_t result;
+ isc_buffer_t *text = NULL;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(fp != NULL);
+
+ isc_buffer_allocate(keytable->mctx, &text, 4096);
+
+ result = dns_keytable_totext(keytable, &text);
+
+ if (isc_buffer_usedlength(text) != 0) {
+ (void)putstr(&text, "\n");
+ } else if (result == ISC_R_SUCCESS) {
+ (void)putstr(&text, "none");
+ } else {
+ (void)putstr(&text, "could not dump key table: ");
+ (void)putstr(&text, isc_result_totext(result));
+ }
+
+ fprintf(fp, "%.*s", (int)isc_buffer_usedlength(text),
+ (char *)isc_buffer_base(text));
+
+ isc_buffer_free(&text);
+ return (result);
+}
+
+static isc_result_t
+keynode_dslist_totext(dns_name_t *name, dns_keynode_t *keynode,
+ isc_buffer_t **text) {
+ isc_result_t result;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char obuf[DNS_NAME_FORMATSIZE + 200];
+ dns_rdataset_t dsset;
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+
+ dns_rdataset_init(&dsset);
+ if (!dns_keynode_dsset(keynode, &dsset)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ for (result = dns_rdataset_first(&dsset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&dsset))
+ {
+ char algbuf[DNS_SECALG_FORMATSIZE];
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_ds_t ds;
+
+ dns_rdataset_current(&dsset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ds, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ dns_secalg_format(ds.algorithm, algbuf, sizeof(algbuf));
+
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ snprintf(obuf, sizeof(obuf), "%s/%s/%d ; %s%s\n", namebuf,
+ algbuf, ds.key_tag,
+ keynode->initial ? "initializing " : "",
+ keynode->managed ? "managed" : "static");
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+
+ result = putstr(text, obuf);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&dsset);
+ return (result);
+ }
+ }
+ dns_rdataset_disassociate(&dsset);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_keytable_totext(dns_keytable_t *keytable, isc_buffer_t **text) {
+ isc_result_t result;
+ dns_keynode_t *knode;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ dns_name_t *foundname, *origin, *fullname;
+ dns_fixedname_t fixedfoundname, fixedorigin, fixedfullname;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+ REQUIRE(text != NULL && *text != NULL);
+
+ origin = dns_fixedname_initname(&fixedorigin);
+ fullname = dns_fixedname_initname(&fixedfullname);
+ foundname = dns_fixedname_initname(&fixedfoundname);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_read);
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, keytable->table, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ goto cleanup;
+ }
+ for (;;) {
+ dns_rbtnodechain_current(&chain, foundname, origin, &node);
+
+ knode = node->data;
+ if (knode != NULL && knode->dslist != NULL) {
+ result = dns_name_concatenate(foundname, origin,
+ fullname, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = keynode_dslist_totext(fullname, knode, text);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ break;
+ }
+ }
+
+cleanup:
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read);
+ return (result);
+}
+
+isc_result_t
+dns_keytable_forall(dns_keytable_t *keytable,
+ void (*func)(dns_keytable_t *, dns_keynode_t *,
+ dns_name_t *, void *),
+ void *arg) {
+ isc_result_t result;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ dns_fixedname_t fixedfoundname, fixedorigin, fixedfullname;
+ dns_name_t *foundname, *origin, *fullname;
+
+ REQUIRE(VALID_KEYTABLE(keytable));
+
+ origin = dns_fixedname_initname(&fixedorigin);
+ fullname = dns_fixedname_initname(&fixedfullname);
+ foundname = dns_fixedname_initname(&fixedfoundname);
+
+ RWLOCK(&keytable->rwlock, isc_rwlocktype_read);
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, keytable->table, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ goto cleanup;
+ }
+
+ for (;;) {
+ dns_rbtnodechain_current(&chain, foundname, origin, &node);
+ if (node->data != NULL) {
+ result = dns_name_concatenate(foundname, origin,
+ fullname, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ (*func)(keytable, node->data, fullname, arg);
+ }
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ break;
+ }
+ }
+
+cleanup:
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read);
+ return (result);
+}
+
+bool
+dns_keynode_dsset(dns_keynode_t *keynode, dns_rdataset_t *rdataset) {
+ bool result;
+ REQUIRE(VALID_KEYNODE(keynode));
+ REQUIRE(rdataset == NULL || DNS_RDATASET_VALID(rdataset));
+
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ if (keynode->dslist != NULL) {
+ if (rdataset != NULL) {
+ keynode_clone(&keynode->dsset, rdataset);
+ }
+ result = true;
+ } else {
+ result = false;
+ }
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ return (result);
+}
+
+bool
+dns_keynode_managed(dns_keynode_t *keynode) {
+ bool managed;
+
+ REQUIRE(VALID_KEYNODE(keynode));
+
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ managed = keynode->managed;
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+
+ return (managed);
+}
+
+bool
+dns_keynode_initial(dns_keynode_t *keynode) {
+ bool initial;
+
+ REQUIRE(VALID_KEYNODE(keynode));
+
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ initial = keynode->initial;
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+
+ return (initial);
+}
+
+void
+dns_keynode_trust(dns_keynode_t *keynode) {
+ REQUIRE(VALID_KEYNODE(keynode));
+
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_write);
+ keynode->initial = false;
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_write);
+}
+
+static void
+keynode_disassociate(dns_rdataset_t *rdataset) {
+ dns_keynode_t *keynode;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->methods == &methods);
+
+ rdataset->methods = NULL;
+ keynode = rdataset->private1;
+ rdataset->private1 = NULL;
+
+ keynode_detach(keynode->mctx, &keynode);
+}
+
+static isc_result_t
+keynode_first(dns_rdataset_t *rdataset) {
+ dns_keynode_t *keynode;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->methods == &methods);
+
+ keynode = rdataset->private1;
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ rdataset->private2 = ISC_LIST_HEAD(keynode->dslist->rdata);
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+
+ if (rdataset->private2 == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+keynode_next(dns_rdataset_t *rdataset) {
+ dns_keynode_t *keynode;
+ dns_rdata_t *rdata;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->methods == &methods);
+
+ rdata = rdataset->private2;
+ if (rdata == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ keynode = rdataset->private1;
+ RWLOCK(&keynode->rwlock, isc_rwlocktype_read);
+ rdataset->private2 = ISC_LIST_NEXT(rdata, link);
+ RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read);
+
+ if (rdataset->private2 == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+keynode_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ dns_rdata_t *list_rdata;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->methods == &methods);
+
+ list_rdata = rdataset->private2;
+ INSIST(list_rdata != NULL);
+
+ dns_rdata_clone(list_rdata, rdata);
+}
+
+static void
+keynode_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ dns_keynode_t *keynode;
+
+ REQUIRE(source != NULL);
+ REQUIRE(target != NULL);
+ REQUIRE(source->methods == &methods);
+
+ keynode = source->private1;
+ isc_refcount_increment(&keynode->refcount);
+
+ *target = *source;
+
+ /*
+ * Reset iterator state.
+ */
+ target->private2 = NULL;
+}
diff --git a/lib/dns/lib.c b/lib/dns/lib.c
new file mode 100644
index 0000000..e8eab73
--- /dev/null
+++ b/lib/dns/lib.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <isc/hash.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/refcount.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/ecdb.h>
+#include <dns/lib.h>
+#include <dns/result.h>
+
+#include <dst/dst.h>
+
+/***
+ *** Globals
+ ***/
+
+LIBDNS_EXTERNAL_DATA unsigned int dns_pps = 0U;
+
+/***
+ *** Functions
+ ***/
+
+static isc_once_t init_once = ISC_ONCE_INIT;
+static isc_mem_t *dns_g_mctx = NULL;
+static dns_dbimplementation_t *dbimp = NULL;
+static bool initialize_done = false;
+static isc_refcount_t references;
+
+static void
+initialize(void) {
+ isc_result_t result;
+
+ REQUIRE(!initialize_done);
+
+ isc_refcount_init(&references, 0);
+
+ isc_mem_create(&dns_g_mctx);
+ dns_result_register();
+ result = dns_ecdb_register(dns_g_mctx, &dbimp);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_mctx;
+ }
+
+ result = dst_lib_init(dns_g_mctx, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_db;
+ }
+
+ initialize_done = true;
+ return;
+
+cleanup_db:
+ if (dbimp != NULL) {
+ dns_ecdb_unregister(&dbimp);
+ }
+cleanup_mctx:
+ if (dns_g_mctx != NULL) {
+ isc_mem_detach(&dns_g_mctx);
+ }
+}
+
+isc_result_t
+dns_lib_init(void) {
+ isc_result_t result;
+
+ /*
+ * Since this routine is expected to be used by a normal application,
+ * it should be better to return an error, instead of an emergency
+ * abort, on any failure.
+ */
+ result = isc_once_do(&init_once, initialize);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (!initialize_done) {
+ return (ISC_R_FAILURE);
+ }
+
+ isc_refcount_increment0(&references);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_lib_shutdown(void) {
+ if (isc_refcount_decrement(&references) == 1) {
+ dst_lib_destroy();
+
+ isc_refcount_destroy(&references);
+
+ if (dbimp != NULL) {
+ dns_ecdb_unregister(&dbimp);
+ }
+ if (dns_g_mctx != NULL) {
+ isc_mem_detach(&dns_g_mctx);
+ }
+ }
+}
diff --git a/lib/dns/log.c b/lib/dns/log.c
new file mode 100644
index 0000000..af8a7d9
--- /dev/null
+++ b/lib/dns/log.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <isc/util.h>
+
+#include <dns/log.h>
+
+/*%
+ * When adding a new category, be sure to add the appropriate
+ * \#define to <dns/log.h>.
+ */
+LIBDNS_EXTERNAL_DATA isc_logcategory_t dns_categories[] = {
+ { "notify", 0 },
+ { "database", 0 },
+ { "security", 0 },
+ { "_placeholder", 0 },
+ { "dnssec", 0 },
+ { "resolver", 0 },
+ { "xfer-in", 0 },
+ { "xfer-out", 0 },
+ { "dispatch", 0 },
+ { "lame-servers", 0 },
+ { "delegation-only", 0 },
+ { "edns-disabled", 0 },
+ { "rpz", 0 },
+ { "rate-limit", 0 },
+ { "cname", 0 },
+ { "spill", 0 },
+ { "dnstap", 0 },
+ { "zoneload", 0 },
+ { "nsid", 0 },
+ { NULL, 0 }
+};
+
+/*%
+ * When adding a new module, be sure to add the appropriate
+ * \#define to <dns/log.h>.
+ */
+LIBDNS_EXTERNAL_DATA isc_logmodule_t dns_modules[] = {
+ { "dns/db", 0 }, { "dns/rbtdb", 0 },
+ { "dns/rbt", 0 }, { "dns/rdata", 0 },
+ { "dns/master", 0 }, { "dns/message", 0 },
+ { "dns/cache", 0 }, { "dns/config", 0 },
+ { "dns/resolver", 0 }, { "dns/zone", 0 },
+ { "dns/journal", 0 }, { "dns/adb", 0 },
+ { "dns/xfrin", 0 }, { "dns/xfrout", 0 },
+ { "dns/acl", 0 }, { "dns/validator", 0 },
+ { "dns/dispatch", 0 }, { "dns/request", 0 },
+ { "dns/masterdump", 0 }, { "dns/tsig", 0 },
+ { "dns/tkey", 0 }, { "dns/sdb", 0 },
+ { "dns/diff", 0 }, { "dns/hints", 0 },
+ { "dns/unused1", 0 }, { "dns/dlz", 0 },
+ { "dns/dnssec", 0 }, { "dns/crypto", 0 },
+ { "dns/packets", 0 }, { "dns/nta", 0 },
+ { "dns/dyndb", 0 }, { "dns/dnstap", 0 },
+ { "dns/ssu", 0 }, { NULL, 0 }
+};
+
+LIBDNS_EXTERNAL_DATA isc_log_t *dns_lctx = NULL;
+
+void
+dns_log_init(isc_log_t *lctx) {
+ REQUIRE(lctx != NULL);
+
+ isc_log_registercategories(lctx, dns_categories);
+ isc_log_registermodules(lctx, dns_modules);
+}
+
+void
+dns_log_setcontext(isc_log_t *lctx) {
+ dns_lctx = lctx;
+}
diff --git a/lib/dns/lookup.c b/lib/dns/lookup.c
new file mode 100644
index 0000000..5d3ee70
--- /dev/null
+++ b/lib/dns/lookup.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/netaddr.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/result.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 isc_result_t
+build_event(dns_lookup_t *lookup) {
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdataset_t *sigrdataset = NULL;
+
+ name = isc_mem_get(lookup->mctx, sizeof(dns_name_t));
+ dns_name_init(name, NULL);
+ dns_name_dup(dns_fixedname_name(&lookup->name), lookup->mctx, name);
+
+ if (dns_rdataset_isassociated(&lookup->rdataset)) {
+ rdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t));
+ dns_rdataset_init(rdataset);
+ dns_rdataset_clone(&lookup->rdataset, rdataset);
+ }
+
+ if (dns_rdataset_isassociated(&lookup->sigrdataset)) {
+ sigrdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t));
+ dns_rdataset_init(sigrdataset);
+ dns_rdataset_clone(&lookup->sigrdataset, sigrdataset);
+ }
+
+ lookup->event->name = name;
+ lookup->event->rdataset = rdataset;
+ lookup->event->sigrdataset = sigrdataset;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+view_find(dns_lookup_t *lookup, dns_name_t *foundname) {
+ isc_result_t result;
+ dns_name_t *name = dns_fixedname_name(&lookup->name);
+ dns_rdatatype_t type;
+
+ if (lookup->type == dns_rdatatype_rrsig) {
+ type = dns_rdatatype_any;
+ } else {
+ type = lookup->type;
+ }
+
+ result = dns_view_find(lookup->view, name, type, 0, 0, false, false,
+ &lookup->event->db, &lookup->event->node,
+ foundname, &lookup->rdataset,
+ &lookup->sigrdataset);
+ return (result);
+}
+
+static void
+lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event) {
+ isc_result_t result;
+ bool want_restart;
+ bool send_event;
+ dns_name_t *name, *fname, *prefix;
+ dns_fixedname_t foundname, fixed;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned int nlabels;
+ int order;
+ dns_namereln_t namereln;
+ dns_rdata_cname_t cname;
+ dns_rdata_dname_t dname;
+
+ REQUIRE(VALID_LOOKUP(lookup));
+
+ LOCK(&lookup->lock);
+
+ result = ISC_R_SUCCESS;
+ name = dns_fixedname_name(&lookup->name);
+
+ do {
+ lookup->restarts++;
+ want_restart = false;
+ send_event = true;
+
+ if (event == NULL && !lookup->canceled) {
+ fname = dns_fixedname_initname(&foundname);
+ INSIST(!dns_rdataset_isassociated(&lookup->rdataset));
+ INSIST(!dns_rdataset_isassociated(
+ &lookup->sigrdataset));
+ /*
+ * If we have restarted then clear the old node.
+ */
+ if (lookup->event->node != NULL) {
+ INSIST(lookup->event->db != NULL);
+ dns_db_detachnode(lookup->event->db,
+ &lookup->event->node);
+ }
+ if (lookup->event->db != NULL) {
+ dns_db_detach(&lookup->event->db);
+ }
+ result = view_find(lookup, fname);
+ if (result == ISC_R_NOTFOUND) {
+ /*
+ * We don't know anything about the name.
+ * Launch a fetch.
+ */
+ if (lookup->event->node != NULL) {
+ INSIST(lookup->event->db != NULL);
+ dns_db_detachnode(lookup->event->db,
+ &lookup->event->node);
+ }
+ if (lookup->event->db != NULL) {
+ dns_db_detach(&lookup->event->db);
+ }
+ result = start_fetch(lookup);
+ if (result == ISC_R_SUCCESS) {
+ send_event = false;
+ }
+ goto done;
+ }
+ } else if (event != NULL) {
+ result = event->result;
+ fname = dns_fixedname_name(&event->foundname);
+ dns_resolver_destroyfetch(&lookup->fetch);
+ INSIST(event->rdataset == &lookup->rdataset);
+ INSIST(event->sigrdataset == &lookup->sigrdataset);
+ } else {
+ fname = NULL; /* Silence compiler warning. */
+ }
+
+ /*
+ * If we've been canceled, forget about the result.
+ */
+ if (lookup->canceled) {
+ result = ISC_R_CANCELED;
+ }
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ result = build_event(lookup);
+ if (event == NULL) {
+ break;
+ }
+ if (event->db != NULL) {
+ dns_db_attach(event->db, &lookup->event->db);
+ }
+ if (event->node != NULL) {
+ dns_db_attachnode(lookup->event->db,
+ event->node,
+ &lookup->event->node);
+ }
+ break;
+ case DNS_R_CNAME:
+ /*
+ * Copy the CNAME's target into the lookup's
+ * query name and start over.
+ */
+ result = dns_rdataset_first(&lookup->rdataset);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ dns_rdataset_current(&lookup->rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ dns_rdata_reset(&rdata);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ dns_name_copynf(&cname.cname, name);
+ dns_rdata_freestruct(&cname);
+ want_restart = true;
+ send_event = false;
+ break;
+ case DNS_R_DNAME:
+ namereln = dns_name_fullcompare(name, fname, &order,
+ &nlabels);
+ INSIST(namereln == dns_namereln_subdomain);
+ /*
+ * Get the target name of the DNAME.
+ */
+ result = dns_rdataset_first(&lookup->rdataset);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ dns_rdataset_current(&lookup->rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &dname, NULL);
+ dns_rdata_reset(&rdata);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ /*
+ * Construct the new query name and start over.
+ */
+ prefix = dns_fixedname_initname(&fixed);
+ dns_name_split(name, nlabels, prefix, NULL);
+ result = dns_name_concatenate(prefix, &dname.dname,
+ name, NULL);
+ dns_rdata_freestruct(&dname);
+ if (result == ISC_R_SUCCESS) {
+ want_restart = true;
+ send_event = false;
+ }
+ break;
+ default:
+ send_event = true;
+ }
+
+ if (dns_rdataset_isassociated(&lookup->rdataset)) {
+ dns_rdataset_disassociate(&lookup->rdataset);
+ }
+ if (dns_rdataset_isassociated(&lookup->sigrdataset)) {
+ dns_rdataset_disassociate(&lookup->sigrdataset);
+ }
+
+ done:
+ if (event != NULL) {
+ if (event->node != NULL) {
+ dns_db_detachnode(event->db, &event->node);
+ }
+ if (event->db != NULL) {
+ dns_db_detach(&event->db);
+ }
+ isc_event_free(ISC_EVENT_PTR(&event));
+ }
+
+ /*
+ * Limit the number of restarts.
+ */
+ if (want_restart && lookup->restarts == MAX_RESTARTS) {
+ want_restart = false;
+ result = ISC_R_QUOTA;
+ send_event = true;
+ }
+ } while (want_restart);
+
+ if (send_event) {
+ lookup->event->result = result;
+ lookup->event->ev_sender = lookup;
+ isc_task_sendanddetach(&lookup->task,
+ (isc_event_t **)&lookup->event);
+ dns_view_detach(&lookup->view);
+ }
+
+ UNLOCK(&lookup->lock);
+}
+
+static void
+levent_destroy(isc_event_t *event) {
+ dns_lookupevent_t *levent;
+ isc_mem_t *mctx;
+
+ REQUIRE(event->ev_type == DNS_EVENT_LOOKUPDONE);
+ mctx = event->ev_destroy_arg;
+ levent = (dns_lookupevent_t *)event;
+
+ if (levent->name != NULL) {
+ if (dns_name_dynamic(levent->name)) {
+ dns_name_free(levent->name, mctx);
+ }
+ isc_mem_put(mctx, levent->name, sizeof(dns_name_t));
+ }
+ if (levent->rdataset != NULL) {
+ dns_rdataset_disassociate(levent->rdataset);
+ isc_mem_put(mctx, levent->rdataset, sizeof(dns_rdataset_t));
+ }
+ if (levent->sigrdataset != NULL) {
+ dns_rdataset_disassociate(levent->sigrdataset);
+ isc_mem_put(mctx, levent->sigrdataset, sizeof(dns_rdataset_t));
+ }
+ if (levent->node != NULL) {
+ dns_db_detachnode(levent->db, &levent->node);
+ }
+ if (levent->db != NULL) {
+ dns_db_detach(&levent->db);
+ }
+ isc_mem_put(mctx, event, event->ev_size);
+}
+
+isc_result_t
+dns_lookup_create(isc_mem_t *mctx, const dns_name_t *name, dns_rdatatype_t type,
+ dns_view_t *view, unsigned int options, isc_task_t *task,
+ isc_taskaction_t action, void *arg, dns_lookup_t **lookupp) {
+ dns_lookup_t *lookup;
+ isc_event_t *ievent;
+
+ lookup = isc_mem_get(mctx, sizeof(*lookup));
+ lookup->mctx = NULL;
+ isc_mem_attach(mctx, &lookup->mctx);
+ lookup->options = options;
+
+ ievent = isc_event_allocate(mctx, lookup, DNS_EVENT_LOOKUPDONE, action,
+ arg, sizeof(*lookup->event));
+ lookup->event = (dns_lookupevent_t *)ievent;
+ lookup->event->ev_destroy = levent_destroy;
+ lookup->event->ev_destroy_arg = mctx;
+ lookup->event->result = ISC_R_FAILURE;
+ lookup->event->name = NULL;
+ lookup->event->rdataset = NULL;
+ lookup->event->sigrdataset = NULL;
+ lookup->event->db = NULL;
+ lookup->event->node = NULL;
+
+ lookup->task = NULL;
+ isc_task_attach(task, &lookup->task);
+
+ isc_mutex_init(&lookup->lock);
+
+ dns_fixedname_init(&lookup->name);
+
+ dns_name_copynf(name, dns_fixedname_name(&lookup->name));
+
+ lookup->type = type;
+ lookup->view = NULL;
+ dns_view_attach(view, &lookup->view);
+ lookup->fetch = NULL;
+ lookup->restarts = 0;
+ lookup->canceled = false;
+ dns_rdataset_init(&lookup->rdataset);
+ dns_rdataset_init(&lookup->sigrdataset);
+ lookup->magic = LOOKUP_MAGIC;
+
+ *lookupp = lookup;
+
+ lookup_find(lookup, NULL);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_lookup_cancel(dns_lookup_t *lookup) {
+ REQUIRE(VALID_LOOKUP(lookup));
+
+ LOCK(&lookup->lock);
+
+ if (!lookup->canceled) {
+ lookup->canceled = true;
+ if (lookup->fetch != NULL) {
+ INSIST(lookup->view != NULL);
+ dns_resolver_cancelfetch(lookup->fetch);
+ }
+ }
+
+ UNLOCK(&lookup->lock);
+}
+
+void
+dns_lookup_destroy(dns_lookup_t **lookupp) {
+ dns_lookup_t *lookup;
+
+ REQUIRE(lookupp != NULL);
+ lookup = *lookupp;
+ *lookupp = NULL;
+ REQUIRE(VALID_LOOKUP(lookup));
+ REQUIRE(lookup->event == NULL);
+ REQUIRE(lookup->task == NULL);
+ REQUIRE(lookup->view == NULL);
+ if (dns_rdataset_isassociated(&lookup->rdataset)) {
+ dns_rdataset_disassociate(&lookup->rdataset);
+ }
+ if (dns_rdataset_isassociated(&lookup->sigrdataset)) {
+ dns_rdataset_disassociate(&lookup->sigrdataset);
+ }
+
+ isc_mutex_destroy(&lookup->lock);
+ lookup->magic = 0;
+ isc_mem_putanddetach(&lookup->mctx, lookup, sizeof(*lookup));
+}
diff --git a/lib/dns/mapapi b/lib/dns/mapapi
new file mode 100644
index 0000000..1b502d3
--- /dev/null
+++ b/lib/dns/mapapi
@@ -0,0 +1,16 @@
+# This value should be increased whenever changing the structure of
+# any object that will appear in a type 'map' master file (which
+# contains a working memory image of an RBT database), as loading
+# an incorrect memory image produces an inconsistent and probably
+# nonfunctional database. These structures include but are not
+# necessarily limited to dns_masterrawheader, rbtdb_file_header,
+# rbt_file_header, dns_rbtdb, dns_rbt, dns_rbtnode, rdatasetheader.
+#
+# Err on the side of caution: if anything in the RBTDB is changed,
+# bump the value. Making map files unreadable protects the system
+# from instability; it's a feature not a bug.
+#
+# Whenever releasing a new major release of BIND9, set this value
+# back to 1.0 when releasing the first alpha. Map files are *never*
+# compatible across major releases.
+MAPAPI=3.0
diff --git a/lib/dns/master.c b/lib/dns/master.c
new file mode 100644
index 0000000..fc56107
--- /dev/null
+++ b/lib/dns/master.c
@@ -0,0 +1,3264 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/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/result.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
+openfile_map(dns_loadctx_t *lctx, const char *master_file);
+
+static isc_result_t
+load_map(dns_loadctx_t *lctx);
+
+static isc_result_t
+pushfile(const char *master_file, dns_name_t *origin, dns_loadctx_t *lctx);
+
+static isc_result_t
+commit(dns_rdatacallbacks_t *, dns_loadctx_t *, rdatalist_head_t *,
+ dns_name_t *, const char *, unsigned int);
+
+static bool
+is_glue(rdatalist_head_t *, dns_name_t *);
+
+static dns_rdatalist_t *
+grow_rdatalist(int, dns_rdatalist_t *, int, rdatalist_head_t *,
+ rdatalist_head_t *, isc_mem_t *mctx);
+
+static dns_rdata_t *
+grow_rdata(int, dns_rdata_t *, int, rdatalist_head_t *, rdatalist_head_t *,
+ isc_mem_t *);
+
+static void
+load_quantum(isc_task_t *task, isc_event_t *event);
+
+static isc_result_t
+task_send(dns_loadctx_t *lctx);
+
+static void
+loadctx_destroy(dns_loadctx_t *lctx);
+
+#define GETTOKENERR(lexer, options, token, eol, err) \
+ do { \
+ result = gettoken(lexer, options, token, eol, callbacks); \
+ switch (result) { \
+ case ISC_R_SUCCESS: \
+ break; \
+ case ISC_R_UNEXPECTED: \
+ goto insist_and_cleanup; \
+ default: \
+ if (MANYERRS(lctx, result)) { \
+ SETRESULT(lctx, result); \
+ LOGIT(result); \
+ read_till_eol = true; \
+ err goto next_line; \
+ } else \
+ goto log_and_cleanup; \
+ } \
+ if ((token)->type == isc_tokentype_special) { \
+ result = DNS_R_SYNTAX; \
+ if (MANYERRS(lctx, result)) { \
+ SETRESULT(lctx, result); \
+ LOGIT(result); \
+ read_till_eol = true; \
+ goto next_line; \
+ } else \
+ goto log_and_cleanup; \
+ } \
+ } while (0)
+#define GETTOKEN(lexer, options, token, eol) \
+ GETTOKENERR(lexer, options, token, eol, {})
+
+#define COMMITALL \
+ do { \
+ result = commit(callbacks, lctx, &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, \
+ dns_result_totext(result)); \
+ else \
+ LOGIT(result)
+
+#define LOGIT(result) \
+ if (result == ISC_R_NOMEMORY) \
+ (*callbacks->error)(callbacks, "dns_master_load: %s", \
+ dns_result_totext(result)); \
+ else \
+ (*callbacks->error)(callbacks, "%s: %s:%lu: %s", \
+ "dns_master_load", source, line, \
+ dns_result_totext(result))
+
+static unsigned char in_addr_arpa_data[] = "\007IN-ADDR\004ARPA";
+static unsigned char in_addr_arpa_offsets[] = { 0, 8, 13 };
+static dns_name_t const in_addr_arpa =
+ DNS_NAME_INITABSOLUTE(in_addr_arpa_data, in_addr_arpa_offsets);
+
+static unsigned char ip6_int_data[] = "\003IP6\003INT";
+static unsigned char ip6_int_offsets[] = { 0, 4, 8 };
+static dns_name_t const ip6_int = DNS_NAME_INITABSOLUTE(ip6_int_data,
+ ip6_int_offsets);
+
+static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA";
+static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 };
+static dns_name_t const ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data,
+ ip6_arpa_offsets);
+
+static bool
+dns_master_isprimary(dns_loadctx_t *lctx) {
+ return ((lctx->options & DNS_MASTER_ZONE) != 0 &&
+ (lctx->options & DNS_MASTER_SLAVE) == 0 &&
+ (lctx->options & DNS_MASTER_KEY) == 0);
+}
+
+static isc_result_t
+gettoken(isc_lex_t *lex, unsigned int options, isc_token_t *token, bool eol,
+ dns_rdatacallbacks_t *callbacks) {
+ isc_result_t result;
+
+ options |= ISC_LEXOPT_EOL | ISC_LEXOPT_EOF | ISC_LEXOPT_DNSMULTILINE |
+ ISC_LEXOPT_ESCAPE;
+ result = isc_lex_gettoken(lex, options, token);
+ if (result != ISC_R_SUCCESS) {
+ switch (result) {
+ case ISC_R_NOMEMORY:
+ return (ISC_R_NOMEMORY);
+ default:
+ (*callbacks->error)(callbacks,
+ "dns_master_load: %s:%lu:"
+ " isc_lex_gettoken() failed: %s",
+ isc_lex_getsourcename(lex),
+ isc_lex_getsourceline(lex),
+ isc_result_totext(result));
+ return (result);
+ }
+ /*NOTREACHED*/
+ }
+ if (eol != true) {
+ if (token->type == isc_tokentype_eol ||
+ token->type == isc_tokentype_eof)
+ {
+ {
+ unsigned long int line;
+ const char *what;
+ const char *file;
+ file = isc_lex_getsourcename(lex);
+ line = isc_lex_getsourceline(lex);
+ if (token->type == isc_tokentype_eol) {
+ line--;
+ what = "line";
+ } else {
+ what = "file";
+ }
+ (*callbacks->error)(callbacks,
+ "dns_master_load: %s:%lu: "
+ "unexpected end of %s",
+ file, line, what);
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_loadctx_attach(dns_loadctx_t *source, dns_loadctx_t **target) {
+ REQUIRE(target != NULL && *target == NULL);
+ REQUIRE(DNS_LCTX_VALID(source));
+
+ isc_refcount_increment(&source->references);
+
+ *target = source;
+}
+
+void
+dns_loadctx_detach(dns_loadctx_t **lctxp) {
+ dns_loadctx_t *lctx;
+
+ REQUIRE(lctxp != NULL);
+ lctx = *lctxp;
+ *lctxp = NULL;
+ REQUIRE(DNS_LCTX_VALID(lctx));
+
+ if (isc_refcount_decrement(&lctx->references) == 1) {
+ loadctx_destroy(lctx);
+ }
+}
+
+static void
+incctx_destroy(isc_mem_t *mctx, dns_incctx_t *ictx) {
+ dns_incctx_t *parent;
+
+again:
+ parent = ictx->parent;
+ ictx->parent = NULL;
+
+ isc_mem_put(mctx, ictx, sizeof(*ictx));
+
+ if (parent != NULL) {
+ ictx = parent;
+ goto again;
+ }
+}
+
+static void
+loadctx_destroy(dns_loadctx_t *lctx) {
+ REQUIRE(DNS_LCTX_VALID(lctx));
+
+ isc_refcount_destroy(&lctx->references);
+
+ lctx->magic = 0;
+ if (lctx->inc != NULL) {
+ incctx_destroy(lctx->mctx, lctx->inc);
+ }
+
+ if (lctx->f != NULL) {
+ isc_result_t result = isc_stdio_close(lctx->f);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_stdio_close() failed: %s",
+ isc_result_totext(result));
+ }
+ }
+
+ /* isc_lex_destroy() will close all open streams */
+ if (lctx->lex != NULL && !lctx->keep_lex) {
+ isc_lex_destroy(&lctx->lex);
+ }
+
+ if (lctx->task != NULL) {
+ isc_task_detach(&lctx->task);
+ }
+
+ isc_mem_putanddetach(&lctx->mctx, lctx, sizeof(*lctx));
+}
+
+static isc_result_t
+incctx_create(isc_mem_t *mctx, dns_name_t *origin, dns_incctx_t **ictxp) {
+ dns_incctx_t *ictx;
+ isc_region_t r;
+ int i;
+
+ ictx = isc_mem_get(mctx, sizeof(*ictx));
+
+ for (i = 0; i < NBUFS; i++) {
+ dns_fixedname_init(&ictx->fixed[i]);
+ ictx->in_use[i] = false;
+ }
+
+ ictx->origin_in_use = 0;
+ ictx->origin = dns_fixedname_name(&ictx->fixed[ictx->origin_in_use]);
+ ictx->in_use[ictx->origin_in_use] = true;
+ dns_name_toregion(origin, &r);
+ dns_name_fromregion(ictx->origin, &r);
+
+ ictx->glue = NULL;
+ ictx->current = NULL;
+ ictx->glue_in_use = -1;
+ ictx->current_in_use = -1;
+ ictx->parent = NULL;
+ ictx->drop = false;
+ ictx->glue_line = 0;
+ ictx->current_line = 0;
+ ictx->origin_changed = true;
+
+ *ictxp = ictx;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loadctx_create(dns_masterformat_t format, isc_mem_t *mctx, unsigned int options,
+ uint32_t resign, dns_name_t *top, dns_rdataclass_t zclass,
+ dns_name_t *origin, dns_rdatacallbacks_t *callbacks,
+ isc_task_t *task, dns_loaddonefunc_t done, void *done_arg,
+ dns_masterincludecb_t include_cb, void *include_arg,
+ isc_lex_t *lex, dns_loadctx_t **lctxp) {
+ dns_loadctx_t *lctx;
+ isc_result_t result;
+ isc_region_t r;
+ isc_lexspecials_t specials;
+
+ REQUIRE(lctxp != NULL && *lctxp == NULL);
+ REQUIRE(callbacks != NULL);
+ REQUIRE(callbacks->add != NULL);
+ REQUIRE(callbacks->error != NULL);
+ REQUIRE(callbacks->warn != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(dns_name_isabsolute(top));
+ REQUIRE(dns_name_isabsolute(origin));
+ REQUIRE((task == NULL && done == NULL) ||
+ (task != NULL && done != NULL));
+
+ lctx = isc_mem_get(mctx, sizeof(*lctx));
+
+ lctx->inc = NULL;
+ result = incctx_create(mctx, origin, &lctx->inc);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_ctx;
+ }
+
+ lctx->maxttl = 0;
+
+ lctx->format = format;
+ switch (format) {
+ case dns_masterformat_text:
+ lctx->openfile = openfile_text;
+ lctx->load = load_text;
+ break;
+ case dns_masterformat_raw:
+ lctx->openfile = openfile_raw;
+ lctx->load = load_raw;
+ break;
+ case dns_masterformat_map:
+ lctx->openfile = openfile_map;
+ lctx->load = load_map;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (lex != NULL) {
+ lctx->lex = lex;
+ lctx->keep_lex = true;
+ } else {
+ lctx->lex = NULL;
+ result = isc_lex_create(mctx, TOKENSIZ, &lctx->lex);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_inc;
+ }
+ lctx->keep_lex = false;
+ /*
+ * If specials change update dns_test_rdatafromstring()
+ * in lib/dns/tests/dnstest.c.
+ */
+ memset(specials, 0, sizeof(specials));
+ specials[0] = 1;
+ specials['('] = 1;
+ specials[')'] = 1;
+ specials['"'] = 1;
+ isc_lex_setspecials(lctx->lex, specials);
+ isc_lex_setcomments(lctx->lex, ISC_LEXCOMMENT_DNSMASTERFILE);
+ }
+
+ lctx->ttl_known = ((options & DNS_MASTER_NOTTL) != 0);
+ lctx->ttl = 0;
+ lctx->default_ttl_known = lctx->ttl_known;
+ lctx->default_ttl = 0;
+ lctx->warn_1035 = true; /* XXX Argument? */
+ lctx->warn_tcr = true; /* XXX Argument? */
+ lctx->warn_sigexpired = true; /* XXX Argument? */
+ lctx->options = options;
+ lctx->seen_include = false;
+ lctx->zclass = zclass;
+ lctx->resign = resign;
+ lctx->result = ISC_R_SUCCESS;
+ lctx->include_cb = include_cb;
+ lctx->include_arg = include_arg;
+ isc_stdtime_get(&lctx->now);
+
+ lctx->top = dns_fixedname_initname(&lctx->fixed_top);
+ dns_name_toregion(top, &r);
+ dns_name_fromregion(lctx->top, &r);
+
+ lctx->f = NULL;
+ lctx->first = true;
+ dns_master_initrawheader(&lctx->header);
+
+ lctx->loop_cnt = (done != NULL) ? 100 : 0;
+ lctx->callbacks = callbacks;
+ lctx->task = NULL;
+ if (task != NULL) {
+ isc_task_attach(task, &lctx->task);
+ }
+ lctx->done = done;
+ lctx->done_arg = done_arg;
+ atomic_init(&lctx->canceled, false);
+ lctx->mctx = NULL;
+ isc_mem_attach(mctx, &lctx->mctx);
+
+ isc_refcount_init(&lctx->references, 1); /* Implicit attach. */
+
+ lctx->magic = DNS_LCTX_MAGIC;
+ *lctxp = lctx;
+ return (ISC_R_SUCCESS);
+
+cleanup_inc:
+ incctx_destroy(mctx, lctx->inc);
+cleanup_ctx:
+ isc_mem_put(mctx, lctx, sizeof(*lctx));
+ return (result);
+}
+
+static const char *hex = "0123456789abcdef0123456789ABCDEF";
+
+/*%
+ * Convert value into a nibble sequence from least significant to most
+ * significant nibble. Zero fill upper most significant nibbles if
+ * required to make the width.
+ *
+ * Returns the number of characters that should have been written without
+ * counting the terminating NUL.
+ */
+static unsigned int
+nibbles(char *numbuf, size_t length, unsigned int width, char mode, int value) {
+ unsigned int count = 0;
+
+ /*
+ * This reserve space for the NUL string terminator.
+ */
+ if (length > 0U) {
+ *numbuf = '\0';
+ length--;
+ }
+ do {
+ char val = hex[(value & 0x0f) + ((mode == 'n') ? 0 : 16)];
+ value >>= 4;
+ if (length > 0U) {
+ *numbuf++ = val;
+ *numbuf = '\0';
+ length--;
+ }
+ if (width > 0) {
+ width--;
+ }
+ count++;
+ /*
+ * If width is non zero then we need to add a label separator.
+ * If value is non zero then we need to add another label and
+ * that requires a label separator.
+ */
+ if (width > 0 || value != 0) {
+ if (length > 0U) {
+ *numbuf++ = '.';
+ *numbuf = '\0';
+ length--;
+ }
+ if (width > 0) {
+ width--;
+ }
+ count++;
+ }
+ } while (value != 0 || width > 0);
+ return (count);
+}
+
+static isc_result_t
+genname(char *name, int it, char *buffer, size_t length) {
+ char fmt[sizeof("%04000000000d")];
+ char numbuf[128];
+ char *cp;
+ char mode[2] = { 0 };
+ char brace[2] = { 0 };
+ char comma1[2] = { 0 };
+ char comma2[2] = { 0 };
+ int delta = 0;
+ isc_textregion_t r;
+ unsigned int n;
+ unsigned int width;
+ bool nibblemode;
+
+ r.base = buffer;
+ r.length = (unsigned int)length;
+
+ while (*name != '\0') {
+ if (*name == '$') {
+ name++;
+ if (*name == '$') {
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = *name++;
+ isc_textregion_consume(&r, 1);
+ continue;
+ }
+ nibblemode = false;
+ strlcpy(fmt, "%d", sizeof(fmt));
+ /* Get format specifier. */
+ if (*name == '{') {
+ n = sscanf(name,
+ "{%d%1[,}]%u%1[,}]%1[doxXnN]%1[}]",
+ &delta, comma1, &width, comma2, mode,
+ brace);
+ if (n < 2 || n > 6) {
+ return (DNS_R_SYNTAX);
+ }
+ if (comma1[0] == '}') {
+ /* %{delta} */
+ } else if (comma1[0] == ',' && comma2[0] == '}')
+ {
+ /* %{delta,width} */
+ n = snprintf(fmt, sizeof(fmt), "%%0%ud",
+ width);
+ } else if (comma1[0] == ',' &&
+ comma2[0] == ',' && mode[0] != 0 &&
+ brace[0] == '}')
+ {
+ /* %{delta,width,format} */
+ if (mode[0] == 'n' || mode[0] == 'N') {
+ nibblemode = true;
+ }
+ n = snprintf(fmt, sizeof(fmt),
+ "%%0%u%c", width, mode[0]);
+ } else {
+ return (DNS_R_SYNTAX);
+ }
+ if (n >= sizeof(fmt)) {
+ return (ISC_R_NOSPACE);
+ }
+ /* Skip past closing brace. */
+ while (*name != '\0' && *name++ != '}') {
+ continue;
+ }
+ }
+ /*
+ * 'it' is >= 0 so we don't need to check for
+ * underflow.
+ */
+ if ((it > 0 && delta > INT_MAX - it)) {
+ return (ISC_R_RANGE);
+ }
+ if (nibblemode) {
+ n = nibbles(numbuf, sizeof(numbuf), width,
+ mode[0], it + delta);
+ } else {
+ n = snprintf(numbuf, sizeof(numbuf), fmt,
+ it + delta);
+ }
+ if (n >= sizeof(numbuf)) {
+ return (ISC_R_NOSPACE);
+ }
+ cp = numbuf;
+ while (*cp != '\0') {
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = *cp++;
+ isc_textregion_consume(&r, 1);
+ }
+ } else if (*name == '\\') {
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = *name++;
+ isc_textregion_consume(&r, 1);
+ if (*name == '\0') {
+ continue;
+ }
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = *name++;
+ isc_textregion_consume(&r, 1);
+ } else {
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = *name++;
+ isc_textregion_consume(&r, 1);
+ }
+ }
+ if (r.length == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = '\0';
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generate(dns_loadctx_t *lctx, char *range, char *lhs, char *gtype, char *rhs,
+ const char *source, unsigned int line) {
+ char *target_mem = NULL;
+ char *lhsbuf = NULL;
+ char *rhsbuf = NULL;
+ dns_fixedname_t ownerfixed;
+ dns_name_t *owner;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdatacallbacks_t *callbacks;
+ dns_rdatalist_t rdatalist;
+ dns_rdatatype_t type;
+ rdatalist_head_t head;
+ int target_size = MINTSIZ; /* only one rdata at a time */
+ isc_buffer_t buffer;
+ isc_buffer_t target;
+ isc_result_t result;
+ isc_textregion_t r;
+ int n, start, stop, step = 0;
+ unsigned int i;
+ dns_incctx_t *ictx;
+ char dummy[2];
+
+ ictx = lctx->inc;
+ callbacks = lctx->callbacks;
+ owner = dns_fixedname_initname(&ownerfixed);
+ ISC_LIST_INIT(head);
+
+ target_mem = isc_mem_get(lctx->mctx, target_size);
+ rhsbuf = isc_mem_get(lctx->mctx, DNS_MASTER_RHS);
+ lhsbuf = isc_mem_get(lctx->mctx, DNS_MASTER_LHS);
+ if (target_mem == NULL || rhsbuf == NULL || lhsbuf == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto error_cleanup;
+ }
+ isc_buffer_init(&target, target_mem, target_size);
+
+ n = sscanf(range, "%d-%d%1[/]%d", &start, &stop, dummy, &step);
+ if ((n != 2 && n != 4) || (start < 0) || (stop < 0) ||
+ (n == 4 && step < 1) || (stop < start))
+ {
+ (*callbacks->error)(callbacks, "%s: %s:%lu: invalid range '%s'",
+ "$GENERATE", source, line, range);
+ result = DNS_R_SYNTAX;
+ goto insist_cleanup;
+ }
+ if (n == 2) {
+ step = 1;
+ }
+
+ /*
+ * Get type.
+ */
+ r.base = gtype;
+ r.length = strlen(gtype);
+ result = dns_rdatatype_fromtext(&type, &r);
+ if (result != ISC_R_SUCCESS) {
+ (*callbacks->error)(callbacks,
+ "%s: %s:%lu: unknown RR type '%s'",
+ "$GENERATE", source, line, gtype);
+ goto insist_cleanup;
+ }
+
+ /*
+ * RFC2930: TKEY and TSIG are not allowed to be loaded
+ * from master files.
+ */
+ if (dns_master_isprimary(lctx) && dns_rdatatype_ismeta(type)) {
+ (*callbacks->error)(callbacks, "%s: %s:%lu: meta RR type '%s'",
+ "$GENERATE", source, line, gtype);
+ result = DNS_R_METATYPE;
+ goto insist_cleanup;
+ }
+
+ for (i = start; i <= (unsigned int)stop; i += step) {
+ result = genname(lhs, i, lhsbuf, DNS_MASTER_LHS);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+ result = genname(rhs, i, rhsbuf, DNS_MASTER_RHS);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+
+ isc_buffer_init(&buffer, lhsbuf, strlen(lhsbuf));
+ isc_buffer_add(&buffer, strlen(lhsbuf));
+ isc_buffer_setactive(&buffer, strlen(lhsbuf));
+ result = dns_name_fromtext(owner, &buffer, ictx->origin, 0,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+
+ if (dns_master_isprimary(lctx) &&
+ !dns_name_issubdomain(owner, lctx->top))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(owner, namebuf, sizeof(namebuf));
+ /*
+ * Ignore out-of-zone data.
+ */
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: "
+ "ignoring out-of-zone data (%s)",
+ source, line, namebuf);
+ continue;
+ }
+
+ isc_buffer_init(&buffer, rhsbuf, strlen(rhsbuf));
+ isc_buffer_add(&buffer, strlen(rhsbuf));
+ isc_buffer_setactive(&buffer, strlen(rhsbuf));
+
+ result = isc_lex_openbuffer(lctx->lex, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+
+ isc_buffer_init(&target, target_mem, target_size);
+ result = dns_rdata_fromtext(&rdata, lctx->zclass, type,
+ lctx->lex, ictx->origin, 0,
+ lctx->mctx, &target, callbacks);
+ RUNTIME_CHECK(isc_lex_close(lctx->lex) == ISC_R_SUCCESS);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+
+ dns_rdatalist_init(&rdatalist);
+ rdatalist.type = type;
+ rdatalist.rdclass = lctx->zclass;
+ rdatalist.ttl = lctx->ttl;
+ ISC_LIST_PREPEND(head, &rdatalist, link);
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
+ result = commit(callbacks, lctx, &head, owner, source, line);
+ ISC_LIST_UNLINK(rdatalist.rdata, &rdata, link);
+ if (result != ISC_R_SUCCESS) {
+ goto error_cleanup;
+ }
+ dns_rdata_reset(&rdata);
+ }
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+
+error_cleanup:
+ if (result == ISC_R_NOMEMORY) {
+ (*callbacks->error)(callbacks, "$GENERATE: %s",
+ dns_result_totext(result));
+ } else {
+ (*callbacks->error)(callbacks, "$GENERATE: %s:%lu: %s", source,
+ line, dns_result_totext(result));
+ }
+
+insist_cleanup:
+ INSIST(result != ISC_R_SUCCESS);
+
+cleanup:
+ if (target_mem != NULL) {
+ isc_mem_put(lctx->mctx, target_mem, target_size);
+ }
+ if (lhsbuf != NULL) {
+ isc_mem_put(lctx->mctx, lhsbuf, DNS_MASTER_LHS);
+ }
+ if (rhsbuf != NULL) {
+ isc_mem_put(lctx->mctx, rhsbuf, DNS_MASTER_RHS);
+ }
+ return (result);
+}
+
+static void
+limit_ttl(dns_rdatacallbacks_t *callbacks, const char *source,
+ unsigned int line, uint32_t *ttlp) {
+ if (*ttlp > 0x7fffffffUL) {
+ (callbacks->warn)(callbacks,
+ "%s: %s:%lu: "
+ "$TTL %lu > MAXTTL, "
+ "setting $TTL to 0",
+ "dns_master_load", source, line, *ttlp);
+ *ttlp = 0;
+ }
+}
+
+static isc_result_t
+check_ns(dns_loadctx_t *lctx, isc_token_t *token, const char *source,
+ unsigned long line) {
+ char *tmp = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ void (*callback)(struct dns_rdatacallbacks *, const char *, ...);
+
+ if ((lctx->options & DNS_MASTER_FATALNS) != 0) {
+ callback = lctx->callbacks->error;
+ } else {
+ callback = lctx->callbacks->warn;
+ }
+
+ if (token->type == isc_tokentype_string) {
+ struct in_addr addr;
+ struct in6_addr addr6;
+
+ tmp = isc_mem_strdup(lctx->mctx, DNS_AS_STR(*token));
+ /*
+ * Catch both "1.2.3.4" and "1.2.3.4."
+ */
+ if (tmp[strlen(tmp) - 1] == '.') {
+ tmp[strlen(tmp) - 1] = '\0';
+ }
+ if (inet_pton(AF_INET, tmp, &addr) == 1 ||
+ inet_pton(AF_INET6, tmp, &addr6) == 1)
+ {
+ result = DNS_R_NSISADDRESS;
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ (*callback)(lctx->callbacks,
+ "%s:%lu: NS record '%s' "
+ "appears to be an address",
+ source, line, DNS_AS_STR(*token));
+ }
+ if (tmp != NULL) {
+ isc_mem_free(lctx->mctx, tmp);
+ }
+ return (result);
+}
+
+static void
+check_wildcard(dns_incctx_t *ictx, const char *source, unsigned long line,
+ dns_rdatacallbacks_t *callbacks) {
+ dns_name_t *name;
+
+ name = (ictx->glue != NULL) ? ictx->glue : ictx->current;
+ if (dns_name_internalwildcard(name)) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: warning: ownername "
+ "'%s' contains an non-terminal wildcard",
+ source, line, namebuf);
+ }
+}
+
+static isc_result_t
+openfile_text(dns_loadctx_t *lctx, const char *master_file) {
+ return (isc_lex_openfile(lctx->lex, master_file));
+}
+
+static int
+find_free_name(dns_incctx_t *incctx) {
+ int i;
+
+ for (i = 0; i < (NBUFS - 1); i++) {
+ if (!incctx->in_use[i]) {
+ break;
+ }
+ }
+ INSIST(!incctx->in_use[i]);
+ return (i);
+}
+
+static isc_result_t
+load_text(dns_loadctx_t *lctx) {
+ dns_rdataclass_t rdclass;
+ dns_rdatatype_t type, covers;
+ uint32_t ttl_offset = 0;
+ dns_name_t *new_name;
+ bool current_has_delegation = false;
+ bool done = false;
+ bool finish_origin = false;
+ bool finish_include = false;
+ bool read_till_eol = false;
+ bool initialws;
+ char *include_file = NULL;
+ isc_token_t token;
+ isc_result_t result = ISC_R_UNEXPECTED;
+ rdatalist_head_t glue_list;
+ rdatalist_head_t current_list;
+ dns_rdatalist_t *this;
+ dns_rdatalist_t *rdatalist = NULL;
+ dns_rdatalist_t *new_rdatalist;
+ int rdlcount = 0;
+ int rdlcount_save = 0;
+ int rdatalist_size = 0;
+ isc_buffer_t buffer;
+ isc_buffer_t target;
+ isc_buffer_t target_ft;
+ isc_buffer_t target_save;
+ dns_rdata_t *rdata = NULL;
+ dns_rdata_t *new_rdata;
+ int rdcount = 0;
+ int rdcount_save = 0;
+ int rdata_size = 0;
+ unsigned char *target_mem = NULL;
+ int target_size = TSIZ;
+ int new_in_use;
+ unsigned int loop_cnt = 0;
+ isc_mem_t *mctx;
+ dns_rdatacallbacks_t *callbacks;
+ dns_incctx_t *ictx;
+ char *range = NULL;
+ char *lhs = NULL;
+ char *gtype = NULL;
+ char *rhs = NULL;
+ const char *source;
+ unsigned long line = 0;
+ bool explicit_ttl;
+ char classname1[DNS_RDATACLASS_FORMATSIZE];
+ char classname2[DNS_RDATACLASS_FORMATSIZE];
+ unsigned int options = 0;
+
+ REQUIRE(DNS_LCTX_VALID(lctx));
+ callbacks = lctx->callbacks;
+ mctx = lctx->mctx;
+ ictx = lctx->inc;
+
+ ISC_LIST_INIT(glue_list);
+ ISC_LIST_INIT(current_list);
+
+ /*
+ * Allocate target_size of buffer space. This is greater than twice
+ * the maximum individual RR data size.
+ */
+ target_mem = isc_mem_get(mctx, target_size);
+ isc_buffer_init(&target, target_mem, target_size);
+ target_save = target;
+
+ if ((lctx->options & DNS_MASTER_CHECKNAMES) != 0) {
+ options |= DNS_RDATA_CHECKNAMES;
+ }
+ if ((lctx->options & DNS_MASTER_CHECKNAMESFAIL) != 0) {
+ options |= DNS_RDATA_CHECKNAMESFAIL;
+ }
+ if ((lctx->options & DNS_MASTER_CHECKMX) != 0) {
+ options |= DNS_RDATA_CHECKMX;
+ }
+ if ((lctx->options & DNS_MASTER_CHECKMXFAIL) != 0) {
+ options |= DNS_RDATA_CHECKMXFAIL;
+ }
+ source = isc_lex_getsourcename(lctx->lex);
+ do {
+ initialws = false;
+ line = isc_lex_getsourceline(lctx->lex);
+ GETTOKEN(lctx->lex, ISC_LEXOPT_INITIALWS | ISC_LEXOPT_QSTRING,
+ &token, true);
+ line = isc_lex_getsourceline(lctx->lex);
+
+ if (token.type == isc_tokentype_eof) {
+ if (read_till_eol) {
+ WARNUNEXPECTEDEOF(lctx->lex);
+ }
+ /* Pop the include stack? */
+ if (ictx->parent != NULL) {
+ COMMITALL;
+ lctx->inc = ictx->parent;
+ ictx->parent = NULL;
+ incctx_destroy(lctx->mctx, ictx);
+ RUNTIME_CHECK(isc_lex_close(lctx->lex) ==
+ ISC_R_SUCCESS);
+ line = isc_lex_getsourceline(lctx->lex);
+ POST(line);
+ source = isc_lex_getsourcename(lctx->lex);
+ ictx = lctx->inc;
+ continue;
+ }
+ done = true;
+ continue;
+ }
+
+ if (token.type == isc_tokentype_eol) {
+ read_till_eol = false;
+ continue; /* blank line */
+ }
+
+ if (read_till_eol) {
+ continue;
+ }
+
+ if (token.type == isc_tokentype_initialws) {
+ /*
+ * Still working on the same name.
+ */
+ initialws = true;
+ } else if (token.type == isc_tokentype_string ||
+ token.type == isc_tokentype_qstring)
+ {
+ /*
+ * "$" Support.
+ *
+ * "$ORIGIN" and "$INCLUDE" can both take domain names.
+ * The processing of "$ORIGIN" and "$INCLUDE" extends
+ * across the normal domain name processing.
+ */
+
+ if (strcasecmp(DNS_AS_STR(token), "$ORIGIN") == 0) {
+ GETTOKEN(lctx->lex, 0, &token, false);
+ finish_origin = true;
+ } else if (strcasecmp(DNS_AS_STR(token), "$TTL") == 0) {
+ GETTOKENERR(lctx->lex, 0, &token, false,
+ lctx->ttl = 0;
+ lctx->default_ttl_known = true;);
+ result = dns_ttl_fromtext(
+ &token.value.as_textregion, &lctx->ttl);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ lctx->ttl = 0;
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ limit_ttl(callbacks, source, line, &lctx->ttl);
+ lctx->default_ttl = lctx->ttl;
+ lctx->default_ttl_known = true;
+ EXPECTEOL;
+ continue;
+ } else if (strcasecmp(DNS_AS_STR(token), "$INCLUDE") ==
+ 0)
+ {
+ COMMITALL;
+ if ((lctx->options & DNS_MASTER_NOINCLUDE) != 0)
+ {
+ (callbacks->error)(callbacks,
+ "%s: %s:%lu: "
+ "$INCLUDE not "
+ "allowed",
+ "dns_master_load",
+ source, line);
+ result = DNS_R_REFUSED;
+ goto insist_and_cleanup;
+ }
+ if (ttl_offset != 0) {
+ (callbacks->error)(callbacks,
+ "%s: %s:%lu: "
+ "$INCLUDE "
+ "may not be used "
+ "with $DATE",
+ "dns_master_load",
+ source, line);
+ result = DNS_R_SYNTAX;
+ goto insist_and_cleanup;
+ }
+ GETTOKEN(lctx->lex, ISC_LEXOPT_QSTRING, &token,
+ false);
+ if (include_file != NULL) {
+ isc_mem_free(mctx, include_file);
+ }
+ include_file =
+ isc_mem_strdup(mctx, DNS_AS_STR(token));
+ GETTOKEN(lctx->lex, 0, &token, true);
+
+ if (token.type == isc_tokentype_eol ||
+ token.type == isc_tokentype_eof)
+ {
+ if (token.type == isc_tokentype_eof) {
+ WARNUNEXPECTEDEOF(lctx->lex);
+ }
+ /*
+ * No origin field.
+ */
+ result = pushfile(include_file,
+ ictx->origin, lctx);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ LOGITFILE(result, include_file);
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ LOGITFILE(result, include_file);
+ goto insist_and_cleanup;
+ }
+ ictx = lctx->inc;
+ source = isc_lex_getsourcename(
+ lctx->lex);
+ line = isc_lex_getsourceline(lctx->lex);
+ POST(line);
+ continue;
+ }
+ /*
+ * There is an origin field. Fall through
+ * to domain name processing code and do
+ * the actual inclusion later.
+ */
+ finish_include = true;
+ } else if (strcasecmp(DNS_AS_STR(token), "$DATE") == 0)
+ {
+ int64_t dump_time64;
+ isc_stdtime_t dump_time, current_time;
+ GETTOKEN(lctx->lex, 0, &token, false);
+ isc_stdtime_get(&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(__FILE__, __LINE__,
+ "%s: %s:%lu: $DATE "
+ "outside epoch",
+ "dns_master_load",
+ source, line);
+ result = ISC_R_UNEXPECTED;
+ goto insist_and_cleanup;
+ }
+ if (dump_time > current_time) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "%s: %s:%lu: "
+ "$DATE in future, "
+ "using current date",
+ "dns_master_load",
+ source, line);
+ dump_time = current_time;
+ }
+ ttl_offset = current_time - dump_time;
+ EXPECTEOL;
+ continue;
+ } else if (strcasecmp(DNS_AS_STR(token), "$GENERATE") ==
+ 0)
+ {
+ /*
+ * Lazy cleanup.
+ */
+ if (range != NULL) {
+ isc_mem_free(mctx, range);
+ }
+ if (lhs != NULL) {
+ isc_mem_free(mctx, lhs);
+ }
+ if (gtype != NULL) {
+ isc_mem_free(mctx, gtype);
+ }
+ if (rhs != NULL) {
+ isc_mem_free(mctx, rhs);
+ }
+ range = lhs = gtype = rhs = NULL;
+ /* RANGE */
+ GETTOKEN(lctx->lex, 0, &token, false);
+ range = isc_mem_strdup(mctx, DNS_AS_STR(token));
+ /* LHS */
+ GETTOKEN(lctx->lex, 0, &token, false);
+ lhs = isc_mem_strdup(mctx, DNS_AS_STR(token));
+ rdclass = 0;
+ explicit_ttl = false;
+ /* CLASS? */
+ GETTOKEN(lctx->lex, 0, &token, false);
+ if (dns_rdataclass_fromtext(
+ &rdclass,
+ &token.value.as_textregion) ==
+ ISC_R_SUCCESS)
+ {
+ GETTOKEN(lctx->lex, 0, &token, false);
+ }
+ /* TTL? */
+ if (dns_ttl_fromtext(&token.value.as_textregion,
+ &lctx->ttl) ==
+ ISC_R_SUCCESS)
+ {
+ limit_ttl(callbacks, source, line,
+ &lctx->ttl);
+ lctx->ttl_known = true;
+ explicit_ttl = true;
+ GETTOKEN(lctx->lex, 0, &token, false);
+ }
+ /* CLASS? */
+ if (rdclass == 0 &&
+ dns_rdataclass_fromtext(
+ &rdclass,
+ &token.value.as_textregion) ==
+ ISC_R_SUCCESS)
+ {
+ GETTOKEN(lctx->lex, 0, &token, false);
+ }
+ /* TYPE */
+ gtype = isc_mem_strdup(mctx, DNS_AS_STR(token));
+ /* RHS */
+ GETTOKEN(lctx->lex, ISC_LEXOPT_QSTRING, &token,
+ false);
+ rhs = isc_mem_strdup(mctx, DNS_AS_STR(token));
+ if (!lctx->ttl_known &&
+ !lctx->default_ttl_known)
+ {
+ (*callbacks->error)(callbacks,
+ "%s: %s:%lu: no "
+ "TTL specified",
+ "dns_master_load",
+ source, line);
+ result = DNS_R_NOTTL;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ lctx->ttl = 0;
+ } else {
+ goto insist_and_cleanup;
+ }
+ } else if (!explicit_ttl &&
+ lctx->default_ttl_known)
+ {
+ lctx->ttl = lctx->default_ttl;
+ }
+ /*
+ * If the class specified does not match the
+ * zone's class print out a error message and
+ * exit.
+ */
+ if (rdclass != 0 && rdclass != lctx->zclass) {
+ goto bad_class;
+ }
+ result = generate(lctx, range, lhs, gtype, rhs,
+ source, line);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ EXPECTEOL;
+ continue;
+ } else if (strncasecmp(DNS_AS_STR(token), "$", 1) == 0)
+ {
+ (callbacks->error)(callbacks,
+ "%s: %s:%lu: "
+ "unknown $ directive '%s'",
+ "dns_master_load", source,
+ line, DNS_AS_STR(token));
+ result = DNS_R_SYNTAX;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ /*
+ * Normal processing resumes.
+ */
+ new_in_use = find_free_name(ictx);
+ new_name = dns_fixedname_initname(
+ &ictx->fixed[new_in_use]);
+ isc_buffer_init(&buffer, token.value.as_region.base,
+ token.value.as_region.length);
+ isc_buffer_add(&buffer, token.value.as_region.length);
+ isc_buffer_setactive(&buffer,
+ token.value.as_region.length);
+ result = dns_name_fromtext(new_name, &buffer,
+ ictx->origin, 0, NULL);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ LOGIT(result);
+ read_till_eol = true;
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ goto log_and_cleanup;
+ }
+
+ /*
+ * Finish $ORIGIN / $INCLUDE processing if required.
+ */
+ if (finish_origin) {
+ if (ictx->origin_in_use != -1) {
+ ictx->in_use[ictx->origin_in_use] =
+ false;
+ }
+ ictx->origin_in_use = new_in_use;
+ ictx->in_use[ictx->origin_in_use] = true;
+ ictx->origin = new_name;
+ ictx->origin_changed = true;
+ finish_origin = false;
+ EXPECTEOL;
+ continue;
+ }
+ if (finish_include) {
+ finish_include = false;
+ EXPECTEOL;
+ result = pushfile(include_file, new_name, lctx);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ LOGITFILE(result, include_file);
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ LOGITFILE(result, include_file);
+ goto insist_and_cleanup;
+ }
+ ictx = lctx->inc;
+ ictx->origin_changed = true;
+ source = isc_lex_getsourcename(lctx->lex);
+ line = isc_lex_getsourceline(lctx->lex);
+ POST(line);
+ continue;
+ }
+
+ /*
+ * "$" Processing Finished
+ */
+
+ /*
+ * If we are processing glue and the new name does
+ * not match the current glue name, commit the glue
+ * and pop stacks leaving us in 'normal' processing
+ * state. Linked lists are undone by commit().
+ */
+ if (ictx->glue != NULL &&
+ !dns_name_caseequal(ictx->glue, new_name))
+ {
+ result = commit(callbacks, lctx, &glue_list,
+ ictx->glue, source,
+ ictx->glue_line);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ if (ictx->glue_in_use != -1) {
+ ictx->in_use[ictx->glue_in_use] = false;
+ }
+ ictx->glue_in_use = -1;
+ ictx->glue = NULL;
+ rdcount = rdcount_save;
+ rdlcount = rdlcount_save;
+ target = target_save;
+ }
+
+ /*
+ * If we are in 'normal' processing state and the new
+ * name does not match the current name, see if the
+ * new name is for glue and treat it as such,
+ * otherwise we have a new name so commit what we
+ * have.
+ */
+ if ((ictx->glue == NULL) &&
+ (ictx->current == NULL ||
+ !dns_name_caseequal(ictx->current, new_name)))
+ {
+ if (current_has_delegation &&
+ is_glue(&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(__FILE__, __LINE__,
+ "%s:%lu: isc_lex_gettoken() returned "
+ "unexpected token type (%d)",
+ source, line, token.type);
+ result = ISC_R_UNEXPECTED;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ LOGIT(result);
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ /*
+ * Find TTL, class and type. Both TTL and class are optional
+ * and may occur in any order if they exist. TTL and class
+ * come before type which must exist.
+ *
+ * [<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(__FILE__, __LINE__,
+ "isc_lex_gettoken() returned "
+ "unexpected token type");
+ result = ISC_R_UNEXPECTED;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ if (rdclass == 0 &&
+ dns_rdataclass_fromtext(&rdclass,
+ &token.value.as_textregion) ==
+ ISC_R_SUCCESS)
+ {
+ GETTOKEN(lctx->lex, 0, &token, false);
+ }
+
+ if (token.type != isc_tokentype_string) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_lex_gettoken() returned "
+ "unexpected token type");
+ result = ISC_R_UNEXPECTED;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ result = dns_rdatatype_fromtext(&type,
+ &token.value.as_textregion);
+ if (result != ISC_R_SUCCESS) {
+ (*callbacks->warn)(
+ callbacks, "%s:%lu: unknown RR type '%.*s'",
+ source, line, token.value.as_textregion.length,
+ token.value.as_textregion.base);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+ }
+
+ /*
+ * If the class specified does not match the zone's class
+ * print out a error message and exit.
+ */
+ if (rdclass != 0 && rdclass != lctx->zclass) {
+ bad_class:
+
+ dns_rdataclass_format(rdclass, classname1,
+ sizeof(classname1));
+ dns_rdataclass_format(lctx->zclass, classname2,
+ sizeof(classname2));
+ (*callbacks->error)(callbacks,
+ "%s:%lu: class '%s' != "
+ "zone class '%s'",
+ source, line, classname1,
+ classname2);
+ result = DNS_R_BADCLASS;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ if (type == dns_rdatatype_ns && ictx->glue == NULL) {
+ current_has_delegation = true;
+ }
+
+ /*
+ * RFC1123: MD and MF are not allowed to be loaded from
+ * master files.
+ */
+ if (dns_master_isprimary(lctx) &&
+ (type == dns_rdatatype_md || type == dns_rdatatype_mf))
+ {
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ result = DNS_R_OBSOLETE;
+
+ dns_rdatatype_format(type, typebuf, sizeof(typebuf));
+ (*callbacks->error)(callbacks, "%s:%lu: %s '%s': %s",
+ source, line, "type", typebuf,
+ dns_result_totext(result));
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ /*
+ * RFC2930: TKEY and TSIG are not allowed to be loaded
+ * from master files.
+ */
+ if (dns_master_isprimary(lctx) && dns_rdatatype_ismeta(type)) {
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ result = DNS_R_METATYPE;
+
+ dns_rdatatype_format(type, typebuf, sizeof(typebuf));
+ (*callbacks->error)(callbacks, "%s:%lu: %s '%s': %s",
+ source, line, "type", typebuf,
+ dns_result_totext(result));
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ /*
+ * Find a rdata structure.
+ */
+ if (rdcount == rdata_size) {
+ new_rdata = grow_rdata(rdata_size + RDSZ, rdata,
+ rdata_size, &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 = dns_result_totext(result);
+ if (CHECKNAMESFAIL(lctx->options) ||
+ type == dns_rdatatype_nsec3)
+ {
+ (*callbacks->error)(
+ callbacks, "%s:%lu: %s: %s",
+ source, line, namebuf, desc);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else {
+ goto cleanup;
+ }
+ } else {
+ (*callbacks->warn)(
+ callbacks, "%s:%lu: %s: %s",
+ source, line, namebuf, desc);
+ }
+ }
+ if (type == dns_rdatatype_ptr &&
+ !dns_name_isdnssd(name) &&
+ (dns_name_issubdomain(name, &in_addr_arpa) ||
+ dns_name_issubdomain(name, &ip6_arpa) ||
+ dns_name_issubdomain(name, &ip6_int)))
+ {
+ options |= DNS_RDATA_CHECKREVERSE;
+ }
+ }
+
+ /*
+ * Read rdata contents.
+ */
+ dns_rdata_init(&rdata[rdcount]);
+ target_ft = target;
+ result = dns_rdata_fromtext(&rdata[rdcount], lctx->zclass, type,
+ lctx->lex, ictx->origin, options,
+ lctx->mctx, &target, callbacks);
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ continue;
+ } else if (result != ISC_R_SUCCESS) {
+ goto insist_and_cleanup;
+ }
+
+ if (ictx->drop) {
+ target = target_ft;
+ continue;
+ }
+
+ if (type == dns_rdatatype_soa &&
+ (lctx->options & DNS_MASTER_ZONE) != 0 &&
+ !dns_name_equal(ictx->current, lctx->top))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(ictx->current, namebuf,
+ sizeof(namebuf));
+ (*callbacks->error)(callbacks,
+ "%s:%lu: SOA "
+ "record not at top of zone (%s)",
+ source, line, namebuf);
+ result = DNS_R_NOTZONETOP;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ read_till_eol = true;
+ target = target_ft;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ if (dns_rdatatype_atparent(type) &&
+ dns_master_isprimary(lctx) &&
+ dns_name_equal(ictx->current, lctx->top))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(ictx->current, namebuf,
+ sizeof(namebuf));
+ dns_rdatatype_format(type, typebuf, sizeof(typebuf));
+ (*callbacks->error)(
+ callbacks,
+ "%s:%lu: %s record at top of zone (%s)", source,
+ line, typebuf, namebuf);
+ result = DNS_R_ATZONETOP;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ target = target_ft;
+ continue;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+
+ if (type == dns_rdatatype_rrsig || type == dns_rdatatype_sig) {
+ covers = dns_rdata_covers(&rdata[rdcount]);
+ } else {
+ covers = 0;
+ }
+
+ if (!lctx->ttl_known && !lctx->default_ttl_known) {
+ if (type == dns_rdatatype_soa) {
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: no TTL specified; "
+ "using SOA MINTTL instead",
+ source, line);
+ lctx->ttl = dns_soa_getminimum(&rdata[rdcount]);
+ limit_ttl(callbacks, source, line, &lctx->ttl);
+ lctx->default_ttl = lctx->ttl;
+ lctx->default_ttl_known = true;
+ } else if ((lctx->options & DNS_MASTER_HINT) != 0) {
+ /*
+ * Zero TTL's are fine for hints.
+ */
+ lctx->ttl = 0;
+ lctx->default_ttl = lctx->ttl;
+ lctx->default_ttl_known = true;
+ } else {
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: no TTL specified; "
+ "zone rejected",
+ source, line);
+ result = DNS_R_NOTTL;
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ lctx->ttl = 0;
+ } else {
+ goto insist_and_cleanup;
+ }
+ }
+ } else if (!explicit_ttl && lctx->default_ttl_known) {
+ lctx->ttl = lctx->default_ttl;
+ } else if (!explicit_ttl && lctx->warn_1035) {
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: "
+ "using RFC1035 TTL semantics",
+ source, line);
+ lctx->warn_1035 = false;
+ }
+
+ if (type == dns_rdatatype_rrsig && lctx->warn_sigexpired) {
+ dns_rdata_rrsig_t sig;
+ result = dns_rdata_tostruct(&rdata[rdcount], &sig,
+ NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (isc_serial_lt(sig.timeexpire, lctx->now)) {
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: "
+ "signature has expired",
+ source, line);
+ lctx->warn_sigexpired = false;
+ }
+ }
+
+ if ((type == dns_rdatatype_sig || type == dns_rdatatype_nxt) &&
+ lctx->warn_tcr && dns_master_isprimary(lctx))
+ {
+ (*callbacks->warn)(callbacks,
+ "%s:%lu: old style DNSSEC "
+ " zone detected",
+ source, line);
+ lctx->warn_tcr = false;
+ }
+
+ if ((lctx->options & DNS_MASTER_AGETTL) != 0) {
+ /*
+ * Adjust the TTL for $DATE. If the RR has
+ * already expired, set its TTL to 0. This
+ * should be okay even if the TTL stretching
+ * feature is not in effect, because it will
+ * just be quickly expired by the cache, and the
+ * way this was written before the patch it
+ * could potentially add 0 TTLs anyway.
+ */
+ if (lctx->ttl < ttl_offset) {
+ lctx->ttl = 0;
+ } else {
+ lctx->ttl -= ttl_offset;
+ }
+ }
+
+ /*
+ * Find type in rdatalist.
+ * If it does not exist create new one and prepend to list
+ * as this will minimise list traversal.
+ */
+ if (ictx->glue != NULL) {
+ this = ISC_LIST_HEAD(glue_list);
+ } else {
+ this = ISC_LIST_HEAD(current_list);
+ }
+
+ while (this != NULL) {
+ if (this->type == type && this->covers == covers) {
+ break;
+ }
+ this = ISC_LIST_NEXT(this, link);
+ }
+
+ if (this == NULL) {
+ if (rdlcount == rdatalist_size) {
+ new_rdatalist = grow_rdatalist(
+ rdatalist_size + RDLSZ, rdatalist,
+ rdatalist_size, &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 &&
+ lctx->format != dns_masterformat_map)
+ {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ callbacks = lctx->callbacks;
+ dns_master_initrawheader(&header);
+
+ INSIST(commonlen <= sizeof(header));
+ isc_buffer_init(&target, data, sizeof(data));
+
+ result = isc_stdio_read(data, 1, commonlen, lctx->f, NULL);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_stdio_read failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ isc_buffer_add(&target, (unsigned int)commonlen);
+ header.format = isc_buffer_getuint32(&target);
+ if (header.format != lctx->format) {
+ (*callbacks->error)(callbacks,
+ "dns_master_load: "
+ "file format mismatch (not %s)",
+ lctx->format == dns_masterformat_map ? "map"
+ : "ra"
+ "w");
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ header.version = isc_buffer_getuint32(&target);
+
+ switch (header.version) {
+ case 0:
+ remainder = sizeof(header.dumptime);
+ break;
+ case DNS_RAWFORMAT_VERSION:
+ remainder = sizeof(header) - commonlen;
+ break;
+ default:
+ (*callbacks->error)(callbacks, "dns_master_load: "
+ "unsupported file format "
+ "version");
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ result = isc_stdio_read(data + commonlen, 1, remainder, lctx->f, NULL);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_stdio_read failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ isc_buffer_add(&target, (unsigned int)remainder);
+ header.dumptime = isc_buffer_getuint32(&target);
+ if (header.version == DNS_RAWFORMAT_VERSION) {
+ header.flags = isc_buffer_getuint32(&target);
+ header.sourceserial = isc_buffer_getuint32(&target);
+ header.lastxfrin = isc_buffer_getuint32(&target);
+ }
+
+ lctx->first = false;
+ lctx->header = header;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+openfile_map(dns_loadctx_t *lctx, const char *master_file) {
+ isc_result_t result;
+
+ result = isc_stdio_open(master_file, "rb", &lctx->f);
+ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_stdio_open() failed: %s",
+ isc_result_totext(result));
+ }
+
+ return (result);
+}
+
+/*
+ * Load a map format file, using mmap() to access RBT trees directly
+ */
+static isc_result_t
+load_map(dns_loadctx_t *lctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdatacallbacks_t *callbacks;
+
+ REQUIRE(DNS_LCTX_VALID(lctx));
+
+ callbacks = lctx->callbacks;
+
+ if (lctx->first) {
+ result = load_header(lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = (*callbacks->deserialize)(
+ callbacks->deserialize_private, lctx->f,
+ sizeof(dns_masterrawheader_t));
+ }
+
+ return (result);
+}
+
+static isc_result_t
+openfile_raw(dns_loadctx_t *lctx, const char *master_file) {
+ isc_result_t result;
+
+ result = isc_stdio_open(master_file, "rb", &lctx->f);
+ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_stdio_open() failed: %s",
+ isc_result_totext(result));
+ }
+
+ return (result);
+}
+
+static isc_result_t
+load_raw(dns_loadctx_t *lctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ bool done = false;
+ unsigned int loop_cnt = 0;
+ dns_rdatacallbacks_t *callbacks;
+ unsigned char namebuf[DNS_NAME_MAXWIRE];
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ rdatalist_head_t head, dummy;
+ dns_rdatalist_t rdatalist;
+ isc_mem_t *mctx = lctx->mctx;
+ dns_rdata_t *rdata = NULL;
+ unsigned int rdata_size = 0;
+ int target_size = TSIZ;
+ isc_buffer_t target, buf;
+ unsigned char *target_mem = NULL;
+ dns_decompress_t dctx;
+
+ callbacks = lctx->callbacks;
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE);
+
+ if (lctx->first) {
+ result = load_header(lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ ISC_LIST_INIT(head);
+ ISC_LIST_INIT(dummy);
+
+ /*
+ * Allocate target_size of buffer space. This is greater than twice
+ * the maximum individual RR data size.
+ */
+ target_mem = isc_mem_get(mctx, target_size);
+ isc_buffer_init(&target, target_mem, target_size);
+
+ name = dns_fixedname_initname(&fixed);
+
+ /*
+ * In the following loop, we regard any error fatal regardless of
+ * whether "MANYERRORS" is set in the context option. This is because
+ * normal errors should already have been checked at creation time.
+ * Besides, it is very unlikely that we can recover from an error
+ * in this format, and so trying to continue parsing erroneous data
+ * does not really make sense.
+ */
+ for (loop_cnt = 0; (lctx->loop_cnt == 0 || loop_cnt < lctx->loop_cnt);
+ loop_cnt++)
+ {
+ unsigned int i, rdcount;
+ uint16_t namelen;
+ uint32_t totallen;
+ size_t minlen, readlen;
+ bool sequential_read = false;
+
+ /* Read the data length */
+ isc_buffer_clear(&target);
+ INSIST(isc_buffer_availablelength(&target) >= sizeof(totallen));
+ result = isc_stdio_read(target.base, 1, sizeof(totallen),
+ lctx->f, NULL);
+ if (result == ISC_R_EOF) {
+ result = ISC_R_SUCCESS;
+ done = true;
+ break;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_buffer_add(&target, sizeof(totallen));
+ totallen = isc_buffer_getuint32(&target);
+
+ /*
+ * Validation: the input data must at least contain the common
+ * header.
+ */
+ minlen = sizeof(totallen) + sizeof(uint16_t) +
+ sizeof(uint16_t) + sizeof(uint16_t) +
+ sizeof(uint32_t) + sizeof(uint32_t);
+ if (totallen < minlen) {
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+ totallen -= sizeof(totallen);
+
+ isc_buffer_clear(&target);
+ if (totallen > isc_buffer_availablelength(&target)) {
+ /*
+ * The default buffer size should typically be large
+ * enough to store the entire RRset. We could try to
+ * allocate enough space if this is not the case, but
+ * it might cause a hazardous result when "totallen"
+ * is forged. Thus, we'd rather take an inefficient
+ * but robust approach in this atypical case: read
+ * data step by step, and commit partial data when
+ * necessary. Note that the buffer must be large
+ * enough to store the "header part", owner name, and
+ * at least one rdata (however large it is).
+ */
+ sequential_read = true;
+ readlen = minlen - sizeof(totallen);
+ } else {
+ /*
+ * Typical case. We can read the whole RRset at once
+ * with the default buffer.
+ */
+ readlen = totallen;
+ }
+ result = isc_stdio_read(target.base, 1, readlen, lctx->f, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_buffer_add(&target, (unsigned int)readlen);
+ totallen -= (uint32_t)readlen;
+
+ /* Construct RRset headers */
+ dns_rdatalist_init(&rdatalist);
+ rdatalist.rdclass = isc_buffer_getuint16(&target);
+ if (lctx->zclass != rdatalist.rdclass) {
+ result = DNS_R_BADCLASS;
+ goto cleanup;
+ }
+ rdatalist.type = isc_buffer_getuint16(&target);
+ rdatalist.covers = isc_buffer_getuint16(&target);
+ rdatalist.ttl = isc_buffer_getuint32(&target);
+ rdcount = isc_buffer_getuint32(&target);
+ if (rdcount == 0 || rdcount > 0xffff) {
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+ INSIST(isc_buffer_consumedlength(&target) <= readlen);
+
+ /* Owner name: length followed by name */
+ result = read_and_check(sequential_read, &target,
+ sizeof(namelen), lctx->f, &totallen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ namelen = isc_buffer_getuint16(&target);
+ if (namelen > sizeof(namebuf)) {
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+
+ result = read_and_check(sequential_read, &target, namelen,
+ lctx->f, &totallen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_buffer_setactive(&target, (unsigned int)namelen);
+ result = dns_name_fromwire(name, &target, &dctx, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if ((lctx->options & DNS_MASTER_CHECKTTL) != 0 &&
+ rdatalist.ttl > lctx->maxttl)
+ {
+ (callbacks->error)(callbacks,
+ "dns_master_load: "
+ "TTL %d exceeds configured "
+ "max-zone-ttl %d",
+ rdatalist.ttl, lctx->maxttl);
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+
+ /* Rdata contents. */
+ if (rdcount > rdata_size) {
+ dns_rdata_t *new_rdata = NULL;
+
+ new_rdata = grow_rdata(rdcount + RDSZ, rdata,
+ rdata_size, &head, &dummy, mctx);
+ if (new_rdata == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ rdata_size = rdcount + RDSZ;
+ rdata = new_rdata;
+ }
+
+ continue_read:
+ for (i = 0; i < rdcount; i++) {
+ uint16_t rdlen;
+
+ dns_rdata_init(&rdata[i]);
+
+ if (sequential_read &&
+ isc_buffer_availablelength(&target) < MINTSIZ)
+ {
+ unsigned int j;
+
+ INSIST(i > 0); /* detect an infinite loop */
+
+ /* Partial Commit. */
+ ISC_LIST_APPEND(head, &rdatalist, link);
+ result = commit(callbacks, lctx, &head, name,
+ NULL, 0);
+ for (j = 0; j < i; j++) {
+ ISC_LIST_UNLINK(rdatalist.rdata,
+ &rdata[j], link);
+ dns_rdata_reset(&rdata[j]);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* Rewind the buffer and continue */
+ isc_buffer_clear(&target);
+
+ rdcount -= i;
+
+ goto continue_read;
+ }
+
+ /* rdata length */
+ result = read_and_check(sequential_read, &target,
+ sizeof(rdlen), lctx->f,
+ &totallen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ rdlen = isc_buffer_getuint16(&target);
+
+ /* rdata */
+ result = read_and_check(sequential_read, &target, rdlen,
+ lctx->f, &totallen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ isc_buffer_setactive(&target, (unsigned int)rdlen);
+ /*
+ * It is safe to have the source active region and
+ * the target available region be the same if
+ * decompression is disabled (see dctx above) and we
+ * are not downcasing names (options == 0).
+ */
+ isc_buffer_init(&buf, isc_buffer_current(&target),
+ (unsigned int)rdlen);
+ result = dns_rdata_fromwire(
+ &rdata[i], rdatalist.rdclass, rdatalist.type,
+ &target, &dctx, 0, &buf);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata[i], link);
+ }
+
+ /*
+ * Sanity check. Still having remaining space is not
+ * necessarily critical, but it very likely indicates broken
+ * or malformed data.
+ */
+ if (isc_buffer_remaininglength(&target) != 0 || totallen != 0) {
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+
+ ISC_LIST_APPEND(head, &rdatalist, link);
+
+ /* Commit this RRset. rdatalist will be unlinked. */
+ result = commit(callbacks, lctx, &head, name, NULL, 0);
+
+ for (i = 0; i < rdcount; i++) {
+ ISC_LIST_UNLINK(rdatalist.rdata, &rdata[i], link);
+ dns_rdata_reset(&rdata[i]);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ if (!done) {
+ INSIST(lctx->done != NULL && lctx->task != NULL);
+ result = DNS_R_CONTINUE;
+ } else if (result == ISC_R_SUCCESS && lctx->result != ISC_R_SUCCESS) {
+ result = lctx->result;
+ }
+
+ if (result == ISC_R_SUCCESS && callbacks->rawdata != NULL) {
+ (*callbacks->rawdata)(callbacks->zone, &lctx->header);
+ }
+
+cleanup:
+ if (rdata != NULL) {
+ isc_mem_put(mctx, rdata, rdata_size * sizeof(*rdata));
+ }
+ if (target_mem != NULL) {
+ isc_mem_put(mctx, target_mem, target_size);
+ }
+ if (result != ISC_R_SUCCESS && result != DNS_R_CONTINUE) {
+ (*callbacks->error)(callbacks, "dns_master_load: %s",
+ dns_result_totext(result));
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_master_loadfile(const char *master_file, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, uint32_t resign,
+ dns_rdatacallbacks_t *callbacks,
+ dns_masterincludecb_t include_cb, void *include_arg,
+ isc_mem_t *mctx, dns_masterformat_t format,
+ dns_ttl_t maxttl) {
+ dns_loadctx_t *lctx = NULL;
+ isc_result_t result;
+
+ result = loadctx_create(format, mctx, options, resign, top, zclass,
+ origin, callbacks, NULL, NULL, NULL, include_cb,
+ include_arg, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ lctx->maxttl = maxttl;
+
+ result = (lctx->openfile)(lctx, master_file);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = (lctx->load)(lctx);
+ INSIST(result != DNS_R_CONTINUE);
+
+cleanup:
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_loadfileinc(const char *master_file, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, uint32_t resign,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **lctxp, dns_masterincludecb_t include_cb,
+ void *include_arg, isc_mem_t *mctx,
+ dns_masterformat_t format, uint32_t maxttl) {
+ dns_loadctx_t *lctx = NULL;
+ isc_result_t result;
+
+ REQUIRE(task != NULL);
+ REQUIRE(done != NULL);
+
+ result = loadctx_create(format, mctx, options, resign, top, zclass,
+ origin, callbacks, task, done, done_arg,
+ include_cb, include_arg, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ lctx->maxttl = maxttl;
+
+ result = (lctx->openfile)(lctx, master_file);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = task_send(lctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_loadctx_attach(lctx, lctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+cleanup:
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_loadstream(FILE *stream, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(stream != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, NULL, NULL, NULL,
+ NULL, NULL, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = isc_lex_openstream(lctx->lex, stream);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = (lctx->load)(lctx);
+ INSIST(result != DNS_R_CONTINUE);
+
+cleanup:
+ if (lctx != NULL) {
+ dns_loadctx_detach(&lctx);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_master_loadstreaminc(FILE *stream, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **lctxp, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(stream != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(done != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, task, done, done_arg,
+ NULL, NULL, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = isc_lex_openstream(lctx->lex, stream);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = task_send(lctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_loadctx_attach(lctx, lctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+cleanup:
+ if (lctx != NULL) {
+ dns_loadctx_detach(&lctx);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_master_loadbuffer(isc_buffer_t *buffer, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(buffer != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, NULL, NULL, NULL,
+ NULL, NULL, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = isc_lex_openbuffer(lctx->lex, buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = (lctx->load)(lctx);
+ INSIST(result != DNS_R_CONTINUE);
+
+cleanup:
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_loadbufferinc(isc_buffer_t *buffer, dns_name_t *top,
+ dns_name_t *origin, dns_rdataclass_t zclass,
+ unsigned int options, dns_rdatacallbacks_t *callbacks,
+ isc_task_t *task, dns_loaddonefunc_t done,
+ void *done_arg, dns_loadctx_t **lctxp,
+ isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(buffer != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(done != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, task, done, done_arg,
+ NULL, NULL, NULL, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = isc_lex_openbuffer(lctx->lex, buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = task_send(lctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_loadctx_attach(lctx, lctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+cleanup:
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_loadlexer(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(lex != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, NULL, NULL, NULL,
+ NULL, NULL, lex, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = (lctx->load)(lctx);
+ INSIST(result != DNS_R_CONTINUE);
+
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_loadlexerinc(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin,
+ dns_rdataclass_t zclass, unsigned int options,
+ dns_rdatacallbacks_t *callbacks, isc_task_t *task,
+ dns_loaddonefunc_t done, void *done_arg,
+ dns_loadctx_t **lctxp, isc_mem_t *mctx) {
+ isc_result_t result;
+ dns_loadctx_t *lctx = NULL;
+
+ REQUIRE(lex != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(done != NULL);
+
+ result = loadctx_create(dns_masterformat_text, mctx, options, 0, top,
+ zclass, origin, callbacks, task, done, done_arg,
+ NULL, NULL, lex, &lctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = task_send(lctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_loadctx_attach(lctx, lctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+ dns_loadctx_detach(&lctx);
+ return (result);
+}
+
+/*
+ * Grow the slab of dns_rdatalist_t structures.
+ * Re-link glue and current list.
+ */
+static dns_rdatalist_t *
+grow_rdatalist(int new_len, dns_rdatalist_t *oldlist, int old_len,
+ rdatalist_head_t *current, rdatalist_head_t *glue,
+ isc_mem_t *mctx) {
+ dns_rdatalist_t *newlist;
+ int rdlcount = 0;
+ ISC_LIST(dns_rdatalist_t) save;
+ dns_rdatalist_t *this;
+
+ newlist = isc_mem_get(mctx, new_len * sizeof(*newlist));
+ if (newlist == NULL) {
+ return (NULL);
+ }
+
+ ISC_LIST_INIT(save);
+ while ((this = ISC_LIST_HEAD(*current)) != NULL) {
+ ISC_LIST_UNLINK(*current, this, link);
+ ISC_LIST_APPEND(save, this, link);
+ }
+ while ((this = ISC_LIST_HEAD(save)) != NULL) {
+ ISC_LIST_UNLINK(save, this, link);
+ INSIST(rdlcount < new_len);
+ newlist[rdlcount] = *this;
+ ISC_LIST_APPEND(*current, &newlist[rdlcount], link);
+ rdlcount++;
+ }
+
+ ISC_LIST_INIT(save);
+ while ((this = ISC_LIST_HEAD(*glue)) != NULL) {
+ ISC_LIST_UNLINK(*glue, this, link);
+ ISC_LIST_APPEND(save, this, link);
+ }
+ while ((this = ISC_LIST_HEAD(save)) != NULL) {
+ ISC_LIST_UNLINK(save, this, link);
+ INSIST(rdlcount < new_len);
+ newlist[rdlcount] = *this;
+ ISC_LIST_APPEND(*glue, &newlist[rdlcount], link);
+ rdlcount++;
+ }
+
+ INSIST(rdlcount == old_len);
+ if (oldlist != NULL) {
+ isc_mem_put(mctx, oldlist, old_len * sizeof(*oldlist));
+ }
+ return (newlist);
+}
+
+/*
+ * Grow the slab of rdata structs.
+ * Re-link the current and glue chains.
+ */
+static dns_rdata_t *
+grow_rdata(int new_len, dns_rdata_t *oldlist, int old_len,
+ rdatalist_head_t *current, rdatalist_head_t *glue, isc_mem_t *mctx) {
+ dns_rdata_t *newlist;
+ int rdcount = 0;
+ ISC_LIST(dns_rdata_t) save;
+ dns_rdatalist_t *this;
+ dns_rdata_t *rdata;
+
+ newlist = isc_mem_get(mctx, new_len * sizeof(*newlist));
+ if (newlist == NULL) {
+ return (NULL);
+ }
+ memset(newlist, 0, new_len * sizeof(*newlist));
+
+ /*
+ * Copy current relinking.
+ */
+ this = ISC_LIST_HEAD(*current);
+ while (this != NULL) {
+ ISC_LIST_INIT(save);
+ while ((rdata = ISC_LIST_HEAD(this->rdata)) != NULL) {
+ ISC_LIST_UNLINK(this->rdata, rdata, link);
+ ISC_LIST_APPEND(save, rdata, link);
+ }
+ while ((rdata = ISC_LIST_HEAD(save)) != NULL) {
+ ISC_LIST_UNLINK(save, rdata, link);
+ INSIST(rdcount < new_len);
+ newlist[rdcount] = *rdata;
+ ISC_LIST_APPEND(this->rdata, &newlist[rdcount], link);
+ rdcount++;
+ }
+ this = ISC_LIST_NEXT(this, link);
+ }
+
+ /*
+ * Copy glue relinking.
+ */
+ this = ISC_LIST_HEAD(*glue);
+ while (this != NULL) {
+ ISC_LIST_INIT(save);
+ while ((rdata = ISC_LIST_HEAD(this->rdata)) != NULL) {
+ ISC_LIST_UNLINK(this->rdata, rdata, link);
+ ISC_LIST_APPEND(save, rdata, link);
+ }
+ while ((rdata = ISC_LIST_HEAD(save)) != NULL) {
+ ISC_LIST_UNLINK(save, rdata, link);
+ INSIST(rdcount < new_len);
+ newlist[rdcount] = *rdata;
+ ISC_LIST_APPEND(this->rdata, &newlist[rdcount], link);
+ rdcount++;
+ }
+ this = ISC_LIST_NEXT(this, link);
+ }
+ INSIST(rdcount == old_len || rdcount == 0);
+ if (oldlist != NULL) {
+ isc_mem_put(mctx, oldlist, old_len * sizeof(*oldlist));
+ }
+ return (newlist);
+}
+
+static uint32_t
+resign_fromlist(dns_rdatalist_t *this, dns_loadctx_t *lctx) {
+ dns_rdata_t *rdata;
+ dns_rdata_rrsig_t sig;
+ uint32_t when;
+
+ rdata = ISC_LIST_HEAD(this->rdata);
+ INSIST(rdata != NULL);
+ (void)dns_rdata_tostruct(rdata, &sig, NULL);
+ if (isc_serial_gt(sig.timesigned, lctx->now)) {
+ when = lctx->now;
+ } else {
+ when = sig.timeexpire - lctx->resign;
+ }
+
+ rdata = ISC_LIST_NEXT(rdata, link);
+ while (rdata != NULL) {
+ (void)dns_rdata_tostruct(rdata, &sig, NULL);
+ if (isc_serial_gt(sig.timesigned, lctx->now)) {
+ when = lctx->now;
+ } else if (sig.timeexpire - lctx->resign < when) {
+ when = sig.timeexpire - lctx->resign;
+ }
+ rdata = ISC_LIST_NEXT(rdata, link);
+ }
+ return (when);
+}
+
+/*
+ * Convert each element from a rdatalist_t to rdataset then call commit.
+ * Unlink each element as we go.
+ */
+
+static isc_result_t
+commit(dns_rdatacallbacks_t *callbacks, dns_loadctx_t *lctx,
+ rdatalist_head_t *head, dns_name_t *owner, const char *source,
+ unsigned int line) {
+ dns_rdatalist_t *this;
+ dns_rdataset_t dataset;
+ isc_result_t result;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ void (*error)(struct dns_rdatacallbacks *, const char *, ...);
+
+ this = ISC_LIST_HEAD(*head);
+ error = callbacks->error;
+
+ if (this == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ do {
+ dns_rdataset_init(&dataset);
+ RUNTIME_CHECK(dns_rdatalist_tordataset(this, &dataset) ==
+ ISC_R_SUCCESS);
+ dataset.trust = dns_trust_ultimate;
+ /*
+ * If this is a secure dynamic zone set the re-signing time.
+ */
+ if (dataset.type == dns_rdatatype_rrsig &&
+ (lctx->options & DNS_MASTER_RESIGN) != 0)
+ {
+ dataset.attributes |= DNS_RDATASETATTR_RESIGN;
+ dataset.resign = resign_fromlist(this, lctx);
+ }
+ result = ((*callbacks->add)(callbacks->add_private, owner,
+ &dataset));
+ if (result == ISC_R_NOMEMORY) {
+ (*error)(callbacks, "dns_master_load: %s",
+ dns_result_totext(result));
+ } else if (result != ISC_R_SUCCESS) {
+ dns_name_format(owner, namebuf, sizeof(namebuf));
+ if (source != NULL) {
+ (*error)(callbacks, "%s: %s:%lu: %s: %s",
+ "dns_master_load", source, line,
+ namebuf, dns_result_totext(result));
+ } else {
+ (*error)(callbacks, "%s: %s: %s",
+ "dns_master_load", namebuf,
+ dns_result_totext(result));
+ }
+ }
+ if (MANYERRS(lctx, result)) {
+ SETRESULT(lctx, result);
+ } else if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ ISC_LIST_UNLINK(*head, this, link);
+ this = ISC_LIST_HEAD(*head);
+ } while (this != NULL);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Returns true if one of the NS rdata's contains 'owner'.
+ */
+
+static bool
+is_glue(rdatalist_head_t *head, dns_name_t *owner) {
+ dns_rdatalist_t *this;
+ dns_rdata_t *rdata;
+ isc_region_t region;
+ dns_name_t name;
+
+ /*
+ * Find NS rrset.
+ */
+ this = ISC_LIST_HEAD(*head);
+ while (this != NULL) {
+ if (this->type == dns_rdatatype_ns) {
+ break;
+ }
+ this = ISC_LIST_NEXT(this, link);
+ }
+ if (this == NULL) {
+ return (false);
+ }
+
+ rdata = ISC_LIST_HEAD(this->rdata);
+ while (rdata != NULL) {
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &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..7561061
--- /dev/null
+++ b/lib/dns/masterdump.c
@@ -0,0 +1,2133 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/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/lib.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/result.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;
+
+LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_keyzone = {
+ DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
+ DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA |
+ DNS_STYLEFLAG_OMIT_TTL | DNS_STYLEFLAG_TTL |
+ DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT |
+ DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_KEYDATA,
+ 24,
+ 24,
+ 24,
+ 32,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_default = {
+ DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
+ DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA |
+ DNS_STYLEFLAG_OMIT_TTL | DNS_STYLEFLAG_TTL |
+ DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT |
+ DNS_STYLEFLAG_MULTILINE,
+ 24,
+ 24,
+ 24,
+ 32,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_full = {
+ DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RESIGN,
+ 46,
+ 46,
+ 46,
+ 64,
+ 120,
+ 8,
+ UINT_MAX
+};
+
+LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_explicitttl = {
+ DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
+ DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA |
+ DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT |
+ DNS_STYLEFLAG_MULTILINE,
+ 24,
+ 32,
+ 32,
+ 40,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_cache = {
+ DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
+ DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_RRCOMMENT |
+ DNS_STYLEFLAG_TRUST | DNS_STYLEFLAG_NCACHE,
+ 24,
+ 32,
+ 32,
+ 40,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+LIBDNS_EXTERNAL_DATA const dns_master_style_t
+ dns_master_style_cache_with_expired = {
+ DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS |
+ DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_RRCOMMENT |
+ DNS_STYLEFLAG_TRUST | DNS_STYLEFLAG_NCACHE |
+ DNS_STYLEFLAG_EXPIRED,
+ 24,
+ 32,
+ 32,
+ 40,
+ 80,
+ 8,
+ UINT_MAX
+ };
+
+LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_simple = {
+ 0, 24, 32, 32, 40, 80, 8, UINT_MAX
+};
+
+/*%
+ * A style suitable for dns_rdataset_totext().
+ */
+LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_debug = {
+ DNS_STYLEFLAG_REL_OWNER, 24, 32, 40, 48, 80, 8, UINT_MAX
+};
+
+/*%
+ * Similar, but indented (i.e., prepended with indentctx.string).
+ */
+LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_indent = {
+ DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_INDENT,
+ 24,
+ 32,
+ 40,
+ 48,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+/*%
+ * Similar, but with each line commented out.
+ */
+LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_comment = {
+ DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_MULTILINE |
+ DNS_STYLEFLAG_RRCOMMENT | DNS_STYLEFLAG_COMMENTDATA,
+ 24,
+ 32,
+ 40,
+ 48,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+/*%
+ * YAML style
+ */
+LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_yaml = {
+ DNS_STYLEFLAG_YAML | DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_INDENT,
+ 24,
+ 32,
+ 40,
+ 48,
+ 80,
+ 8,
+ UINT_MAX
+};
+
+#define N_SPACES 10
+static char spaces[N_SPACES + 1] = " ";
+
+#define N_TABS 10
+static char tabs[N_TABS + 1] = "\t\t\t\t\t\t\t\t\t\t";
+
+struct dns_dumpctx {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ isc_refcount_t references;
+ atomic_bool canceled;
+ bool do_date;
+ isc_stdtime_t now;
+ FILE *f;
+ dns_db_t *db;
+ dns_dbversion_t *version;
+ dns_dbiterator_t *dbiter;
+ dns_totext_ctx_t tctx;
+ isc_task_t *task;
+ dns_dumpdonefunc_t done;
+ void *done_arg;
+ /* dns_master_dumpasync() */
+ isc_result_t result;
+ char *file;
+ char *tmpfile;
+ dns_masterformat_t format;
+ dns_masterrawheader_t header;
+ isc_result_t (*dumpsets)(isc_mem_t *mctx, const dns_name_t *name,
+ dns_rdatasetiter_t *rdsiter,
+ dns_totext_ctx_t *ctx, isc_buffer_t *buffer,
+ FILE *f);
+};
+
+#define NXDOMAIN(x) (((x)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
+
+static const dns_indent_t default_indent = { "\t", 1 };
+static const dns_indent_t default_yamlindent = { " ", 1 };
+
+/*%
+ * Output tabs and spaces to go from column '*current' to
+ * column 'to', and update '*current' to reflect the new
+ * current column.
+ */
+static isc_result_t
+indent(unsigned int *current, unsigned int to, int tabwidth,
+ isc_buffer_t *target) {
+ isc_region_t r;
+ unsigned char *p;
+ unsigned int from;
+ int ntabs, nspaces, t;
+
+ from = *current;
+
+ if (to < from + 1) {
+ to = from + 1;
+ }
+
+ ntabs = to / tabwidth - from / tabwidth;
+ if (ntabs < 0) {
+ ntabs = 0;
+ }
+
+ if (ntabs > 0) {
+ isc_buffer_availableregion(target, &r);
+ if (r.length < (unsigned)ntabs) {
+ return (ISC_R_NOSPACE);
+ }
+ p = r.base;
+
+ t = ntabs;
+ while (t) {
+ int n = t;
+ if (n > N_TABS) {
+ n = N_TABS;
+ }
+ memmove(p, tabs, n);
+ p += n;
+ t -= n;
+ }
+ isc_buffer_add(target, ntabs);
+ from = (to / tabwidth) * tabwidth;
+ }
+
+ nspaces = to - from;
+ INSIST(nspaces >= 0);
+
+ isc_buffer_availableregion(target, &r);
+ if (r.length < (unsigned)nspaces) {
+ return (ISC_R_NOSPACE);
+ }
+ p = r.base;
+
+ t = nspaces;
+ while (t) {
+ int n = t;
+ if (n > N_SPACES) {
+ n = N_SPACES;
+ }
+ memmove(p, spaces, n);
+ p += n;
+ t -= n;
+ }
+ isc_buffer_add(target, nspaces);
+
+ *current = to;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_ctx_init(const dns_master_style_t *style, const dns_indent_t *indentctx,
+ dns_totext_ctx_t *ctx) {
+ isc_result_t result;
+
+ REQUIRE(style->tab_width != 0);
+
+ if (indentctx == NULL) {
+ if ((style->flags & DNS_STYLEFLAG_YAML) != 0) {
+ indentctx = &default_yamlindent;
+ } else {
+ indentctx = &default_indent;
+ }
+ }
+
+ ctx->style = *style;
+ ctx->class_printed = false;
+
+ dns_fixedname_init(&ctx->origin_fixname);
+
+ /*
+ * Set up the line break string if needed.
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ isc_buffer_t buf;
+ isc_region_t r;
+ unsigned int col = 0;
+
+ isc_buffer_init(&buf, ctx->linebreak_buf,
+ sizeof(ctx->linebreak_buf));
+
+ isc_buffer_availableregion(&buf, &r);
+ if (r.length < 1) {
+ return (DNS_R_TEXTTOOLONG);
+ }
+ r.base[0] = '\n';
+ isc_buffer_add(&buf, 1);
+
+ if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
+ (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
+ {
+ unsigned int i, len = strlen(indentctx->string);
+ for (i = 0; i < indentctx->count; i++) {
+ if (isc_buffer_availablelength(&buf) < len) {
+ return (DNS_R_TEXTTOOLONG);
+ }
+ isc_buffer_putstr(&buf, indentctx->string);
+ }
+ }
+
+ if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) {
+ isc_buffer_availableregion(&buf, &r);
+ if (r.length < 1) {
+ return (DNS_R_TEXTTOOLONG);
+ }
+ r.base[0] = ';';
+ isc_buffer_add(&buf, 1);
+ }
+
+ result = indent(&col, ctx->style.rdata_column,
+ ctx->style.tab_width, &buf);
+ /*
+ * Do not return ISC_R_NOSPACE if the line break string
+ * buffer is too small, because that would just make
+ * dump_rdataset() retry indefinitely with ever
+ * bigger target buffers. That's a different buffer,
+ * so it won't help. Use DNS_R_TEXTTOOLONG as a substitute.
+ */
+ if (result == ISC_R_NOSPACE) {
+ return (DNS_R_TEXTTOOLONG);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_availableregion(&buf, &r);
+ if (r.length < 1) {
+ return (DNS_R_TEXTTOOLONG);
+ }
+ r.base[0] = '\0';
+ isc_buffer_add(&buf, 1);
+ ctx->linebreak = ctx->linebreak_buf;
+ } else {
+ ctx->linebreak = NULL;
+ }
+
+ ctx->origin = NULL;
+ ctx->neworigin = NULL;
+ ctx->current_ttl = 0;
+ ctx->current_ttl_valid = false;
+ ctx->serve_stale_ttl = 0;
+ ctx->indent = *indentctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+#define INDENT_TO(col) \
+ do { \
+ if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) { \
+ if ((result = str_totext(" ", target)) != \
+ ISC_R_SUCCESS) \
+ return ((result)); \
+ } else if ((result = indent(&column, ctx->style.col, \
+ ctx->style.tab_width, target)) != \
+ ISC_R_SUCCESS) \
+ return ((result)); \
+ } while (0)
+
+static isc_result_t
+str_totext(const char *source, isc_buffer_t *target) {
+ unsigned int l;
+ isc_region_t region;
+
+ isc_buffer_availableregion(target, &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_copynf(owner_name, name);
+ dns_rdataset_getownercase(rdataset, name);
+ }
+
+ while (result == ISC_R_SUCCESS) {
+ column = 0;
+
+ /*
+ * Indent?
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
+ (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
+ {
+ for (i = 0; i < ctx->indent.count; i++) {
+ RETERR(str_totext(ctx->indent.string, target));
+ }
+ }
+
+ /*
+ * YAML or comment prefix?
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) {
+ RETERR(str_totext("- ", target));
+ } else if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0)
+ {
+ RETERR(str_totext(";", target));
+ }
+
+ /*
+ * Owner name.
+ */
+ if (name != NULL &&
+ !((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0 &&
+ !first))
+ {
+ unsigned int name_start = target->used;
+ RETERR(dns_name_totext(name, omit_final_dot, target));
+ column += target->used - name_start;
+ }
+
+ /*
+ * TTL.
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_NO_TTL) == 0 &&
+ !((ctx->style.flags & DNS_STYLEFLAG_OMIT_TTL) != 0 &&
+ current_ttl_valid && rdataset->ttl == current_ttl))
+ {
+ char ttlbuf[64];
+ isc_region_t r;
+ unsigned int length;
+
+ INDENT_TO(ttl_column);
+ if ((ctx->style.flags & DNS_STYLEFLAG_TTL_UNITS) != 0) {
+ length = target->used;
+ result = dns_ttl_totext(rdataset->ttl, false,
+ false, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ column += target->used - length;
+ } else {
+ length = snprintf(ttlbuf, sizeof(ttlbuf), "%u",
+ rdataset->ttl);
+ INSIST(length <= sizeof(ttlbuf));
+ isc_buffer_availableregion(target, &r);
+ if (r.length < length) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(r.base, ttlbuf, length);
+ isc_buffer_add(target, length);
+ column += length;
+ }
+
+ /*
+ * If the $TTL directive is not in use, the TTL we
+ * just printed becomes the default for subsequent RRs.
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_TTL) == 0) {
+ current_ttl = rdataset->ttl;
+ current_ttl_valid = true;
+ }
+ }
+
+ /*
+ * Class.
+ */
+ if ((ctx->style.flags & DNS_STYLEFLAG_NO_CLASS) == 0 &&
+ ((ctx->style.flags & DNS_STYLEFLAG_OMIT_CLASS) == 0 ||
+ !ctx->class_printed))
+ {
+ unsigned int class_start;
+ INDENT_TO(class_column);
+ class_start = target->used;
+ if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) !=
+ 0)
+ {
+ result = dns_rdataclass_tounknowntext(
+ rdataset->rdclass, target);
+ } else {
+ result = dns_rdataclass_totext(
+ rdataset->rdclass, target);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ column += (target->used - class_start);
+ }
+
+ /*
+ * Type.
+ */
+
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ type = rdataset->covers;
+ } else {
+ type = rdataset->type;
+ }
+
+ INDENT_TO(type_column);
+ type_start = target->used;
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ RETERR(str_totext("\\-", target));
+ }
+ switch (type) {
+ case dns_rdatatype_keydata:
+#define KEYDATA "KEYDATA"
+ if ((ctx->style.flags & DNS_STYLEFLAG_KEYDATA) != 0) {
+ if (isc_buffer_availablelength(target) <
+ (sizeof(KEYDATA) - 1))
+ {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putstr(target, KEYDATA);
+ break;
+ }
+ FALLTHROUGH;
+ default:
+ if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) !=
+ 0)
+ {
+ result = dns_rdatatype_tounknowntext(type,
+ target);
+ } else {
+ result = dns_rdatatype_totext(type, target);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ column += (target->used - type_start);
+
+ /*
+ * Rdata.
+ */
+ INDENT_TO(rdata_column);
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ if (NXDOMAIN(rdataset)) {
+ RETERR(str_totext(";-$NXDOMAIN\n", target));
+ } else {
+ RETERR(str_totext(";-$NXRRSET\n", target));
+ }
+ /*
+ * Print a summary of the cached records which make
+ * up the negative response.
+ */
+ RETERR(ncache_summary(rdataset, omit_final_dot, ctx,
+ target));
+ break;
+ } else {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_region_t r;
+
+ dns_rdataset_current(rdataset, &rdata);
+
+ RETERR(dns_rdata_tofmttext(
+ &rdata, ctx->origin, ctx->style.flags,
+ ctx->style.line_length -
+ ctx->style.rdata_column,
+ ctx->style.split_width, ctx->linebreak,
+ target));
+
+ isc_buffer_availableregion(target, &r);
+ if (r.length < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = '\n';
+ isc_buffer_add(target, 1);
+ }
+
+ first = false;
+ result = dns_rdataset_next(rdataset);
+ }
+
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ /*
+ * Update the ctx state to reflect what we just printed.
+ * This is done last, only when we are sure we will return
+ * success, because this function may be called multiple
+ * times with increasing buffer sizes until it succeeds,
+ * and failed attempts must not update the state prematurely.
+ */
+ ctx->class_printed = true;
+ ctx->current_ttl = current_ttl;
+ ctx->current_ttl_valid = current_ttl_valid;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Print the name, type, and class of an empty rdataset,
+ * such as those used to represent the question section
+ * of a DNS message.
+ */
+static isc_result_t
+question_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_totext_ctx_t *ctx, bool omit_final_dot,
+ isc_buffer_t *target) {
+ unsigned int column;
+ isc_result_t result;
+ isc_region_t r;
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ result = dns_rdataset_first(rdataset);
+ REQUIRE(result == ISC_R_NOMORE);
+
+ column = 0;
+
+ /* Owner name */
+ {
+ unsigned int name_start = target->used;
+ RETERR(dns_name_totext(owner_name, omit_final_dot, target));
+ column += target->used - name_start;
+ }
+
+ /* Class */
+ {
+ unsigned int class_start;
+ INDENT_TO(class_column);
+ class_start = target->used;
+ if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) {
+ result = dns_rdataclass_tounknowntext(rdataset->rdclass,
+ target);
+ } else {
+ result = dns_rdataclass_totext(rdataset->rdclass,
+ target);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ column += (target->used - class_start);
+ }
+
+ /* Type */
+ {
+ unsigned int type_start;
+ INDENT_TO(type_column);
+ type_start = target->used;
+ if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) {
+ result = dns_rdatatype_tounknowntext(rdataset->type,
+ target);
+ } else {
+ result = dns_rdatatype_totext(rdataset->type, target);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ column += (target->used - type_start);
+ }
+
+ isc_buffer_availableregion(target, &r);
+ if (r.length < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ r.base[0] = '\n';
+ isc_buffer_add(target, 1);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ bool omit_final_dot, bool question, isc_buffer_t *target) {
+ dns_totext_ctx_t ctx;
+ isc_result_t result;
+ result = totext_ctx_init(&dns_master_style_debug, NULL, &ctx);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "could not set master file style");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * The caller might want to give us an empty owner
+ * name (e.g. if they are outputting into a master
+ * file and this rdataset has the same name as the
+ * previous one.)
+ */
+ if (dns_name_countlabels(owner_name) == 0) {
+ owner_name = NULL;
+ }
+
+ if (question) {
+ return (question_totext(rdataset, owner_name, &ctx,
+ omit_final_dot, target));
+ } else {
+ return (rdataset_totext(rdataset, owner_name, &ctx,
+ omit_final_dot, target));
+ }
+}
+
+isc_result_t
+dns_master_rdatasettotext(const dns_name_t *owner_name,
+ dns_rdataset_t *rdataset,
+ const dns_master_style_t *style, dns_indent_t *indent,
+ isc_buffer_t *target) {
+ dns_totext_ctx_t ctx;
+ isc_result_t result;
+ result = totext_ctx_init(style, indent, &ctx);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "could not set master file style");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (rdataset_totext(rdataset, owner_name, &ctx, false, target));
+}
+
+isc_result_t
+dns_master_questiontotext(const dns_name_t *owner_name,
+ dns_rdataset_t *rdataset,
+ const dns_master_style_t *style,
+ isc_buffer_t *target) {
+ dns_totext_ctx_t ctx;
+ isc_result_t result;
+ result = totext_ctx_init(style, NULL, &ctx);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "could not set master file style");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (question_totext(rdataset, owner_name, &ctx, false, target));
+}
+
+/*
+ * Print an rdataset. 'buffer' is a scratch buffer, which must have been
+ * dynamically allocated by the caller. It must be large enough to
+ * hold the result from dns_ttl_totext(). If more than that is needed,
+ * the buffer will be grown automatically.
+ */
+
+static isc_result_t
+dump_rdataset(isc_mem_t *mctx, const dns_name_t *name, dns_rdataset_t *rdataset,
+ dns_totext_ctx_t *ctx, isc_buffer_t *buffer, FILE *f) {
+ isc_region_t r;
+ isc_result_t result;
+
+ REQUIRE(buffer->length > 0);
+
+ /*
+ * Output a $TTL directive if needed.
+ */
+
+ if ((ctx->style.flags & DNS_STYLEFLAG_TTL) != 0) {
+ if (!ctx->current_ttl_valid ||
+ ctx->current_ttl != rdataset->ttl)
+ {
+ if ((ctx->style.flags & DNS_STYLEFLAG_COMMENT) != 0) {
+ isc_buffer_clear(buffer);
+ result = dns_ttl_totext(rdataset->ttl, true,
+ true, buffer);
+ INSIST(result == ISC_R_SUCCESS);
+ isc_buffer_usedregion(buffer, &r);
+ fprintf(f, "$TTL %u\t; %.*s\n", rdataset->ttl,
+ (int)r.length, (char *)r.base);
+ } else {
+ fprintf(f, "$TTL %u\n", rdataset->ttl);
+ }
+ ctx->current_ttl = rdataset->ttl;
+ ctx->current_ttl_valid = true;
+ }
+ }
+
+ isc_buffer_clear(buffer);
+
+ /*
+ * Generate the text representation of the rdataset into
+ * the buffer. If the buffer is too small, grow it.
+ */
+ for (;;) {
+ int newlength;
+ void *newmem;
+ result = rdataset_totext(rdataset, name, ctx, false, buffer);
+ if (result != ISC_R_NOSPACE) {
+ break;
+ }
+
+ newlength = buffer->length * 2;
+ newmem = isc_mem_get(mctx, newlength);
+ isc_mem_put(mctx, buffer->base, buffer->length);
+ isc_buffer_init(buffer, newmem, newlength);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Write the buffer contents to the master file.
+ */
+ isc_buffer_usedregion(buffer, &r);
+ result = isc_stdio_write(r.base, 1, (size_t)r.length, f, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "master file write failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Define the order in which rdatasets should be printed in zone
+ * files. We will print SOA and NS records before others, SIGs
+ * immediately following the things they sign, and order everything
+ * else by RR number. This is all just for aesthetics and
+ * compatibility with buggy software that expects the SOA to be first;
+ * the DNS specifications allow any order.
+ */
+
+static int
+dump_order(const dns_rdataset_t *rds) {
+ int t;
+ int sig;
+ if (rds->type == dns_rdatatype_rrsig) {
+ t = rds->covers;
+ sig = 1;
+ } else {
+ t = rds->type;
+ sig = 0;
+ }
+ switch (t) {
+ case dns_rdatatype_soa:
+ t = 0;
+ break;
+ case dns_rdatatype_ns:
+ t = 1;
+ break;
+ default:
+ t += 2;
+ break;
+ }
+ return ((t << 1) + sig);
+}
+
+static int
+dump_order_compare(const void *a, const void *b) {
+ return (dump_order(*((const dns_rdataset_t *const *)a)) -
+ dump_order(*((const dns_rdataset_t *const *)b)));
+}
+
+/*
+ * Dump all the rdatasets of a domain name to a master file. We make
+ * a "best effort" attempt to sort the RRsets in a nice order, but if
+ * there are more than MAXSORT RRsets, we punt and only sort them in
+ * groups of MAXSORT. This is not expected to ever happen in practice
+ * since much less than 64 RR types have been registered with the
+ * IANA, so far, and the output will be correct (though not
+ * aesthetically pleasing) even if it does happen.
+ */
+
+#define MAXSORT 64
+
+static isc_result_t
+dump_rdatasets_text(isc_mem_t *mctx, const dns_name_t *name,
+ dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx,
+ isc_buffer_t *buffer, FILE *f) {
+ isc_result_t itresult, dumpresult;
+ isc_region_t r;
+ dns_rdataset_t rdatasets[MAXSORT];
+ dns_rdataset_t *sorted[MAXSORT];
+ int i, n;
+
+ itresult = dns_rdatasetiter_first(rdsiter);
+ dumpresult = ISC_R_SUCCESS;
+
+ if (itresult == ISC_R_SUCCESS && ctx->neworigin != NULL) {
+ isc_buffer_clear(buffer);
+ itresult = dns_name_totext(ctx->neworigin, false, buffer);
+ RUNTIME_CHECK(itresult == ISC_R_SUCCESS);
+ isc_buffer_usedregion(buffer, &r);
+ fprintf(f, "$ORIGIN %.*s\n", (int)r.length, (char *)r.base);
+ ctx->neworigin = NULL;
+ }
+
+again:
+ for (i = 0; itresult == ISC_R_SUCCESS && i < MAXSORT;
+ itresult = dns_rdatasetiter_next(rdsiter), i++)
+ {
+ dns_rdataset_init(&rdatasets[i]);
+ dns_rdatasetiter_current(rdsiter, &rdatasets[i]);
+ sorted[i] = &rdatasets[i];
+ }
+ n = i;
+ INSIST(n <= MAXSORT);
+
+ qsort(sorted, n, sizeof(sorted[0]), dump_order_compare);
+
+ for (i = 0; i < n; i++) {
+ dns_rdataset_t *rds = sorted[i];
+
+ if (ANCIENT(rds) &&
+ (ctx->style.flags & DNS_STYLEFLAG_EXPIRED) == 0)
+ {
+ /* Omit expired entries */
+ dns_rdataset_disassociate(rds);
+ continue;
+ }
+
+ if ((ctx->style.flags & DNS_STYLEFLAG_TRUST) != 0) {
+ if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
+ (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
+ {
+ unsigned int j;
+ for (j = 0; j < ctx->indent.count; j++) {
+ fprintf(f, "%s", ctx->indent.string);
+ }
+ }
+ fprintf(f, "; %s\n", dns_trust_totext(rds->trust));
+ }
+ if (((rds->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) &&
+ (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0)
+ {
+ /* Omit negative cache entries */
+ } else {
+ isc_result_t result;
+ if (STALE(rds)) {
+ fprintf(f, "; stale\n");
+ } else if (ANCIENT(rds)) {
+ isc_buffer_t b;
+ char buf[sizeof("YYYYMMDDHHMMSS")];
+ memset(buf, 0, sizeof(buf));
+ isc_buffer_init(&b, buf, sizeof(buf) - 1);
+ dns_time64_totext((uint64_t)rds->ttl, &b);
+ fprintf(f,
+ "; expired since %s "
+ "(awaiting cleanup)\n",
+ buf);
+ }
+ result = dump_rdataset(mctx, name, rds, ctx, buffer, f);
+ if (result != ISC_R_SUCCESS) {
+ dumpresult = result;
+ }
+ if ((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0)
+ {
+ name = NULL;
+ }
+ }
+ if (((ctx->style.flags & DNS_STYLEFLAG_RESIGN) != 0) &&
+ ((rds->attributes & DNS_RDATASETATTR_RESIGN) != 0))
+ {
+ isc_buffer_t b;
+ char buf[sizeof("YYYYMMDDHHMMSS")];
+ memset(buf, 0, sizeof(buf));
+ isc_buffer_init(&b, buf, sizeof(buf) - 1);
+ dns_time64_totext((uint64_t)rds->resign, &b);
+ if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 ||
+ (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0)
+ {
+ unsigned int j;
+ for (j = 0; j < ctx->indent.count; j++) {
+ fprintf(f, "%s", ctx->indent.string);
+ }
+ }
+ fprintf(f, "; resign=%s\n", buf);
+ }
+ dns_rdataset_disassociate(rds);
+ }
+
+ if (dumpresult != ISC_R_SUCCESS) {
+ return (dumpresult);
+ }
+
+ /*
+ * If we got more data than could be sorted at once,
+ * go handle the rest.
+ */
+ if (itresult == ISC_R_SUCCESS) {
+ goto again;
+ }
+
+ if (itresult == ISC_R_NOMORE) {
+ itresult = ISC_R_SUCCESS;
+ }
+
+ return (itresult);
+}
+
+/*
+ * Dump given RRsets in the "raw" format.
+ */
+static isc_result_t
+dump_rdataset_raw(isc_mem_t *mctx, const dns_name_t *name,
+ dns_rdataset_t *rdataset, isc_buffer_t *buffer, FILE *f) {
+ isc_result_t result;
+ uint32_t totallen;
+ uint16_t dlen;
+ isc_region_t r, r_hdr;
+
+ REQUIRE(buffer->length > 0);
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+
+ rdataset->attributes |= DNS_RDATASETATTR_LOADORDER;
+restart:
+ totallen = 0;
+ result = dns_rdataset_first(rdataset);
+ REQUIRE(result == ISC_R_SUCCESS);
+
+ isc_buffer_clear(buffer);
+
+ /*
+ * Common header and owner name (length followed by name)
+ * These fields should be in a moderate length, so we assume we
+ * can store all of them in the initial buffer.
+ */
+ isc_buffer_availableregion(buffer, &r_hdr);
+ INSIST(r_hdr.length >= sizeof(dns_masterrawrdataset_t));
+ isc_buffer_putuint32(buffer, totallen); /* XXX: leave space */
+ isc_buffer_putuint16(buffer, rdataset->rdclass); /* 16-bit class */
+ isc_buffer_putuint16(buffer, rdataset->type); /* 16-bit type */
+ isc_buffer_putuint16(buffer, rdataset->covers); /* same as type */
+ isc_buffer_putuint32(buffer, rdataset->ttl); /* 32-bit TTL */
+ isc_buffer_putuint32(buffer, dns_rdataset_count(rdataset));
+ totallen = isc_buffer_usedlength(buffer);
+ INSIST(totallen <= sizeof(dns_masterrawrdataset_t));
+
+ dns_name_toregion(name, &r);
+ INSIST(isc_buffer_availablelength(buffer) >= (sizeof(dlen) + r.length));
+ dlen = (uint16_t)r.length;
+ isc_buffer_putuint16(buffer, dlen);
+ isc_buffer_copyregion(buffer, &r);
+ totallen += sizeof(dlen) + r.length;
+
+ do {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(rdataset, &rdata);
+ dns_rdata_toregion(&rdata, &r);
+ INSIST(r.length <= 0xffffU);
+ dlen = (uint16_t)r.length;
+
+ /*
+ * Copy the rdata into the buffer. If the buffer is too small,
+ * grow it. This should be rare, so we'll simply restart the
+ * entire procedure (or should we copy the old data and
+ * continue?).
+ */
+ if (isc_buffer_availablelength(buffer) <
+ sizeof(dlen) + r.length)
+ {
+ int newlength;
+ void *newmem;
+
+ newlength = buffer->length * 2;
+ newmem = isc_mem_get(mctx, newlength);
+ isc_mem_put(mctx, buffer->base, buffer->length);
+ isc_buffer_init(buffer, newmem, newlength);
+ goto restart;
+ }
+ isc_buffer_putuint16(buffer, dlen);
+ isc_buffer_copyregion(buffer, &r);
+ totallen += sizeof(dlen) + r.length;
+
+ result = dns_rdataset_next(rdataset);
+ } while (result == ISC_R_SUCCESS);
+
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ /*
+ * Fill in the total length field.
+ * XXX: this is a bit tricky. Since we have already "used" the space
+ * for the total length in the buffer, we first remember the entire
+ * buffer length in the region, "rewind", and then write the value.
+ */
+ isc_buffer_usedregion(buffer, &r);
+ isc_buffer_clear(buffer);
+ isc_buffer_putuint32(buffer, totallen);
+ INSIST(isc_buffer_usedlength(buffer) < totallen);
+
+ /*
+ * Write the buffer contents to the raw master file.
+ */
+ result = isc_stdio_write(r.base, 1, (size_t)r.length, f, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "raw master file write failed: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dump_rdatasets_raw(isc_mem_t *mctx, const dns_name_t *owner_name,
+ dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx,
+ isc_buffer_t *buffer, FILE *f) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+
+ name = dns_fixedname_initname(&fixed);
+ dns_name_copynf(owner_name, name);
+ for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsiter))
+ {
+ dns_rdataset_init(&rdataset);
+ dns_rdatasetiter_current(rdsiter, &rdataset);
+
+ dns_rdataset_getownercase(&rdataset, name);
+
+ if (((rdataset.attributes & DNS_RDATASETATTR_NEGATIVE) != 0) &&
+ (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0)
+ {
+ /* Omit negative cache entries */
+ } else {
+ result = dump_rdataset_raw(mctx, name, &rdataset,
+ buffer, f);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dump_rdatasets_map(isc_mem_t *mctx, const dns_name_t *name,
+ dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx,
+ isc_buffer_t *buffer, FILE *f) {
+ UNUSED(mctx);
+ UNUSED(name);
+ UNUSED(rdsiter);
+ UNUSED(ctx);
+ UNUSED(buffer);
+ UNUSED(f);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+/*
+ * Initial size of text conversion buffer. The buffer is used
+ * for several purposes: converting origin names, rdatasets,
+ * $DATE timestamps, and comment strings for $TTL directives.
+ *
+ * When converting rdatasets, it is dynamically resized, but
+ * when converting origins, timestamps, etc it is not. Therefore,
+ * the initial size must large enough to hold the longest possible
+ * text representation of any domain name (for $ORIGIN).
+ */
+static const int initial_buffer_length = 1200;
+
+static isc_result_t
+dumptostream(dns_dumpctx_t *dctx);
+
+static void
+dumpctx_destroy(dns_dumpctx_t *dctx) {
+ dctx->magic = 0;
+ isc_mutex_destroy(&dctx->lock);
+ dns_dbiterator_destroy(&dctx->dbiter);
+ if (dctx->version != NULL) {
+ dns_db_closeversion(dctx->db, &dctx->version, false);
+ }
+ dns_db_detach(&dctx->db);
+ if (dctx->task != NULL) {
+ isc_task_detach(&dctx->task);
+ }
+ if (dctx->file != NULL) {
+ isc_mem_free(dctx->mctx, dctx->file);
+ }
+ if (dctx->tmpfile != NULL) {
+ isc_mem_free(dctx->mctx, dctx->tmpfile);
+ }
+ isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(*dctx));
+}
+
+void
+dns_dumpctx_attach(dns_dumpctx_t *source, dns_dumpctx_t **target) {
+ REQUIRE(DNS_DCTX_VALID(source));
+ REQUIRE(target != NULL && *target == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *target = source;
+}
+
+void
+dns_dumpctx_detach(dns_dumpctx_t **dctxp) {
+ dns_dumpctx_t *dctx;
+
+ REQUIRE(dctxp != NULL);
+ dctx = *dctxp;
+ *dctxp = NULL;
+ REQUIRE(DNS_DCTX_VALID(dctx));
+
+ if (isc_refcount_decrement(&dctx->references) == 1) {
+ dumpctx_destroy(dctx);
+ }
+}
+
+dns_dbversion_t *
+dns_dumpctx_version(dns_dumpctx_t *dctx) {
+ REQUIRE(DNS_DCTX_VALID(dctx));
+ return (dctx->version);
+}
+
+dns_db_t *
+dns_dumpctx_db(dns_dumpctx_t *dctx) {
+ REQUIRE(DNS_DCTX_VALID(dctx));
+ return (dctx->db);
+}
+
+void
+dns_dumpctx_cancel(dns_dumpctx_t *dctx) {
+ REQUIRE(DNS_DCTX_VALID(dctx));
+
+ atomic_store_release(&dctx->canceled, true);
+}
+
+static isc_result_t
+flushandsync(FILE *f, isc_result_t result, const char *temp) {
+ bool logit = (result == ISC_R_SUCCESS);
+
+ if (result == ISC_R_SUCCESS) {
+ result = isc_stdio_flush(f);
+ }
+ if (result != ISC_R_SUCCESS && logit) {
+ if (temp != NULL) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping to master file: %s: flush: %s",
+ temp, isc_result_totext(result));
+ } else {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping to stream: flush: %s",
+ isc_result_totext(result));
+ }
+ logit = false;
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ result = isc_stdio_sync(f);
+ }
+ if (result != ISC_R_SUCCESS && logit) {
+ if (temp != NULL) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping to master file: %s: fsync: %s",
+ temp, isc_result_totext(result));
+ } else {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping to stream: fsync: %s",
+ isc_result_totext(result));
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+closeandrename(FILE *f, isc_result_t result, const char *temp,
+ const char *file) {
+ isc_result_t tresult;
+ bool logit = (result == ISC_R_SUCCESS);
+
+ result = flushandsync(f, result, temp);
+ if (result != ISC_R_SUCCESS) {
+ logit = false;
+ }
+
+ tresult = isc_stdio_close(f);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ if (result != ISC_R_SUCCESS && logit) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping master file: %s: fclose: %s", temp,
+ isc_result_totext(result));
+ logit = false;
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = isc_file_rename(temp, file);
+ } else {
+ (void)isc_file_remove(temp);
+ }
+ if (result != ISC_R_SUCCESS && logit) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping master file: rename: %s: %s", file,
+ isc_result_totext(result));
+ }
+ return (result);
+}
+
+/*
+ * This will run in a libuv threadpool thread.
+ */
+static void
+master_dump_cb(void *data) {
+ isc_result_t result = ISC_R_UNSET;
+ dns_dumpctx_t *dctx = data;
+ REQUIRE(DNS_DCTX_VALID(dctx));
+
+ if (atomic_load_acquire(&dctx->canceled)) {
+ result = ISC_R_CANCELED;
+ } else {
+ result = dumptostream(dctx);
+ }
+
+ if (dctx->file != NULL) {
+ isc_result_t tresult = ISC_R_UNSET;
+ tresult = closeandrename(dctx->f, result, dctx->tmpfile,
+ dctx->file);
+ if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ } else {
+ result = flushandsync(dctx->f, result, NULL);
+ }
+
+ dctx->result = result;
+}
+
+/*
+ * This will run in a network/task manager thread when the dump is complete.
+ */
+static void
+master_dump_done_cb(void *data, isc_result_t result) {
+ dns_dumpctx_t *dctx = data;
+
+ if (result == ISC_R_SUCCESS && dctx->result != ISC_R_SUCCESS) {
+ result = dctx->result;
+ }
+
+ (dctx->done)(dctx->done_arg, result);
+ dns_dumpctx_detach(&dctx);
+}
+
+/*
+ * This must be run from a network/task manager thread.
+ */
+static void
+setup_dump(isc_task_t *task, isc_event_t *event) {
+ dns_dumpctx_t *dctx = NULL;
+
+ REQUIRE(isc_nm_tid() >= 0);
+ REQUIRE(event != NULL);
+
+ dctx = event->ev_arg;
+
+ REQUIRE(DNS_DCTX_VALID(dctx));
+
+ isc_nm_work_offload(isc_task_getnetmgr(task), master_dump_cb,
+ master_dump_done_cb, dctx);
+
+ isc_event_free(&event);
+}
+
+static isc_result_t
+task_send(dns_dumpctx_t *dctx) {
+ isc_event_t *event;
+
+ event = isc_event_allocate(dctx->mctx, NULL, DNS_EVENT_DUMPQUANTUM,
+ setup_dump, dctx, sizeof(*event));
+ isc_task_send(dctx->task, &event);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dumpctx_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style, FILE *f, dns_dumpctx_t **dctxp,
+ dns_masterformat_t format, dns_masterrawheader_t *header) {
+ dns_dumpctx_t *dctx;
+ isc_result_t result;
+ unsigned int options;
+
+ dctx = isc_mem_get(mctx, sizeof(*dctx));
+
+ dctx->mctx = NULL;
+ dctx->f = f;
+ dctx->dbiter = NULL;
+ dctx->db = NULL;
+ dctx->version = NULL;
+ dctx->done = NULL;
+ dctx->done_arg = NULL;
+ dctx->task = NULL;
+ atomic_init(&dctx->canceled, false);
+ dctx->file = NULL;
+ dctx->tmpfile = NULL;
+ dctx->format = format;
+ if (header == NULL) {
+ dns_master_initrawheader(&dctx->header);
+ } else {
+ dctx->header = *header;
+ }
+
+ switch (format) {
+ case dns_masterformat_text:
+ dctx->dumpsets = dump_rdatasets_text;
+ break;
+ case dns_masterformat_raw:
+ dctx->dumpsets = dump_rdatasets_raw;
+ break;
+ case dns_masterformat_map:
+ dctx->dumpsets = dump_rdatasets_map;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ result = totext_ctx_init(style, NULL, &dctx->tctx);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "could not set master file style");
+ goto cleanup;
+ }
+
+ isc_stdtime_get(&dctx->now);
+ dns_db_attach(db, &dctx->db);
+
+ dctx->do_date = dns_db_iscache(dctx->db);
+ if (dctx->do_date) {
+ (void)dns_db_getservestalettl(dctx->db,
+ &dctx->tctx.serve_stale_ttl);
+ }
+
+ if (dctx->format == dns_masterformat_text &&
+ (dctx->tctx.style.flags & DNS_STYLEFLAG_REL_OWNER) != 0)
+ {
+ options = DNS_DB_RELATIVENAMES;
+ } else {
+ options = 0;
+ }
+ result = dns_db_createiterator(dctx->db, options, &dctx->dbiter);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_mutex_init(&dctx->lock);
+
+ if (version != NULL) {
+ dns_db_attachversion(dctx->db, version, &dctx->version);
+ } else if (!dns_db_iscache(db)) {
+ dns_db_currentversion(dctx->db, &dctx->version);
+ }
+ isc_mem_attach(mctx, &dctx->mctx);
+
+ isc_refcount_init(&dctx->references, 1);
+ dctx->magic = DNS_DCTX_MAGIC;
+ *dctxp = dctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (dctx->dbiter != NULL) {
+ dns_dbiterator_destroy(&dctx->dbiter);
+ }
+ if (dctx->db != NULL) {
+ dns_db_detach(&dctx->db);
+ }
+ isc_mem_put(mctx, dctx, sizeof(*dctx));
+ return (result);
+}
+
+static isc_result_t
+writeheader(dns_dumpctx_t *dctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_buffer_t buffer;
+ char *bufmem;
+ isc_region_t r;
+ dns_masterrawheader_t rawheader;
+ uint32_t rawversion, now32;
+
+ bufmem = isc_mem_get(dctx->mctx, initial_buffer_length);
+
+ isc_buffer_init(&buffer, bufmem, initial_buffer_length);
+
+ switch (dctx->format) {
+ case dns_masterformat_text:
+ /*
+ * If the database has cache semantics, output an
+ * RFC2540 $DATE directive so that the TTLs can be
+ * adjusted when it is reloaded. For zones it is not
+ * really needed, and it would make the file
+ * incompatible with pre-RFC2540 software, so we omit
+ * it in the zone case.
+ */
+ if (dctx->do_date) {
+ fprintf(dctx->f, "; using a %u second stale ttl\n",
+ dctx->tctx.serve_stale_ttl);
+ result = dns_time32_totext(dctx->now, &buffer);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_buffer_usedregion(&buffer, &r);
+ fprintf(dctx->f, "$DATE %.*s\n", (int)r.length,
+ (char *)r.base);
+ }
+ break;
+ case dns_masterformat_raw:
+ case dns_masterformat_map:
+ r.base = (unsigned char *)&rawheader;
+ r.length = sizeof(rawheader);
+ isc_buffer_region(&buffer, &r);
+ now32 = dctx->now;
+ rawversion = 1;
+ if ((dctx->header.flags & DNS_MASTERRAW_COMPAT) != 0) {
+ rawversion = 0;
+ }
+
+ isc_buffer_putuint32(&buffer, dctx->format);
+ isc_buffer_putuint32(&buffer, rawversion);
+ isc_buffer_putuint32(&buffer, now32);
+
+ if (rawversion == 1) {
+ isc_buffer_putuint32(&buffer, dctx->header.flags);
+ isc_buffer_putuint32(&buffer,
+ dctx->header.sourceserial);
+ isc_buffer_putuint32(&buffer, dctx->header.lastxfrin);
+ }
+
+ INSIST(isc_buffer_usedlength(&buffer) <= sizeof(rawheader));
+ result = isc_stdio_write(buffer.base, 1,
+ isc_buffer_usedlength(&buffer),
+ dctx->f, NULL);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ isc_mem_put(dctx->mctx, buffer.base, buffer.length);
+ return (result);
+}
+
+static isc_result_t
+dumptostream(dns_dumpctx_t *dctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_buffer_t buffer;
+ char *bufmem;
+ dns_name_t *name;
+ dns_fixedname_t fixname;
+ unsigned int options = DNS_DB_STALEOK;
+
+ if ((dctx->tctx.style.flags & DNS_STYLEFLAG_EXPIRED) != 0) {
+ options |= DNS_DB_EXPIREDOK;
+ }
+
+ bufmem = isc_mem_get(dctx->mctx, initial_buffer_length);
+
+ isc_buffer_init(&buffer, bufmem, initial_buffer_length);
+
+ name = dns_fixedname_initname(&fixname);
+
+ CHECK(writeheader(dctx));
+
+ /*
+ * Fast format is not currently written incrementally,
+ * so we make the call to dns_db_serialize() here.
+ * If the database is anything other than an rbtdb,
+ * this should result in not implemented
+ */
+ if (dctx->format == dns_masterformat_map) {
+ result = dns_db_serialize(dctx->db, dctx->version, dctx->f);
+ goto cleanup;
+ }
+
+ result = dns_dbiterator_first(dctx->dbiter);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) {
+ goto cleanup;
+ }
+
+ while (result == ISC_R_SUCCESS) {
+ dns_rdatasetiter_t *rdsiter = NULL;
+ dns_dbnode_t *node = NULL;
+
+ result = dns_dbiterator_current(dctx->dbiter, &node, name);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ break;
+ }
+ if (result == DNS_R_NEWORIGIN) {
+ dns_name_t *origin =
+ dns_fixedname_name(&dctx->tctx.origin_fixname);
+ result = dns_dbiterator_origin(dctx->dbiter, origin);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if ((dctx->tctx.style.flags & DNS_STYLEFLAG_REL_DATA) !=
+ 0)
+ {
+ dctx->tctx.origin = origin;
+ }
+ dctx->tctx.neworigin = origin;
+ }
+
+ result = dns_dbiterator_pause(dctx->dbiter);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ result = dns_db_allrdatasets(dctx->db, node, dctx->version,
+ options, dctx->now, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(dctx->db, &node);
+ goto cleanup;
+ }
+ result = (dctx->dumpsets)(dctx->mctx, name, rdsiter,
+ &dctx->tctx, &buffer, dctx->f);
+ dns_rdatasetiter_destroy(&rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(dctx->db, &node);
+ goto cleanup;
+ }
+ dns_db_detachnode(dctx->db, &node);
+ result = dns_dbiterator_next(dctx->dbiter);
+ }
+
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+cleanup:
+ RUNTIME_CHECK(dns_dbiterator_pause(dctx->dbiter) == ISC_R_SUCCESS);
+ isc_mem_put(dctx->mctx, buffer.base, buffer.length);
+ return (result);
+}
+
+isc_result_t
+dns_master_dumptostreamasync(isc_mem_t *mctx, dns_db_t *db,
+ dns_dbversion_t *version,
+ const dns_master_style_t *style, FILE *f,
+ isc_task_t *task, dns_dumpdonefunc_t done,
+ void *done_arg, dns_dumpctx_t **dctxp) {
+ dns_dumpctx_t *dctx = NULL;
+ isc_result_t result;
+
+ REQUIRE(task != NULL);
+ REQUIRE(f != NULL);
+ REQUIRE(done != NULL);
+
+ result = dumpctx_create(mctx, db, version, style, f, &dctx,
+ dns_masterformat_text, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_task_attach(task, &dctx->task);
+ dctx->done = done;
+ dctx->done_arg = done_arg;
+
+ result = task_send(dctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_dumpctx_attach(dctx, dctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+ dns_dumpctx_detach(&dctx);
+ return (result);
+}
+
+isc_result_t
+dns_master_dumptostream(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style,
+ dns_masterformat_t format,
+ dns_masterrawheader_t *header, FILE *f) {
+ dns_dumpctx_t *dctx = NULL;
+ isc_result_t result;
+
+ result = dumpctx_create(mctx, db, version, style, f, &dctx, format,
+ header);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dumptostream(dctx);
+ INSIST(result != DNS_R_CONTINUE);
+ dns_dumpctx_detach(&dctx);
+
+ result = flushandsync(f, result, NULL);
+ return (result);
+}
+
+static isc_result_t
+opentmp(isc_mem_t *mctx, dns_masterformat_t format, const char *file,
+ char **tempp, FILE **fp) {
+ FILE *f = NULL;
+ isc_result_t result;
+ char *tempname = NULL;
+ int tempnamelen;
+
+ tempnamelen = strlen(file) + 20;
+ tempname = isc_mem_allocate(mctx, tempnamelen);
+
+ result = isc_file_mktemplate(file, tempname, tempnamelen);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (format == dns_masterformat_text) {
+ result = isc_file_openunique(tempname, &f);
+ } else {
+ result = isc_file_bopenunique(tempname, &f);
+ }
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping master file: %s: open: %s", tempname,
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+#if defined(POSIX_FADV_DONTNEED)
+ posix_fadvise(fileno(f), 0, 0, POSIX_FADV_DONTNEED);
+#endif
+
+ *tempp = tempname;
+ *fp = f;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ isc_mem_free(mctx, tempname);
+ return (result);
+}
+
+isc_result_t
+dns_master_dumpasync(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style, const char *filename,
+ isc_task_t *task, dns_dumpdonefunc_t done, void *done_arg,
+ dns_dumpctx_t **dctxp, dns_masterformat_t format,
+ dns_masterrawheader_t *header) {
+ FILE *f = NULL;
+ isc_result_t result;
+ char *tempname = NULL;
+ char *file = NULL;
+ dns_dumpctx_t *dctx = NULL;
+
+ file = isc_mem_strdup(mctx, filename);
+
+ result = opentmp(mctx, format, filename, &tempname, &f);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dumpctx_create(mctx, db, version, style, f, &dctx, format,
+ header);
+ if (result != ISC_R_SUCCESS) {
+ (void)isc_stdio_close(f);
+ (void)isc_file_remove(tempname);
+ goto cleanup;
+ }
+
+ isc_task_attach(task, &dctx->task);
+ dctx->done = done;
+ dctx->done_arg = done_arg;
+ dctx->file = file;
+ file = NULL;
+ dctx->tmpfile = tempname;
+ tempname = NULL;
+
+ result = task_send(dctx);
+ if (result == ISC_R_SUCCESS) {
+ dns_dumpctx_attach(dctx, dctxp);
+ return (DNS_R_CONTINUE);
+ }
+
+cleanup:
+ if (dctx != NULL) {
+ dns_dumpctx_detach(&dctx);
+ }
+ if (file != NULL) {
+ isc_mem_free(mctx, file);
+ }
+ if (tempname != NULL) {
+ isc_mem_free(mctx, tempname);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_master_dump(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ const dns_master_style_t *style, const char *filename,
+ dns_masterformat_t format, dns_masterrawheader_t *header) {
+ FILE *f = NULL;
+ isc_result_t result;
+ char *tempname;
+ dns_dumpctx_t *dctx = NULL;
+
+ result = opentmp(mctx, format, filename, &tempname, &f);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dumpctx_create(mctx, db, version, style, f, &dctx, format,
+ header);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dumptostream(dctx);
+ INSIST(result != DNS_R_CONTINUE);
+ dns_dumpctx_detach(&dctx);
+
+ result = closeandrename(f, result, tempname, filename);
+
+cleanup:
+ isc_mem_free(mctx, tempname);
+ return (result);
+}
+
+/*
+ * Dump a database node into a master file.
+ * XXX: this function assumes the text format.
+ */
+isc_result_t
+dns_master_dumpnodetostream(isc_mem_t *mctx, dns_db_t *db,
+ dns_dbversion_t *version, dns_dbnode_t *node,
+ const dns_name_t *name,
+ const dns_master_style_t *style, FILE *f) {
+ isc_result_t result;
+ isc_buffer_t buffer;
+ char *bufmem;
+ isc_stdtime_t now;
+ dns_totext_ctx_t ctx;
+ dns_rdatasetiter_t *rdsiter = NULL;
+ unsigned int options = DNS_DB_STALEOK;
+
+ if ((style->flags & DNS_STYLEFLAG_EXPIRED) != 0) {
+ options |= DNS_DB_EXPIREDOK;
+ }
+
+ result = totext_ctx_init(style, NULL, &ctx);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "could not set master file style");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ isc_stdtime_get(&now);
+
+ bufmem = isc_mem_get(mctx, initial_buffer_length);
+
+ isc_buffer_init(&buffer, bufmem, initial_buffer_length);
+
+ result = dns_db_allrdatasets(db, node, version, options, now, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ result = dump_rdatasets_text(mctx, name, rdsiter, &ctx, &buffer, f);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ dns_rdatasetiter_destroy(&rdsiter);
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ isc_mem_put(mctx, buffer.base, buffer.length);
+ return (result);
+}
+
+isc_result_t
+dns_master_dumpnode(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
+ dns_dbnode_t *node, const dns_name_t *name,
+ const dns_master_style_t *style, const char *filename) {
+ FILE *f = NULL;
+ isc_result_t result;
+
+ result = isc_stdio_open(filename, "w", &f);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping node to file: %s: open: %s", filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+
+ result = dns_master_dumpnodetostream(mctx, db, version, node, name,
+ style, f);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping master file: %s: dump: %s", filename,
+ isc_result_totext(result));
+ (void)isc_stdio_close(f);
+ return (ISC_R_UNEXPECTED);
+ }
+
+ result = isc_stdio_close(f);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR,
+ "dumping master file: %s: close: %s", filename,
+ isc_result_totext(result));
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (result);
+}
+
+dns_masterstyle_flags_t
+dns_master_styleflags(const dns_master_style_t *style) {
+ REQUIRE(style != NULL);
+ return (style->flags);
+}
+
+isc_result_t
+dns_master_stylecreate(dns_master_style_t **stylep,
+ dns_masterstyle_flags_t flags, unsigned int ttl_column,
+ unsigned int class_column, unsigned int type_column,
+ unsigned int rdata_column, unsigned int line_length,
+ unsigned int tab_width, unsigned int split_width,
+ isc_mem_t *mctx) {
+ dns_master_style_t *style;
+
+ REQUIRE(stylep != NULL && *stylep == NULL);
+ style = isc_mem_get(mctx, sizeof(*style));
+
+ style->flags = flags;
+ style->ttl_column = ttl_column;
+ style->class_column = class_column;
+ style->type_column = type_column;
+ style->rdata_column = rdata_column;
+ style->line_length = line_length;
+ style->tab_width = tab_width;
+ style->split_width = split_width;
+ *stylep = style;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_master_styledestroy(dns_master_style_t **stylep, isc_mem_t *mctx) {
+ dns_master_style_t *style;
+
+ REQUIRE(stylep != NULL && *stylep != NULL);
+ style = *stylep;
+ *stylep = NULL;
+ isc_mem_put(mctx, style, sizeof(*style));
+}
diff --git a/lib/dns/message.c b/lib/dns/message.c
new file mode 100644
index 0000000..09645c2
--- /dev/null
+++ b/lib/dns/message.c
@@ -0,0 +1,4748 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+/***
+ *** Imports
+ ***/
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/print.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/result.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);
+ if (msgblock == NULL) {
+ return (NULL);
+ }
+
+ ISC_LIST_APPEND(msg->rdatas, msgblock, link);
+
+ rdata = msgblock_get(msgblock, dns_rdata_t);
+ }
+
+ dns_rdata_init(rdata);
+ return (rdata);
+}
+
+static void
+releaserdatalist(dns_message_t *msg, dns_rdatalist_t *rdatalist) {
+ ISC_LIST_PREPEND(msg->freerdatalist, rdatalist, link);
+}
+
+static dns_rdatalist_t *
+newrdatalist(dns_message_t *msg) {
+ dns_msgblock_t *msgblock;
+ dns_rdatalist_t *rdatalist;
+
+ rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
+ if (rdatalist != NULL) {
+ ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link);
+ goto out;
+ }
+
+ msgblock = ISC_LIST_TAIL(msg->rdatalists);
+ rdatalist = msgblock_get(msgblock, dns_rdatalist_t);
+ if (rdatalist == NULL) {
+ msgblock = msgblock_allocate(msg->mctx, sizeof(dns_rdatalist_t),
+ RDATALIST_COUNT);
+ if (msgblock == NULL) {
+ return (NULL);
+ }
+
+ ISC_LIST_APPEND(msg->rdatalists, msgblock, link);
+
+ rdatalist = msgblock_get(msgblock, dns_rdatalist_t);
+ }
+out:
+ if (rdatalist != NULL) {
+ dns_rdatalist_init(rdatalist);
+ }
+
+ return (rdatalist);
+}
+
+static dns_offsets_t *
+newoffsets(dns_message_t *msg) {
+ dns_msgblock_t *msgblock;
+ dns_offsets_t *offsets;
+
+ msgblock = ISC_LIST_TAIL(msg->offsets);
+ offsets = msgblock_get(msgblock, dns_offsets_t);
+ if (offsets == NULL) {
+ msgblock = msgblock_allocate(msg->mctx, sizeof(dns_offsets_t),
+ OFFSET_COUNT);
+ if (msgblock == NULL) {
+ return (NULL);
+ }
+
+ ISC_LIST_APPEND(msg->offsets, msgblock, link);
+
+ offsets = msgblock_get(msgblock, dns_offsets_t);
+ }
+
+ return (offsets);
+}
+
+static void
+msginitheader(dns_message_t *m) {
+ m->id = 0;
+ m->flags = 0;
+ m->rcode = 0;
+ m->opcode = 0;
+ m->rdclass = 0;
+}
+
+static void
+msginitprivate(dns_message_t *m) {
+ unsigned int i;
+
+ for (i = 0; i < DNS_SECTION_MAX; i++) {
+ m->cursors[i] = NULL;
+ m->counts[i] = 0;
+ }
+ m->opt = NULL;
+ m->sig0 = NULL;
+ m->sig0name = NULL;
+ m->tsig = NULL;
+ m->tsigname = NULL;
+ m->state = DNS_SECTION_ANY; /* indicate nothing parsed or rendered */
+ m->opt_reserved = 0;
+ m->sig_reserved = 0;
+ m->reserved = 0;
+ m->padding = 0;
+ m->padding_off = 0;
+ m->buffer = NULL;
+}
+
+static void
+msginittsig(dns_message_t *m) {
+ m->tsigstatus = dns_rcode_noerror;
+ m->querytsigstatus = dns_rcode_noerror;
+ m->tsigkey = NULL;
+ m->tsigctx = NULL;
+ m->sigstart = -1;
+ m->sig0key = NULL;
+ m->sig0status = dns_rcode_noerror;
+ m->timeadjust = 0;
+}
+
+/*
+ * Init elements to default state. Used both when allocating a new element
+ * and when resetting one.
+ */
+static void
+msginit(dns_message_t *m) {
+ msginitheader(m);
+ msginitprivate(m);
+ msginittsig(m);
+ m->header_ok = 0;
+ m->question_ok = 0;
+ m->tcp_continuation = 0;
+ m->verified_sig = 0;
+ m->verify_attempted = 0;
+ m->order = NULL;
+ m->order_arg.env = NULL;
+ m->order_arg.acl = NULL;
+ m->order_arg.element = NULL;
+ m->query.base = NULL;
+ m->query.length = 0;
+ m->free_query = 0;
+ m->saved.base = NULL;
+ m->saved.length = 0;
+ m->free_saved = 0;
+ m->cc_ok = 0;
+ m->cc_bad = 0;
+ m->tkey = 0;
+ m->rdclass_set = 0;
+ m->querytsig = NULL;
+ m->indent.string = "\t";
+ m->indent.count = 0;
+}
+
+static void
+msgresetnames(dns_message_t *msg, unsigned int first_section) {
+ unsigned int i;
+ dns_name_t *name, *next_name;
+ dns_rdataset_t *rds, *next_rds;
+
+ /*
+ * Clean up name lists by calling the rdataset disassociate function.
+ */
+ for (i = first_section; i < DNS_SECTION_MAX; i++) {
+ name = ISC_LIST_HEAD(msg->sections[i]);
+ while (name != NULL) {
+ next_name = ISC_LIST_NEXT(name, link);
+ ISC_LIST_UNLINK(msg->sections[i], name, link);
+
+ rds = ISC_LIST_HEAD(name->list);
+ while (rds != NULL) {
+ next_rds = ISC_LIST_NEXT(rds, link);
+ ISC_LIST_UNLINK(name->list, rds, link);
+
+ INSIST(dns_rdataset_isassociated(rds));
+ dns_rdataset_disassociate(rds);
+ isc_mempool_put(msg->rdspool, rds);
+ rds = next_rds;
+ }
+ dns_message_puttempname(msg, &name);
+ name = next_name;
+ }
+ }
+}
+
+static void
+msgresetopt(dns_message_t *msg) {
+ if (msg->opt != NULL) {
+ if (msg->opt_reserved > 0) {
+ dns_message_renderrelease(msg, msg->opt_reserved);
+ msg->opt_reserved = 0;
+ }
+ INSIST(dns_rdataset_isassociated(msg->opt));
+ dns_rdataset_disassociate(msg->opt);
+ isc_mempool_put(msg->rdspool, msg->opt);
+ msg->opt = NULL;
+ msg->cc_ok = 0;
+ msg->cc_bad = 0;
+ }
+}
+
+static void
+msgresetsigs(dns_message_t *msg, bool replying) {
+ if (msg->sig_reserved > 0) {
+ dns_message_renderrelease(msg, msg->sig_reserved);
+ msg->sig_reserved = 0;
+ }
+ if (msg->tsig != NULL) {
+ INSIST(dns_rdataset_isassociated(msg->tsig));
+ INSIST(msg->namepool != NULL);
+ if (replying) {
+ INSIST(msg->querytsig == NULL);
+ msg->querytsig = msg->tsig;
+ } else {
+ dns_rdataset_disassociate(msg->tsig);
+ isc_mempool_put(msg->rdspool, msg->tsig);
+ if (msg->querytsig != NULL) {
+ dns_rdataset_disassociate(msg->querytsig);
+ isc_mempool_put(msg->rdspool, msg->querytsig);
+ }
+ }
+ dns_message_puttempname(msg, &msg->tsigname);
+ msg->tsig = NULL;
+ } else if (msg->querytsig != NULL && !replying) {
+ dns_rdataset_disassociate(msg->querytsig);
+ isc_mempool_put(msg->rdspool, msg->querytsig);
+ msg->querytsig = NULL;
+ }
+ if (msg->sig0 != NULL) {
+ INSIST(dns_rdataset_isassociated(msg->sig0));
+ dns_rdataset_disassociate(msg->sig0);
+ isc_mempool_put(msg->rdspool, msg->sig0);
+ if (msg->sig0name != NULL) {
+ dns_message_puttempname(msg, &msg->sig0name);
+ }
+ msg->sig0 = NULL;
+ msg->sig0name = NULL;
+ }
+}
+
+/*
+ * Free all but one (or everything) for this message. This is used by
+ * both dns_message_reset() and dns__message_destroy().
+ */
+static void
+msgreset(dns_message_t *msg, bool everything) {
+ dns_msgblock_t *msgblock, *next_msgblock;
+ isc_buffer_t *dynbuf, *next_dynbuf;
+ dns_rdata_t *rdata;
+ dns_rdatalist_t *rdatalist;
+
+ msgresetnames(msg, 0);
+ msgresetopt(msg);
+ msgresetsigs(msg, false);
+
+ /*
+ * Clean up linked lists.
+ */
+
+ /*
+ * Run through the free lists, and just unlink anything found there.
+ * The memory isn't lost since these are part of message blocks we
+ * have allocated.
+ */
+ rdata = ISC_LIST_HEAD(msg->freerdata);
+ while (rdata != NULL) {
+ ISC_LIST_UNLINK(msg->freerdata, rdata, link);
+ rdata = ISC_LIST_HEAD(msg->freerdata);
+ }
+ rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
+ while (rdatalist != NULL) {
+ ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link);
+ rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
+ }
+
+ dynbuf = ISC_LIST_HEAD(msg->scratchpad);
+ INSIST(dynbuf != NULL);
+ if (!everything) {
+ isc_buffer_clear(dynbuf);
+ dynbuf = ISC_LIST_NEXT(dynbuf, link);
+ }
+ while (dynbuf != NULL) {
+ next_dynbuf = ISC_LIST_NEXT(dynbuf, link);
+ ISC_LIST_UNLINK(msg->scratchpad, dynbuf, link);
+ isc_buffer_free(&dynbuf);
+ dynbuf = next_dynbuf;
+ }
+
+ msgblock = ISC_LIST_HEAD(msg->rdatas);
+ if (!everything && msgblock != NULL) {
+ msgblock_reset(msgblock);
+ msgblock = ISC_LIST_NEXT(msgblock, link);
+ }
+ while (msgblock != NULL) {
+ next_msgblock = ISC_LIST_NEXT(msgblock, link);
+ ISC_LIST_UNLINK(msg->rdatas, msgblock, link);
+ msgblock_free(msg->mctx, msgblock, sizeof(dns_rdata_t));
+ msgblock = next_msgblock;
+ }
+
+ /*
+ * rdatalists could be empty.
+ */
+
+ msgblock = ISC_LIST_HEAD(msg->rdatalists);
+ if (!everything && msgblock != NULL) {
+ msgblock_reset(msgblock);
+ msgblock = ISC_LIST_NEXT(msgblock, link);
+ }
+ while (msgblock != NULL) {
+ next_msgblock = ISC_LIST_NEXT(msgblock, link);
+ ISC_LIST_UNLINK(msg->rdatalists, msgblock, link);
+ msgblock_free(msg->mctx, msgblock, sizeof(dns_rdatalist_t));
+ msgblock = next_msgblock;
+ }
+
+ msgblock = ISC_LIST_HEAD(msg->offsets);
+ if (!everything && msgblock != NULL) {
+ msgblock_reset(msgblock);
+ msgblock = ISC_LIST_NEXT(msgblock, link);
+ }
+ while (msgblock != NULL) {
+ next_msgblock = ISC_LIST_NEXT(msgblock, link);
+ ISC_LIST_UNLINK(msg->offsets, msgblock, link);
+ msgblock_free(msg->mctx, msgblock, sizeof(dns_offsets_t));
+ msgblock = next_msgblock;
+ }
+
+ if (msg->tsigkey != NULL) {
+ dns_tsigkey_detach(&msg->tsigkey);
+ msg->tsigkey = NULL;
+ }
+
+ if (msg->tsigctx != NULL) {
+ dst_context_destroy(&msg->tsigctx);
+ }
+
+ if (msg->query.base != NULL) {
+ if (msg->free_query != 0) {
+ isc_mem_put(msg->mctx, msg->query.base,
+ msg->query.length);
+ }
+ msg->query.base = NULL;
+ msg->query.length = 0;
+ }
+
+ if (msg->saved.base != NULL) {
+ if (msg->free_saved != 0) {
+ isc_mem_put(msg->mctx, msg->saved.base,
+ msg->saved.length);
+ }
+ msg->saved.base = NULL;
+ msg->saved.length = 0;
+ }
+
+ /*
+ * cleanup the buffer cleanup list
+ */
+ dynbuf = ISC_LIST_HEAD(msg->cleanup);
+ while (dynbuf != NULL) {
+ next_dynbuf = ISC_LIST_NEXT(dynbuf, link);
+ ISC_LIST_UNLINK(msg->cleanup, dynbuf, link);
+ isc_buffer_free(&dynbuf);
+ dynbuf = next_dynbuf;
+ }
+
+ /*
+ * Set other bits to normal default values.
+ */
+ if (!everything) {
+ msginit(msg);
+ }
+
+ ENSURE(isc_mempool_getallocated(msg->namepool) == 0);
+ ENSURE(isc_mempool_getallocated(msg->rdspool) == 0);
+}
+
+static unsigned int
+spacefortsig(dns_tsigkey_t *key, int otherlen) {
+ isc_region_t r1, r2;
+ unsigned int x;
+ isc_result_t result;
+
+ /*
+ * The space required for an TSIG record is:
+ *
+ * n1 bytes for the name
+ * 2 bytes for the type
+ * 2 bytes for the class
+ * 4 bytes for the ttl
+ * 2 bytes for the rdlength
+ * n2 bytes for the algorithm name
+ * 6 bytes for the time signed
+ * 2 bytes for the fudge
+ * 2 bytes for the MAC size
+ * x bytes for the MAC
+ * 2 bytes for the original id
+ * 2 bytes for the error
+ * 2 bytes for the other data length
+ * y bytes for the other data (at most)
+ * ---------------------------------
+ * 26 + n1 + n2 + x + y bytes
+ */
+
+ dns_name_toregion(&key->name, &r1);
+ dns_name_toregion(key->algorithm, &r2);
+ if (key->key == NULL) {
+ x = 0;
+ } else {
+ result = dst_key_sigsize(key->key, &x);
+ if (result != ISC_R_SUCCESS) {
+ x = 0;
+ }
+ }
+ return (26 + r1.length + r2.length + x + otherlen);
+}
+
+void
+dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp) {
+ dns_message_t *m = NULL;
+ isc_buffer_t *dynbuf = NULL;
+ unsigned int i;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(msgp != NULL);
+ REQUIRE(*msgp == NULL);
+ REQUIRE(intent == DNS_MESSAGE_INTENTPARSE ||
+ intent == DNS_MESSAGE_INTENTRENDER);
+
+ m = isc_mem_get(mctx, sizeof(dns_message_t));
+ *m = (dns_message_t){ .from_to_wire = intent };
+ isc_mem_attach(mctx, &m->mctx);
+ msginit(m);
+
+ for (i = 0; i < DNS_SECTION_MAX; i++) {
+ ISC_LIST_INIT(m->sections[i]);
+ }
+
+ ISC_LIST_INIT(m->scratchpad);
+ ISC_LIST_INIT(m->cleanup);
+ ISC_LIST_INIT(m->rdatas);
+ ISC_LIST_INIT(m->rdatalists);
+ ISC_LIST_INIT(m->offsets);
+ ISC_LIST_INIT(m->freerdata);
+ ISC_LIST_INIT(m->freerdatalist);
+
+ isc_mempool_create(m->mctx, sizeof(dns_fixedname_t), &m->namepool);
+ isc_mempool_setfillcount(m->namepool, NAME_FILLCOUNT);
+ isc_mempool_setfreemax(m->namepool, NAME_FREEMAX);
+ isc_mempool_setname(m->namepool, "msg:names");
+
+ isc_mempool_create(m->mctx, sizeof(dns_rdataset_t), &m->rdspool);
+ isc_mempool_setfillcount(m->rdspool, RDATASET_FILLCOUNT);
+ isc_mempool_setfreemax(m->rdspool, RDATASET_FREEMAX);
+ isc_mempool_setname(m->rdspool, "msg:rdataset");
+
+ isc_buffer_allocate(mctx, &dynbuf, SCRATCHPAD_SIZE);
+ ISC_LIST_APPEND(m->scratchpad, dynbuf, link);
+
+ isc_refcount_init(&m->refcount, 1);
+ m->magic = DNS_MESSAGE_MAGIC;
+
+ *msgp = m;
+}
+
+void
+dns_message_reset(dns_message_t *msg, unsigned int intent) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(intent == DNS_MESSAGE_INTENTPARSE ||
+ intent == DNS_MESSAGE_INTENTRENDER);
+
+ msgreset(msg, false);
+ msg->from_to_wire = intent;
+}
+
+static void
+dns__message_destroy(dns_message_t *msg) {
+ REQUIRE(msg != NULL);
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ msgreset(msg, true);
+ isc_mempool_destroy(&msg->namepool);
+ isc_mempool_destroy(&msg->rdspool);
+ isc_refcount_destroy(&msg->refcount);
+ msg->magic = 0;
+ isc_mem_putanddetach(&msg->mctx, msg, sizeof(dns_message_t));
+}
+
+void
+dns_message_attach(dns_message_t *source, dns_message_t **target) {
+ REQUIRE(DNS_MESSAGE_VALID(source));
+
+ isc_refcount_increment(&source->refcount);
+ *target = source;
+}
+
+void
+dns_message_detach(dns_message_t **messagep) {
+ REQUIRE(messagep != NULL && DNS_MESSAGE_VALID(*messagep));
+ dns_message_t *msg = *messagep;
+ *messagep = NULL;
+
+ if (isc_refcount_decrement(&msg->refcount) == 1) {
+ dns__message_destroy(msg);
+ }
+}
+
+static isc_result_t
+findname(dns_name_t **foundname, const dns_name_t *target,
+ dns_namelist_t *section) {
+ dns_name_t *curr;
+
+ for (curr = ISC_LIST_TAIL(*section); curr != NULL;
+ curr = ISC_LIST_PREV(curr, link))
+ {
+ if (dns_name_equal(curr, target)) {
+ if (foundname != NULL) {
+ *foundname = curr;
+ }
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_message_find(const dns_name_t *name, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, dns_rdatatype_t covers,
+ dns_rdataset_t **rdataset) {
+ dns_rdataset_t *curr;
+
+ REQUIRE(name != NULL);
+ REQUIRE(rdataset == NULL || *rdataset == NULL);
+
+ for (curr = ISC_LIST_TAIL(name->list); curr != NULL;
+ curr = ISC_LIST_PREV(curr, link))
+ {
+ if (curr->rdclass == rdclass && curr->type == type &&
+ curr->covers == covers)
+ {
+ if (rdataset != NULL) {
+ *rdataset = curr;
+ }
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_message_findtype(const dns_name_t *name, dns_rdatatype_t type,
+ dns_rdatatype_t covers, dns_rdataset_t **rdataset) {
+ dns_rdataset_t *curr;
+
+ REQUIRE(name != NULL);
+ REQUIRE(rdataset == NULL || *rdataset == NULL);
+
+ for (curr = ISC_LIST_TAIL(name->list); curr != NULL;
+ curr = ISC_LIST_PREV(curr, link))
+ {
+ if (curr->type == type && curr->covers == covers) {
+ if (ISC_UNLIKELY(rdataset != NULL)) {
+ *rdataset = curr;
+ }
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+/*
+ * Read a name from buffer "source".
+ */
+static isc_result_t
+getname(dns_name_t *name, isc_buffer_t *source, dns_message_t *msg,
+ dns_decompress_t *dctx) {
+ isc_buffer_t *scratch;
+ isc_result_t result;
+ unsigned int tries;
+
+ scratch = currentbuffer(msg);
+
+ /*
+ * First try: use current buffer.
+ * Second try: allocate a new buffer and use that.
+ */
+ tries = 0;
+ while (tries < 2) {
+ result = dns_name_fromwire(name, source, dctx, 0, scratch);
+
+ if (result == ISC_R_NOSPACE) {
+ tries++;
+
+ result = newbuffer(msg, SCRATCHPAD_SIZE);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ scratch = currentbuffer(msg);
+ dns_name_reset(name);
+ } else {
+ return (result);
+ }
+ }
+
+ UNREACHABLE();
+}
+
+static isc_result_t
+getrdata(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
+ dns_rdataclass_t rdclass, dns_rdatatype_t rdtype,
+ unsigned int rdatalen, dns_rdata_t *rdata) {
+ isc_buffer_t *scratch;
+ isc_result_t result;
+ unsigned int tries;
+ unsigned int trysize;
+
+ scratch = currentbuffer(msg);
+
+ isc_buffer_setactive(source, rdatalen);
+
+ /*
+ * First try: use current buffer.
+ * Second try: allocate a new buffer of size
+ * max(SCRATCHPAD_SIZE, 2 * compressed_rdatalen)
+ * (the data will fit if it was not more than 50% compressed)
+ * Subsequent tries: double buffer size on each try.
+ */
+ tries = 0;
+ trysize = 0;
+ /* XXX possibly change this to a while (tries < 2) loop */
+ for (;;) {
+ result = dns_rdata_fromwire(rdata, rdclass, rdtype, source,
+ dctx, 0, scratch);
+
+ if (result == ISC_R_NOSPACE) {
+ if (tries == 0) {
+ trysize = 2 * rdatalen;
+ if (trysize < SCRATCHPAD_SIZE) {
+ trysize = SCRATCHPAD_SIZE;
+ }
+ } else {
+ INSIST(trysize != 0);
+ if (trysize >= 65535) {
+ return (ISC_R_NOSPACE);
+ }
+ /* XXX DNS_R_RRTOOLONG? */
+ trysize *= 2;
+ }
+ tries++;
+ result = newbuffer(msg, trysize);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ scratch = currentbuffer(msg);
+ } else {
+ return (result);
+ }
+ }
+}
+
+#define DO_ERROR(r) \
+ do { \
+ if (best_effort) { \
+ seen_problem = true; \
+ } else { \
+ result = r; \
+ goto cleanup; \
+ } \
+ } while (0)
+
+static isc_result_t
+getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
+ unsigned int options) {
+ isc_region_t r;
+ unsigned int count;
+ dns_name_t *name = NULL;
+ dns_name_t *name2 = NULL;
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdatalist_t *rdatalist = NULL;
+ isc_result_t result;
+ dns_rdatatype_t rdtype;
+ dns_rdataclass_t rdclass;
+ dns_namelist_t *section = &msg->sections[DNS_SECTION_QUESTION];
+ bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0);
+ bool seen_problem = false;
+ bool free_name = false;
+
+ for (count = 0; count < msg->counts[DNS_SECTION_QUESTION]; count++) {
+ name = NULL;
+ result = dns_message_gettempname(msg, &name);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ name->offsets = (unsigned char *)newoffsets(msg);
+ free_name = true;
+
+ /*
+ * Parse the name out of this packet.
+ */
+ isc_buffer_remainingregion(source, &r);
+ isc_buffer_setactive(source, r.length);
+ result = getname(name, source, msg, dctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Run through the section, looking to see if this name
+ * is already there. If it is found, put back the allocated
+ * name since we no longer need it, and set our name pointer
+ * to point to the name we found.
+ */
+ result = findname(&name2, name, section);
+
+ /*
+ * If it is the first name in the section, accept it.
+ *
+ * If it is not, but is not the same as the name already
+ * in the question section, append to the section. Note that
+ * here in the question section this is illegal, so return
+ * FORMERR. In the future, check the opcode to see if
+ * this should be legal or not. In either case we no longer
+ * need this name pointer.
+ */
+ if (result != ISC_R_SUCCESS) {
+ if (!ISC_LIST_EMPTY(*section)) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+ ISC_LIST_APPEND(*section, name, link);
+ free_name = false;
+ } else {
+ dns_message_puttempname(msg, &name);
+ name = name2;
+ name2 = NULL;
+ free_name = false;
+ }
+
+ /*
+ * Get type and class.
+ */
+ isc_buffer_remainingregion(source, &r);
+ if (r.length < 4) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto cleanup;
+ }
+ rdtype = isc_buffer_getuint16(source);
+ rdclass = isc_buffer_getuint16(source);
+
+ /*
+ * If this class is different than the one we already read,
+ * this is an error.
+ */
+ if (msg->rdclass_set == 0) {
+ msg->rdclass = rdclass;
+ msg->rdclass_set = 1;
+ } else if (msg->rdclass != rdclass) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ /*
+ * Is this a TKEY query?
+ */
+ if (rdtype == dns_rdatatype_tkey) {
+ msg->tkey = 1;
+ }
+
+ /*
+ * Can't ask the same question twice.
+ */
+ result = dns_message_find(name, rdclass, rdtype, 0, NULL);
+ if (result == ISC_R_SUCCESS) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ /*
+ * Allocate a new rdatalist.
+ */
+ rdatalist = newrdatalist(msg);
+ if (rdatalist == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ rdataset = isc_mempool_get(msg->rdspool);
+ if (rdataset == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /*
+ * Convert rdatalist to rdataset, and attach the latter to
+ * the name.
+ */
+ rdatalist->type = rdtype;
+ rdatalist->rdclass = rdclass;
+
+ dns_rdataset_init(rdataset);
+ result = dns_rdatalist_tordataset(rdatalist, rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ rdataset->attributes |= DNS_RDATASETATTR_QUESTION;
+
+ ISC_LIST_APPEND(name->list, rdataset, link);
+ rdataset = NULL;
+ }
+
+ if (seen_problem) {
+ return (DNS_R_RECOVERABLE);
+ }
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (rdataset != NULL) {
+ INSIST(!dns_rdataset_isassociated(rdataset));
+ isc_mempool_put(msg->rdspool, rdataset);
+ }
+ if (free_name) {
+ dns_message_puttempname(msg, &name);
+ }
+
+ return (result);
+}
+
+static bool
+update(dns_section_t section, dns_rdataclass_t rdclass) {
+ if (section == DNS_SECTION_PREREQUISITE) {
+ return (rdclass == dns_rdataclass_any ||
+ rdclass == dns_rdataclass_none);
+ }
+ if (section == DNS_SECTION_UPDATE) {
+ return (rdclass == dns_rdataclass_any);
+ }
+ return (false);
+}
+
+/*
+ * Check to confirm that all DNSSEC records (DS, NSEC, NSEC3) have
+ * covering RRSIGs.
+ */
+static bool
+auth_signed(dns_namelist_t *section) {
+ dns_name_t *name;
+
+ for (name = ISC_LIST_HEAD(*section); name != NULL;
+ name = ISC_LIST_NEXT(name, link))
+ {
+ int auth_dnssec = 0, auth_rrsig = 0;
+ dns_rdataset_t *rds;
+
+ for (rds = ISC_LIST_HEAD(name->list); rds != NULL;
+ rds = ISC_LIST_NEXT(rds, link))
+ {
+ switch (rds->type) {
+ case dns_rdatatype_ds:
+ auth_dnssec |= 0x1;
+ break;
+ case dns_rdatatype_nsec:
+ auth_dnssec |= 0x2;
+ break;
+ case dns_rdatatype_nsec3:
+ auth_dnssec |= 0x4;
+ break;
+ case dns_rdatatype_rrsig:
+ break;
+ default:
+ continue;
+ }
+
+ switch (rds->covers) {
+ case dns_rdatatype_ds:
+ auth_rrsig |= 0x1;
+ break;
+ case dns_rdatatype_nsec:
+ auth_rrsig |= 0x2;
+ break;
+ case dns_rdatatype_nsec3:
+ auth_rrsig |= 0x4;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (auth_dnssec != auth_rrsig) {
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+static isc_result_t
+getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
+ dns_section_t sectionid, unsigned int options) {
+ isc_region_t r;
+ unsigned int count, rdatalen;
+ dns_name_t *name = NULL;
+ dns_name_t *name2 = NULL;
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdatalist_t *rdatalist = NULL;
+ isc_result_t result;
+ dns_rdatatype_t rdtype, covers;
+ dns_rdataclass_t rdclass;
+ dns_rdata_t *rdata = NULL;
+ dns_ttl_t ttl;
+ dns_namelist_t *section = &msg->sections[sectionid];
+ bool free_name = false, free_rdataset = false, seen_problem = false;
+ bool preserve_order = ((options & DNS_MESSAGEPARSE_PRESERVEORDER) != 0);
+ bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0);
+ bool isedns, issigzero, istsig;
+
+ for (count = 0; count < msg->counts[sectionid]; count++) {
+ int recstart = source->current;
+ bool skip_name_search, skip_type_search;
+
+ skip_name_search = false;
+ skip_type_search = false;
+ free_rdataset = false;
+ isedns = false;
+ issigzero = false;
+ istsig = false;
+
+ name = NULL;
+ result = dns_message_gettempname(msg, &name);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ name->offsets = (unsigned char *)newoffsets(msg);
+ free_name = true;
+
+ /*
+ * Parse the name out of this packet.
+ */
+ isc_buffer_remainingregion(source, &r);
+ isc_buffer_setactive(source, r.length);
+ result = getname(name, source, msg, dctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Get type, class, ttl, and rdatalen. Verify that at least
+ * rdatalen bytes remain. (Some of this is deferred to
+ * later.)
+ */
+ isc_buffer_remainingregion(source, &r);
+ if (r.length < 2 + 2 + 4 + 2) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto cleanup;
+ }
+ rdtype = isc_buffer_getuint16(source);
+ rdclass = isc_buffer_getuint16(source);
+
+ /*
+ * If there was no question section, we may not yet have
+ * established a class. Do so now.
+ */
+ if (msg->rdclass_set == 0 &&
+ rdtype != dns_rdatatype_opt && /* class is UDP SIZE */
+ rdtype != dns_rdatatype_tsig && /* class is ANY */
+ rdtype != dns_rdatatype_tkey)
+ { /* class is undefined */
+ msg->rdclass = rdclass;
+ msg->rdclass_set = 1;
+ }
+
+ /*
+ * If this class is different than the one in the question
+ * section, bail.
+ */
+ if (msg->opcode != dns_opcode_update &&
+ rdtype != dns_rdatatype_tsig &&
+ rdtype != dns_rdatatype_opt &&
+ rdtype != dns_rdatatype_key && /* in a TKEY query */
+ rdtype != dns_rdatatype_sig && /* SIG(0) */
+ rdtype != dns_rdatatype_tkey && /* Win2000 TKEY */
+ msg->rdclass != dns_rdataclass_any &&
+ msg->rdclass != rdclass)
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ /*
+ * If this is not a TKEY query/response then the KEY
+ * record's class needs to match.
+ */
+ if (msg->opcode != dns_opcode_update && !msg->tkey &&
+ rdtype == dns_rdatatype_key &&
+ msg->rdclass != dns_rdataclass_any &&
+ msg->rdclass != rdclass)
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ /*
+ * Special type handling for TSIG, OPT, and TKEY.
+ */
+ if (rdtype == dns_rdatatype_tsig) {
+ /*
+ * If it is a tsig, verify that it is in the
+ * additional data section.
+ */
+ if (sectionid != DNS_SECTION_ADDITIONAL ||
+ rdclass != dns_rdataclass_any ||
+ count != msg->counts[sectionid] - 1)
+ {
+ DO_ERROR(DNS_R_BADTSIG);
+ } else {
+ skip_name_search = true;
+ skip_type_search = true;
+ istsig = true;
+ }
+ } else if (rdtype == dns_rdatatype_opt) {
+ /*
+ * The name of an OPT record must be ".", it
+ * must be in the additional data section, and
+ * it must be the first OPT we've seen.
+ */
+ if (!dns_name_equal(dns_rootname, name) ||
+ sectionid != DNS_SECTION_ADDITIONAL ||
+ msg->opt != NULL)
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ } else {
+ skip_name_search = true;
+ skip_type_search = true;
+ isedns = true;
+ }
+ } else if (rdtype == dns_rdatatype_tkey) {
+ /*
+ * A TKEY must be in the additional section if this
+ * is a query, and the answer section if this is a
+ * response. Unless it's a Win2000 client.
+ *
+ * Its class is ignored.
+ */
+ dns_section_t tkeysection;
+
+ if ((msg->flags & DNS_MESSAGEFLAG_QR) == 0) {
+ tkeysection = DNS_SECTION_ADDITIONAL;
+ } else {
+ tkeysection = DNS_SECTION_ANSWER;
+ }
+ if (sectionid != tkeysection &&
+ sectionid != DNS_SECTION_ANSWER)
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+ }
+
+ /*
+ * ... now get ttl and rdatalen, and check buffer.
+ */
+ ttl = isc_buffer_getuint32(source);
+ rdatalen = isc_buffer_getuint16(source);
+ r.length -= (2 + 2 + 4 + 2);
+ if (r.length < rdatalen) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto cleanup;
+ }
+
+ /*
+ * Read the rdata from the wire format. Interpret the
+ * rdata according to its actual class, even if it had a
+ * DynDNS meta-class in the packet (unless this is a TSIG).
+ * Then put the meta-class back into the finished rdata.
+ */
+ rdata = newrdata(msg);
+ if (rdata == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ if (msg->opcode == dns_opcode_update &&
+ update(sectionid, rdclass))
+ {
+ if (rdatalen != 0) {
+ result = DNS_R_FORMERR;
+ goto cleanup;
+ }
+ /*
+ * When the rdata is empty, the data pointer is
+ * never dereferenced, but it must still be non-NULL.
+ * Casting 1 rather than "" avoids warnings about
+ * discarding the const attribute of a string,
+ * for compilers that would warn about such things.
+ */
+ rdata->data = (unsigned char *)1;
+ rdata->length = 0;
+ rdata->rdclass = rdclass;
+ rdata->type = rdtype;
+ rdata->flags = DNS_RDATA_UPDATE;
+ result = ISC_R_SUCCESS;
+ } else if (rdclass == dns_rdataclass_none &&
+ msg->opcode == dns_opcode_update &&
+ sectionid == DNS_SECTION_UPDATE)
+ {
+ result = getrdata(source, msg, dctx, msg->rdclass,
+ rdtype, rdatalen, rdata);
+ } else {
+ result = getrdata(source, msg, dctx, rdclass, rdtype,
+ rdatalen, rdata);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ rdata->rdclass = rdclass;
+ if (rdtype == dns_rdatatype_rrsig && rdata->flags == 0) {
+ covers = dns_rdata_covers(rdata);
+ if (covers == 0) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+ } else if (rdtype == dns_rdatatype_sig /* SIG(0) */ &&
+ rdata->flags == 0)
+ {
+ covers = dns_rdata_covers(rdata);
+ if (covers == 0) {
+ if (sectionid != DNS_SECTION_ADDITIONAL ||
+ count != msg->counts[sectionid] - 1)
+ {
+ DO_ERROR(DNS_R_BADSIG0);
+ } else {
+ skip_name_search = true;
+ skip_type_search = true;
+ issigzero = true;
+ }
+ } else {
+ if (msg->rdclass != dns_rdataclass_any &&
+ msg->rdclass != rdclass)
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+ }
+ } else {
+ covers = 0;
+ }
+
+ /*
+ * Check the ownername of NSEC3 records
+ */
+ if (rdtype == dns_rdatatype_nsec3 &&
+ !dns_rdata_checkowner(name, msg->rdclass, rdtype, false))
+ {
+ result = DNS_R_BADOWNERNAME;
+ goto cleanup;
+ }
+
+ /*
+ * If we are doing a dynamic update or this is a meta-type,
+ * don't bother searching for a name, just append this one
+ * to the end of the message.
+ */
+ if (preserve_order || msg->opcode == dns_opcode_update ||
+ skip_name_search)
+ {
+ if (!isedns && !istsig && !issigzero) {
+ ISC_LIST_APPEND(*section, name, link);
+ free_name = false;
+ }
+ } else {
+ /*
+ * Run through the section, looking to see if this name
+ * is already there. If it is found, put back the
+ * allocated name since we no longer need it, and set
+ * our name pointer to point to the name we found.
+ */
+ result = findname(&name2, name, section);
+
+ /*
+ * If it is a new name, append to the section.
+ */
+ if (result == ISC_R_SUCCESS) {
+ dns_message_puttempname(msg, &name);
+ name = name2;
+ } else {
+ ISC_LIST_APPEND(*section, name, link);
+ }
+ free_name = false;
+ }
+
+ /*
+ * Search name for the particular type and class.
+ * Skip this stage if in update mode or this is a meta-type.
+ */
+ if (preserve_order || msg->opcode == dns_opcode_update ||
+ skip_type_search)
+ {
+ result = ISC_R_NOTFOUND;
+ } else {
+ /*
+ * If this is a type that can only occur in
+ * the question section, fail.
+ */
+ if (dns_rdatatype_questiononly(rdtype)) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ rdataset = NULL;
+ result = dns_message_find(name, rdclass, rdtype, covers,
+ &rdataset);
+ }
+
+ /*
+ * If we found an rdataset that matches, we need to
+ * append this rdata to that set. If we did not, we need
+ * to create a new rdatalist, store the important bits there,
+ * convert it to an rdataset, and link the latter to the name.
+ * Yuck. When appending, make certain that the type isn't
+ * a singleton type, such as SOA or CNAME.
+ *
+ * Note that this check will be bypassed when preserving order,
+ * the opcode is an update, or the type search is skipped.
+ */
+ if (result == ISC_R_SUCCESS) {
+ if (dns_rdatatype_issingleton(rdtype)) {
+ dns_rdata_t *first;
+ dns_rdatalist_fromrdataset(rdataset,
+ &rdatalist);
+ first = ISC_LIST_HEAD(rdatalist->rdata);
+ INSIST(first != NULL);
+ if (dns_rdata_compare(rdata, first) != 0) {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+ }
+ }
+
+ if (result == ISC_R_NOTFOUND) {
+ rdataset = isc_mempool_get(msg->rdspool);
+ if (rdataset == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ free_rdataset = true;
+
+ rdatalist = newrdatalist(msg);
+ if (rdatalist == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ rdatalist->type = rdtype;
+ rdatalist->covers = covers;
+ rdatalist->rdclass = rdclass;
+ rdatalist->ttl = ttl;
+
+ dns_rdataset_init(rdataset);
+ RUNTIME_CHECK(
+ dns_rdatalist_tordataset(rdatalist, rdataset) ==
+ ISC_R_SUCCESS);
+ dns_rdataset_setownercase(rdataset, name);
+
+ if (!isedns && !istsig && !issigzero) {
+ ISC_LIST_APPEND(name->list, rdataset, link);
+ free_rdataset = false;
+ }
+ }
+
+ /*
+ * Minimize TTLs.
+ *
+ * Section 5.2 of RFC2181 says we should drop
+ * nonauthoritative rrsets where the TTLs differ, but we
+ * currently treat them the as if they were authoritative and
+ * minimize them.
+ */
+ if (ttl != rdataset->ttl) {
+ rdataset->attributes |= DNS_RDATASETATTR_TTLADJUSTED;
+ if (ttl < rdataset->ttl) {
+ rdataset->ttl = ttl;
+ }
+ }
+
+ /* Append this rdata to the rdataset. */
+ dns_rdatalist_fromrdataset(rdataset, &rdatalist);
+ ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
+
+ /*
+ * If this is an OPT, SIG(0) or TSIG record, remember it.
+ * Also, set the extended rcode for TSIG.
+ *
+ * Note msg->opt, msg->sig0 and msg->tsig will only be
+ * already set if best-effort parsing is enabled otherwise
+ * there will only be at most one of each.
+ */
+ if (isedns) {
+ dns_rcode_t ercode;
+
+ msg->opt = rdataset;
+ rdataset = NULL;
+ free_rdataset = false;
+ ercode = (dns_rcode_t)((msg->opt->ttl &
+ DNS_MESSAGE_EDNSRCODE_MASK) >>
+ 20);
+ msg->rcode |= ercode;
+ dns_message_puttempname(msg, &name);
+ free_name = false;
+ } else if (issigzero) {
+ msg->sig0 = rdataset;
+ msg->sig0name = name;
+ msg->sigstart = recstart;
+ rdataset = NULL;
+ free_rdataset = false;
+ free_name = false;
+ } else if (istsig) {
+ msg->tsig = rdataset;
+ msg->tsigname = name;
+ msg->sigstart = recstart;
+ /*
+ * Windows doesn't like TSIG names to be compressed.
+ */
+ msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
+ rdataset = NULL;
+ free_rdataset = false;
+ free_name = false;
+ }
+
+ if (seen_problem) {
+ if (free_name) {
+ dns_message_puttempname(msg, &name);
+ }
+ if (free_rdataset) {
+ isc_mempool_put(msg->rdspool, rdataset);
+ }
+ free_name = free_rdataset = false;
+ }
+ INSIST(!free_name);
+ INSIST(!free_rdataset);
+ }
+
+ /*
+ * If any of DS, NSEC or NSEC3 appeared in the
+ * authority section of a query response without
+ * a covering RRSIG, FORMERR
+ */
+ if (sectionid == DNS_SECTION_AUTHORITY &&
+ msg->opcode == dns_opcode_query &&
+ ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) &&
+ ((msg->flags & DNS_MESSAGEFLAG_TC) == 0) && !preserve_order &&
+ !auth_signed(section))
+ {
+ DO_ERROR(DNS_R_FORMERR);
+ }
+
+ if (seen_problem) {
+ return (DNS_R_RECOVERABLE);
+ }
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (free_name) {
+ dns_message_puttempname(msg, &name);
+ }
+ if (free_rdataset) {
+ isc_mempool_put(msg->rdspool, rdataset);
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_message_parse(dns_message_t *msg, isc_buffer_t *source,
+ unsigned int options) {
+ isc_region_t r;
+ dns_decompress_t dctx;
+ isc_result_t ret;
+ uint16_t tmpflags;
+ isc_buffer_t origsource;
+ bool seen_problem;
+ bool ignore_tc;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(source != NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
+
+ seen_problem = false;
+ ignore_tc = ((options & DNS_MESSAGEPARSE_IGNORETRUNCATION) != 0);
+
+ origsource = *source;
+
+ msg->header_ok = 0;
+ msg->question_ok = 0;
+
+ if ((options & DNS_MESSAGEPARSE_CLONEBUFFER) == 0) {
+ isc_buffer_usedregion(&origsource, &msg->saved);
+ } else {
+ msg->saved.length = isc_buffer_usedlength(&origsource);
+ msg->saved.base = isc_mem_get(msg->mctx, msg->saved.length);
+ memmove(msg->saved.base, isc_buffer_base(&origsource),
+ msg->saved.length);
+ msg->free_saved = 1;
+ }
+
+ isc_buffer_remainingregion(source, &r);
+ if (r.length < DNS_MESSAGE_HEADERLEN) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ msg->id = isc_buffer_getuint16(source);
+ tmpflags = isc_buffer_getuint16(source);
+ msg->opcode = ((tmpflags & DNS_MESSAGE_OPCODE_MASK) >>
+ DNS_MESSAGE_OPCODE_SHIFT);
+ msg->rcode = (dns_rcode_t)(tmpflags & DNS_MESSAGE_RCODE_MASK);
+ msg->flags = (tmpflags & DNS_MESSAGE_FLAG_MASK);
+ msg->counts[DNS_SECTION_QUESTION] = isc_buffer_getuint16(source);
+ msg->counts[DNS_SECTION_ANSWER] = isc_buffer_getuint16(source);
+ msg->counts[DNS_SECTION_AUTHORITY] = isc_buffer_getuint16(source);
+ msg->counts[DNS_SECTION_ADDITIONAL] = isc_buffer_getuint16(source);
+
+ msg->header_ok = 1;
+ msg->state = DNS_SECTION_QUESTION;
+
+ /*
+ * -1 means no EDNS.
+ */
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY);
+
+ dns_decompress_setmethods(&dctx, DNS_COMPRESS_GLOBAL14);
+
+ ret = getquestions(source, msg, &dctx, options);
+ if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
+ goto truncated;
+ }
+ if (ret == DNS_R_RECOVERABLE) {
+ seen_problem = true;
+ ret = ISC_R_SUCCESS;
+ }
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+ msg->question_ok = 1;
+
+ ret = getsection(source, msg, &dctx, DNS_SECTION_ANSWER, options);
+ if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
+ goto truncated;
+ }
+ if (ret == DNS_R_RECOVERABLE) {
+ seen_problem = true;
+ ret = ISC_R_SUCCESS;
+ }
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ ret = getsection(source, msg, &dctx, DNS_SECTION_AUTHORITY, options);
+ if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
+ goto truncated;
+ }
+ if (ret == DNS_R_RECOVERABLE) {
+ seen_problem = true;
+ ret = ISC_R_SUCCESS;
+ }
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ ret = getsection(source, msg, &dctx, DNS_SECTION_ADDITIONAL, options);
+ if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
+ goto truncated;
+ }
+ if (ret == DNS_R_RECOVERABLE) {
+ seen_problem = true;
+ ret = ISC_R_SUCCESS;
+ }
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ isc_buffer_remainingregion(source, &r);
+ if (r.length != 0) {
+ isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MESSAGE, ISC_LOG_DEBUG(3),
+ "message has %u byte(s) of trailing garbage",
+ r.length);
+ }
+
+truncated:
+
+ if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
+ return (DNS_R_RECOVERABLE);
+ }
+ if (seen_problem) {
+ return (DNS_R_RECOVERABLE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_renderbegin(dns_message_t *msg, dns_compress_t *cctx,
+ isc_buffer_t *buffer) {
+ isc_region_t r;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(buffer != NULL);
+ REQUIRE(msg->buffer == NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+
+ msg->cctx = cctx;
+
+ /*
+ * Erase the contents of this buffer.
+ */
+ isc_buffer_clear(buffer);
+
+ /*
+ * Make certain there is enough for at least the header in this
+ * buffer.
+ */
+ isc_buffer_availableregion(buffer, &r);
+ if (r.length < DNS_MESSAGE_HEADERLEN) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (r.length - DNS_MESSAGE_HEADERLEN < msg->reserved) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Reserve enough space for the header in this buffer.
+ */
+ isc_buffer_add(buffer, DNS_MESSAGE_HEADERLEN);
+
+ msg->buffer = buffer;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_renderchangebuffer(dns_message_t *msg, isc_buffer_t *buffer) {
+ isc_region_t r, rn;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(buffer != NULL);
+ REQUIRE(msg->buffer != NULL);
+
+ /*
+ * Ensure that the new buffer is empty, and has enough space to
+ * hold the current contents.
+ */
+ isc_buffer_clear(buffer);
+
+ isc_buffer_availableregion(buffer, &rn);
+ isc_buffer_usedregion(msg->buffer, &r);
+ REQUIRE(rn.length > r.length);
+
+ /*
+ * Copy the contents from the old to the new buffer.
+ */
+ isc_buffer_add(buffer, r.length);
+ memmove(rn.base, r.base, r.length);
+
+ msg->buffer = buffer;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_renderrelease(dns_message_t *msg, unsigned int space) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(space <= msg->reserved);
+
+ msg->reserved -= space;
+}
+
+isc_result_t
+dns_message_renderreserve(dns_message_t *msg, unsigned int space) {
+ isc_region_t r;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ if (msg->buffer != NULL) {
+ isc_buffer_availableregion(msg->buffer, &r);
+ if (r.length < (space + msg->reserved)) {
+ return (ISC_R_NOSPACE);
+ }
+ }
+
+ msg->reserved += space;
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+wrong_priority(dns_rdataset_t *rds, int pass, dns_rdatatype_t preferred_glue) {
+ int pass_needed;
+
+ /*
+ * If we are not rendering class IN, this ordering is bogus.
+ */
+ if (rds->rdclass != dns_rdataclass_in) {
+ return (false);
+ }
+
+ switch (rds->type) {
+ case dns_rdatatype_a:
+ case dns_rdatatype_aaaa:
+ if (preferred_glue == rds->type) {
+ pass_needed = 4;
+ } else {
+ pass_needed = 3;
+ }
+ break;
+ case dns_rdatatype_rrsig:
+ case dns_rdatatype_dnskey:
+ pass_needed = 2;
+ break;
+ default:
+ pass_needed = 1;
+ }
+
+ if (pass_needed >= pass) {
+ return (false);
+ }
+
+ return (true);
+}
+
+static isc_result_t
+renderset(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_compress_t *cctx, isc_buffer_t *target, unsigned int reserved,
+ unsigned int options, unsigned int *countp) {
+ isc_result_t result;
+
+ /*
+ * Shrink the space in the buffer by the reserved amount.
+ */
+ if (target->length - target->used < reserved) {
+ return (ISC_R_NOSPACE);
+ }
+
+ target->length -= reserved;
+ result = dns_rdataset_towire(rdataset, owner_name, cctx, target,
+ options, countp);
+ target->length += reserved;
+
+ return (result);
+}
+
+static void
+maybe_clear_ad(dns_message_t *msg, dns_section_t sectionid) {
+ if (msg->counts[sectionid] == 0 &&
+ (sectionid == DNS_SECTION_ANSWER ||
+ (sectionid == DNS_SECTION_AUTHORITY &&
+ msg->counts[DNS_SECTION_ANSWER] == 0)))
+ {
+ msg->flags &= ~DNS_MESSAGEFLAG_AD;
+ }
+}
+
+isc_result_t
+dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid,
+ unsigned int options) {
+ dns_namelist_t *section;
+ dns_name_t *name, *next_name;
+ dns_rdataset_t *rdataset, *next_rdataset;
+ unsigned int count, total;
+ isc_result_t result;
+ isc_buffer_t st; /* for rollbacks */
+ int pass;
+ bool partial = false;
+ unsigned int rd_options;
+ dns_rdatatype_t preferred_glue = 0;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->buffer != NULL);
+ REQUIRE(VALID_NAMED_SECTION(sectionid));
+
+ section = &msg->sections[sectionid];
+
+ if ((sectionid == DNS_SECTION_ADDITIONAL) &&
+ (options & DNS_MESSAGERENDER_ORDERED) == 0)
+ {
+ if ((options & DNS_MESSAGERENDER_PREFER_A) != 0) {
+ preferred_glue = dns_rdatatype_a;
+ pass = 4;
+ } else if ((options & DNS_MESSAGERENDER_PREFER_AAAA) != 0) {
+ preferred_glue = dns_rdatatype_aaaa;
+ pass = 4;
+ } else {
+ pass = 3;
+ }
+ } else {
+ pass = 1;
+ }
+
+ if ((options & DNS_MESSAGERENDER_OMITDNSSEC) == 0) {
+ rd_options = 0;
+ } else {
+ rd_options = DNS_RDATASETTOWIRE_OMITDNSSEC;
+ }
+
+ /*
+ * Shrink the space in the buffer by the reserved amount.
+ */
+ if (msg->buffer->length - msg->buffer->used < msg->reserved) {
+ return (ISC_R_NOSPACE);
+ }
+ msg->buffer->length -= msg->reserved;
+
+ total = 0;
+ if (msg->reserved == 0 && (options & DNS_MESSAGERENDER_PARTIAL) != 0) {
+ partial = true;
+ }
+
+ /*
+ * Render required glue first. Set TC if it won't fit.
+ */
+ name = ISC_LIST_HEAD(*section);
+ if (name != NULL) {
+ rdataset = ISC_LIST_HEAD(name->list);
+ if (rdataset != NULL &&
+ (rdataset->attributes & DNS_RDATASETATTR_REQUIREDGLUE) !=
+ 0 &&
+ (rdataset->attributes & DNS_RDATASETATTR_RENDERED) == 0)
+ {
+ const void *order_arg = &msg->order_arg;
+ st = *(msg->buffer);
+ count = 0;
+ if (partial) {
+ result = dns_rdataset_towirepartial(
+ rdataset, name, msg->cctx, msg->buffer,
+ msg->order, order_arg, rd_options,
+ &count, NULL);
+ } else {
+ result = dns_rdataset_towiresorted(
+ rdataset, name, msg->cctx, msg->buffer,
+ msg->order, order_arg, rd_options,
+ &count);
+ }
+ total += count;
+ if (partial && result == ISC_R_NOSPACE) {
+ msg->flags |= DNS_MESSAGEFLAG_TC;
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+ return (result);
+ }
+ if (result == ISC_R_NOSPACE) {
+ msg->flags |= DNS_MESSAGEFLAG_TC;
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(st.used < 65536);
+ dns_compress_rollback(msg->cctx,
+ (uint16_t)st.used);
+ *(msg->buffer) = st; /* rollback */
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+ return (result);
+ }
+ rdataset->attributes |= DNS_RDATASETATTR_RENDERED;
+ }
+ }
+
+ do {
+ name = ISC_LIST_HEAD(*section);
+ if (name == NULL) {
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+ return (ISC_R_SUCCESS);
+ }
+
+ while (name != NULL) {
+ next_name = ISC_LIST_NEXT(name, link);
+
+ rdataset = ISC_LIST_HEAD(name->list);
+ while (rdataset != NULL) {
+ next_rdataset = ISC_LIST_NEXT(rdataset, link);
+
+ if ((rdataset->attributes &
+ DNS_RDATASETATTR_RENDERED) != 0)
+ {
+ goto next;
+ }
+
+ if (((options & DNS_MESSAGERENDER_ORDERED) ==
+ 0) &&
+ (sectionid == DNS_SECTION_ADDITIONAL) &&
+ wrong_priority(rdataset, pass,
+ preferred_glue))
+ {
+ goto next;
+ }
+
+ st = *(msg->buffer);
+
+ count = 0;
+ if (partial) {
+ result = dns_rdataset_towirepartial(
+ rdataset, name, msg->cctx,
+ msg->buffer, msg->order,
+ &msg->order_arg, rd_options,
+ &count, NULL);
+ } else {
+ result = dns_rdataset_towiresorted(
+ rdataset, name, msg->cctx,
+ msg->buffer, msg->order,
+ &msg->order_arg, rd_options,
+ &count);
+ }
+
+ total += count;
+
+ /*
+ * If out of space, record stats on what we
+ * rendered so far, and return that status.
+ *
+ * XXXMLG Need to change this when
+ * dns_rdataset_towire() can render partial
+ * sets starting at some arbitrary point in the
+ * set. This will include setting a bit in the
+ * rdataset to indicate that a partial
+ * rendering was done, and some state saved
+ * somewhere (probably in the message struct)
+ * to indicate where to continue from.
+ */
+ if (partial && result == ISC_R_NOSPACE) {
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+ return (result);
+ }
+ if (result != ISC_R_SUCCESS) {
+ INSIST(st.used < 65536);
+ dns_compress_rollback(
+ msg->cctx, (uint16_t)st.used);
+ *(msg->buffer) = st; /* rollback */
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+ maybe_clear_ad(msg, sectionid);
+ return (result);
+ }
+
+ /*
+ * If we have rendered non-validated data,
+ * ensure that the AD bit is not set.
+ */
+ if (rdataset->trust != dns_trust_secure &&
+ (sectionid == DNS_SECTION_ANSWER ||
+ sectionid == DNS_SECTION_AUTHORITY))
+ {
+ msg->flags &= ~DNS_MESSAGEFLAG_AD;
+ }
+ if (OPTOUT(rdataset)) {
+ msg->flags &= ~DNS_MESSAGEFLAG_AD;
+ }
+
+ rdataset->attributes |=
+ DNS_RDATASETATTR_RENDERED;
+
+ next:
+ rdataset = next_rdataset;
+ }
+
+ name = next_name;
+ }
+ } while (--pass != 0);
+
+ msg->buffer->length += msg->reserved;
+ msg->counts[sectionid] += total;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target) {
+ uint16_t tmp;
+ isc_region_t r;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(target != NULL);
+
+ isc_buffer_availableregion(target, &r);
+ REQUIRE(r.length >= DNS_MESSAGE_HEADERLEN);
+
+ isc_buffer_putuint16(target, msg->id);
+
+ tmp = ((msg->opcode << DNS_MESSAGE_OPCODE_SHIFT) &
+ DNS_MESSAGE_OPCODE_MASK);
+ tmp |= (msg->rcode & DNS_MESSAGE_RCODE_MASK);
+ tmp |= (msg->flags & DNS_MESSAGE_FLAG_MASK);
+
+ INSIST(msg->counts[DNS_SECTION_QUESTION] < 65536 &&
+ msg->counts[DNS_SECTION_ANSWER] < 65536 &&
+ msg->counts[DNS_SECTION_AUTHORITY] < 65536 &&
+ msg->counts[DNS_SECTION_ADDITIONAL] < 65536);
+
+ isc_buffer_putuint16(target, tmp);
+ isc_buffer_putuint16(target,
+ (uint16_t)msg->counts[DNS_SECTION_QUESTION]);
+ isc_buffer_putuint16(target, (uint16_t)msg->counts[DNS_SECTION_ANSWER]);
+ isc_buffer_putuint16(target,
+ (uint16_t)msg->counts[DNS_SECTION_AUTHORITY]);
+ isc_buffer_putuint16(target,
+ (uint16_t)msg->counts[DNS_SECTION_ADDITIONAL]);
+}
+
+isc_result_t
+dns_message_renderend(dns_message_t *msg) {
+ isc_buffer_t tmpbuf;
+ isc_region_t r;
+ int result;
+ unsigned int count;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->buffer != NULL);
+
+ if ((msg->rcode & ~DNS_MESSAGE_RCODE_MASK) != 0 && msg->opt == NULL) {
+ /*
+ * We have an extended rcode but are not using EDNS.
+ */
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * If we're adding a OPT, TSIG or SIG(0) to a truncated message,
+ * clear all rdatasets from the message except for the question
+ * before adding the OPT, TSIG or SIG(0). If the question doesn't
+ * fit, don't include it.
+ */
+ if ((msg->tsigkey != NULL || msg->sig0key != NULL || msg->opt) &&
+ (msg->flags & DNS_MESSAGEFLAG_TC) != 0)
+ {
+ isc_buffer_t *buf;
+
+ msgresetnames(msg, DNS_SECTION_ANSWER);
+ buf = msg->buffer;
+ dns_message_renderreset(msg);
+ msg->buffer = buf;
+ isc_buffer_clear(msg->buffer);
+ isc_buffer_add(msg->buffer, DNS_MESSAGE_HEADERLEN);
+ dns_compress_rollback(msg->cctx, 0);
+ result = dns_message_rendersection(msg, DNS_SECTION_QUESTION,
+ 0);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOSPACE) {
+ return (result);
+ }
+ }
+
+ /*
+ * If we've got an OPT record, render it.
+ */
+ if (msg->opt != NULL) {
+ dns_message_renderrelease(msg, msg->opt_reserved);
+ msg->opt_reserved = 0;
+ /*
+ * Set the extended rcode. Cast msg->rcode to dns_ttl_t
+ * so that we do a unsigned shift.
+ */
+ msg->opt->ttl &= ~DNS_MESSAGE_EDNSRCODE_MASK;
+ msg->opt->ttl |= (((dns_ttl_t)(msg->rcode) << 20) &
+ DNS_MESSAGE_EDNSRCODE_MASK);
+ /*
+ * Render.
+ */
+ count = 0;
+ result = renderset(msg->opt, dns_rootname, msg->cctx,
+ msg->buffer, msg->reserved, 0, &count);
+ msg->counts[DNS_SECTION_ADDITIONAL] += count;
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ /*
+ * Deal with EDNS padding.
+ *
+ * padding_off is the length of the OPT with the 0-length PAD
+ * at the end.
+ */
+ if (msg->padding_off > 0) {
+ unsigned char *cp = isc_buffer_used(msg->buffer);
+ unsigned int used, remaining;
+ uint16_t len, padsize = 0;
+
+ /* Check PAD */
+ if ((cp[-4] != 0) || (cp[-3] != DNS_OPT_PAD) || (cp[-2] != 0) ||
+ (cp[-1] != 0))
+ {
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * Zero-fill the PAD to the computed size;
+ * patch PAD length and OPT rdlength
+ */
+
+ /* Aligned used length + reserved to padding block */
+ used = isc_buffer_usedlength(msg->buffer);
+ if (msg->padding != 0) {
+ padsize = ((uint16_t)used + msg->reserved) %
+ msg->padding;
+ }
+ if (padsize != 0) {
+ padsize = msg->padding - padsize;
+ }
+ /* Stay below the available length */
+ remaining = isc_buffer_availablelength(msg->buffer);
+ if (padsize > remaining) {
+ padsize = remaining;
+ }
+
+ isc_buffer_add(msg->buffer, padsize);
+ memset(cp, 0, padsize);
+ cp[-2] = (unsigned char)((padsize & 0xff00U) >> 8);
+ cp[-1] = (unsigned char)(padsize & 0x00ffU);
+ cp -= msg->padding_off;
+ len = ((uint16_t)(cp[-2])) << 8;
+ len |= ((uint16_t)(cp[-1]));
+ len += padsize;
+ cp[-2] = (unsigned char)((len & 0xff00U) >> 8);
+ cp[-1] = (unsigned char)(len & 0x00ffU);
+ }
+
+ /*
+ * If we're adding a TSIG record, generate and render it.
+ */
+ if (msg->tsigkey != NULL) {
+ dns_message_renderrelease(msg, msg->sig_reserved);
+ msg->sig_reserved = 0;
+ result = dns_tsig_sign(msg);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ count = 0;
+ result = renderset(msg->tsig, msg->tsigname, msg->cctx,
+ msg->buffer, msg->reserved, 0, &count);
+ msg->counts[DNS_SECTION_ADDITIONAL] += count;
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ /*
+ * If we're adding a SIG(0) record, generate and render it.
+ */
+ if (msg->sig0key != NULL) {
+ dns_message_renderrelease(msg, msg->sig_reserved);
+ msg->sig_reserved = 0;
+ result = dns_dnssec_signmessage(msg, msg->sig0key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ count = 0;
+ /*
+ * Note: dns_rootname is used here, not msg->sig0name, since
+ * the owner name of a SIG(0) is irrelevant, and will not
+ * be set in a message being rendered.
+ */
+ result = renderset(msg->sig0, dns_rootname, msg->cctx,
+ msg->buffer, msg->reserved, 0, &count);
+ msg->counts[DNS_SECTION_ADDITIONAL] += count;
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ isc_buffer_usedregion(msg->buffer, &r);
+ isc_buffer_init(&tmpbuf, r.base, r.length);
+
+ dns_message_renderheader(msg, &tmpbuf);
+
+ msg->buffer = NULL; /* forget about this buffer only on success XXX */
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_renderreset(dns_message_t *msg) {
+ unsigned int i;
+ dns_name_t *name;
+ dns_rdataset_t *rds;
+
+ /*
+ * Reset the message so that it may be rendered again.
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+
+ msg->buffer = NULL;
+
+ for (i = 0; i < DNS_SECTION_MAX; i++) {
+ msg->cursors[i] = NULL;
+ msg->counts[i] = 0;
+ for (name = ISC_LIST_HEAD(msg->sections[i]); name != NULL;
+ name = ISC_LIST_NEXT(name, link))
+ {
+ for (rds = ISC_LIST_HEAD(name->list); rds != NULL;
+ rds = ISC_LIST_NEXT(rds, link))
+ {
+ rds->attributes &= ~DNS_RDATASETATTR_RENDERED;
+ }
+ }
+ }
+ if (msg->tsigname != NULL) {
+ dns_message_puttempname(msg, &msg->tsigname);
+ }
+ if (msg->tsig != NULL) {
+ dns_rdataset_disassociate(msg->tsig);
+ dns_message_puttemprdataset(msg, &msg->tsig);
+ }
+ if (msg->sig0 != NULL) {
+ dns_rdataset_disassociate(msg->sig0);
+ dns_message_puttemprdataset(msg, &msg->sig0);
+ }
+}
+
+isc_result_t
+dns_message_firstname(dns_message_t *msg, dns_section_t section) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(VALID_NAMED_SECTION(section));
+
+ msg->cursors[section] = ISC_LIST_HEAD(msg->sections[section]);
+
+ if (msg->cursors[section] == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_nextname(dns_message_t *msg, dns_section_t section) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(VALID_NAMED_SECTION(section));
+ REQUIRE(msg->cursors[section] != NULL);
+
+ msg->cursors[section] = ISC_LIST_NEXT(msg->cursors[section], link);
+
+ if (msg->cursors[section] == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_currentname(dns_message_t *msg, dns_section_t section,
+ dns_name_t **name) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(VALID_NAMED_SECTION(section));
+ REQUIRE(name != NULL && *name == NULL);
+ REQUIRE(msg->cursors[section] != NULL);
+
+ *name = msg->cursors[section];
+}
+
+isc_result_t
+dns_message_findname(dns_message_t *msg, dns_section_t section,
+ const dns_name_t *target, dns_rdatatype_t type,
+ dns_rdatatype_t covers, dns_name_t **name,
+ dns_rdataset_t **rdataset) {
+ dns_name_t *foundname;
+ isc_result_t result;
+
+ /*
+ * XXX These requirements are probably too intensive, especially
+ * where things can be NULL, but as they are they ensure that if
+ * something is NON-NULL, indicating that the caller expects it
+ * to be filled in, that we can in fact fill it in.
+ */
+ REQUIRE(msg != NULL);
+ REQUIRE(VALID_SECTION(section));
+ REQUIRE(target != NULL);
+ REQUIRE(name == NULL || *name == NULL);
+
+ if (type == dns_rdatatype_any) {
+ REQUIRE(rdataset == NULL);
+ } else {
+ REQUIRE(rdataset == NULL || *rdataset == NULL);
+ }
+
+ result = findname(&foundname, target, &msg->sections[section]);
+
+ if (result == ISC_R_NOTFOUND) {
+ return (DNS_R_NXDOMAIN);
+ } else if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (name != NULL) {
+ *name = foundname;
+ }
+
+ /*
+ * And now look for the type.
+ */
+ if (ISC_UNLIKELY(type == dns_rdatatype_any)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_message_findtype(foundname, type, covers, rdataset);
+ if (result == ISC_R_NOTFOUND) {
+ return (DNS_R_NXRRSET);
+ }
+
+ return (result);
+}
+
+void
+dns_message_movename(dns_message_t *msg, dns_name_t *name,
+ dns_section_t fromsection, dns_section_t tosection) {
+ REQUIRE(msg != NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+ REQUIRE(name != NULL);
+ REQUIRE(VALID_NAMED_SECTION(fromsection));
+ REQUIRE(VALID_NAMED_SECTION(tosection));
+
+ /*
+ * Unlink the name from the old section
+ */
+ ISC_LIST_UNLINK(msg->sections[fromsection], name, link);
+ ISC_LIST_APPEND(msg->sections[tosection], name, link);
+}
+
+void
+dns_message_addname(dns_message_t *msg, dns_name_t *name,
+ dns_section_t section) {
+ REQUIRE(msg != NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+ REQUIRE(name != NULL);
+ REQUIRE(VALID_NAMED_SECTION(section));
+
+ ISC_LIST_APPEND(msg->sections[section], name, link);
+}
+
+void
+dns_message_removename(dns_message_t *msg, dns_name_t *name,
+ dns_section_t section) {
+ REQUIRE(msg != NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+ REQUIRE(name != NULL);
+ REQUIRE(VALID_NAMED_SECTION(section));
+
+ ISC_LIST_UNLINK(msg->sections[section], name, link);
+}
+
+isc_result_t
+dns_message_gettempname(dns_message_t *msg, dns_name_t **item) {
+ dns_fixedname_t *fn = NULL;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item == NULL);
+
+ fn = isc_mempool_get(msg->namepool);
+ if (fn == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ *item = dns_fixedname_initname(fn);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item == NULL);
+
+ *item = newrdata(msg);
+ if (*item == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_gettemprdataset(dns_message_t *msg, dns_rdataset_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item == NULL);
+
+ *item = isc_mempool_get(msg->rdspool);
+ if (*item == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ dns_rdataset_init(*item);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_gettemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item == NULL);
+
+ *item = newrdatalist(msg);
+ if (*item == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_puttempname(dns_message_t *msg, dns_name_t **itemp) {
+ dns_name_t *item = NULL;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(itemp != NULL && *itemp != NULL);
+
+ item = *itemp;
+ *itemp = NULL;
+
+ REQUIRE(!ISC_LINK_LINKED(item, link));
+ REQUIRE(ISC_LIST_HEAD(item->list) == NULL);
+
+ /*
+ * we need to check this in case dns_name_dup() was used.
+ */
+ if (dns_name_dynamic(item)) {
+ dns_name_free(item, msg->mctx);
+ }
+
+ /*
+ * 'name' is the first field in dns_fixedname_t, so putting
+ * back the address of name is the same as putting back
+ * the fixedname.
+ */
+ isc_mempool_put(msg->namepool, item);
+}
+
+void
+dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item != NULL);
+
+ releaserdata(msg, *item);
+ *item = NULL;
+}
+
+void
+dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item != NULL);
+
+ REQUIRE(!dns_rdataset_isassociated(*item));
+ isc_mempool_put(msg->rdspool, *item);
+ *item = NULL;
+}
+
+void
+dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(item != NULL && *item != NULL);
+
+ releaserdatalist(msg, *item);
+ *item = NULL;
+}
+
+isc_result_t
+dns_message_peekheader(isc_buffer_t *source, dns_messageid_t *idp,
+ unsigned int *flagsp) {
+ isc_region_t r;
+ isc_buffer_t buffer;
+ dns_messageid_t id;
+ unsigned int flags;
+
+ REQUIRE(source != NULL);
+
+ buffer = *source;
+
+ isc_buffer_remainingregion(&buffer, &r);
+ if (r.length < DNS_MESSAGE_HEADERLEN) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ id = isc_buffer_getuint16(&buffer);
+ flags = isc_buffer_getuint16(&buffer);
+ flags &= DNS_MESSAGE_FLAG_MASK;
+
+ if (flagsp != NULL) {
+ *flagsp = flags;
+ }
+ if (idp != NULL) {
+ *idp = id;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_reply(dns_message_t *msg, bool want_question_section) {
+ unsigned int clear_from;
+ isc_result_t result;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE((msg->flags & DNS_MESSAGEFLAG_QR) == 0);
+
+ if (!msg->header_ok) {
+ return (DNS_R_FORMERR);
+ }
+ if (msg->opcode != dns_opcode_query && msg->opcode != dns_opcode_notify)
+ {
+ want_question_section = false;
+ }
+ if (msg->opcode == dns_opcode_update) {
+ clear_from = DNS_SECTION_PREREQUISITE;
+ } else if (want_question_section) {
+ if (!msg->question_ok) {
+ return (DNS_R_FORMERR);
+ }
+ clear_from = DNS_SECTION_ANSWER;
+ } else {
+ clear_from = DNS_SECTION_QUESTION;
+ }
+ msg->from_to_wire = DNS_MESSAGE_INTENTRENDER;
+ msgresetnames(msg, clear_from);
+ msgresetopt(msg);
+ msgresetsigs(msg, true);
+ msginitprivate(msg);
+ /*
+ * We now clear most flags and then set QR, ensuring that the
+ * reply's flags will be in a reasonable state.
+ */
+ if (msg->opcode == dns_opcode_query) {
+ msg->flags &= DNS_MESSAGE_REPLYPRESERVE;
+ } else {
+ msg->flags = 0;
+ }
+ msg->flags |= DNS_MESSAGEFLAG_QR;
+
+ /*
+ * This saves the query TSIG status, if the query was signed, and
+ * reserves space in the reply for the TSIG.
+ */
+ if (msg->tsigkey != NULL) {
+ unsigned int otherlen = 0;
+ msg->querytsigstatus = msg->tsigstatus;
+ msg->tsigstatus = dns_rcode_noerror;
+ if (msg->querytsigstatus == dns_tsigerror_badtime) {
+ otherlen = 6;
+ }
+ msg->sig_reserved = spacefortsig(msg->tsigkey, otherlen);
+ result = dns_message_renderreserve(msg, msg->sig_reserved);
+ if (result != ISC_R_SUCCESS) {
+ msg->sig_reserved = 0;
+ return (result);
+ }
+ }
+ if (msg->saved.base != NULL) {
+ msg->query.base = msg->saved.base;
+ msg->query.length = msg->saved.length;
+ msg->free_query = msg->free_saved;
+ msg->saved.base = NULL;
+ msg->saved.length = 0;
+ msg->free_saved = 0;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+dns_rdataset_t *
+dns_message_getopt(dns_message_t *msg) {
+ /*
+ * Get the OPT record for 'msg'.
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ return (msg->opt);
+}
+
+isc_result_t
+dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ /*
+ * Set the OPT record for 'msg'.
+ */
+
+ /*
+ * The space required for an OPT record is:
+ *
+ * 1 byte for the name
+ * 2 bytes for the type
+ * 2 bytes for the class
+ * 4 bytes for the ttl
+ * 2 bytes for the rdata length
+ * ---------------------------------
+ * 11 bytes
+ *
+ * plus the length of the rdata.
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(opt->type == dns_rdatatype_opt);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+ REQUIRE(msg->state == DNS_SECTION_ANY);
+
+ msgresetopt(msg);
+
+ result = dns_rdataset_first(opt);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ dns_rdataset_current(opt, &rdata);
+ msg->opt_reserved = 11 + rdata.length;
+ result = dns_message_renderreserve(msg, msg->opt_reserved);
+ if (result != ISC_R_SUCCESS) {
+ msg->opt_reserved = 0;
+ goto cleanup;
+ }
+
+ msg->opt = opt;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dns_rdataset_disassociate(opt);
+ dns_message_puttemprdataset(msg, &opt);
+ return (result);
+}
+
+dns_rdataset_t *
+dns_message_gettsig(dns_message_t *msg, const dns_name_t **owner) {
+ /*
+ * Get the TSIG record and owner for 'msg'.
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(owner == NULL || *owner == NULL);
+
+ if (owner != NULL) {
+ *owner = msg->tsigname;
+ }
+ return (msg->tsig);
+}
+
+isc_result_t
+dns_message_settsigkey(dns_message_t *msg, dns_tsigkey_t *key) {
+ isc_result_t result;
+
+ /*
+ * Set the TSIG key for 'msg'
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ if (key == NULL && msg->tsigkey != NULL) {
+ if (msg->sig_reserved != 0) {
+ dns_message_renderrelease(msg, msg->sig_reserved);
+ msg->sig_reserved = 0;
+ }
+ dns_tsigkey_detach(&msg->tsigkey);
+ }
+ if (key != NULL) {
+ REQUIRE(msg->tsigkey == NULL && msg->sig0key == NULL);
+ dns_tsigkey_attach(key, &msg->tsigkey);
+ if (msg->from_to_wire == DNS_MESSAGE_INTENTRENDER) {
+ msg->sig_reserved = spacefortsig(msg->tsigkey, 0);
+ result = dns_message_renderreserve(msg,
+ msg->sig_reserved);
+ if (result != ISC_R_SUCCESS) {
+ dns_tsigkey_detach(&msg->tsigkey);
+ msg->sig_reserved = 0;
+ return (result);
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+dns_tsigkey_t *
+dns_message_gettsigkey(dns_message_t *msg) {
+ /*
+ * Get the TSIG key for 'msg'
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ return (msg->tsigkey);
+}
+
+isc_result_t
+dns_message_setquerytsig(dns_message_t *msg, isc_buffer_t *querytsig) {
+ dns_rdata_t *rdata = NULL;
+ dns_rdatalist_t *list = NULL;
+ dns_rdataset_t *set = NULL;
+ isc_buffer_t *buf = NULL;
+ isc_region_t r;
+ isc_result_t result;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->querytsig == NULL);
+
+ if (querytsig == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_message_gettemprdata(msg, &rdata);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_message_gettemprdatalist(msg, &list);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_gettemprdataset(msg, &set);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ isc_buffer_usedregion(querytsig, &r);
+ isc_buffer_allocate(msg->mctx, &buf, r.length);
+ isc_buffer_putmem(buf, r.base, r.length);
+ isc_buffer_usedregion(buf, &r);
+ dns_rdata_init(rdata);
+ dns_rdata_fromregion(rdata, dns_rdataclass_any, dns_rdatatype_tsig, &r);
+ dns_message_takebuffer(msg, &buf);
+ ISC_LIST_APPEND(list->rdata, rdata, link);
+ result = dns_rdatalist_tordataset(list, set);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ msg->querytsig = set;
+
+ return (result);
+
+cleanup:
+ if (rdata != NULL) {
+ dns_message_puttemprdata(msg, &rdata);
+ }
+ if (list != NULL) {
+ dns_message_puttemprdatalist(msg, &list);
+ }
+ if (set != NULL) {
+ dns_message_puttemprdataset(msg, &set);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+isc_result_t
+dns_message_getquerytsig(dns_message_t *msg, isc_mem_t *mctx,
+ isc_buffer_t **querytsig) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_region_t r;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(mctx != NULL);
+ REQUIRE(querytsig != NULL && *querytsig == NULL);
+
+ if (msg->tsig == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_rdataset_first(msg->tsig);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_rdataset_current(msg->tsig, &rdata);
+ dns_rdata_toregion(&rdata, &r);
+
+ isc_buffer_allocate(mctx, querytsig, r.length);
+ isc_buffer_putmem(*querytsig, r.base, r.length);
+ return (ISC_R_SUCCESS);
+}
+
+dns_rdataset_t *
+dns_message_getsig0(dns_message_t *msg, const dns_name_t **owner) {
+ /*
+ * Get the SIG(0) record for 'msg'.
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(owner == NULL || *owner == NULL);
+
+ if (msg->sig0 != NULL && owner != NULL) {
+ /* If dns_message_getsig0 is called on a rendered message
+ * after the SIG(0) has been applied, we need to return the
+ * root name, not NULL.
+ */
+ if (msg->sig0name == NULL) {
+ *owner = dns_rootname;
+ } else {
+ *owner = msg->sig0name;
+ }
+ }
+ return (msg->sig0);
+}
+
+isc_result_t
+dns_message_setsig0key(dns_message_t *msg, dst_key_t *key) {
+ isc_region_t r;
+ unsigned int x;
+ isc_result_t result;
+
+ /*
+ * Set the SIG(0) key for 'msg'
+ */
+
+ /*
+ * The space required for an SIG(0) record is:
+ *
+ * 1 byte for the name
+ * 2 bytes for the type
+ * 2 bytes for the class
+ * 4 bytes for the ttl
+ * 2 bytes for the type covered
+ * 1 byte for the algorithm
+ * 1 bytes for the labels
+ * 4 bytes for the original ttl
+ * 4 bytes for the signature expiration
+ * 4 bytes for the signature inception
+ * 2 bytes for the key tag
+ * n bytes for the signer's name
+ * x bytes for the signature
+ * ---------------------------------
+ * 27 + n + x bytes
+ */
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
+ REQUIRE(msg->state == DNS_SECTION_ANY);
+
+ if (key != NULL) {
+ REQUIRE(msg->sig0key == NULL && msg->tsigkey == NULL);
+ dns_name_toregion(dst_key_name(key), &r);
+ result = dst_key_sigsize(key, &x);
+ if (result != ISC_R_SUCCESS) {
+ msg->sig_reserved = 0;
+ return (result);
+ }
+ msg->sig_reserved = 27 + r.length + x;
+ result = dns_message_renderreserve(msg, msg->sig_reserved);
+ if (result != ISC_R_SUCCESS) {
+ msg->sig_reserved = 0;
+ return (result);
+ }
+ msg->sig0key = key;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+dst_key_t *
+dns_message_getsig0key(dns_message_t *msg) {
+ /*
+ * Get the SIG(0) key for 'msg'
+ */
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ return (msg->sig0key);
+}
+
+void
+dns_message_takebuffer(dns_message_t *msg, isc_buffer_t **buffer) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(buffer != NULL);
+ REQUIRE(ISC_BUFFER_VALID(*buffer));
+
+ ISC_LIST_APPEND(msg->cleanup, *buffer, link);
+ *buffer = NULL;
+}
+
+isc_result_t
+dns_message_signer(dns_message_t *msg, dns_name_t *signer) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(signer != NULL);
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
+
+ if (msg->tsig == NULL && msg->sig0 == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (msg->verify_attempted == 0) {
+ return (DNS_R_NOTVERIFIEDYET);
+ }
+
+ if (!dns_name_hasbuffer(signer)) {
+ isc_buffer_t *dynbuf = NULL;
+ isc_buffer_allocate(msg->mctx, &dynbuf, 512);
+ dns_name_setbuffer(signer, dynbuf);
+ dns_message_takebuffer(msg, &dynbuf);
+ }
+
+ if (msg->sig0 != NULL) {
+ dns_rdata_sig_t sig;
+
+ result = dns_rdataset_first(msg->sig0);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(msg->sig0, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &sig, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (msg->verified_sig && msg->sig0status == dns_rcode_noerror) {
+ result = ISC_R_SUCCESS;
+ } else {
+ result = DNS_R_SIGINVALID;
+ }
+ dns_name_clone(&sig.signer, signer);
+ dns_rdata_freestruct(&sig);
+ } else {
+ const dns_name_t *identity;
+ dns_rdata_any_tsig_t tsig;
+
+ result = dns_rdataset_first(msg->tsig);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(msg->tsig, &rdata);
+
+ result = dns_rdata_tostruct(&rdata, &tsig, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+ if (msg->verified_sig && msg->tsigstatus == dns_rcode_noerror &&
+ tsig.error == dns_rcode_noerror)
+ {
+ result = ISC_R_SUCCESS;
+ } else if ((!msg->verified_sig) ||
+ (msg->tsigstatus != dns_rcode_noerror))
+ {
+ result = DNS_R_TSIGVERIFYFAILURE;
+ } else {
+ INSIST(tsig.error != dns_rcode_noerror);
+ result = DNS_R_TSIGERRORSET;
+ }
+ dns_rdata_freestruct(&tsig);
+
+ if (msg->tsigkey == NULL) {
+ /*
+ * If msg->tsigstatus & tsig.error are both
+ * dns_rcode_noerror, the message must have been
+ * verified, which means msg->tsigkey will be
+ * non-NULL.
+ */
+ INSIST(result != ISC_R_SUCCESS);
+ } else {
+ identity = dns_tsigkey_identity(msg->tsigkey);
+ if (identity == NULL) {
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NOIDENTITY;
+ }
+ identity = &msg->tsigkey->name;
+ }
+ dns_name_clone(identity, signer);
+ }
+ }
+
+ return (result);
+}
+
+void
+dns_message_resetsig(dns_message_t *msg) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ msg->verified_sig = 0;
+ msg->verify_attempted = 0;
+ msg->tsigstatus = dns_rcode_noerror;
+ msg->sig0status = dns_rcode_noerror;
+ msg->timeadjust = 0;
+ if (msg->tsigkey != NULL) {
+ dns_tsigkey_detach(&msg->tsigkey);
+ msg->tsigkey = NULL;
+ }
+}
+
+isc_result_t
+dns_message_rechecksig(dns_message_t *msg, dns_view_t *view) {
+ dns_message_resetsig(msg);
+ return (dns_message_checksig(msg, view));
+}
+
+#ifdef SKAN_MSG_DEBUG
+void
+dns_message_dumpsig(dns_message_t *msg, char *txt1) {
+ dns_rdata_t querytsigrdata = DNS_RDATA_INIT;
+ dns_rdata_any_tsig_t querytsig;
+ isc_result_t result;
+
+ if (msg->tsig != NULL) {
+ result = dns_rdataset_first(msg->tsig);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_current(msg->tsig, &querytsigrdata);
+ result = dns_rdata_tostruct(&querytsigrdata, &querytsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ hexdump(txt1, "TSIG", querytsig.signature, querytsig.siglen);
+ }
+
+ if (msg->querytsig != NULL) {
+ result = dns_rdataset_first(msg->querytsig);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_current(msg->querytsig, &querytsigrdata);
+ result = dns_rdata_tostruct(&querytsigrdata, &querytsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ hexdump(txt1, "QUERYTSIG", querytsig.signature,
+ querytsig.siglen);
+ }
+}
+#endif /* ifdef SKAN_MSG_DEBUG */
+
+isc_result_t
+dns_message_checksig(dns_message_t *msg, dns_view_t *view) {
+ isc_buffer_t b, msgb;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ if (msg->tsigkey == NULL && msg->tsig == NULL && msg->sig0 == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ INSIST(msg->saved.base != NULL);
+ isc_buffer_init(&msgb, msg->saved.base, msg->saved.length);
+ isc_buffer_add(&msgb, msg->saved.length);
+ if (msg->tsigkey != NULL || msg->tsig != NULL) {
+#ifdef SKAN_MSG_DEBUG
+ dns_message_dumpsig(msg, "dns_message_checksig#1");
+#endif /* ifdef SKAN_MSG_DEBUG */
+ if (view != NULL) {
+ return (dns_view_checksig(view, &msgb, msg));
+ } else {
+ return (dns_tsig_verify(&msgb, msg, NULL, NULL));
+ }
+ } else {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_sig_t sig;
+ dns_rdataset_t keyset;
+ isc_result_t result;
+
+ result = dns_rdataset_first(msg->sig0);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(msg->sig0, &rdata);
+
+ /*
+ * This can occur when the message is a dynamic update, since
+ * the rdata length checking is relaxed. This should not
+ * happen in a well-formed message, since the SIG(0) is only
+ * looked for in the additional section, and the dynamic update
+ * meta-records are in the prerequisite and update sections.
+ */
+ if (rdata.length == 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ result = dns_rdata_tostruct(&rdata, &sig, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdataset_init(&keyset);
+ if (view == NULL) {
+ result = DNS_R_KEYUNAUTHORIZED;
+ goto freesig;
+ }
+ result = dns_view_simplefind(view, &sig.signer,
+ dns_rdatatype_key /* SIG(0) */, 0,
+ 0, false, &keyset, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ /* XXXBEW Should possibly create a fetch here */
+ result = DNS_R_KEYUNAUTHORIZED;
+ goto freesig;
+ } else if (keyset.trust < dns_trust_secure) {
+ /* XXXBEW Should call a validator here */
+ result = DNS_R_KEYUNAUTHORIZED;
+ goto freesig;
+ }
+ result = dns_rdataset_first(&keyset);
+ INSIST(result == ISC_R_SUCCESS);
+ for (; result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&keyset))
+ {
+ dst_key_t *key = NULL;
+
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&keyset, &rdata);
+ isc_buffer_init(&b, rdata.data, rdata.length);
+ isc_buffer_add(&b, rdata.length);
+
+ result = dst_key_fromdns(&sig.signer, rdata.rdclass, &b,
+ view->mctx, &key);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (dst_key_alg(key) != sig.algorithm ||
+ dst_key_id(key) != sig.keyid ||
+ !(dst_key_proto(key) == DNS_KEYPROTO_DNSSEC ||
+ dst_key_proto(key) == DNS_KEYPROTO_ANY))
+ {
+ dst_key_free(&key);
+ continue;
+ }
+ result = dns_dnssec_verifymessage(&msgb, msg, key);
+ dst_key_free(&key);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = DNS_R_KEYUNAUTHORIZED;
+ }
+
+ freesig:
+ if (dns_rdataset_isassociated(&keyset)) {
+ dns_rdataset_disassociate(&keyset);
+ }
+ dns_rdata_freestruct(&sig);
+ return (result);
+ }
+}
+
+#define INDENT(sp) \
+ do { \
+ unsigned int __i; \
+ dns_masterstyle_flags_t __flags = dns_master_styleflags(sp); \
+ if ((__flags & DNS_STYLEFLAG_INDENT) == 0ULL && \
+ (__flags & DNS_STYLEFLAG_YAML) == 0ULL) \
+ break; \
+ for (__i = 0; __i < msg->indent.count; __i++) { \
+ ADD_STRING(target, msg->indent.string); \
+ } \
+ } while (0)
+
+isc_result_t
+dns_message_sectiontotext(dns_message_t *msg, dns_section_t section,
+ const dns_master_style_t *style,
+ dns_messagetextflag_t flags, isc_buffer_t *target) {
+ dns_name_t *name, empty_name;
+ dns_rdataset_t *rdataset;
+ isc_result_t result = ISC_R_SUCCESS;
+ bool seensoa = false;
+ size_t saved_count;
+ dns_masterstyle_flags_t sflags;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(target != NULL);
+ REQUIRE(VALID_SECTION(section));
+
+ saved_count = msg->indent.count;
+
+ if (ISC_LIST_EMPTY(msg->sections[section])) {
+ goto cleanup;
+ }
+
+ sflags = dns_master_styleflags(style);
+
+ INDENT(style);
+ if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
+ if (msg->opcode != dns_opcode_update) {
+ ADD_STRING(target, sectiontext[section]);
+ } else {
+ ADD_STRING(target, updsectiontext[section]);
+ }
+ ADD_STRING(target, "_SECTION:\n");
+ } else if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
+ ADD_STRING(target, ";; ");
+ if (msg->opcode != dns_opcode_update) {
+ ADD_STRING(target, sectiontext[section]);
+ } else {
+ ADD_STRING(target, updsectiontext[section]);
+ }
+ ADD_STRING(target, " SECTION:\n");
+ }
+
+ dns_name_init(&empty_name, NULL);
+ result = dns_message_firstname(msg, section);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
+ msg->indent.count++;
+ }
+ do {
+ name = NULL;
+ dns_message_currentname(msg, section, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (section == DNS_SECTION_ANSWER &&
+ rdataset->type == dns_rdatatype_soa)
+ {
+ if ((flags & DNS_MESSAGETEXTFLAG_OMITSOA) != 0)
+ {
+ continue;
+ }
+ if (seensoa &&
+ (flags & DNS_MESSAGETEXTFLAG_ONESOA) != 0)
+ {
+ continue;
+ }
+ seensoa = true;
+ }
+ if (section == DNS_SECTION_QUESTION) {
+ INDENT(style);
+ if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
+ ADD_STRING(target, "- ");
+ } else {
+ ADD_STRING(target, ";");
+ }
+ result = dns_master_questiontotext(
+ name, rdataset, style, target);
+ } else {
+ result = dns_master_rdatasettotext(
+ name, rdataset, style, &msg->indent,
+ target);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ result = dns_message_nextname(msg, section);
+ } while (result == ISC_R_SUCCESS);
+ if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
+ msg->indent.count--;
+ }
+ if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
+ (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0 &&
+ (sflags & DNS_STYLEFLAG_YAML) == 0)
+ {
+ INDENT(style);
+ ADD_STRING(target, "\n");
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+cleanup:
+ msg->indent.count = saved_count;
+ return (result);
+}
+
+static isc_result_t
+render_ecs(isc_buffer_t *ecsbuf, isc_buffer_t *target) {
+ int i;
+ char addr[16], addr_text[64];
+ uint16_t family;
+ uint8_t addrlen, addrbytes, scopelen;
+ isc_result_t result;
+
+ /*
+ * Note: This routine needs to handle malformed ECS options.
+ */
+
+ if (isc_buffer_remaininglength(ecsbuf) < 4) {
+ return (DNS_R_OPTERR);
+ }
+ family = isc_buffer_getuint16(ecsbuf);
+ addrlen = isc_buffer_getuint8(ecsbuf);
+ scopelen = isc_buffer_getuint8(ecsbuf);
+
+ addrbytes = (addrlen + 7) / 8;
+ if (isc_buffer_remaininglength(ecsbuf) < addrbytes) {
+ return (DNS_R_OPTERR);
+ }
+
+ if (addrbytes > sizeof(addr)) {
+ return (DNS_R_OPTERR);
+ }
+
+ memset(addr, 0, sizeof(addr));
+ for (i = 0; i < addrbytes; i++) {
+ addr[i] = isc_buffer_getuint8(ecsbuf);
+ }
+
+ switch (family) {
+ case 0:
+ if (addrlen != 0U || scopelen != 0U) {
+ return (DNS_R_OPTERR);
+ }
+ strlcpy(addr_text, "0", sizeof(addr_text));
+ break;
+ case 1:
+ if (addrlen > 32 || scopelen > 32) {
+ return (DNS_R_OPTERR);
+ }
+ inet_ntop(AF_INET, addr, addr_text, sizeof(addr_text));
+ break;
+ case 2:
+ if (addrlen > 128 || scopelen > 128) {
+ return (DNS_R_OPTERR);
+ }
+ inet_ntop(AF_INET6, addr, addr_text, sizeof(addr_text));
+ break;
+ default:
+ return (DNS_R_OPTERR);
+ }
+
+ ADD_STRING(target, " ");
+ ADD_STRING(target, addr_text);
+ snprintf(addr_text, sizeof(addr_text), "/%d/%d", addrlen, scopelen);
+ ADD_STRING(target, addr_text);
+
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+render_llq(isc_buffer_t *optbuf, isc_buffer_t *target) {
+ char buf[sizeof("18446744073709551615")]; /* 2^64-1 */
+ isc_result_t result = ISC_R_SUCCESS;
+ uint32_t u;
+ uint64_t q;
+
+ u = isc_buffer_getuint16(optbuf);
+ ADD_STRING(target, " Version: ");
+ snprintf(buf, sizeof(buf), "%u", u);
+ ADD_STRING(target, buf);
+
+ u = isc_buffer_getuint16(optbuf);
+ ADD_STRING(target, ", Opcode: ");
+ snprintf(buf, sizeof(buf), "%u", u);
+ ADD_STRING(target, buf);
+
+ u = isc_buffer_getuint16(optbuf);
+ ADD_STRING(target, ", Error: ");
+ snprintf(buf, sizeof(buf), "%u", u);
+ ADD_STRING(target, buf);
+
+ q = isc_buffer_getuint32(optbuf);
+ q <<= 32;
+ q |= isc_buffer_getuint32(optbuf);
+ ADD_STRING(target, ", Identifier: ");
+ snprintf(buf, sizeof(buf), "%" PRIu64, q);
+ ADD_STRING(target, buf);
+
+ u = isc_buffer_getuint32(optbuf);
+ ADD_STRING(target, ", Lifetime: ");
+ snprintf(buf, sizeof(buf), "%u", u);
+ ADD_STRING(target, buf);
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section,
+ const dns_master_style_t *style,
+ dns_messagetextflag_t flags,
+ isc_buffer_t *target) {
+ dns_rdataset_t *ps = NULL;
+ const dns_name_t *name = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ char buf[sizeof("1234567890")];
+ uint32_t mbz;
+ dns_rdata_t rdata;
+ isc_buffer_t optbuf;
+ uint16_t optcode, optlen;
+ size_t saved_count;
+ unsigned char *optdata;
+ unsigned int indent;
+
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(target != NULL);
+ REQUIRE(VALID_PSEUDOSECTION(section));
+
+ saved_count = msg->indent.count;
+
+ switch (section) {
+ case DNS_PSEUDOSECTION_OPT:
+ ps = dns_message_getopt(msg);
+ if (ps == NULL) {
+ goto cleanup;
+ }
+
+ INDENT(style);
+ ADD_STRING(target, "OPT_PSEUDOSECTION:\n");
+ msg->indent.count++;
+
+ INDENT(style);
+ ADD_STRING(target, "EDNS:\n");
+ indent = ++msg->indent.count;
+
+ INDENT(style);
+ ADD_STRING(target, "version: ");
+ snprintf(buf, sizeof(buf), "%u",
+ (unsigned int)((ps->ttl & 0x00ff0000) >> 16));
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ INDENT(style);
+ ADD_STRING(target, "flags:");
+ if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0) {
+ ADD_STRING(target, " do");
+ }
+ ADD_STRING(target, "\n");
+ mbz = ps->ttl & 0xffff;
+ mbz &= ~DNS_MESSAGEEXTFLAG_DO; /* Known Flags. */
+ if (mbz != 0) {
+ INDENT(style);
+ ADD_STRING(target, "MBZ: ");
+ snprintf(buf, sizeof(buf), "0x%.4x", mbz);
+ ADD_STRING(target, buf);
+ ADD_STRING(target, "\n");
+ }
+ INDENT(style);
+ ADD_STRING(target, "udp: ");
+ snprintf(buf, sizeof(buf), "%u\n", (unsigned int)ps->rdclass);
+ ADD_STRING(target, buf);
+ result = dns_rdataset_first(ps);
+ if (result != ISC_R_SUCCESS) {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ /*
+ * Print EDNS info, if any.
+ *
+ * WARNING: The option contents may be malformed as
+ * dig +ednsopt=value:<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, const dns_acl_t *acl,
+ const dns_aclelement_t *elem) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE((order == NULL) == (env == NULL));
+ REQUIRE(env == NULL || (acl != NULL || elem != NULL));
+
+ msg->order = order;
+ msg->order_arg.env = env;
+ msg->order_arg.acl = acl;
+ msg->order_arg.element = elem;
+}
+
+void
+dns_message_settimeadjust(dns_message_t *msg, int timeadjust) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ msg->timeadjust = timeadjust;
+}
+
+int
+dns_message_gettimeadjust(dns_message_t *msg) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ return (msg->timeadjust);
+}
+
+isc_result_t
+dns_opcode_totext(dns_opcode_t opcode, isc_buffer_t *target) {
+ REQUIRE(opcode < 16);
+
+ if (isc_buffer_availablelength(target) < strlen(opcodetext[opcode])) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putstr(target, opcodetext[opcode]);
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_message_logpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ int level, isc_mem_t *mctx) {
+ REQUIRE(address != NULL);
+
+ logfmtpacket(message, description, address, category, module,
+ &dns_master_style_debug, level, mctx);
+}
+
+void
+dns_message_logfmtpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ const dns_master_style_t *style, int level,
+ isc_mem_t *mctx) {
+ REQUIRE(address != NULL);
+
+ logfmtpacket(message, description, address, category, module, style,
+ level, mctx);
+}
+
+static void
+logfmtpacket(dns_message_t *message, const char *description,
+ const isc_sockaddr_t *address, isc_logcategory_t *category,
+ isc_logmodule_t *module, const dns_master_style_t *style,
+ int level, isc_mem_t *mctx) {
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE] = { 0 };
+ const char *newline = "\n";
+ const char *space = " ";
+ isc_buffer_t buffer;
+ char *buf = NULL;
+ int len = 1024;
+ isc_result_t result;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ /*
+ * Note that these are multiline debug messages. We want a newline
+ * to appear in the log after each message.
+ */
+
+ if (address != NULL) {
+ isc_sockaddr_format(address, addrbuf, sizeof(addrbuf));
+ } else {
+ newline = space = "";
+ }
+
+ do {
+ buf = isc_mem_get(mctx, len);
+ isc_buffer_init(&buffer, buf, len);
+ result = dns_message_totext(message, style, 0, &buffer);
+ if (result == ISC_R_NOSPACE) {
+ isc_mem_put(mctx, buf, len);
+ len += 1024;
+ } else if (result == ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, category, module, level,
+ "%s%s%s%s%.*s", description, space,
+ addrbuf, newline,
+ (int)isc_buffer_usedlength(&buffer), buf);
+ }
+ } while (result == ISC_R_NOSPACE);
+
+ if (buf != NULL) {
+ isc_mem_put(mctx, buf, len);
+ }
+}
+
+isc_result_t
+dns_message_buildopt(dns_message_t *message, dns_rdataset_t **rdatasetp,
+ unsigned int version, uint16_t udpsize, unsigned int flags,
+ dns_ednsopt_t *ednsopts, size_t count) {
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdatalist_t *rdatalist = NULL;
+ dns_rdata_t *rdata = NULL;
+ isc_result_t result;
+ unsigned int len = 0, i;
+
+ REQUIRE(DNS_MESSAGE_VALID(message));
+ REQUIRE(rdatasetp != NULL && *rdatasetp == NULL);
+
+ result = dns_message_gettemprdatalist(message, &rdatalist);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dns_message_gettemprdata(message, &rdata);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_gettemprdataset(message, &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ rdatalist->type = dns_rdatatype_opt;
+
+ /*
+ * Set Maximum UDP buffer size.
+ */
+ rdatalist->rdclass = udpsize;
+
+ /*
+ * Set EXTENDED-RCODE and Z to 0.
+ */
+ rdatalist->ttl = (version << 16);
+ rdatalist->ttl |= (flags & 0xffff);
+
+ /*
+ * Set EDNS options if applicable
+ */
+ if (count != 0U) {
+ isc_buffer_t *buf = NULL;
+ bool seenpad = false;
+ for (i = 0; i < count; i++) {
+ len += ednsopts[i].length + 4;
+ }
+
+ if (len > 0xffffU) {
+ result = ISC_R_NOSPACE;
+ goto cleanup;
+ }
+
+ isc_buffer_allocate(message->mctx, &buf, len);
+
+ for (i = 0; i < count; i++) {
+ if (ednsopts[i].code == DNS_OPT_PAD &&
+ ednsopts[i].length == 0U && !seenpad)
+ {
+ seenpad = true;
+ continue;
+ }
+ isc_buffer_putuint16(buf, ednsopts[i].code);
+ isc_buffer_putuint16(buf, ednsopts[i].length);
+ if (ednsopts[i].length != 0) {
+ isc_buffer_putmem(buf, ednsopts[i].value,
+ ednsopts[i].length);
+ }
+ }
+
+ /* Padding must be the final option */
+ if (seenpad) {
+ isc_buffer_putuint16(buf, DNS_OPT_PAD);
+ isc_buffer_putuint16(buf, 0);
+ }
+ rdata->data = isc_buffer_base(buf);
+ rdata->length = len;
+ dns_message_takebuffer(message, &buf);
+ if (seenpad) {
+ message->padding_off = len;
+ }
+ } else {
+ rdata->data = NULL;
+ rdata->length = 0;
+ }
+
+ rdata->rdclass = rdatalist->rdclass;
+ rdata->type = rdatalist->type;
+ rdata->flags = 0;
+
+ ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
+ result = dns_rdatalist_tordataset(rdatalist, rdataset);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ *rdatasetp = rdataset;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (rdata != NULL) {
+ dns_message_puttemprdata(message, &rdata);
+ }
+ if (rdataset != NULL) {
+ dns_message_puttemprdataset(message, &rdataset);
+ }
+ if (rdatalist != NULL) {
+ dns_message_puttemprdatalist(message, &rdatalist);
+ }
+ return (result);
+}
+
+void
+dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+ REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
+ REQUIRE(msg->state == DNS_SECTION_ANY);
+ REQUIRE(msg->rdclass_set == 0);
+
+ msg->rdclass = rdclass;
+ msg->rdclass_set = 1;
+}
+
+void
+dns_message_setpadding(dns_message_t *msg, uint16_t padding) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ /* Avoid silly large padding */
+ if (padding > 512) {
+ padding = 512;
+ }
+ msg->padding = padding;
+}
+
+void
+dns_message_clonebuffer(dns_message_t *msg) {
+ REQUIRE(DNS_MESSAGE_VALID(msg));
+
+ if (msg->free_saved == 0 && msg->saved.base != NULL) {
+ msg->saved.base =
+ memmove(isc_mem_get(msg->mctx, msg->saved.length),
+ msg->saved.base, msg->saved.length);
+ msg->free_saved = 1;
+ }
+ if (msg->free_query == 0 && msg->query.base != NULL) {
+ msg->query.base =
+ memmove(isc_mem_get(msg->mctx, msg->query.length),
+ msg->query.base, msg->query.length);
+ msg->free_query = 1;
+ }
+}
diff --git a/lib/dns/name.c b/lib/dns/name.c
new file mode 100644
index 0000000..96f95b3
--- /dev/null
+++ b/lib/dns/name.c
@@ -0,0 +1,2692 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/string.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <dns/compress.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/result.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);
+LIBDNS_EXTERNAL_DATA const dns_name_t *dns_rootname = &root;
+
+static unsigned char wild_ndata[] = { "\001*" };
+static unsigned char wild_offsets[] = { 0 };
+
+static dns_name_t const wild = DNS_NAME_INITNONABSOLUTE(wild_ndata,
+ wild_offsets);
+
+LIBDNS_EXTERNAL_DATA const dns_name_t *dns_wildcardname = &wild;
+
+/*
+ * dns_name_t to text post-conversion procedure.
+ */
+ISC_THREAD_LOCAL dns_name_totextfilter_t *totext_filter_proc = NULL;
+
+static void
+set_offsets(const dns_name_t *name, unsigned char *offsets,
+ dns_name_t *set_name);
+
+void
+dns_name_init(dns_name_t *name, unsigned char *offsets) {
+ /*
+ * Initialize 'name'.
+ */
+ DNS_NAME_INIT(name, offsets);
+}
+
+void
+dns_name_reset(dns_name_t *name) {
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(BINDABLE(name));
+
+ DNS_NAME_RESET(name);
+}
+
+void
+dns_name_invalidate(dns_name_t *name) {
+ /*
+ * Make 'name' invalid.
+ */
+
+ REQUIRE(VALID_NAME(name));
+
+ name->magic = 0;
+ name->ndata = NULL;
+ name->length = 0;
+ name->labels = 0;
+ name->attributes = 0;
+ name->offsets = NULL;
+ name->buffer = NULL;
+ ISC_LINK_INIT(name, link);
+}
+
+bool
+dns_name_isvalid(const dns_name_t *name) {
+ unsigned char *ndata, *offsets;
+ unsigned int offset, count, length, nlabels;
+
+ if (!VALID_NAME(name)) {
+ return (false);
+ }
+
+ if (name->length > 255U || name->labels > 127U) {
+ return (false);
+ }
+
+ ndata = name->ndata;
+ length = name->length;
+ offsets = name->offsets;
+ offset = 0;
+ nlabels = 0;
+
+ while (offset != length) {
+ count = *ndata;
+ if (count > 63U) {
+ return (false);
+ }
+ if (offsets != NULL && offsets[nlabels] != offset) {
+ return (false);
+ }
+
+ nlabels++;
+ offset += count + 1;
+ ndata += count + 1;
+ if (offset > length) {
+ return (false);
+ }
+
+ if (count == 0) {
+ break;
+ }
+ }
+
+ if (nlabels != name->labels || offset != name->length) {
+ return (false);
+ }
+
+ return (true);
+}
+
+void
+dns_name_setbuffer(dns_name_t *name, isc_buffer_t *buffer) {
+ /*
+ * Dedicate a buffer for use with 'name'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE((buffer != NULL && name->buffer == NULL) || (buffer == NULL));
+
+ name->buffer = buffer;
+}
+
+bool
+dns_name_hasbuffer(const dns_name_t *name) {
+ /*
+ * Does 'name' have a dedicated buffer?
+ */
+
+ REQUIRE(VALID_NAME(name));
+
+ if (name->buffer != NULL) {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+dns_name_isabsolute(const dns_name_t *name) {
+ /*
+ * Does 'name' end in the root label?
+ */
+
+ REQUIRE(VALID_NAME(name));
+
+ if ((name->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ return (true);
+ }
+ return (false);
+}
+
+#define hyphenchar(c) ((c) == 0x2d)
+#define asterchar(c) ((c) == 0x2a)
+#define alphachar(c) \
+ (((c) >= 0x41 && (c) <= 0x5a) || ((c) >= 0x61 && (c) <= 0x7a))
+#define digitchar(c) ((c) >= 0x30 && (c) <= 0x39)
+#define borderchar(c) (alphachar(c) || digitchar(c))
+#define middlechar(c) (borderchar(c) || hyphenchar(c))
+#define domainchar(c) ((c) > 0x20 && (c) < 0x7f)
+
+bool
+dns_name_ismailbox(const dns_name_t *name) {
+ unsigned char *ndata, ch;
+ unsigned int n;
+ bool first;
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+ REQUIRE(name->attributes & DNS_NAMEATTR_ABSOLUTE);
+
+ /*
+ * Root label.
+ */
+ if (name->length == 1) {
+ return (true);
+ }
+
+ ndata = name->ndata;
+ n = *ndata++;
+ INSIST(n <= 63);
+ while (n--) {
+ ch = *ndata++;
+ if (!domainchar(ch)) {
+ return (false);
+ }
+ }
+
+ if (ndata == name->ndata + name->length) {
+ return (false);
+ }
+
+ /*
+ * RFC292/RFC1123 hostname.
+ */
+ while (ndata < (name->ndata + name->length)) {
+ n = *ndata++;
+ INSIST(n <= 63);
+ first = true;
+ while (n--) {
+ ch = *ndata++;
+ if (first || n == 0) {
+ if (!borderchar(ch)) {
+ return (false);
+ }
+ } else {
+ if (!middlechar(ch)) {
+ return (false);
+ }
+ }
+ first = false;
+ }
+ }
+ return (true);
+}
+
+bool
+dns_name_ishostname(const dns_name_t *name, bool wildcard) {
+ unsigned char *ndata, ch;
+ unsigned int n;
+ bool first;
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+ REQUIRE(name->attributes & DNS_NAMEATTR_ABSOLUTE);
+
+ /*
+ * Root label.
+ */
+ if (name->length == 1) {
+ return (true);
+ }
+
+ /*
+ * Skip wildcard if this is a ownername.
+ */
+ ndata = name->ndata;
+ if (wildcard && ndata[0] == 1 && ndata[1] == '*') {
+ ndata += 2;
+ }
+
+ /*
+ * RFC292/RFC1123 hostname.
+ */
+ while (ndata < (name->ndata + name->length)) {
+ n = *ndata++;
+ INSIST(n <= 63);
+ first = true;
+ while (n--) {
+ ch = *ndata++;
+ if (first || n == 0) {
+ if (!borderchar(ch)) {
+ return (false);
+ }
+ } else {
+ if (!middlechar(ch)) {
+ return (false);
+ }
+ }
+ first = false;
+ }
+ }
+ return (true);
+}
+
+bool
+dns_name_iswildcard(const dns_name_t *name) {
+ unsigned char *ndata;
+
+ /*
+ * Is 'name' a wildcard name?
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+
+ if (name->length >= 2) {
+ ndata = name->ndata;
+ if (ndata[0] == 1 && ndata[1] == '*') {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+bool
+dns_name_internalwildcard(const dns_name_t *name) {
+ unsigned char *ndata;
+ unsigned int count;
+ unsigned int label;
+
+ /*
+ * Does 'name' contain a internal wildcard?
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+
+ /*
+ * Skip first label.
+ */
+ ndata = name->ndata;
+ count = *ndata++;
+ INSIST(count <= 63);
+ ndata += count;
+ label = 1;
+ /*
+ * Check all but the last of the remaining labels.
+ */
+ while (label + 1 < name->labels) {
+ count = *ndata++;
+ INSIST(count <= 63);
+ if (count == 1 && *ndata == '*') {
+ return (true);
+ }
+ ndata += count;
+ label++;
+ }
+ return (false);
+}
+
+unsigned int
+dns_name_hash(const dns_name_t *name, bool case_sensitive) {
+ unsigned int length;
+
+ /*
+ * Provide a hash value for 'name'.
+ */
+ REQUIRE(VALID_NAME(name));
+
+ if (name->labels == 0) {
+ return (0);
+ }
+
+ length = name->length;
+ if (length > 16) {
+ length = 16;
+ }
+
+ /* High bits are more random. */
+ return (isc_hash32(name->ndata, length, case_sensitive));
+}
+
+unsigned int
+dns_name_fullhash(const dns_name_t *name, bool case_sensitive) {
+ /*
+ * Provide a hash value for 'name'.
+ */
+ REQUIRE(VALID_NAME(name));
+
+ if (name->labels == 0) {
+ return (0);
+ }
+
+ /* High bits are more random. */
+ return (isc_hash32(name->ndata, name->length, case_sensitive));
+}
+
+dns_namereln_t
+dns_name_fullcompare(const dns_name_t *name1, const dns_name_t *name2,
+ int *orderp, unsigned int *nlabelsp) {
+ unsigned int l1, l2, l, count1, count2, count, nlabels;
+ int cdiff, ldiff, chdiff;
+ unsigned char *label1, *label2;
+ unsigned char *offsets1, *offsets2;
+ dns_offsets_t odata1, odata2;
+ dns_namereln_t namereln = dns_namereln_none;
+
+ /*
+ * Determine the relative ordering under the DNSSEC order relation of
+ * 'name1' and 'name2', and also determine the hierarchical
+ * relationship of the names.
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ */
+
+ REQUIRE(VALID_NAME(name1));
+ REQUIRE(VALID_NAME(name2));
+ REQUIRE(orderp != NULL);
+ REQUIRE(nlabelsp != NULL);
+ /*
+ * Either name1 is absolute and name2 is absolute, or neither is.
+ */
+ REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) ==
+ (name2->attributes & DNS_NAMEATTR_ABSOLUTE));
+
+ if (ISC_UNLIKELY(name1 == name2)) {
+ *orderp = 0;
+ *nlabelsp = name1->labels;
+ return (dns_namereln_equal);
+ }
+
+ SETUP_OFFSETS(name1, offsets1, odata1);
+ SETUP_OFFSETS(name2, offsets2, odata2);
+
+ nlabels = 0;
+ l1 = name1->labels;
+ l2 = name2->labels;
+ if (l2 > l1) {
+ l = l1;
+ ldiff = 0 - (l2 - l1);
+ } else {
+ l = l2;
+ ldiff = l1 - l2;
+ }
+
+ offsets1 += l1;
+ offsets2 += l2;
+
+ while (ISC_LIKELY(l > 0)) {
+ l--;
+ offsets1--;
+ offsets2--;
+ label1 = &name1->ndata[*offsets1];
+ label2 = &name2->ndata[*offsets2];
+ count1 = *label1++;
+ count2 = *label2++;
+
+ /*
+ * We dropped bitstring labels, and we don't support any
+ * other extended label types.
+ */
+ INSIST(count1 <= 63 && count2 <= 63);
+
+ cdiff = (int)count1 - (int)count2;
+ if (cdiff < 0) {
+ count = count1;
+ } else {
+ count = count2;
+ }
+
+ /* Loop unrolled for performance */
+ while (ISC_LIKELY(count > 3)) {
+ chdiff = (int)maptolower[label1[0]] -
+ (int)maptolower[label2[0]];
+ if (chdiff != 0) {
+ *orderp = chdiff;
+ goto done;
+ }
+ chdiff = (int)maptolower[label1[1]] -
+ (int)maptolower[label2[1]];
+ if (chdiff != 0) {
+ *orderp = chdiff;
+ goto done;
+ }
+ chdiff = (int)maptolower[label1[2]] -
+ (int)maptolower[label2[2]];
+ if (chdiff != 0) {
+ *orderp = chdiff;
+ goto done;
+ }
+ chdiff = (int)maptolower[label1[3]] -
+ (int)maptolower[label2[3]];
+ if (chdiff != 0) {
+ *orderp = chdiff;
+ goto done;
+ }
+ count -= 4;
+ label1 += 4;
+ label2 += 4;
+ }
+ while (ISC_LIKELY(count-- > 0)) {
+ chdiff = (int)maptolower[*label1++] -
+ (int)maptolower[*label2++];
+ if (chdiff != 0) {
+ *orderp = chdiff;
+ goto done;
+ }
+ }
+ if (cdiff != 0) {
+ *orderp = cdiff;
+ goto done;
+ }
+ nlabels++;
+ }
+
+ *orderp = ldiff;
+ if (ldiff < 0) {
+ namereln = dns_namereln_contains;
+ } else if (ldiff > 0) {
+ namereln = dns_namereln_subdomain;
+ } else {
+ namereln = dns_namereln_equal;
+ }
+ *nlabelsp = nlabels;
+ return (namereln);
+
+done:
+ *nlabelsp = nlabels;
+ if (nlabels > 0) {
+ namereln = dns_namereln_commonancestor;
+ }
+
+ return (namereln);
+}
+
+int
+dns_name_compare(const dns_name_t *name1, const dns_name_t *name2) {
+ int order;
+ unsigned int nlabels;
+
+ /*
+ * Determine the relative ordering under the DNSSEC order relation of
+ * 'name1' and 'name2'.
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ */
+
+ (void)dns_name_fullcompare(name1, name2, &order, &nlabels);
+
+ return (order);
+}
+
+bool
+dns_name_equal(const dns_name_t *name1, const dns_name_t *name2) {
+ unsigned int l, count;
+ unsigned char c;
+ unsigned char *label1, *label2;
+
+ /*
+ * Are 'name1' and 'name2' equal?
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ */
+
+ REQUIRE(VALID_NAME(name1));
+ REQUIRE(VALID_NAME(name2));
+ /*
+ * Either name1 is absolute and name2 is absolute, or neither is.
+ */
+ REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) ==
+ (name2->attributes & DNS_NAMEATTR_ABSOLUTE));
+
+ if (ISC_UNLIKELY(name1 == name2)) {
+ return (true);
+ }
+
+ if (name1->length != name2->length) {
+ return (false);
+ }
+
+ l = name1->labels;
+
+ if (l != name2->labels) {
+ return (false);
+ }
+
+ label1 = name1->ndata;
+ label2 = name2->ndata;
+ while (ISC_LIKELY(l-- > 0)) {
+ count = *label1++;
+ if (count != *label2++) {
+ return (false);
+ }
+
+ INSIST(count <= 63); /* no bitstring support */
+
+ /* Loop unrolled for performance */
+ while (ISC_LIKELY(count > 3)) {
+ c = maptolower[label1[0]];
+ if (c != maptolower[label2[0]]) {
+ return (false);
+ }
+ c = maptolower[label1[1]];
+ if (c != maptolower[label2[1]]) {
+ return (false);
+ }
+ c = maptolower[label1[2]];
+ if (c != maptolower[label2[2]]) {
+ return (false);
+ }
+ c = maptolower[label1[3]];
+ if (c != maptolower[label2[3]]) {
+ return (false);
+ }
+ count -= 4;
+ label1 += 4;
+ label2 += 4;
+ }
+ while (ISC_LIKELY(count-- > 0)) {
+ c = maptolower[*label1++];
+ if (c != maptolower[*label2++]) {
+ return (false);
+ }
+ }
+ }
+
+ return (true);
+}
+
+bool
+dns_name_caseequal(const dns_name_t *name1, const dns_name_t *name2) {
+ /*
+ * Are 'name1' and 'name2' equal?
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ */
+
+ REQUIRE(VALID_NAME(name1));
+ REQUIRE(VALID_NAME(name2));
+ /*
+ * Either name1 is absolute and name2 is absolute, or neither is.
+ */
+ REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) ==
+ (name2->attributes & DNS_NAMEATTR_ABSOLUTE));
+
+ if (name1->length != name2->length) {
+ return (false);
+ }
+
+ if (memcmp(name1->ndata, name2->ndata, name1->length) != 0) {
+ return (false);
+ }
+
+ return (true);
+}
+
+int
+dns_name_rdatacompare(const dns_name_t *name1, const dns_name_t *name2) {
+ unsigned int l1, l2, l, count1, count2, count;
+ unsigned char c1, c2;
+ unsigned char *label1, *label2;
+
+ /*
+ * Compare two absolute names as rdata.
+ */
+
+ REQUIRE(VALID_NAME(name1));
+ REQUIRE(name1->labels > 0);
+ REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) != 0);
+ REQUIRE(VALID_NAME(name2));
+ REQUIRE(name2->labels > 0);
+ REQUIRE((name2->attributes & DNS_NAMEATTR_ABSOLUTE) != 0);
+
+ l1 = name1->labels;
+ l2 = name2->labels;
+
+ l = (l1 < l2) ? l1 : l2;
+
+ label1 = name1->ndata;
+ label2 = name2->ndata;
+ while (l > 0) {
+ l--;
+ count1 = *label1++;
+ count2 = *label2++;
+
+ /* no bitstring support */
+ INSIST(count1 <= 63 && count2 <= 63);
+
+ if (count1 != count2) {
+ return ((count1 < count2) ? -1 : 1);
+ }
+ count = count1;
+ while (count > 0) {
+ count--;
+ c1 = maptolower[*label1++];
+ c2 = maptolower[*label2++];
+ if (c1 < c2) {
+ return (-1);
+ } else if (c1 > c2) {
+ return (1);
+ }
+ }
+ }
+
+ /*
+ * If one name had more labels than the other, their common
+ * prefix must have been different because the shorter name
+ * ended with the root label and the longer one can't have
+ * a root label in the middle of it. Therefore, if we get
+ * to this point, the lengths must be equal.
+ */
+ INSIST(l1 == l2);
+
+ return (0);
+}
+
+bool
+dns_name_issubdomain(const dns_name_t *name1, const dns_name_t *name2) {
+ int order;
+ unsigned int nlabels;
+ dns_namereln_t namereln;
+
+ /*
+ * Is 'name1' a subdomain of 'name2'?
+ *
+ * Note: It makes no sense for one of the names to be relative and the
+ * other absolute. If both names are relative, then to be meaningfully
+ * compared the caller must ensure that they are both relative to the
+ * same domain.
+ */
+
+ namereln = dns_name_fullcompare(name1, name2, &order, &nlabels);
+ if (namereln == dns_namereln_subdomain ||
+ namereln == dns_namereln_equal)
+ {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+dns_name_matcheswildcard(const dns_name_t *name, const dns_name_t *wname) {
+ int order;
+ unsigned int nlabels, labels;
+ dns_name_t tname;
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+ REQUIRE(VALID_NAME(wname));
+ labels = wname->labels;
+ REQUIRE(labels > 0);
+ REQUIRE(dns_name_iswildcard(wname));
+
+ DNS_NAME_INIT(&tname, NULL);
+ dns_name_getlabelsequence(wname, 1, labels - 1, &tname);
+ if (dns_name_fullcompare(name, &tname, &order, &nlabels) ==
+ dns_namereln_subdomain)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+unsigned int
+dns_name_countlabels(const dns_name_t *name) {
+ /*
+ * How many labels does 'name' have?
+ */
+
+ REQUIRE(VALID_NAME(name));
+
+ ENSURE(name->labels <= 128);
+
+ return (name->labels);
+}
+
+void
+dns_name_getlabel(const dns_name_t *name, unsigned int n, dns_label_t *label) {
+ unsigned char *offsets;
+ dns_offsets_t odata;
+
+ /*
+ * Make 'label' refer to the 'n'th least significant label of 'name'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(name->labels > 0);
+ REQUIRE(n < name->labels);
+ REQUIRE(label != NULL);
+
+ SETUP_OFFSETS(name, offsets, odata);
+
+ label->base = &name->ndata[offsets[n]];
+ if (n == name->labels - 1) {
+ label->length = name->length - offsets[n];
+ } else {
+ label->length = offsets[n + 1] - offsets[n];
+ }
+}
+
+void
+dns_name_getlabelsequence(const dns_name_t *source, unsigned int first,
+ unsigned int n, dns_name_t *target) {
+ unsigned char *p, l;
+ unsigned int firstoffset, endoffset;
+ unsigned int i;
+
+ /*
+ * Make 'target' refer to the 'n' labels including and following
+ * 'first' in 'source'.
+ */
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(VALID_NAME(target));
+ REQUIRE(first <= source->labels);
+ REQUIRE(n <= source->labels - first); /* note first+n could overflow */
+ REQUIRE(BINDABLE(target));
+
+ p = source->ndata;
+ if (ISC_UNLIKELY(first == source->labels)) {
+ firstoffset = source->length;
+ } else {
+ for (i = 0; i < first; i++) {
+ l = *p;
+ p += l + 1;
+ }
+ firstoffset = (unsigned int)(p - source->ndata);
+ }
+
+ if (ISC_LIKELY(first + n == source->labels)) {
+ endoffset = source->length;
+ } else {
+ for (i = 0; i < n; i++) {
+ l = *p;
+ p += l + 1;
+ }
+ endoffset = (unsigned int)(p - source->ndata);
+ }
+
+ target->ndata = &source->ndata[firstoffset];
+ target->length = endoffset - firstoffset;
+
+ if (first + n == source->labels && n > 0 &&
+ (source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0)
+ {
+ target->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ } else {
+ target->attributes &= ~DNS_NAMEATTR_ABSOLUTE;
+ }
+
+ target->labels = n;
+
+ /*
+ * If source and target are the same, and we're making target
+ * a prefix of source, the offsets table is correct already
+ * so we don't need to call set_offsets().
+ */
+ if (target->offsets != NULL && (target != source || first != 0)) {
+ set_offsets(target, target->offsets, NULL);
+ }
+}
+
+void
+dns_name_clone(const dns_name_t *source, dns_name_t *target) {
+ /*
+ * Make 'target' refer to the same name as 'source'.
+ */
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(VALID_NAME(target));
+ REQUIRE(BINDABLE(target));
+
+ target->ndata = source->ndata;
+ target->length = source->length;
+ target->labels = source->labels;
+ target->attributes = source->attributes &
+ (unsigned int)~(DNS_NAMEATTR_READONLY |
+ DNS_NAMEATTR_DYNAMIC |
+ DNS_NAMEATTR_DYNOFFSETS);
+ if (target->offsets != NULL && source->labels > 0) {
+ if (source->offsets != NULL) {
+ memmove(target->offsets, source->offsets,
+ source->labels);
+ } else {
+ set_offsets(target, target->offsets, NULL);
+ }
+ }
+}
+
+void
+dns_name_fromregion(dns_name_t *name, const isc_region_t *r) {
+ unsigned char *offsets;
+ dns_offsets_t odata;
+ unsigned int len;
+ isc_region_t r2;
+
+ /*
+ * Make 'name' refer to region 'r'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(r != NULL);
+ REQUIRE(BINDABLE(name));
+
+ INIT_OFFSETS(name, offsets, odata);
+
+ if (name->buffer != NULL) {
+ isc_buffer_clear(name->buffer);
+ isc_buffer_availableregion(name->buffer, &r2);
+ len = (r->length < r2.length) ? r->length : r2.length;
+ if (len > DNS_NAME_MAXWIRE) {
+ len = DNS_NAME_MAXWIRE;
+ }
+ if (len != 0) {
+ memmove(r2.base, r->base, len);
+ }
+ name->ndata = r2.base;
+ name->length = len;
+ } else {
+ name->ndata = r->base;
+ name->length = (r->length <= DNS_NAME_MAXWIRE)
+ ? r->length
+ : DNS_NAME_MAXWIRE;
+ }
+
+ if (r->length > 0) {
+ set_offsets(name, offsets, name);
+ } else {
+ name->labels = 0;
+ name->attributes &= ~DNS_NAMEATTR_ABSOLUTE;
+ }
+
+ if (name->buffer != NULL) {
+ isc_buffer_add(name->buffer, name->length);
+ }
+}
+
+void
+dns_name_toregion(const dns_name_t *name, isc_region_t *r) {
+ /*
+ * Make 'r' refer to 'name'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(r != NULL);
+
+ DNS_NAME_TOREGION(name, r);
+}
+
+isc_result_t
+dns_name_fromtext(dns_name_t *name, isc_buffer_t *source,
+ const dns_name_t *origin, unsigned int options,
+ isc_buffer_t *target) {
+ unsigned char *ndata, *label = NULL;
+ char *tdata;
+ char c;
+ ft_state state;
+ unsigned int value = 0, count = 0;
+ unsigned int n1 = 0, n2 = 0;
+ unsigned int tlen, nrem, nused, digits = 0, labels, tused;
+ bool done;
+ unsigned char *offsets;
+ dns_offsets_t odata;
+ bool downcase;
+
+ /*
+ * Convert the textual representation of a DNS name at source
+ * into uncompressed wire form stored in target.
+ *
+ * Notes:
+ * Relative domain names will have 'origin' appended to them
+ * unless 'origin' is NULL, in which case relative domain names
+ * will remain relative.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(ISC_BUFFER_VALID(source));
+ REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) ||
+ (target == NULL && ISC_BUFFER_VALID(name->buffer)));
+
+ downcase = ((options & DNS_NAME_DOWNCASE) != 0);
+
+ if (target == NULL && name->buffer != NULL) {
+ target = name->buffer;
+ isc_buffer_clear(target);
+ }
+
+ REQUIRE(BINDABLE(name));
+
+ INIT_OFFSETS(name, offsets, odata);
+ offsets[0] = 0;
+
+ /*
+ * Make 'name' empty in case of failure.
+ */
+ MAKE_EMPTY(name);
+
+ /*
+ * Set up the state machine.
+ */
+ tdata = (char *)source->base + source->current;
+ tlen = isc_buffer_remaininglength(source);
+ tused = 0;
+ ndata = isc_buffer_used(target);
+ nrem = isc_buffer_availablelength(target);
+ if (nrem > 255) {
+ nrem = 255;
+ }
+ nused = 0;
+ labels = 0;
+ done = false;
+ state = ft_init;
+
+ while (nrem > 0 && tlen > 0 && !done) {
+ c = *tdata++;
+ tlen--;
+ tused++;
+
+ switch (state) {
+ case ft_init:
+ /*
+ * Is this the root name?
+ */
+ if (c == '.') {
+ if (tlen != 0) {
+ return (DNS_R_EMPTYLABEL);
+ }
+ labels++;
+ *ndata++ = 0;
+ nrem--;
+ nused++;
+ done = true;
+ break;
+ }
+ if (c == '@' && tlen == 0) {
+ state = ft_at;
+ break;
+ }
+
+ FALLTHROUGH;
+ case ft_start:
+ label = ndata;
+ ndata++;
+ nrem--;
+ nused++;
+ count = 0;
+ if (c == '\\') {
+ state = ft_initialescape;
+ break;
+ }
+ state = ft_ordinary;
+ if (nrem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ FALLTHROUGH;
+ case ft_ordinary:
+ if (c == '.') {
+ if (count == 0) {
+ return (DNS_R_EMPTYLABEL);
+ }
+ *label = count;
+ labels++;
+ INSIST(labels <= 127);
+ offsets[labels] = nused;
+ if (tlen == 0) {
+ labels++;
+ *ndata++ = 0;
+ nrem--;
+ nused++;
+ done = true;
+ }
+ state = ft_start;
+ } else if (c == '\\') {
+ state = ft_escape;
+ } else {
+ if (count >= 63) {
+ return (DNS_R_LABELTOOLONG);
+ }
+ count++;
+ CONVERTTOASCII(c);
+ if (downcase) {
+ c = maptolower[c & 0xff];
+ }
+ *ndata++ = c;
+ nrem--;
+ nused++;
+ }
+ break;
+ case ft_initialescape:
+ if (c == '[') {
+ /*
+ * This looks like a bitstring label, which
+ * was deprecated. Intentionally drop it.
+ */
+ return (DNS_R_BADLABELTYPE);
+ }
+ state = ft_escape;
+ POST(state);
+ FALLTHROUGH;
+ case ft_escape:
+ if (!isdigit((unsigned char)c)) {
+ if (count >= 63) {
+ return (DNS_R_LABELTOOLONG);
+ }
+ count++;
+ CONVERTTOASCII(c);
+ if (downcase) {
+ c = maptolower[c & 0xff];
+ }
+ *ndata++ = c;
+ nrem--;
+ nused++;
+ state = ft_ordinary;
+ break;
+ }
+ digits = 0;
+ value = 0;
+ state = ft_escdecimal;
+ FALLTHROUGH;
+ case ft_escdecimal:
+ if (!isdigit((unsigned char)c)) {
+ return (DNS_R_BADESCAPE);
+ }
+ value *= 10;
+ value += digitvalue[c & 0xff];
+ digits++;
+ if (digits == 3) {
+ if (value > 255) {
+ return (DNS_R_BADESCAPE);
+ }
+ if (count >= 63) {
+ return (DNS_R_LABELTOOLONG);
+ }
+ count++;
+ if (downcase) {
+ value = maptolower[value];
+ }
+ *ndata++ = value;
+ nrem--;
+ nused++;
+ state = ft_ordinary;
+ }
+ break;
+ default:
+ FATAL_ERROR(__FILE__, __LINE__, "Unexpected state %d",
+ state);
+ /* Does not return. */
+ }
+ }
+
+ if (!done) {
+ if (nrem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ INSIST(tlen == 0);
+ if (state != ft_ordinary && state != ft_at) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (state == ft_ordinary) {
+ INSIST(count != 0);
+ INSIST(label != NULL);
+ *label = count;
+ labels++;
+ INSIST(labels <= 127);
+ offsets[labels] = nused;
+ }
+ if (origin != NULL) {
+ if (nrem < origin->length) {
+ return (ISC_R_NOSPACE);
+ }
+ label = origin->ndata;
+ n1 = origin->length;
+ nrem -= n1;
+ POST(nrem);
+ while (n1 > 0) {
+ n2 = *label++;
+ INSIST(n2 <= 63); /* no bitstring support */
+ *ndata++ = n2;
+ n1 -= n2 + 1;
+ nused += n2 + 1;
+ while (n2 > 0) {
+ c = *label++;
+ if (downcase) {
+ c = maptolower[c & 0xff];
+ }
+ *ndata++ = c;
+ n2--;
+ }
+ labels++;
+ if (n1 > 0) {
+ INSIST(labels <= 127);
+ offsets[labels] = nused;
+ }
+ }
+ if ((origin->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ name->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ }
+ }
+ } else {
+ name->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ }
+
+ name->ndata = (unsigned char *)target->base + target->used;
+ name->labels = labels;
+ name->length = nused;
+
+ isc_buffer_forward(source, tused);
+ isc_buffer_add(target, name->length);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_totext(const dns_name_t *name, bool omit_final_dot,
+ isc_buffer_t *target) {
+ unsigned int options = DNS_NAME_MASTERFILE;
+
+ if (omit_final_dot) {
+ options |= DNS_NAME_OMITFINALDOT;
+ }
+ return (dns_name_totext2(name, options, target));
+}
+
+isc_result_t
+dns_name_toprincipal(const dns_name_t *name, isc_buffer_t *target) {
+ return (dns_name_totext2(name, DNS_NAME_OMITFINALDOT, target));
+}
+
+isc_result_t
+dns_name_totext2(const dns_name_t *name, unsigned int options,
+ isc_buffer_t *target) {
+ unsigned char *ndata;
+ char *tdata;
+ unsigned int nlen, tlen;
+ unsigned char c;
+ unsigned int trem, count;
+ unsigned int labels;
+ bool saw_root = false;
+ unsigned int oused;
+ bool omit_final_dot = ((options & DNS_NAME_OMITFINALDOT) != 0);
+
+ /*
+ * This function assumes the name is in proper uncompressed
+ * wire format.
+ */
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(ISC_BUFFER_VALID(target));
+
+ oused = target->used;
+
+ ndata = name->ndata;
+ nlen = name->length;
+ labels = name->labels;
+ tdata = isc_buffer_used(target);
+ tlen = isc_buffer_availablelength(target);
+
+ trem = tlen;
+
+ if (labels == 0 && nlen == 0) {
+ /*
+ * Special handling for an empty name.
+ */
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * The names of these booleans are misleading in this case.
+ * This empty name is not necessarily from the root node of
+ * the DNS root zone, nor is a final dot going to be included.
+ * They need to be set this way, though, to keep the "@"
+ * from being trounced.
+ */
+ saw_root = true;
+ omit_final_dot = false;
+ *tdata++ = '@';
+ trem--;
+
+ /*
+ * Skip the while() loop.
+ */
+ nlen = 0;
+ } else if (nlen == 1 && labels == 1 && *ndata == '\0') {
+ /*
+ * Special handling for the root label.
+ */
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+
+ saw_root = true;
+ omit_final_dot = false;
+ *tdata++ = '.';
+ trem--;
+
+ /*
+ * Skip the while() loop.
+ */
+ nlen = 0;
+ }
+
+ while (labels > 0 && nlen > 0 && trem > 0) {
+ labels--;
+ count = *ndata++;
+ nlen--;
+ if (count == 0) {
+ saw_root = true;
+ break;
+ }
+ if (count < 64) {
+ INSIST(nlen >= count);
+ while (count > 0) {
+ c = *ndata;
+ switch (c) {
+ /* Special modifiers in zone files. */
+ case 0x40: /* '@' */
+ case 0x24: /* '$' */
+ if ((options & DNS_NAME_MASTERFILE) ==
+ 0)
+ {
+ goto no_escape;
+ }
+ FALLTHROUGH;
+ case 0x22: /* '"' */
+ case 0x28: /* '(' */
+ case 0x29: /* ')' */
+ case 0x2E: /* '.' */
+ case 0x3B: /* ';' */
+ case 0x5C: /* '\\' */
+ if (trem < 2) {
+ return (ISC_R_NOSPACE);
+ }
+ *tdata++ = '\\';
+ CONVERTFROMASCII(c);
+ *tdata++ = c;
+ ndata++;
+ trem -= 2;
+ nlen--;
+ break;
+ no_escape:
+ default:
+ if (c > 0x20 && c < 0x7f) {
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ CONVERTFROMASCII(c);
+ *tdata++ = c;
+ ndata++;
+ trem--;
+ nlen--;
+ } else {
+ if (trem < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ *tdata++ = 0x5c;
+ *tdata++ = 0x30 +
+ ((c / 100) % 10);
+ *tdata++ = 0x30 +
+ ((c / 10) % 10);
+ *tdata++ = 0x30 + (c % 10);
+ trem -= 4;
+ ndata++;
+ nlen--;
+ }
+ }
+ count--;
+ }
+ } else {
+ FATAL_ERROR(__FILE__, __LINE__,
+ "Unexpected label type %02x", count);
+ UNREACHABLE();
+ }
+
+ /*
+ * The following assumes names are absolute. If not, we
+ * fix things up later. Note that this means that in some
+ * cases one more byte of text buffer is required than is
+ * needed in the final output.
+ */
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ *tdata++ = '.';
+ trem--;
+ }
+
+ if (nlen != 0 && trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (!saw_root || omit_final_dot) {
+ trem++;
+ tdata--;
+ }
+ if (trem > 0) {
+ *tdata = 0;
+ }
+ isc_buffer_add(target, tlen - trem);
+
+ if (totext_filter_proc != NULL) {
+ return ((totext_filter_proc)(target, oused));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_tofilenametext(const dns_name_t *name, bool omit_final_dot,
+ isc_buffer_t *target) {
+ unsigned char *ndata;
+ char *tdata;
+ unsigned int nlen, tlen;
+ unsigned char c;
+ unsigned int trem, count;
+ unsigned int labels;
+
+ /*
+ * This function assumes the name is in proper uncompressed
+ * wire format.
+ */
+ REQUIRE(VALID_NAME(name));
+ REQUIRE((name->attributes & DNS_NAMEATTR_ABSOLUTE) != 0);
+ REQUIRE(ISC_BUFFER_VALID(target));
+
+ ndata = name->ndata;
+ nlen = name->length;
+ labels = name->labels;
+ tdata = isc_buffer_used(target);
+ tlen = isc_buffer_availablelength(target);
+
+ trem = tlen;
+
+ if (nlen == 1 && labels == 1 && *ndata == '\0') {
+ /*
+ * Special handling for the root label.
+ */
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+
+ omit_final_dot = false;
+ *tdata++ = '.';
+ trem--;
+
+ /*
+ * Skip the while() loop.
+ */
+ nlen = 0;
+ }
+
+ while (labels > 0 && nlen > 0 && trem > 0) {
+ labels--;
+ count = *ndata++;
+ nlen--;
+ if (count == 0) {
+ break;
+ }
+ if (count < 64) {
+ INSIST(nlen >= count);
+ while (count > 0) {
+ c = *ndata;
+ if ((c >= 0x30 && c <= 0x39) || /* digit */
+ (c >= 0x41 && c <= 0x5A) || /* uppercase */
+ (c >= 0x61 && c <= 0x7A) || /* lowercase */
+ c == 0x2D || /* hyphen */
+ c == 0x5F) /* underscore */
+ {
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ /* downcase */
+ if (c >= 0x41 && c <= 0x5A) {
+ c += 0x20;
+ }
+ CONVERTFROMASCII(c);
+ *tdata++ = c;
+ ndata++;
+ trem--;
+ nlen--;
+ } else {
+ if (trem < 4) {
+ return (ISC_R_NOSPACE);
+ }
+ snprintf(tdata, trem, "%%%02X", c);
+ tdata += 3;
+ trem -= 3;
+ ndata++;
+ nlen--;
+ }
+ count--;
+ }
+ } else {
+ FATAL_ERROR(__FILE__, __LINE__,
+ "Unexpected label type %02x", count);
+ UNREACHABLE();
+ }
+
+ /*
+ * The following assumes names are absolute. If not, we
+ * fix things up later. Note that this means that in some
+ * cases one more byte of text buffer is required than is
+ * needed in the final output.
+ */
+ if (trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+ *tdata++ = '.';
+ trem--;
+ }
+
+ if (nlen != 0 && trem == 0) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (omit_final_dot) {
+ trem++;
+ }
+
+ isc_buffer_add(target, tlen - trem);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_downcase(const dns_name_t *source, dns_name_t *name,
+ isc_buffer_t *target) {
+ unsigned char *sndata, *ndata;
+ unsigned int nlen, count, labels;
+ isc_buffer_t buffer;
+
+ /*
+ * Downcase 'source'.
+ */
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(VALID_NAME(name));
+ if (source == name) {
+ REQUIRE((name->attributes & DNS_NAMEATTR_READONLY) == 0);
+ isc_buffer_init(&buffer, source->ndata, source->length);
+ target = &buffer;
+ ndata = source->ndata;
+ } else {
+ REQUIRE(BINDABLE(name));
+ REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) ||
+ (target == NULL && ISC_BUFFER_VALID(name->buffer)));
+ if (target == NULL) {
+ target = name->buffer;
+ isc_buffer_clear(name->buffer);
+ }
+ ndata = (unsigned char *)target->base + target->used;
+ name->ndata = ndata;
+ }
+
+ sndata = source->ndata;
+ nlen = source->length;
+ labels = source->labels;
+
+ if (nlen > (target->length - target->used)) {
+ MAKE_EMPTY(name);
+ return (ISC_R_NOSPACE);
+ }
+
+ while (labels > 0 && nlen > 0) {
+ labels--;
+ count = *sndata++;
+ *ndata++ = count;
+ nlen--;
+ if (count < 64) {
+ INSIST(nlen >= count);
+ while (count > 0) {
+ *ndata++ = maptolower[(*sndata++)];
+ nlen--;
+ count--;
+ }
+ } else {
+ FATAL_ERROR(__FILE__, __LINE__,
+ "Unexpected label type %02x", count);
+ /* Does not return. */
+ }
+ }
+
+ if (source != name) {
+ name->labels = source->labels;
+ name->length = source->length;
+ if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ name->attributes = DNS_NAMEATTR_ABSOLUTE;
+ } else {
+ name->attributes = 0;
+ }
+ if (name->labels > 0 && name->offsets != NULL) {
+ set_offsets(name, name->offsets, NULL);
+ }
+ }
+
+ isc_buffer_add(target, name->length);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+set_offsets(const dns_name_t *name, unsigned char *offsets,
+ dns_name_t *set_name) {
+ unsigned int offset, count, length, nlabels;
+ unsigned char *ndata;
+ bool absolute;
+
+ ndata = name->ndata;
+ length = name->length;
+ offset = 0;
+ nlabels = 0;
+ absolute = false;
+ while (ISC_LIKELY(offset != length)) {
+ INSIST(nlabels < 128);
+ offsets[nlabels++] = offset;
+ count = *ndata;
+ INSIST(count <= 63);
+ offset += count + 1;
+ ndata += count + 1;
+ INSIST(offset <= length);
+ if (ISC_UNLIKELY(count == 0)) {
+ absolute = true;
+ break;
+ }
+ }
+ if (set_name != NULL) {
+ INSIST(set_name == name);
+
+ set_name->labels = nlabels;
+ set_name->length = offset;
+ if (absolute) {
+ set_name->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ } else {
+ set_name->attributes &= ~DNS_NAMEATTR_ABSOLUTE;
+ }
+ }
+ INSIST(nlabels == name->labels);
+ INSIST(offset == name->length);
+}
+
+isc_result_t
+dns_name_fromwire(dns_name_t *name, isc_buffer_t *source,
+ dns_decompress_t *dctx, unsigned int options,
+ isc_buffer_t *target) {
+ unsigned char *cdata, *ndata;
+ unsigned int cused; /* Bytes of compressed name data used */
+ unsigned int nused, labels, n, nmax;
+ unsigned int current, new_current, biggest_pointer;
+ bool done;
+ fw_state state = fw_start;
+ unsigned int c;
+ unsigned char *offsets;
+ dns_offsets_t odata;
+ bool downcase;
+ bool seen_pointer;
+
+ /*
+ * Copy the possibly-compressed name at source into target,
+ * decompressing it. Loop prevention is performed by checking
+ * the new pointer against biggest_pointer.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) ||
+ (target == NULL && ISC_BUFFER_VALID(name->buffer)));
+
+ downcase = ((options & DNS_NAME_DOWNCASE) != 0);
+
+ if (target == NULL && name->buffer != NULL) {
+ target = name->buffer;
+ isc_buffer_clear(target);
+ }
+
+ REQUIRE(dctx != NULL);
+ REQUIRE(BINDABLE(name));
+
+ INIT_OFFSETS(name, offsets, odata);
+
+ /*
+ * Make 'name' empty in case of failure.
+ */
+ MAKE_EMPTY(name);
+
+ /*
+ * Initialize things to make the compiler happy; they're not required.
+ */
+ n = 0;
+ new_current = 0;
+
+ /*
+ * Set up.
+ */
+ labels = 0;
+ done = false;
+
+ ndata = isc_buffer_used(target);
+ nused = 0;
+ seen_pointer = false;
+
+ /*
+ * Find the maximum number of uncompressed target name
+ * bytes we are willing to generate. This is the smaller
+ * of the available target buffer length and the
+ * maximum legal domain name length (255).
+ */
+ nmax = isc_buffer_availablelength(target);
+ if (nmax > DNS_NAME_MAXWIRE) {
+ nmax = DNS_NAME_MAXWIRE;
+ }
+
+ cdata = isc_buffer_current(source);
+ cused = 0;
+
+ current = source->current;
+ biggest_pointer = current;
+
+ /*
+ * Note: The following code is not optimized for speed, but
+ * rather for correctness. Speed will be addressed in the future.
+ */
+
+ while (current < source->active && !done) {
+ c = *cdata++;
+ current++;
+ if (!seen_pointer) {
+ cused++;
+ }
+
+ switch (state) {
+ case fw_start:
+ if (c < 64) {
+ offsets[labels] = nused;
+ labels++;
+ if (nused + c + 1 > nmax) {
+ goto full;
+ }
+ nused += c + 1;
+ *ndata++ = c;
+ if (c == 0) {
+ done = true;
+ }
+ n = c;
+ state = fw_ordinary;
+ } else if (c >= 128 && c < 192) {
+ /*
+ * 14 bit local compression pointer.
+ * Local compression is no longer an
+ * IETF draft.
+ */
+ return (DNS_R_BADLABELTYPE);
+ } else if (c >= 192) {
+ /*
+ * Ordinary 14-bit pointer.
+ */
+ if ((dctx->allowed & DNS_COMPRESS_GLOBAL14) ==
+ 0)
+ {
+ return (DNS_R_DISALLOWED);
+ }
+ new_current = c & 0x3F;
+ state = fw_newcurrent;
+ } else {
+ return (DNS_R_BADLABELTYPE);
+ }
+ break;
+ case fw_ordinary:
+ if (downcase) {
+ c = maptolower[c];
+ }
+ *ndata++ = c;
+ n--;
+ if (n == 0) {
+ state = fw_start;
+ }
+ break;
+ case fw_newcurrent:
+ new_current *= 256;
+ new_current += c;
+ if (new_current >= biggest_pointer) {
+ return (DNS_R_BADPOINTER);
+ }
+ biggest_pointer = new_current;
+ current = new_current;
+ cdata = (unsigned char *)source->base + current;
+ seen_pointer = true;
+ state = fw_start;
+ break;
+ default:
+ FATAL_ERROR(__FILE__, __LINE__, "Unknown state %d",
+ state);
+ /* Does not return. */
+ }
+ }
+
+ if (!done) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ name->ndata = (unsigned char *)target->base + target->used;
+ name->labels = labels;
+ name->length = nused;
+ name->attributes |= DNS_NAMEATTR_ABSOLUTE;
+
+ isc_buffer_forward(source, cused);
+ isc_buffer_add(target, name->length);
+
+ return (ISC_R_SUCCESS);
+
+full:
+ if (nmax == DNS_NAME_MAXWIRE) {
+ /*
+ * The name did not fit even though we had a buffer
+ * big enough to fit a maximum-length name.
+ */
+ return (DNS_R_NAMETOOLONG);
+ } else {
+ /*
+ * The name might fit if only the caller could give us a
+ * big enough buffer.
+ */
+ return (ISC_R_NOSPACE);
+ }
+}
+
+isc_result_t
+dns_name_towire(const dns_name_t *name, dns_compress_t *cctx,
+ isc_buffer_t *target) {
+ return (dns_name_towire2(name, cctx, target, NULL));
+}
+
+isc_result_t
+dns_name_towire2(const dns_name_t *name, dns_compress_t *cctx,
+ isc_buffer_t *target, uint16_t *comp_offsetp) {
+ unsigned int methods;
+ uint16_t offset;
+ dns_name_t gp; /* Global compression prefix */
+ bool gf; /* Global compression target found */
+ uint16_t go; /* Global compression offset */
+ dns_offsets_t clo;
+ dns_name_t clname;
+
+ /*
+ * Convert 'name' into wire format, compressing it as specified by the
+ * compression context 'cctx', and storing the result in 'target'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(cctx != NULL);
+ REQUIRE(ISC_BUFFER_VALID(target));
+
+ /*
+ * If this exact name was already rendered before, and the
+ * offset of the previously rendered name is passed to us, write
+ * a compression pointer directly.
+ */
+ methods = dns_compress_getmethods(cctx);
+ if (comp_offsetp != NULL && *comp_offsetp < 0x4000 &&
+ (name->attributes & DNS_NAMEATTR_NOCOMPRESS) == 0 &&
+ (methods & DNS_COMPRESS_GLOBAL14) != 0)
+ {
+ if (ISC_UNLIKELY(target->length - target->used < 2)) {
+ return (ISC_R_NOSPACE);
+ }
+ offset = *comp_offsetp;
+ offset |= 0xc000;
+ isc_buffer_putuint16(target, offset);
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * If 'name' doesn't have an offsets table, make a clone which
+ * has one.
+ */
+ if (name->offsets == NULL) {
+ DNS_NAME_INIT(&clname, clo);
+ dns_name_clone(name, &clname);
+ name = &clname;
+ }
+ DNS_NAME_INIT(&gp, NULL);
+
+ offset = target->used; /*XXX*/
+
+ if ((name->attributes & DNS_NAMEATTR_NOCOMPRESS) == 0 &&
+ (methods & DNS_COMPRESS_GLOBAL14) != 0)
+ {
+ gf = dns_compress_findglobal(cctx, name, &gp, &go);
+ } else {
+ gf = false;
+ }
+
+ /*
+ * If the offset is too high for 14 bit global compression, we're
+ * out of luck.
+ */
+ if (gf && ISC_UNLIKELY(go >= 0x4000)) {
+ gf = false;
+ }
+
+ /*
+ * Will the compression pointer reduce the message size?
+ */
+ if (gf && (gp.length + 2) >= name->length) {
+ gf = false;
+ }
+
+ if (gf) {
+ if (ISC_UNLIKELY(target->length - target->used < gp.length)) {
+ return (ISC_R_NOSPACE);
+ }
+ if (gp.length != 0) {
+ unsigned char *base = target->base;
+ (void)memmove(base + target->used, gp.ndata,
+ (size_t)gp.length);
+ }
+ isc_buffer_add(target, gp.length);
+ if (ISC_UNLIKELY(target->length - target->used < 2)) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint16(target, go | 0xc000);
+ if (gp.length != 0) {
+ dns_compress_add(cctx, name, &gp, offset);
+ if (comp_offsetp != NULL) {
+ *comp_offsetp = offset;
+ }
+ } else if (comp_offsetp != NULL) {
+ *comp_offsetp = go;
+ }
+ } else {
+ if (ISC_UNLIKELY(target->length - target->used < name->length))
+ {
+ return (ISC_R_NOSPACE);
+ }
+ if (name->length != 0) {
+ unsigned char *base = target->base;
+ (void)memmove(base + target->used, name->ndata,
+ (size_t)name->length);
+ }
+ isc_buffer_add(target, name->length);
+ dns_compress_add(cctx, name, name, offset);
+ if (comp_offsetp != NULL) {
+ *comp_offsetp = offset;
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_concatenate(const dns_name_t *prefix, const dns_name_t *suffix,
+ dns_name_t *name, isc_buffer_t *target) {
+ unsigned char *ndata, *offsets;
+ unsigned int nrem, labels, prefix_length, length;
+ bool copy_prefix = true;
+ bool copy_suffix = true;
+ bool absolute = false;
+ dns_name_t tmp_name;
+ dns_offsets_t odata;
+
+ /*
+ * Concatenate 'prefix' and 'suffix'.
+ */
+
+ REQUIRE(prefix == NULL || VALID_NAME(prefix));
+ REQUIRE(suffix == NULL || VALID_NAME(suffix));
+ REQUIRE(name == NULL || VALID_NAME(name));
+ REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) ||
+ (target == NULL && name != NULL &&
+ ISC_BUFFER_VALID(name->buffer)));
+ if (prefix == NULL || prefix->labels == 0) {
+ copy_prefix = false;
+ }
+ if (suffix == NULL || suffix->labels == 0) {
+ copy_suffix = false;
+ }
+ if (copy_prefix && (prefix->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ absolute = true;
+ REQUIRE(!copy_suffix);
+ }
+ if (name == NULL) {
+ DNS_NAME_INIT(&tmp_name, odata);
+ name = &tmp_name;
+ }
+ if (target == NULL) {
+ INSIST(name->buffer != NULL);
+ target = name->buffer;
+ isc_buffer_clear(name->buffer);
+ }
+
+ REQUIRE(BINDABLE(name));
+
+ /*
+ * Set up.
+ */
+ nrem = target->length - target->used;
+ ndata = (unsigned char *)target->base + target->used;
+ if (nrem > DNS_NAME_MAXWIRE) {
+ nrem = DNS_NAME_MAXWIRE;
+ }
+ length = 0;
+ prefix_length = 0;
+ labels = 0;
+ if (copy_prefix) {
+ prefix_length = prefix->length;
+ length += prefix_length;
+ labels += prefix->labels;
+ }
+ if (copy_suffix) {
+ length += suffix->length;
+ labels += suffix->labels;
+ }
+ if (length > DNS_NAME_MAXWIRE) {
+ MAKE_EMPTY(name);
+ return (DNS_R_NAMETOOLONG);
+ }
+ if (length > nrem) {
+ MAKE_EMPTY(name);
+ return (ISC_R_NOSPACE);
+ }
+
+ if (copy_suffix) {
+ if ((suffix->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ absolute = true;
+ }
+ memmove(ndata + prefix_length, suffix->ndata, suffix->length);
+ }
+
+ /*
+ * If 'prefix' and 'name' are the same object, and the object has
+ * a dedicated buffer, and we're using it, then we don't have to
+ * copy anything.
+ */
+ if (copy_prefix && (prefix != name || prefix->buffer != target)) {
+ memmove(ndata, prefix->ndata, prefix_length);
+ }
+
+ name->ndata = ndata;
+ name->labels = labels;
+ name->length = length;
+ if (absolute) {
+ name->attributes = DNS_NAMEATTR_ABSOLUTE;
+ } else {
+ name->attributes = 0;
+ }
+
+ if (name->labels > 0 && name->offsets != NULL) {
+ INIT_OFFSETS(name, offsets, odata);
+ set_offsets(name, offsets, NULL);
+ }
+
+ isc_buffer_add(target, name->length);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_name_split(const dns_name_t *name, unsigned int suffixlabels,
+ dns_name_t *prefix, dns_name_t *suffix)
+
+{
+ unsigned int splitlabel;
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(suffixlabels > 0);
+ REQUIRE(suffixlabels <= name->labels);
+ REQUIRE(prefix != NULL || suffix != NULL);
+ REQUIRE(prefix == NULL || (VALID_NAME(prefix) && BINDABLE(prefix)));
+ REQUIRE(suffix == NULL || (VALID_NAME(suffix) && BINDABLE(suffix)));
+
+ splitlabel = name->labels - suffixlabels;
+
+ if (prefix != NULL) {
+ dns_name_getlabelsequence(name, 0, splitlabel, prefix);
+ }
+
+ if (suffix != NULL) {
+ dns_name_getlabelsequence(name, splitlabel, suffixlabels,
+ suffix);
+ }
+
+ return;
+}
+
+void
+dns_name_dup(const dns_name_t *source, isc_mem_t *mctx, dns_name_t *target) {
+ /*
+ * Make 'target' a dynamically allocated copy of 'source'.
+ */
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(source->length > 0);
+ REQUIRE(VALID_NAME(target));
+ REQUIRE(BINDABLE(target));
+
+ /*
+ * Make 'target' empty in case of failure.
+ */
+ MAKE_EMPTY(target);
+
+ target->ndata = isc_mem_get(mctx, source->length);
+
+ memmove(target->ndata, source->ndata, source->length);
+
+ target->length = source->length;
+ target->labels = source->labels;
+ target->attributes = DNS_NAMEATTR_DYNAMIC;
+ if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ target->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ }
+ if (target->offsets != NULL) {
+ if (source->offsets != NULL) {
+ memmove(target->offsets, source->offsets,
+ source->labels);
+ } else {
+ set_offsets(target, target->offsets, NULL);
+ }
+ }
+}
+
+isc_result_t
+dns_name_dupwithoffsets(const dns_name_t *source, isc_mem_t *mctx,
+ dns_name_t *target) {
+ /*
+ * Make 'target' a read-only dynamically allocated copy of 'source'.
+ * 'target' will also have a dynamically allocated offsets table.
+ */
+
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(source->length > 0);
+ REQUIRE(VALID_NAME(target));
+ REQUIRE(BINDABLE(target));
+ REQUIRE(target->offsets == NULL);
+
+ /*
+ * Make 'target' empty in case of failure.
+ */
+ MAKE_EMPTY(target);
+
+ target->ndata = isc_mem_get(mctx, source->length + source->labels);
+
+ memmove(target->ndata, source->ndata, source->length);
+
+ target->length = source->length;
+ target->labels = source->labels;
+ target->attributes = DNS_NAMEATTR_DYNAMIC | DNS_NAMEATTR_DYNOFFSETS |
+ DNS_NAMEATTR_READONLY;
+ if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ target->attributes |= DNS_NAMEATTR_ABSOLUTE;
+ }
+ target->offsets = target->ndata + source->length;
+ if (source->offsets != NULL) {
+ memmove(target->offsets, source->offsets, source->labels);
+ } else {
+ set_offsets(target, target->offsets, NULL);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_name_free(dns_name_t *name, isc_mem_t *mctx) {
+ size_t size;
+
+ /*
+ * Free 'name'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE((name->attributes & DNS_NAMEATTR_DYNAMIC) != 0);
+
+ size = name->length;
+ if ((name->attributes & DNS_NAMEATTR_DYNOFFSETS) != 0) {
+ size += name->labels;
+ }
+ isc_mem_put(mctx, name->ndata, size);
+ dns_name_invalidate(name);
+}
+
+isc_result_t
+dns_name_digest(const dns_name_t *name, dns_digestfunc_t digest, void *arg) {
+ dns_name_t downname;
+ unsigned char data[256];
+ isc_buffer_t buffer;
+ isc_result_t result;
+ isc_region_t r;
+
+ /*
+ * Send 'name' in DNSSEC canonical form to 'digest'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+ REQUIRE(digest != NULL);
+
+ DNS_NAME_INIT(&downname, NULL);
+
+ isc_buffer_init(&buffer, data, sizeof(data));
+
+ result = dns_name_downcase(name, &downname, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_usedregion(&buffer, &r);
+
+ return ((digest)(arg, &r));
+}
+
+bool
+dns_name_dynamic(const dns_name_t *name) {
+ REQUIRE(VALID_NAME(name));
+
+ /*
+ * Returns whether there is dynamic memory associated with this name.
+ */
+
+ return ((name->attributes & DNS_NAMEATTR_DYNAMIC) != 0 ? true : false);
+}
+
+isc_result_t
+dns_name_print(const dns_name_t *name, FILE *stream) {
+ isc_result_t result;
+ isc_buffer_t b;
+ isc_region_t r;
+ char t[1024];
+
+ /*
+ * Print 'name' on 'stream'.
+ */
+
+ REQUIRE(VALID_NAME(name));
+
+ isc_buffer_init(&b, t, sizeof(t));
+ result = dns_name_totext(name, false, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_usedregion(&b, &r);
+ fprintf(stream, "%.*s", (int)r.length, (char *)r.base);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_settotextfilter(dns_name_totextfilter_t *proc) {
+ /*
+ * If we already have been here set / clear as appropriate.
+ */
+ if (totext_filter_proc != NULL && proc != NULL) {
+ if (totext_filter_proc == proc) {
+ return (ISC_R_SUCCESS);
+ }
+ }
+ if (proc == NULL && totext_filter_proc != NULL) {
+ totext_filter_proc = NULL;
+ return (ISC_R_SUCCESS);
+ }
+
+ totext_filter_proc = proc;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_name_format(const dns_name_t *name, char *cp, unsigned int size) {
+ isc_result_t result;
+ isc_buffer_t buf;
+
+ REQUIRE(size > 0);
+
+ /*
+ * Leave room for null termination after buffer.
+ */
+ isc_buffer_init(&buf, cp, size - 1);
+ result = dns_name_totext(name, true, &buf);
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_putuint8(&buf, (uint8_t)'\0');
+ } else {
+ snprintf(cp, size, "<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);
+}
+
+static isc_result_t
+name_copy(const dns_name_t *source, dns_name_t *dest, isc_buffer_t *target) {
+ unsigned char *ndata = NULL;
+
+ /*
+ * Make dest a copy of source.
+ */
+
+ REQUIRE(BINDABLE(dest));
+
+ /*
+ * Set up.
+ */
+ if (target->length - target->used < source->length) {
+ return (ISC_R_NOSPACE);
+ }
+
+ ndata = (unsigned char *)target->base + target->used;
+ dest->ndata = target->base;
+
+ if (source->length != 0) {
+ memmove(ndata, source->ndata, source->length);
+ }
+
+ dest->ndata = ndata;
+ dest->labels = source->labels;
+ dest->length = source->length;
+ if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) {
+ dest->attributes = DNS_NAMEATTR_ABSOLUTE;
+ } else {
+ dest->attributes = 0;
+ }
+
+ if (dest->labels > 0 && dest->offsets != NULL) {
+ if (source->offsets != NULL && source->labels != 0) {
+ memmove(dest->offsets, source->offsets, source->labels);
+ } else {
+ set_offsets(dest, dest->offsets, NULL);
+ }
+ }
+
+ isc_buffer_add(target, dest->length);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_name_copy(const dns_name_t *source, dns_name_t *dest,
+ isc_buffer_t *target) {
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(VALID_NAME(dest));
+ REQUIRE(target != NULL);
+
+ return (name_copy(source, dest, target));
+}
+
+void
+dns_name_copynf(const dns_name_t *source, dns_name_t *dest) {
+ REQUIRE(VALID_NAME(source));
+ REQUIRE(VALID_NAME(dest));
+ REQUIRE(dest->buffer != NULL);
+
+ isc_buffer_clear(dest->buffer);
+ RUNTIME_CHECK(name_copy(source, dest, dest->buffer) == ISC_R_SUCCESS);
+}
+
+/*
+ * Service Discovery Prefixes RFC 6763.
+ */
+static unsigned char b_dns_sd_udp_data[] = "\001b\007_dns-sd\004_udp";
+static unsigned char b_dns_sd_udp_offsets[] = { 0, 2, 10 };
+static unsigned char db_dns_sd_udp_data[] = "\002db\007_dns-sd\004_udp";
+static unsigned char db_dns_sd_udp_offsets[] = { 0, 3, 11 };
+static unsigned char r_dns_sd_udp_data[] = "\001r\007_dns-sd\004_udp";
+static unsigned char r_dns_sd_udp_offsets[] = { 0, 2, 10 };
+static unsigned char dr_dns_sd_udp_data[] = "\002dr\007_dns-sd\004_udp";
+static unsigned char dr_dns_sd_udp_offsets[] = { 0, 3, 11 };
+static unsigned char lb_dns_sd_udp_data[] = "\002lb\007_dns-sd\004_udp";
+static unsigned char lb_dns_sd_udp_offsets[] = { 0, 3, 11 };
+
+static dns_name_t const dns_sd[] = {
+ DNS_NAME_INITNONABSOLUTE(b_dns_sd_udp_data, b_dns_sd_udp_offsets),
+ DNS_NAME_INITNONABSOLUTE(db_dns_sd_udp_data, db_dns_sd_udp_offsets),
+ DNS_NAME_INITNONABSOLUTE(r_dns_sd_udp_data, r_dns_sd_udp_offsets),
+ DNS_NAME_INITNONABSOLUTE(dr_dns_sd_udp_data, dr_dns_sd_udp_offsets),
+ DNS_NAME_INITNONABSOLUTE(lb_dns_sd_udp_data, lb_dns_sd_udp_offsets)
+};
+
+bool
+dns_name_isdnssd(const dns_name_t *name) {
+ size_t i;
+ dns_name_t prefix;
+
+ if (dns_name_countlabels(name) > 3U) {
+ dns_name_init(&prefix, NULL);
+ dns_name_getlabelsequence(name, 0, 3, &prefix);
+ for (i = 0; i < (sizeof(dns_sd) / sizeof(dns_sd[0])); i++) {
+ if (dns_name_equal(&prefix, &dns_sd[i])) {
+ return (true);
+ }
+ }
+ }
+
+ return (false);
+}
+
+static unsigned char inaddr10_offsets[] = { 0, 3, 11, 16 };
+static unsigned char inaddr172_offsets[] = { 0, 3, 7, 15, 20 };
+static unsigned char inaddr192_offsets[] = { 0, 4, 8, 16, 21 };
+
+static unsigned char inaddr10[] = "\00210\007IN-ADDR\004ARPA";
+
+static unsigned char inaddr16172[] = "\00216\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr17172[] = "\00217\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr18172[] = "\00218\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr19172[] = "\00219\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr20172[] = "\00220\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr21172[] = "\00221\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr22172[] = "\00222\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr23172[] = "\00223\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr24172[] = "\00224\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr25172[] = "\00225\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr26172[] = "\00226\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr27172[] = "\00227\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr28172[] = "\00228\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr29172[] = "\00229\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr30172[] = "\00230\003172\007IN-ADDR\004ARPA";
+static unsigned char inaddr31172[] = "\00231\003172\007IN-ADDR\004ARPA";
+
+static unsigned char inaddr168192[] = "\003168\003192\007IN-ADDR\004ARPA";
+
+static dns_name_t const rfc1918names[] = {
+ DNS_NAME_INITABSOLUTE(inaddr10, inaddr10_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr16172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr17172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr18172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr19172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr20172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr21172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr22172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr23172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr24172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr25172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr26172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr27172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr28172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr29172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr30172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr31172, inaddr172_offsets),
+ DNS_NAME_INITABSOLUTE(inaddr168192, inaddr192_offsets)
+};
+
+bool
+dns_name_isrfc1918(const dns_name_t *name) {
+ size_t i;
+
+ for (i = 0; i < (sizeof(rfc1918names) / sizeof(*rfc1918names)); i++) {
+ if (dns_name_issubdomain(name, &rfc1918names[i])) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+static unsigned char ulaoffsets[] = { 0, 2, 4, 8, 13 };
+static unsigned char ip6fc[] = "\001c\001f\003ip6\004ARPA";
+static unsigned char ip6fd[] = "\001d\001f\003ip6\004ARPA";
+
+static dns_name_t const ulanames[] = { DNS_NAME_INITABSOLUTE(ip6fc, ulaoffsets),
+ DNS_NAME_INITABSOLUTE(ip6fd,
+ ulaoffsets) };
+
+bool
+dns_name_isula(const dns_name_t *name) {
+ size_t i;
+
+ for (i = 0; i < (sizeof(ulanames) / sizeof(*ulanames)); i++) {
+ if (dns_name_issubdomain(name, &ulanames[i])) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/*
+ * Use a simple table as we don't want all the locale stuff
+ * associated with ishexdigit().
+ */
+const char ishex[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+bool
+dns_name_istat(const dns_name_t *name) {
+ unsigned char len;
+ const unsigned char *ndata;
+
+ REQUIRE(VALID_NAME(name));
+
+ if (name->labels < 1) {
+ return (false);
+ }
+
+ ndata = name->ndata;
+ len = ndata[0];
+ INSIST(len <= name->length);
+ ndata++;
+
+ /*
+ * Is there at least one trust anchor reported and is the
+ * label length consistent with a trust-anchor-telemetry label.
+ */
+ if ((len < 8) || (len - 3) % 5 != 0) {
+ return (false);
+ }
+
+ if (ndata[0] != '_' || maptolower[ndata[1]] != 't' ||
+ maptolower[ndata[2]] != 'a')
+ {
+ return (false);
+ }
+ ndata += 3;
+ len -= 3;
+
+ while (len > 0) {
+ INSIST(len >= 5);
+ if (ndata[0] != '-' || !ishex[ndata[1]] || !ishex[ndata[2]] ||
+ !ishex[ndata[3]] || !ishex[ndata[4]])
+ {
+ return (false);
+ }
+ ndata += 5;
+ len -= 5;
+ }
+ return (true);
+}
diff --git a/lib/dns/ncache.c b/lib/dns/ncache.c
new file mode 100644
index 0000000..9247ac1
--- /dev/null
+++ b/lib/dns/ncache.c
@@ -0,0 +1,777 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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..3089c9e
--- /dev/null
+++ b/lib/dns/nsec.c
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/log.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 <dns/result.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, bool *answer) {
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_dnskey_t dnskey;
+ isc_result_t result;
+
+ REQUIRE(answer != NULL);
+
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, 0,
+ 0, &rdataset, NULL);
+ dns_db_detachnode(db, &node);
+
+ if (result == ISC_R_NOTFOUND) {
+ *answer = false;
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &dnskey, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (dnskey.algorithm == DST_ALG_RSAMD5 ||
+ dnskey.algorithm == DST_ALG_RSASHA1)
+ {
+ break;
+ }
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result == ISC_R_SUCCESS) {
+ *answer = true;
+ }
+ if (result == ISC_R_NOMORE) {
+ *answer = false;
+ result = ISC_R_SUCCESS;
+ }
+ return (result);
+}
+
+/*%
+ * Return ISC_R_SUCCESS if we can determine that the name doesn't exist
+ * or we can determine whether there is data or not at the name.
+ * If the name does not exist return the wildcard name.
+ *
+ * Return ISC_R_IGNORE when the NSEC is not the appropriate one.
+ */
+isc_result_t
+dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
+ const dns_name_t *nsecname, dns_rdataset_t *nsecset,
+ bool *exists, bool *data, dns_name_t *wild,
+ dns_nseclog_t logit, void *arg) {
+ int order;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ dns_namereln_t relation;
+ unsigned int olabels, nlabels, labels;
+ dns_rdata_nsec_t nsec;
+ bool atparent;
+ bool ns;
+ bool soa;
+
+ REQUIRE(exists != NULL);
+ REQUIRE(data != NULL);
+ REQUIRE(nsecset != NULL && nsecset->type == dns_rdatatype_nsec);
+
+ result = dns_rdataset_first(nsecset);
+ if (result != ISC_R_SUCCESS) {
+ (*logit)(arg, ISC_LOG_DEBUG(3), "failure processing NSEC set");
+ return (result);
+ }
+ dns_rdataset_current(nsecset, &rdata);
+
+ (*logit)(arg, ISC_LOG_DEBUG(3), "looking for relevant NSEC");
+ relation = dns_name_fullcompare(name, nsecname, &order, &olabels);
+
+ if (order < 0) {
+ /*
+ * The name is not within the NSEC range.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC does not cover name, before NSEC");
+ return (ISC_R_IGNORE);
+ }
+
+ if (order == 0) {
+ /*
+ * The names are the same. If we are validating "."
+ * then atparent should not be set as there is no parent.
+ */
+ atparent = (olabels != 1) && dns_rdatatype_atparent(type);
+ ns = dns_nsec_typepresent(&rdata, dns_rdatatype_ns);
+ soa = dns_nsec_typepresent(&rdata, dns_rdatatype_soa);
+ if (ns && !soa) {
+ if (!atparent) {
+ /*
+ * This NSEC record is from somewhere higher in
+ * the DNS, and at the parent of a delegation.
+ * It can not be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring parent nsec");
+ return (ISC_R_IGNORE);
+ }
+ } else if (atparent && ns && soa) {
+ /*
+ * This NSEC record is from the child.
+ * It can not be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring child nsec");
+ return (ISC_R_IGNORE);
+ }
+ if (type == dns_rdatatype_cname || type == dns_rdatatype_nxt ||
+ type == dns_rdatatype_nsec || type == dns_rdatatype_key ||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_cname))
+ {
+ *exists = true;
+ *data = dns_nsec_typepresent(&rdata, type);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "nsec proves name exists (owner) data=%d",
+ *data);
+ return (ISC_R_SUCCESS);
+ }
+ (*logit)(arg, ISC_LOG_DEBUG(3), "NSEC proves CNAME exists");
+ return (ISC_R_IGNORE);
+ }
+
+ if (relation == dns_namereln_subdomain &&
+ dns_nsec_typepresent(&rdata, dns_rdatatype_ns) &&
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_soa))
+ {
+ /*
+ * This NSEC record is from somewhere higher in
+ * the DNS, and at the parent of a delegation or
+ * at a DNAME.
+ * It can not be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring parent nsec");
+ return (ISC_R_IGNORE);
+ }
+
+ if (relation == dns_namereln_subdomain &&
+ dns_nsec_typepresent(&rdata, dns_rdatatype_dname))
+ {
+ (*logit)(arg, ISC_LOG_DEBUG(3), "nsec proves covered by dname");
+ *exists = false;
+ return (DNS_R_DNAME);
+ }
+
+ result = dns_rdata_tostruct(&rdata, &nsec, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ relation = dns_name_fullcompare(&nsec.next, name, &order, &nlabels);
+ if (order == 0) {
+ dns_rdata_freestruct(&nsec);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring nsec matches next name");
+ return (ISC_R_IGNORE);
+ }
+
+ if (order < 0 && !dns_name_issubdomain(nsecname, &nsec.next)) {
+ /*
+ * The name is not within the NSEC range.
+ */
+ dns_rdata_freestruct(&nsec);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring nsec because name is past end of range");
+ return (ISC_R_IGNORE);
+ }
+
+ if (order > 0 && relation == dns_namereln_subdomain) {
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "nsec proves name exist (empty)");
+ dns_rdata_freestruct(&nsec);
+ *exists = true;
+ *data = false;
+ return (ISC_R_SUCCESS);
+ }
+ if (wild != NULL) {
+ dns_name_t common;
+ dns_name_init(&common, NULL);
+ if (olabels > nlabels) {
+ labels = dns_name_countlabels(nsecname);
+ dns_name_getlabelsequence(nsecname, labels - olabels,
+ olabels, &common);
+ } else {
+ labels = dns_name_countlabels(&nsec.next);
+ dns_name_getlabelsequence(&nsec.next, labels - nlabels,
+ nlabels, &common);
+ }
+ result = dns_name_concatenate(dns_wildcardname, &common, wild,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdata_freestruct(&nsec);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "failure generating wildcard name");
+ return (result);
+ }
+ }
+ dns_rdata_freestruct(&nsec);
+ (*logit)(arg, ISC_LOG_DEBUG(3), "nsec range ok");
+ *exists = false;
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c
new file mode 100644
index 0000000..7f68580
--- /dev/null
+++ b/lib/dns/nsec3.c
@@ -0,0 +1,2195 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <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/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/result.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_copynf(zone, zonename);
+ }
+
+ if (!dns_name_equal(zone, zonename)) {
+ return (ISC_R_IGNORE);
+ }
+
+ /*
+ * Are we only looking for the most enclosing zone?
+ */
+ if (exists == NULL || data == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Only set unknown once we are sure that this NSEC3 is from
+ * the deepest covering zone.
+ */
+ if (!dns_nsec3_supportedhash(nsec3.hash)) {
+ if (unknown != NULL) {
+ *unknown = true;
+ }
+ return (ISC_R_IGNORE);
+ }
+
+ /*
+ * Recover the hash from the first label.
+ */
+ dns_name_getlabel(nsec3name, 0, &hashlabel);
+ isc_region_consume(&hashlabel, 1);
+ isc_buffer_init(&buffer, owner, sizeof(owner));
+ result = isc_base32hex_decoderegion(&hashlabel, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * The hash lengths should match. If not ignore the record.
+ */
+ if (isc_buffer_usedlength(&buffer) != nsec3.next_length) {
+ return (ISC_R_IGNORE);
+ }
+
+ /*
+ * Work out what this NSEC3 covers.
+ * Inside (<0) or outside (>=0).
+ */
+ scope = memcmp(owner, nsec3.next, nsec3.next_length);
+
+ /*
+ * Prepare to compute all the hashes.
+ */
+ qname = dns_fixedname_initname(&qfixed);
+ dns_name_downcase(name, qname, NULL);
+ qlabels = dns_name_countlabels(qname);
+ first = true;
+
+ while (qlabels >= zlabels) {
+ /*
+ * If there are too many iterations reject the NSEC3 record.
+ */
+ if (nsec3.iterations > DNS_NSEC3_MAXITERATIONS) {
+ return (DNS_R_NSEC3ITERRANGE);
+ }
+
+ length = isc_iterated_hash(hash, nsec3.hash, nsec3.iterations,
+ nsec3.salt, nsec3.salt_length,
+ qname->ndata, qname->length);
+ /*
+ * The computed hash length should match.
+ */
+ if (length != nsec3.next_length) {
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring NSEC bad length %u vs %u", length,
+ nsec3.next_length);
+ return (ISC_R_IGNORE);
+ }
+
+ order = memcmp(hash, owner, length);
+ if (first && order == 0) {
+ /*
+ * The hashes are the same.
+ */
+ atparent = dns_rdatatype_atparent(type);
+ ns = dns_nsec3_typepresent(&rdata, dns_rdatatype_ns);
+ soa = dns_nsec3_typepresent(&rdata, dns_rdatatype_soa);
+ if (ns && !soa) {
+ if (!atparent) {
+ /*
+ * This NSEC3 record is from somewhere
+ * higher in the DNS, and at the
+ * parent of a delegation. It can not
+ * be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring parent NSEC3");
+ return (ISC_R_IGNORE);
+ }
+ } else if (atparent && ns && soa) {
+ /*
+ * This NSEC3 record is from the child.
+ * It can not be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring child NSEC3");
+ return (ISC_R_IGNORE);
+ }
+ if (type == dns_rdatatype_cname ||
+ type == dns_rdatatype_nxt ||
+ type == dns_rdatatype_nsec ||
+ type == dns_rdatatype_key ||
+ !dns_nsec3_typepresent(&rdata, dns_rdatatype_cname))
+ {
+ *exists = true;
+ *data = dns_nsec3_typepresent(&rdata, type);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC3 proves name exists (owner) "
+ "data=%d",
+ *data);
+ return (ISC_R_SUCCESS);
+ }
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC3 proves CNAME exists");
+ return (ISC_R_IGNORE);
+ }
+
+ if (order == 0 &&
+ dns_nsec3_typepresent(&rdata, dns_rdatatype_ns) &&
+ !dns_nsec3_typepresent(&rdata, dns_rdatatype_soa))
+ {
+ /*
+ * This NSEC3 record is from somewhere higher in
+ * the DNS, and at the parent of a delegation.
+ * It can not be legitimately used here.
+ */
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "ignoring parent NSEC3");
+ return (ISC_R_IGNORE);
+ }
+
+ /*
+ * Potential closest encloser.
+ */
+ if (order == 0) {
+ if (closest != NULL &&
+ (dns_name_countlabels(closest) == 0 ||
+ dns_name_issubdomain(qname, closest)) &&
+ !dns_nsec3_typepresent(&rdata, dns_rdatatype_ds) &&
+ !dns_nsec3_typepresent(&rdata,
+ dns_rdatatype_dname) &&
+ (dns_nsec3_typepresent(&rdata, dns_rdatatype_soa) ||
+ !dns_nsec3_typepresent(&rdata, dns_rdatatype_ns)))
+ {
+ dns_name_format(qname, namebuf,
+ sizeof(namebuf));
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC3 indicates potential closest "
+ "encloser: '%s'",
+ namebuf);
+ dns_name_copynf(qname, closest);
+ *setclosest = true;
+ }
+ dns_name_format(qname, namebuf, sizeof(namebuf));
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC3 at super-domain %s", namebuf);
+ return (answer);
+ }
+
+ /*
+ * Find if the name does not exist.
+ *
+ * We continue as we need to find the name closest to the
+ * closest encloser that doesn't exist.
+ *
+ * We also need to continue to ensure that we are not
+ * proving the non-existence of a record in a sub-zone.
+ * If that would be the case we will return ISC_R_IGNORE
+ * above.
+ */
+ if ((scope < 0 && order > 0 &&
+ memcmp(hash, nsec3.next, length) < 0) ||
+ (scope >= 0 &&
+ (order > 0 || memcmp(hash, nsec3.next, length) < 0)))
+ {
+ dns_name_format(qname, namebuf, sizeof(namebuf));
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ "NSEC3 proves "
+ "name does not exist: '%s'",
+ namebuf);
+ if (nearest != NULL &&
+ (dns_name_countlabels(nearest) == 0 ||
+ dns_name_issubdomain(nearest, qname)))
+ {
+ dns_name_copynf(qname, nearest);
+ *setnearest = true;
+ }
+
+ *exists = false;
+ *data = false;
+ if (optout != NULL) {
+ *optout = ((nsec3.flags &
+ DNS_NSEC3FLAG_OPTOUT) != 0);
+ (*logit)(arg, ISC_LOG_DEBUG(3),
+ (*optout ? "NSEC3 indicates optout"
+ : "NSEC3 indicates secure "
+ "range"));
+ }
+ answer = ISC_R_SUCCESS;
+ }
+
+ qlabels--;
+ if (qlabels > 0) {
+ dns_name_split(qname, qlabels, NULL, qname);
+ }
+ first = false;
+ }
+ return (answer);
+}
diff --git a/lib/dns/nta.c b/lib/dns/nta.c
new file mode 100644
index 0000000..c6725c7
--- /dev/null
+++ b/lib/dns/nta.c
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/print.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/result.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_copynf(name, nta->name);
+
+ nta->magic = NTA_MAGIC;
+
+ *target = nta;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_ntatable_add(dns_ntatable_t *ntatable, const dns_name_t *name, bool force,
+ isc_stdtime_t now, uint32_t lifetime) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_nta_t *nta = NULL;
+ dns_rbtnode_t *node;
+ dns_view_t *view;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+
+ view = ntatable->view;
+
+ RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+
+ if (ntatable->shuttingdown) {
+ goto unlock;
+ }
+
+ result = nta_create(ntatable, name, &nta);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+
+ nta->expiry = now + lifetime;
+ nta->forced = force;
+
+ node = NULL;
+ result = dns_rbt_addnode(ntatable->table, name, &node);
+ if (result == ISC_R_SUCCESS) {
+ if (!force) {
+ (void)settimer(ntatable, nta, lifetime);
+ }
+ node->data = nta;
+ nta = NULL;
+ } else if (result == ISC_R_EXISTS) {
+ dns_nta_t *n = node->data;
+ if (n == NULL) {
+ if (!force) {
+ (void)settimer(ntatable, nta, lifetime);
+ }
+ node->data = nta;
+ nta = NULL;
+ } else {
+ n->expiry = nta->expiry;
+ nta_detach(view->mctx, &nta);
+ }
+ result = ISC_R_SUCCESS;
+ }
+
+unlock:
+ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+
+ if (nta != NULL) {
+ nta_detach(view->mctx, &nta);
+ }
+
+ return (result);
+}
+
+/*
+ * Caller must hold a write lock on rwlock.
+ */
+static isc_result_t
+deletenode(dns_ntatable_t *ntatable, const dns_name_t *name) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+ REQUIRE(name != NULL);
+
+ result = dns_rbt_findnode(ntatable->table, name, NULL, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ if (node->data != NULL) {
+ result = dns_rbt_deletenode(ntatable->table, node,
+ false);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ } else if (result == DNS_R_PARTIALMATCH) {
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_ntatable_delete(dns_ntatable_t *ntatable, const dns_name_t *name) {
+ isc_result_t result;
+
+ RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+ result = deletenode(ntatable, name);
+ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+bool
+dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now,
+ const dns_name_t *name, const dns_name_t *anchor) {
+ isc_result_t result;
+ dns_fixedname_t fn;
+ dns_rbtnode_t *node;
+ dns_name_t *foundname;
+ dns_nta_t *nta = NULL;
+ bool answer = false;
+ isc_rwlocktype_t locktype = isc_rwlocktype_read;
+
+ REQUIRE(ntatable == NULL || VALID_NTATABLE(ntatable));
+ REQUIRE(dns_name_isabsolute(name));
+
+ if (ntatable == NULL) {
+ return (false);
+ }
+
+ foundname = dns_fixedname_initname(&fn);
+
+relock:
+ RWLOCK(&ntatable->rwlock, locktype);
+again:
+ node = NULL;
+ result = dns_rbt_findnode(ntatable->table, name, foundname, &node, NULL,
+ DNS_RBTFIND_NOOPTIONS, NULL, NULL);
+ if (result == DNS_R_PARTIALMATCH) {
+ if (dns_name_issubdomain(foundname, anchor)) {
+ result = ISC_R_SUCCESS;
+ }
+ }
+ if (result == ISC_R_SUCCESS) {
+ nta = (dns_nta_t *)node->data;
+ answer = (nta->expiry > now);
+ }
+
+ /* Deal with expired NTA */
+ if (result == ISC_R_SUCCESS && !answer) {
+ char nb[DNS_NAME_FORMATSIZE];
+
+ if (locktype == isc_rwlocktype_read) {
+ RWUNLOCK(&ntatable->rwlock, locktype);
+ locktype = isc_rwlocktype_write;
+ goto relock;
+ }
+
+ dns_name_format(foundname, nb, sizeof(nb));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_NTA, ISC_LOG_INFO,
+ "deleting expired NTA at %s", nb);
+
+ if (nta->timer != NULL) {
+ (void)isc_timer_reset(nta->timer,
+ isc_timertype_inactive, NULL,
+ NULL, true);
+ isc_timer_destroy(&nta->timer);
+ }
+
+ result = deletenode(ntatable, foundname);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
+ DNS_LOGMODULE_NTA, ISC_LOG_INFO,
+ "deleting NTA failed: %s",
+ isc_result_totext(result));
+ }
+ goto again;
+ }
+ RWUNLOCK(&ntatable->rwlock, locktype);
+
+ return (answer);
+}
+
+static isc_result_t
+putstr(isc_buffer_t **b, const char *str) {
+ isc_result_t result;
+
+ result = isc_buffer_reserve(b, strlen(str));
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_buffer_putstr(*b, str);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_ntatable_totext(dns_ntatable_t *ntatable, const char *view,
+ isc_buffer_t **buf) {
+ isc_result_t result;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ bool first = true;
+ isc_stdtime_t now;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+
+ isc_stdtime_get(&now);
+
+ RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_SUCCESS;
+ }
+ goto cleanup;
+ }
+ for (;;) {
+ dns_rbtnodechain_current(&chain, NULL, NULL, &node);
+ if (node->data != NULL) {
+ dns_nta_t *n = (dns_nta_t *)node->data;
+ char nbuf[DNS_NAME_FORMATSIZE];
+ char tbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
+ char obuf[DNS_NAME_FORMATSIZE +
+ ISC_FORMATHTTPTIMESTAMP_SIZE +
+ sizeof("expired: \n")];
+ dns_fixedname_t fn;
+ dns_name_t *name;
+ isc_time_t t;
+
+ /*
+ * Skip "validate-except" entries.
+ */
+ if (n->expiry != 0xffffffffU) {
+ name = dns_fixedname_initname(&fn);
+ dns_rbt_fullnamefromnode(node, name);
+ dns_name_format(name, nbuf, sizeof(nbuf));
+ isc_time_set(&t, n->expiry, 0);
+ isc_time_formattimestamp(&t, tbuf,
+ sizeof(tbuf));
+
+ snprintf(obuf, sizeof(obuf), "%s%s%s%s: %s %s",
+ first ? "" : "\n", nbuf,
+ view != NULL ? "/" : "",
+ view != NULL ? view : "",
+ n->expiry <= now ? "expired"
+ : "expiry",
+ tbuf);
+ first = false;
+ result = putstr(buf, obuf);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ }
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ break;
+ }
+ }
+
+cleanup:
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read);
+ return (result);
+}
+
+isc_result_t
+dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp) {
+ isc_result_t result;
+ isc_buffer_t *text = NULL;
+ int len = 4096;
+
+ isc_buffer_allocate(ntatable->view->mctx, &text, len);
+
+ result = dns_ntatable_totext(ntatable, NULL, &text);
+
+ if (isc_buffer_usedlength(text) != 0) {
+ (void)putstr(&text, "\n");
+ } else if (result == ISC_R_SUCCESS) {
+ (void)putstr(&text, "none");
+ } else {
+ (void)putstr(&text, "could not dump NTA table: ");
+ (void)putstr(&text, isc_result_totext(result));
+ }
+
+ fprintf(fp, "%.*s", (int)isc_buffer_usedlength(text),
+ (char *)isc_buffer_base(text));
+ isc_buffer_free(&text);
+ return (result);
+}
+
+isc_result_t
+dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp) {
+ isc_result_t result;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+ isc_stdtime_t now;
+ bool written = false;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+
+ isc_stdtime_get(&now);
+
+ RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ goto cleanup;
+ }
+
+ for (;;) {
+ dns_rbtnodechain_current(&chain, NULL, NULL, &node);
+ if (node->data != NULL) {
+ isc_buffer_t b;
+ char nbuf[DNS_NAME_FORMATSIZE + 1], tbuf[80];
+ dns_fixedname_t fn;
+ dns_name_t *name;
+ dns_nta_t *n = (dns_nta_t *)node->data;
+
+ /*
+ * Skip this node if the expiry is already in the
+ * past, or if this is a "validate-except" entry.
+ */
+ if (n->expiry <= now || n->expiry == 0xffffffffU) {
+ goto skip;
+ }
+
+ name = dns_fixedname_initname(&fn);
+ dns_rbt_fullnamefromnode(node, name);
+
+ isc_buffer_init(&b, nbuf, sizeof(nbuf));
+ result = dns_name_totext(name, false, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto skip;
+ }
+
+ /* Zero terminate. */
+ isc_buffer_putuint8(&b, 0);
+
+ isc_buffer_init(&b, tbuf, sizeof(tbuf));
+ dns_time32_totext(n->expiry, &b);
+
+ /* Zero terminate. */
+ isc_buffer_putuint8(&b, 0);
+
+ fprintf(fp, "%s %s %s\n", nbuf,
+ n->forced ? "forced" : "regular", tbuf);
+ written = true;
+ }
+ skip:
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ break;
+ }
+ }
+
+cleanup:
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read);
+
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ } else {
+ return (written ? ISC_R_SUCCESS : ISC_R_NOTFOUND);
+ }
+}
+
+void
+dns_ntatable_shutdown(dns_ntatable_t *ntatable) {
+ isc_result_t result;
+ dns_rbtnode_t *node;
+ dns_rbtnodechain_t chain;
+
+ REQUIRE(VALID_NTATABLE(ntatable));
+
+ RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+ ntatable->shuttingdown = true;
+
+ dns_rbtnodechain_init(&chain);
+ result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL);
+ while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) {
+ dns_rbtnodechain_current(&chain, NULL, NULL, &node);
+ if (node->data != NULL) {
+ dns_nta_t *nta = (dns_nta_t *)node->data;
+ if (nta->timer != NULL) {
+ (void)isc_timer_reset(nta->timer,
+ isc_timertype_inactive,
+ NULL, NULL, true);
+ }
+ }
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ }
+
+ dns_rbtnodechain_invalidate(&chain);
+ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
+}
diff --git a/lib/dns/openssl_link.c b/lib/dns/openssl_link.c
new file mode 100644
index 0000000..4878f57
--- /dev/null
+++ b/lib/dns/openssl_link.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/mutexblock.h>
+#include <isc/platform.h>
+#include <isc/string.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+
+#include <dst/result.h>
+
+#include "dst_internal.h"
+#include "dst_openssl.h"
+
+#if !defined(OPENSSL_NO_ENGINE)
+#include <openssl/engine.h>
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+
+#if !defined(OPENSSL_NO_ENGINE)
+static ENGINE *e = NULL;
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+
+static void
+enable_fips_mode(void) {
+#ifdef HAVE_FIPS_MODE
+ if (FIPS_mode() != 0) {
+ /*
+ * FIPS mode is already enabled.
+ */
+ return;
+ }
+
+ if (FIPS_mode_set(1) == 0) {
+ dst__openssl_toresult2("FIPS_mode_set", DST_R_OPENSSLFAILURE);
+ exit(1);
+ }
+#endif /* HAVE_FIPS_MODE */
+}
+
+isc_result_t
+dst__openssl_init(const char *engine) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ enable_fips_mode();
+
+#if !defined(OPENSSL_NO_ENGINE)
+ if (engine != NULL && *engine == '\0') {
+ engine = NULL;
+ }
+
+ if (engine != NULL) {
+ e = ENGINE_by_id(engine);
+ if (e == NULL) {
+ result = DST_R_NOENGINE;
+ goto cleanup_rm;
+ }
+ /* This will init the engine. */
+ if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
+ result = DST_R_NOENGINE;
+ goto cleanup_rm;
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+cleanup_rm:
+ if (e != NULL) {
+ ENGINE_free(e);
+ }
+ e = NULL;
+#else
+ UNUSED(engine);
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+ return (result);
+}
+
+void
+dst__openssl_destroy(void) {
+#if !defined(OPENSSL_NO_ENGINE)
+ if (e != NULL) {
+ ENGINE_free(e);
+ }
+ e = NULL;
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+}
+
+static isc_result_t
+toresult(isc_result_t fallback) {
+ isc_result_t result = fallback;
+ unsigned long err = ERR_peek_error();
+#if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED)
+ int lib = ERR_GET_LIB(err);
+#endif /* if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) */
+ int reason = ERR_GET_REASON(err);
+
+ switch (reason) {
+ /*
+ * ERR_* errors are globally unique; others
+ * are unique per sublibrary
+ */
+ case ERR_R_MALLOC_FAILURE:
+ result = ISC_R_NOMEMORY;
+ break;
+ default:
+#if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED)
+ if (lib == ERR_R_ECDSA_LIB &&
+ reason == ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED)
+ {
+ result = ISC_R_NOENTROPY;
+ break;
+ }
+#endif /* if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) */
+ break;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dst__openssl_toresult(isc_result_t fallback) {
+ isc_result_t result;
+
+ result = toresult(fallback);
+
+ ERR_clear_error();
+ return (result);
+}
+
+isc_result_t
+dst__openssl_toresult2(const char *funcname, isc_result_t fallback) {
+ return (dst__openssl_toresult3(DNS_LOGCATEGORY_GENERAL, funcname,
+ fallback));
+}
+
+isc_result_t
+dst__openssl_toresult3(isc_logcategory_t *category, const char *funcname,
+ isc_result_t fallback) {
+ isc_result_t result;
+ unsigned long err;
+ const char *file, *data;
+ int line, flags;
+ char buf[256];
+
+ result = toresult(fallback);
+
+ isc_log_write(dns_lctx, category, DNS_LOGMODULE_CRYPTO, ISC_LOG_WARNING,
+ "%s failed (%s)", funcname, isc_result_totext(result));
+
+ if (result == ISC_R_NOMEMORY) {
+ goto done;
+ }
+
+ for (;;) {
+ err = ERR_get_error_line_data(&file, &line, &data, &flags);
+ if (err == 0U) {
+ goto done;
+ }
+ ERR_error_string_n(err, buf, sizeof(buf));
+ isc_log_write(dns_lctx, category, DNS_LOGMODULE_CRYPTO,
+ ISC_LOG_INFO, "%s:%s:%d:%s", buf, file, line,
+ ((flags & ERR_TXT_STRING) != 0) ? data : "");
+ }
+
+done:
+ ERR_clear_error();
+ return (result);
+}
+
+#if !defined(OPENSSL_NO_ENGINE)
+ENGINE *
+dst__openssl_getengine(const char *engine) {
+ if (engine == NULL) {
+ return (NULL);
+ }
+ if (e == NULL) {
+ return (NULL);
+ }
+ if (strcmp(engine, ENGINE_get_id(e)) == 0) {
+ return (e);
+ }
+ return (NULL);
+}
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+
+/*! \file */
diff --git a/lib/dns/openssldh_link.c b/lib/dns/openssldh_link.c
new file mode 100644
index 0000000..f514931
--- /dev/null
+++ b/lib/dns/openssldh_link.c
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) Network Associates, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <openssl/opensslv.h>
+
+#include <isc/mem.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <pk11/site.h>
+
+#include <dst/result.h>
+
+#include "dst_internal.h"
+#include "dst_openssl.h"
+#include "dst_parse.h"
+
+#define PRIME2 "02"
+
+#define PRIME768 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088" \
+ "A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25" \
+ "F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFF" \
+ "F"
+
+#define PRIME1024 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" \
+ "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF2" \
+ "5F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406" \
+ "B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF"
+
+#define PRIME1536 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
+ "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"
+
+static BIGNUM *bn2 = NULL, *bn768 = NULL, *bn1024 = NULL, *bn1536 = NULL;
+
+#if !HAVE_DH_GET0_KEY
+/*
+ * DH_get0_key, DH_set0_key, DH_get0_pqg and DH_set0_pqg
+ * are from OpenSSL 1.1.0.
+ */
+static void
+DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) {
+ if (pub_key != NULL) {
+ *pub_key = dh->pub_key;
+ }
+ if (priv_key != NULL) {
+ *priv_key = dh->priv_key;
+ }
+}
+
+static int
+DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) {
+ if (pub_key != NULL) {
+ BN_free(dh->pub_key);
+ dh->pub_key = pub_key;
+ }
+
+ if (priv_key != NULL) {
+ BN_free(dh->priv_key);
+ dh->priv_key = priv_key;
+ }
+
+ return (1);
+}
+
+static void
+DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q,
+ const BIGNUM **g) {
+ if (p != NULL) {
+ *p = dh->p;
+ }
+ if (q != NULL) {
+ *q = dh->q;
+ }
+ if (g != NULL) {
+ *g = dh->g;
+ }
+}
+
+static int
+DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) {
+ /* If the fields p and g in d are NULL, the corresponding input
+ * parameters MUST be non-NULL. q may remain NULL.
+ */
+ if ((dh->p == NULL && p == NULL) || (dh->g == NULL && g == NULL)) {
+ return (0);
+ }
+
+ if (p != NULL) {
+ BN_free(dh->p);
+ dh->p = p;
+ }
+ if (q != NULL) {
+ BN_free(dh->q);
+ dh->q = q;
+ }
+ if (g != NULL) {
+ BN_free(dh->g);
+ dh->g = g;
+ }
+
+ if (q != NULL) {
+ dh->length = BN_num_bits(q);
+ }
+
+ return (1);
+}
+
+#define DH_clear_flags(d, f) (d)->flags &= ~(f)
+
+#endif /* !HAVE_DH_GET0_KEY */
+
+static isc_result_t
+openssldh_computesecret(const dst_key_t *pub, const dst_key_t *priv,
+ isc_buffer_t *secret) {
+ DH *dhpub, *dhpriv;
+ const BIGNUM *pub_key = NULL;
+ int ret;
+ isc_region_t r;
+ unsigned int len;
+
+ REQUIRE(pub->keydata.dh != NULL);
+ REQUIRE(priv->keydata.dh != NULL);
+
+ dhpub = pub->keydata.dh;
+ dhpriv = priv->keydata.dh;
+
+ len = DH_size(dhpriv);
+ isc_buffer_availableregion(secret, &r);
+ if (r.length < len) {
+ return (ISC_R_NOSPACE);
+ }
+
+ DH_get0_key(dhpub, &pub_key, NULL);
+ ret = DH_compute_key(r.base, pub_key, dhpriv);
+ if (ret <= 0) {
+ return (dst__openssl_toresult2("DH_compute_key",
+ DST_R_COMPUTESECRETFAILURE));
+ }
+ isc_buffer_add(secret, len);
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+openssldh_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ DH *dh1, *dh2;
+ const BIGNUM *pub_key1 = NULL, *pub_key2 = NULL;
+ const BIGNUM *priv_key1 = NULL, *priv_key2 = NULL;
+ const BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL;
+
+ dh1 = key1->keydata.dh;
+ dh2 = key2->keydata.dh;
+
+ if (dh1 == NULL && dh2 == NULL) {
+ return (true);
+ } else if (dh1 == NULL || dh2 == NULL) {
+ return (false);
+ }
+
+ DH_get0_key(dh1, &pub_key1, &priv_key1);
+ DH_get0_key(dh2, &pub_key2, &priv_key2);
+ DH_get0_pqg(dh1, &p1, NULL, &g1);
+ DH_get0_pqg(dh2, &p2, NULL, &g2);
+
+ if (BN_cmp(p1, p2) != 0 || BN_cmp(g1, g2) != 0 ||
+ BN_cmp(pub_key1, pub_key2) != 0)
+ {
+ return (false);
+ }
+
+ if (priv_key1 != NULL || priv_key2 != NULL) {
+ if (priv_key1 == NULL || priv_key2 == NULL) {
+ return (false);
+ }
+ if (BN_cmp(priv_key1, priv_key2) != 0) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+static bool
+openssldh_paramcompare(const dst_key_t *key1, const dst_key_t *key2) {
+ DH *dh1, *dh2;
+ const BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL;
+
+ dh1 = key1->keydata.dh;
+ dh2 = key2->keydata.dh;
+
+ if (dh1 == NULL && dh2 == NULL) {
+ return (true);
+ } else if (dh1 == NULL || dh2 == NULL) {
+ return (false);
+ }
+
+ DH_get0_pqg(dh1, &p1, NULL, &g1);
+ DH_get0_pqg(dh2, &p2, NULL, &g2);
+
+ if (BN_cmp(p1, p2) != 0 || BN_cmp(g1, g2) != 0) {
+ return (false);
+ }
+ return (true);
+}
+
+static int
+progress_cb(int p, int n, BN_GENCB *cb) {
+ union {
+ void *dptr;
+ void (*fptr)(int);
+ } u;
+
+ UNUSED(n);
+
+ u.dptr = BN_GENCB_get_arg(cb);
+ if (u.fptr != NULL) {
+ u.fptr(p);
+ }
+ return (1);
+}
+
+static isc_result_t
+openssldh_generate(dst_key_t *key, int generator, void (*callback)(int)) {
+ DH *dh = NULL;
+ BN_GENCB *cb;
+#if !HAVE_BN_GENCB_NEW
+ BN_GENCB _cb;
+#endif /* !HAVE_BN_GENCB_NEW */
+ union {
+ void *dptr;
+ void (*fptr)(int);
+ } u;
+
+ if (generator == 0) {
+ if (key->key_size == 768 || key->key_size == 1024 ||
+ key->key_size == 1536)
+ {
+ BIGNUM *p, *g;
+ dh = DH_new();
+ if (key->key_size == 768) {
+ p = BN_dup(bn768);
+ } else if (key->key_size == 1024) {
+ p = BN_dup(bn1024);
+ } else {
+ p = BN_dup(bn1536);
+ }
+ g = BN_dup(bn2);
+ if (dh == NULL || p == NULL || g == NULL) {
+ if (dh != NULL) {
+ DH_free(dh);
+ }
+ if (p != NULL) {
+ BN_free(p);
+ }
+ if (g != NULL) {
+ BN_free(g);
+ }
+ return (dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+ DH_set0_pqg(dh, p, NULL, g);
+ } else {
+ generator = 2;
+ }
+ }
+
+ if (generator != 0) {
+ dh = DH_new();
+ if (dh == NULL) {
+ return (dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+ cb = BN_GENCB_new();
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+ if (cb == NULL) {
+ DH_free(dh);
+ return (dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+#endif /* if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ * !defined(LIBRESSL_VERSION_NUMBER) */
+ if (callback == NULL) {
+ BN_GENCB_set_old(cb, NULL, NULL);
+ } else {
+ u.fptr = callback;
+ BN_GENCB_set(cb, progress_cb, u.dptr);
+ }
+
+ if (!DH_generate_parameters_ex(dh, key->key_size, generator,
+ cb))
+ {
+ DH_free(dh);
+ BN_GENCB_free(cb);
+ return (dst__openssl_toresult2("DH_generate_parameters_"
+ "ex",
+ DST_R_OPENSSLFAILURE));
+ }
+ BN_GENCB_free(cb);
+ cb = NULL;
+ }
+
+ if (DH_generate_key(dh) == 0) {
+ DH_free(dh);
+ return (dst__openssl_toresult2("DH_generate_key",
+ DST_R_OPENSSLFAILURE));
+ }
+ DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P);
+ key->keydata.dh = dh;
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+openssldh_isprivate(const dst_key_t *key) {
+ DH *dh = key->keydata.dh;
+ const BIGNUM *priv_key = NULL;
+
+ DH_get0_key(dh, NULL, &priv_key);
+ return (dh != NULL && priv_key != NULL);
+}
+
+static void
+openssldh_destroy(dst_key_t *key) {
+ DH *dh = key->keydata.dh;
+
+ if (dh == NULL) {
+ return;
+ }
+
+ DH_free(dh);
+ key->keydata.dh = NULL;
+}
+
+static void
+uint16_toregion(uint16_t val, isc_region_t *region) {
+ *region->base = (val & 0xff00) >> 8;
+ isc_region_consume(region, 1);
+ *region->base = (val & 0x00ff);
+ isc_region_consume(region, 1);
+}
+
+static uint16_t
+uint16_fromregion(isc_region_t *region) {
+ uint16_t val;
+ unsigned char *cp = region->base;
+
+ val = ((unsigned int)(cp[0])) << 8;
+ val |= ((unsigned int)(cp[1]));
+
+ isc_region_consume(region, 2);
+
+ return (val);
+}
+
+static isc_result_t
+openssldh_todns(const dst_key_t *key, isc_buffer_t *data) {
+ DH *dh;
+ const BIGNUM *pub_key = NULL, *p = NULL, *g = NULL;
+ isc_region_t r;
+ uint16_t dnslen, plen, glen, publen;
+
+ REQUIRE(key->keydata.dh != NULL);
+
+ dh = key->keydata.dh;
+
+ isc_buffer_availableregion(data, &r);
+
+ DH_get0_pqg(dh, &p, NULL, &g);
+ if (BN_cmp(g, bn2) == 0 &&
+ (BN_cmp(p, bn768) == 0 || BN_cmp(p, bn1024) == 0 ||
+ BN_cmp(p, bn1536) == 0))
+ {
+ plen = 1;
+ glen = 0;
+ } else {
+ plen = BN_num_bytes(p);
+ glen = BN_num_bytes(g);
+ }
+ DH_get0_key(dh, &pub_key, NULL);
+ publen = BN_num_bytes(pub_key);
+ dnslen = plen + glen + publen + 6;
+ if (r.length < (unsigned int)dnslen) {
+ return (ISC_R_NOSPACE);
+ }
+
+ uint16_toregion(plen, &r);
+ if (plen == 1) {
+ if (BN_cmp(p, bn768) == 0) {
+ *r.base = 1;
+ } else if (BN_cmp(p, bn1024) == 0) {
+ *r.base = 2;
+ } else {
+ *r.base = 3;
+ }
+ } else {
+ BN_bn2bin(p, r.base);
+ }
+ isc_region_consume(&r, plen);
+
+ uint16_toregion(glen, &r);
+ if (glen > 0) {
+ BN_bn2bin(g, r.base);
+ }
+ isc_region_consume(&r, glen);
+
+ uint16_toregion(publen, &r);
+ BN_bn2bin(pub_key, r.base);
+ isc_region_consume(&r, publen);
+
+ isc_buffer_add(data, dnslen);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+openssldh_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ DH *dh;
+ BIGNUM *pub_key = NULL, *p = NULL, *g = NULL;
+ isc_region_t r;
+ uint16_t plen, glen, publen;
+ int special = 0;
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ dh = DH_new();
+ if (dh == NULL) {
+ return (dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+ DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P);
+
+ /*
+ * Read the prime length. 1 & 2 are table entries, > 16 means a
+ * prime follows, otherwise an error.
+ */
+ if (r.length < 2) {
+ DH_free(dh);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ plen = uint16_fromregion(&r);
+ if (plen < 16 && plen != 1 && plen != 2) {
+ DH_free(dh);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ if (r.length < plen) {
+ DH_free(dh);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ if (plen == 1 || plen == 2) {
+ if (plen == 1) {
+ special = *r.base;
+ isc_region_consume(&r, 1);
+ } else {
+ special = uint16_fromregion(&r);
+ }
+ switch (special) {
+ case 1:
+ p = BN_dup(bn768);
+ break;
+ case 2:
+ p = BN_dup(bn1024);
+ break;
+ case 3:
+ p = BN_dup(bn1536);
+ break;
+ default:
+ DH_free(dh);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ } else {
+ p = BN_bin2bn(r.base, plen, NULL);
+ isc_region_consume(&r, plen);
+ }
+
+ /*
+ * Read the generator length. This should be 0 if the prime was
+ * special, but it might not be. If it's 0 and the prime is not
+ * special, we have a problem.
+ */
+ if (r.length < 2) {
+ DH_free(dh);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ glen = uint16_fromregion(&r);
+ if (r.length < glen) {
+ DH_free(dh);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ if (special != 0) {
+ if (glen == 0) {
+ g = BN_dup(bn2);
+ } else {
+ g = BN_bin2bn(r.base, glen, NULL);
+ if (g != NULL && BN_cmp(g, bn2) != 0) {
+ DH_free(dh);
+ BN_free(g);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ }
+ } else {
+ if (glen == 0) {
+ DH_free(dh);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ g = BN_bin2bn(r.base, glen, NULL);
+ }
+ isc_region_consume(&r, glen);
+
+ if (p == NULL || g == NULL) {
+ DH_free(dh);
+ if (p != NULL) {
+ BN_free(p);
+ }
+ if (g != NULL) {
+ BN_free(g);
+ }
+ return (dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+ DH_set0_pqg(dh, p, NULL, g);
+
+ if (r.length < 2) {
+ DH_free(dh);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ publen = uint16_fromregion(&r);
+ if (r.length < publen) {
+ DH_free(dh);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ pub_key = BN_bin2bn(r.base, publen, NULL);
+ if (pub_key == NULL) {
+ DH_free(dh);
+ return (dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+#if (LIBRESSL_VERSION_NUMBER >= 0x2070000fL) && \
+ (LIBRESSL_VERSION_NUMBER <= 0x2070200fL)
+ /*
+ * LibreSSL << 2.7.3 DH_get0_key requires priv_key to be set when
+ * DH structure is empty, hence we cannot use DH_get0_key().
+ */
+ dh->pub_key = pub_key;
+#else /* LIBRESSL_VERSION_NUMBER */
+ DH_set0_key(dh, pub_key, NULL);
+#endif /* LIBRESSL_VERSION_NUMBER */
+ isc_region_consume(&r, publen);
+
+ key->key_size = BN_num_bits(p);
+
+ isc_buffer_forward(data, plen + glen + publen + 6);
+
+ key->keydata.dh = dh;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+openssldh_tofile(const dst_key_t *key, const char *directory) {
+ int i;
+ DH *dh;
+ const BIGNUM *pub_key = NULL, *priv_key = NULL, *p = NULL, *g = NULL;
+ dst_private_t priv;
+ unsigned char *bufs[4];
+ isc_result_t result;
+
+ if (key->keydata.dh == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ return (DST_R_EXTERNALKEY);
+ }
+
+ dh = key->keydata.dh;
+ DH_get0_key(dh, &pub_key, &priv_key);
+ DH_get0_pqg(dh, &p, NULL, &g);
+
+ memset(bufs, 0, sizeof(bufs));
+ for (i = 0; i < 4; i++) {
+ bufs[i] = isc_mem_get(key->mctx, BN_num_bytes(p));
+ }
+
+ i = 0;
+
+ priv.elements[i].tag = TAG_DH_PRIME;
+ priv.elements[i].length = BN_num_bytes(p);
+ BN_bn2bin(p, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.elements[i].tag = TAG_DH_GENERATOR;
+ priv.elements[i].length = BN_num_bytes(g);
+ BN_bn2bin(g, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.elements[i].tag = TAG_DH_PRIVATE;
+ priv.elements[i].length = BN_num_bytes(priv_key);
+ BN_bn2bin(priv_key, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.elements[i].tag = TAG_DH_PUBLIC;
+ priv.elements[i].length = BN_num_bytes(pub_key);
+ BN_bn2bin(pub_key, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.nelements = i;
+ result = dst__privstruct_writefile(key, &priv, directory);
+
+ for (i = 0; i < 4; i++) {
+ if (bufs[i] == NULL) {
+ break;
+ }
+ isc_mem_put(key->mctx, bufs[i], BN_num_bytes(p));
+ }
+ return (result);
+}
+
+static isc_result_t
+openssldh_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t ret;
+ int i;
+ DH *dh = NULL;
+ BIGNUM *pub_key = NULL, *priv_key = NULL, *p = NULL, *g = NULL;
+ isc_mem_t *mctx;
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+ UNUSED(pub);
+ mctx = key->mctx;
+
+ /* read private key file */
+ ret = dst__privstruct_parse(key, DST_ALG_DH, lexer, mctx, &priv);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ if (key->external) {
+ DST_RET(DST_R_EXTERNALKEY);
+ }
+
+ dh = DH_new();
+ if (dh == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P);
+ key->keydata.dh = dh;
+
+ for (i = 0; i < priv.nelements; i++) {
+ BIGNUM *bn;
+ bn = BN_bin2bn(priv.elements[i].data, priv.elements[i].length,
+ NULL);
+ if (bn == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+
+ switch (priv.elements[i].tag) {
+ case TAG_DH_PRIME:
+ p = bn;
+ break;
+ case TAG_DH_GENERATOR:
+ g = bn;
+ break;
+ case TAG_DH_PRIVATE:
+ priv_key = bn;
+ break;
+ case TAG_DH_PUBLIC:
+ pub_key = bn;
+ break;
+ }
+ }
+ dst__privstruct_free(&priv, mctx);
+ DH_set0_key(dh, pub_key, priv_key);
+ DH_set0_pqg(dh, p, NULL, g);
+
+ key->key_size = BN_num_bits(p);
+ return (ISC_R_SUCCESS);
+
+err:
+ if (p != NULL) {
+ BN_free(p);
+ }
+ if (g != NULL) {
+ BN_free(g);
+ }
+ if (pub_key != NULL) {
+ BN_free(pub_key);
+ }
+ if (priv_key != NULL) {
+ BN_free(priv_key);
+ }
+ openssldh_destroy(key);
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (ret);
+}
+
+static void
+openssldh_cleanup(void) {
+ BN_free(bn2);
+ bn2 = NULL;
+
+ BN_free(bn768);
+ bn768 = NULL;
+
+ BN_free(bn1024);
+ bn1024 = NULL;
+
+ BN_free(bn1536);
+ bn1536 = NULL;
+}
+
+static dst_func_t openssldh_functions = {
+ NULL, /*%< createctx */
+ NULL, /*%< createctx2 */
+ NULL, /*%< destroyctx */
+ NULL, /*%< adddata */
+ NULL, /*%< openssldh_sign */
+ NULL, /*%< openssldh_verify */
+ NULL, /*%< openssldh_verify2 */
+ openssldh_computesecret,
+ openssldh_compare,
+ openssldh_paramcompare,
+ openssldh_generate,
+ openssldh_isprivate,
+ openssldh_destroy,
+ openssldh_todns,
+ openssldh_fromdns,
+ openssldh_tofile,
+ openssldh_parse,
+ openssldh_cleanup,
+ NULL, /*%< fromlabel */
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+isc_result_t
+dst__openssldh_init(dst_func_t **funcp) {
+ REQUIRE(funcp != NULL);
+ if (*funcp == NULL) {
+ if (BN_hex2bn(&bn2, PRIME2) == 0 || bn2 == NULL) {
+ goto cleanup;
+ }
+ if (BN_hex2bn(&bn768, PRIME768) == 0 || bn768 == NULL) {
+ goto cleanup;
+ }
+ if (BN_hex2bn(&bn1024, PRIME1024) == 0 || bn1024 == NULL) {
+ goto cleanup;
+ }
+ if (BN_hex2bn(&bn1536, PRIME1536) == 0 || bn1536 == NULL) {
+ goto cleanup;
+ }
+ *funcp = &openssldh_functions;
+ }
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (bn2 != NULL) {
+ BN_free(bn2);
+ }
+ if (bn768 != NULL) {
+ BN_free(bn768);
+ }
+ if (bn1024 != NULL) {
+ BN_free(bn1024);
+ }
+ if (bn1536 != NULL) {
+ BN_free(bn1536);
+ }
+ return (ISC_R_NOMEMORY);
+}
diff --git a/lib/dns/opensslecdsa_link.c b/lib/dns/opensslecdsa_link.c
new file mode 100644
index 0000000..e9f5185
--- /dev/null
+++ b/lib/dns/opensslecdsa_link.c
@@ -0,0 +1,905 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#if !USE_PKCS11
+
+#include <stdbool.h>
+
+#include <openssl/bn.h>
+#include <openssl/ecdsa.h>
+#include <openssl/err.h>
+#include <openssl/objects.h>
+#if !defined(OPENSSL_NO_ENGINE)
+#include <openssl/engine.h>
+#endif
+
+#include <isc/mem.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/keyvalues.h>
+
+#include <dst/result.h>
+
+#include "dst_internal.h"
+#include "dst_openssl.h"
+#include "dst_parse.h"
+
+#ifndef NID_X9_62_prime256v1
+#error "P-256 group is not known (NID_X9_62_prime256v1)"
+#endif /* ifndef NID_X9_62_prime256v1 */
+#ifndef NID_secp384r1
+#error "P-384 group is not known (NID_secp384r1)"
+#endif /* ifndef NID_secp384r1 */
+
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+#if !HAVE_ECDSA_SIG_GET0
+/* From OpenSSL 1.1 */
+static void
+ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) {
+ if (pr != NULL) {
+ *pr = sig->r;
+ }
+ if (ps != NULL) {
+ *ps = sig->s;
+ }
+}
+
+static int
+ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) {
+ if (r == NULL || s == NULL) {
+ return (0);
+ }
+
+ BN_clear_free(sig->r);
+ BN_clear_free(sig->s);
+ sig->r = r;
+ sig->s = s;
+
+ return (1);
+}
+#endif /* !HAVE_ECDSA_SIG_GET0 */
+
+static isc_result_t
+opensslecdsa_createctx(dst_key_t *key, dst_context_t *dctx) {
+ EVP_MD_CTX *evp_md_ctx;
+ const EVP_MD *type = NULL;
+
+ UNUSED(key);
+ REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 ||
+ dctx->key->key_alg == DST_ALG_ECDSA384);
+
+ evp_md_ctx = EVP_MD_CTX_create();
+ if (evp_md_ctx == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ if (dctx->key->key_alg == DST_ALG_ECDSA256) {
+ type = EVP_sha256();
+ } else {
+ type = EVP_sha384();
+ }
+
+ if (!EVP_DigestInit_ex(evp_md_ctx, type, NULL)) {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ return (dst__openssl_toresult3(
+ dctx->category, "EVP_DigestInit_ex", ISC_R_FAILURE));
+ }
+
+ dctx->ctxdata.evp_md_ctx = evp_md_ctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+opensslecdsa_destroyctx(dst_context_t *dctx) {
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 ||
+ dctx->key->key_alg == DST_ALG_ECDSA384);
+
+ if (evp_md_ctx != NULL) {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ dctx->ctxdata.evp_md_ctx = NULL;
+ }
+}
+
+static isc_result_t
+opensslecdsa_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 ||
+ dctx->key->key_alg == DST_ALG_ECDSA384);
+
+ if (!EVP_DigestUpdate(evp_md_ctx, data->base, data->length)) {
+ return (dst__openssl_toresult3(
+ dctx->category, "EVP_DigestUpdate", ISC_R_FAILURE));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static int
+BN_bn2bin_fixed(const BIGNUM *bn, unsigned char *buf, int size) {
+ int bytes = size - BN_num_bytes(bn);
+
+ while (bytes-- > 0) {
+ *buf++ = 0;
+ }
+ BN_bn2bin(bn, buf);
+ return (size);
+}
+
+static isc_result_t
+opensslecdsa_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ isc_result_t ret;
+ dst_key_t *key = dctx->key;
+ isc_region_t region;
+ ECDSA_SIG *ecdsasig;
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+ EVP_PKEY *pkey = key->keydata.pkey;
+ EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(pkey);
+ unsigned int dgstlen, siglen;
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ const BIGNUM *r, *s;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+
+ if (eckey == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ siglen = DNS_SIG_ECDSA256SIZE;
+ } else {
+ siglen = DNS_SIG_ECDSA384SIZE;
+ }
+
+ isc_buffer_availableregion(sig, &region);
+ if (region.length < siglen) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+
+ if (!EVP_DigestFinal_ex(evp_md_ctx, digest, &dgstlen)) {
+ DST_RET(dst__openssl_toresult3(
+ dctx->category, "EVP_DigestFinal_ex", ISC_R_FAILURE));
+ }
+
+ ecdsasig = ECDSA_do_sign(digest, dgstlen, eckey);
+ if (ecdsasig == NULL) {
+ DST_RET(dst__openssl_toresult3(dctx->category, "ECDSA_do_sign",
+ DST_R_SIGNFAILURE));
+ }
+ ECDSA_SIG_get0(ecdsasig, &r, &s);
+ BN_bn2bin_fixed(r, region.base, siglen / 2);
+ isc_region_consume(&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:
+ EC_KEY_free(eckey);
+ return (ret);
+}
+
+static isc_result_t
+opensslecdsa_verify(dst_context_t *dctx, const isc_region_t *sig) {
+ isc_result_t ret;
+ dst_key_t *key = dctx->key;
+ int status;
+ unsigned char *cp = sig->base;
+ ECDSA_SIG *ecdsasig = NULL;
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+ EVP_PKEY *pkey = key->keydata.pkey;
+ EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(pkey);
+ unsigned int dgstlen, siglen;
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ BIGNUM *r = NULL, *s = NULL;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+
+ if (eckey == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ siglen = DNS_SIG_ECDSA256SIZE;
+ } else {
+ siglen = DNS_SIG_ECDSA384SIZE;
+ }
+
+ if (sig->length != siglen) {
+ DST_RET(DST_R_VERIFYFAILURE);
+ }
+
+ if (!EVP_DigestFinal_ex(evp_md_ctx, digest, &dgstlen)) {
+ DST_RET(dst__openssl_toresult3(
+ dctx->category, "EVP_DigestFinal_ex", ISC_R_FAILURE));
+ }
+
+ ecdsasig = ECDSA_SIG_new();
+ if (ecdsasig == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ r = BN_bin2bn(cp, siglen / 2, NULL);
+ cp += siglen / 2;
+ s = BN_bin2bn(cp, siglen / 2, NULL);
+ ECDSA_SIG_set0(ecdsasig, r, s);
+ /* cp += siglen / 2; */
+
+ status = ECDSA_do_verify(digest, dgstlen, ecdsasig, eckey);
+ switch (status) {
+ case 1:
+ ret = ISC_R_SUCCESS;
+ break;
+ case 0:
+ ret = dst__openssl_toresult(DST_R_VERIFYFAILURE);
+ break;
+ default:
+ ret = dst__openssl_toresult3(dctx->category, "ECDSA_do_verify",
+ DST_R_VERIFYFAILURE);
+ break;
+ }
+
+err:
+ if (ecdsasig != NULL) {
+ ECDSA_SIG_free(ecdsasig);
+ }
+ EC_KEY_free(eckey);
+ return (ret);
+}
+
+static bool
+opensslecdsa_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ bool ret;
+ int status;
+ EVP_PKEY *pkey1 = key1->keydata.pkey;
+ EVP_PKEY *pkey2 = key2->keydata.pkey;
+ EC_KEY *eckey1 = NULL;
+ EC_KEY *eckey2 = NULL;
+ const BIGNUM *priv1, *priv2;
+
+ if (pkey1 == NULL && pkey2 == NULL) {
+ return (true);
+ } else if (pkey1 == NULL || pkey2 == NULL) {
+ return (false);
+ }
+
+ eckey1 = EVP_PKEY_get1_EC_KEY(pkey1);
+ eckey2 = EVP_PKEY_get1_EC_KEY(pkey2);
+ if (eckey1 == NULL && eckey2 == NULL) {
+ DST_RET(true);
+ } else if (eckey1 == NULL || eckey2 == NULL) {
+ DST_RET(false);
+ }
+
+ status = EVP_PKEY_cmp(pkey1, pkey2);
+ if (status != 1) {
+ DST_RET(false);
+ }
+
+ priv1 = EC_KEY_get0_private_key(eckey1);
+ priv2 = EC_KEY_get0_private_key(eckey2);
+ if (priv1 != NULL || priv2 != NULL) {
+ if (priv1 == NULL || priv2 == NULL) {
+ DST_RET(false);
+ }
+ if (BN_cmp(priv1, priv2) != 0) {
+ DST_RET(false);
+ }
+ }
+ ret = true;
+
+err:
+ if (eckey1 != NULL) {
+ EC_KEY_free(eckey1);
+ }
+ if (eckey2 != NULL) {
+ EC_KEY_free(eckey2);
+ }
+ return (ret);
+}
+
+static isc_result_t
+opensslecdsa_generate(dst_key_t *key, int unused, void (*callback)(int)) {
+ isc_result_t ret;
+ EVP_PKEY *pkey;
+ EC_KEY *eckey = NULL;
+ int group_nid;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+ UNUSED(unused);
+ UNUSED(callback);
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ group_nid = NID_X9_62_prime256v1;
+ key->key_size = DNS_KEY_ECDSA256SIZE * 4;
+ } else {
+ group_nid = NID_secp384r1;
+ key->key_size = DNS_KEY_ECDSA384SIZE * 4;
+ }
+
+ eckey = EC_KEY_new_by_curve_name(group_nid);
+ if (eckey == NULL) {
+ return (dst__openssl_toresult2("EC_KEY_new_by_curve_name",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ if (EC_KEY_generate_key(eckey) != 1) {
+ DST_RET(dst__openssl_toresult2("EC_KEY_generate_key",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) {
+ EVP_PKEY_free(pkey);
+ DST_RET(ISC_R_FAILURE);
+ }
+ key->keydata.pkey = pkey;
+ ret = ISC_R_SUCCESS;
+
+err:
+ EC_KEY_free(eckey);
+ return (ret);
+}
+
+static bool
+opensslecdsa_isprivate(const dst_key_t *key) {
+ bool ret;
+ EVP_PKEY *pkey = key->keydata.pkey;
+ EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(pkey);
+
+ ret = (eckey != NULL && EC_KEY_get0_private_key(eckey) != NULL);
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+ return (ret);
+}
+
+static void
+opensslecdsa_destroy(dst_key_t *key) {
+ EVP_PKEY *pkey = key->keydata.pkey;
+
+ EVP_PKEY_free(pkey);
+ key->keydata.pkey = NULL;
+}
+
+static isc_result_t
+opensslecdsa_todns(const dst_key_t *key, isc_buffer_t *data) {
+ isc_result_t ret;
+ EVP_PKEY *pkey;
+ EC_KEY *eckey = NULL;
+ isc_region_t r;
+ int len;
+ unsigned char *cp;
+ unsigned char buf[DNS_KEY_ECDSA384SIZE + 1];
+
+ REQUIRE(key->keydata.pkey != NULL);
+
+ pkey = key->keydata.pkey;
+ eckey = EVP_PKEY_get1_EC_KEY(pkey);
+ if (eckey == NULL) {
+ return (dst__openssl_toresult(ISC_R_FAILURE));
+ }
+ len = i2o_ECPublicKey(eckey, NULL);
+ /* skip form */
+ len--;
+
+ isc_buffer_availableregion(data, &r);
+ if (r.length < (unsigned int)len) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+ cp = buf;
+ if (!i2o_ECPublicKey(eckey, &cp)) {
+ DST_RET(dst__openssl_toresult(ISC_R_FAILURE));
+ }
+ memmove(r.base, buf + 1, len);
+ isc_buffer_add(data, len);
+ ret = ISC_R_SUCCESS;
+
+err:
+ EC_KEY_free(eckey);
+ return (ret);
+}
+
+static isc_result_t
+opensslecdsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ isc_result_t ret;
+ EVP_PKEY *pkey;
+ EC_KEY *eckey = NULL;
+ isc_region_t r;
+ int group_nid;
+ unsigned int len;
+ const unsigned char *cp;
+ unsigned char buf[DNS_KEY_ECDSA384SIZE + 1];
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ len = DNS_KEY_ECDSA256SIZE;
+ group_nid = NID_X9_62_prime256v1;
+ } else {
+ len = DNS_KEY_ECDSA384SIZE;
+ group_nid = NID_secp384r1;
+ }
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ if (r.length < len) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+
+ eckey = EC_KEY_new_by_curve_name(group_nid);
+ if (eckey == NULL) {
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ buf[0] = POINT_CONVERSION_UNCOMPRESSED;
+ memmove(buf + 1, r.base, len);
+ cp = buf;
+ if (o2i_ECPublicKey(&eckey, (const unsigned char **)&cp,
+ (long)len + 1) == NULL)
+ {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPUBLICKEY));
+ }
+ if (EC_KEY_check_key(eckey) != 1) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPUBLICKEY));
+ }
+
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) {
+ EVP_PKEY_free(pkey);
+ DST_RET(dst__openssl_toresult(ISC_R_FAILURE));
+ }
+
+ isc_buffer_forward(data, len);
+ key->keydata.pkey = pkey;
+ key->key_size = len * 4;
+ ret = ISC_R_SUCCESS;
+
+err:
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+ return (ret);
+}
+
+static isc_result_t
+opensslecdsa_tofile(const dst_key_t *key, const char *directory) {
+ isc_result_t ret;
+ EVP_PKEY *pkey;
+ EC_KEY *eckey = NULL;
+ const BIGNUM *privkey;
+ dst_private_t priv;
+ unsigned char *buf = NULL;
+ unsigned short i;
+
+ if (key->keydata.pkey == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ priv.nelements = 0;
+ return (dst__privstruct_writefile(key, &priv, directory));
+ }
+
+ pkey = key->keydata.pkey;
+ eckey = EVP_PKEY_get1_EC_KEY(pkey);
+ if (eckey == NULL) {
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ privkey = EC_KEY_get0_private_key(eckey);
+ if (privkey == NULL) {
+ ret = dst__openssl_toresult(DST_R_OPENSSLFAILURE);
+ goto err;
+ }
+
+ buf = isc_mem_get(key->mctx, BN_num_bytes(privkey));
+
+ i = 0;
+
+ priv.elements[i].tag = TAG_ECDSA_PRIVATEKEY;
+ priv.elements[i].length = BN_num_bytes(privkey);
+ BN_bn2bin(privkey, buf);
+ priv.elements[i].data = buf;
+ i++;
+
+ if (key->engine != NULL) {
+ priv.elements[i].tag = TAG_ECDSA_ENGINE;
+ priv.elements[i].length = (unsigned short)strlen(key->engine) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->engine;
+ i++;
+ }
+
+ if (key->label != NULL) {
+ priv.elements[i].tag = TAG_ECDSA_LABEL;
+ priv.elements[i].length = (unsigned short)strlen(key->label) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->label;
+ i++;
+ }
+
+ priv.nelements = i;
+ ret = dst__privstruct_writefile(key, &priv, directory);
+
+err:
+ EC_KEY_free(eckey);
+ if (buf != NULL) {
+ isc_mem_put(key->mctx, buf, BN_num_bytes(privkey));
+ }
+ return (ret);
+}
+
+static isc_result_t
+ecdsa_check(EC_KEY *eckey, EC_KEY *pubeckey) {
+ const EC_POINT *pubkey;
+
+ pubkey = EC_KEY_get0_public_key(eckey);
+ if (pubkey != NULL) {
+ return (ISC_R_SUCCESS);
+ } else if (pubeckey != NULL) {
+ pubkey = EC_KEY_get0_public_key(pubeckey);
+ if (pubkey == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ if (EC_KEY_set_public_key(eckey, pubkey) != 1) {
+ return (ISC_R_SUCCESS);
+ }
+ }
+ if (EC_KEY_check_key(eckey) == 1) {
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_FAILURE);
+}
+
+static isc_result_t
+load_privkey_from_privstruct(EC_KEY *eckey, dst_private_t *priv) {
+ BIGNUM *privkey = BN_bin2bn(priv->elements[0].data,
+ priv->elements[0].length, NULL);
+ isc_result_t result = ISC_R_SUCCESS;
+
+ if (privkey == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (!EC_KEY_set_private_key(eckey, privkey)) {
+ result = ISC_R_NOMEMORY;
+ }
+
+ BN_clear_free(privkey);
+ return (result);
+}
+
+static isc_result_t
+eckey_to_pkey(EC_KEY *eckey, EVP_PKEY **pkey) {
+ REQUIRE(pkey != NULL && *pkey == NULL);
+
+ *pkey = EVP_PKEY_new();
+ if (*pkey == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ if (!EVP_PKEY_set1_EC_KEY(*pkey, eckey)) {
+ EVP_PKEY_free(*pkey);
+ *pkey = NULL;
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+finalize_eckey(dst_key_t *key, EC_KEY *eckey, const char *engine,
+ const char *label) {
+ isc_result_t result = ISC_R_SUCCESS;
+ EVP_PKEY *pkey = NULL;
+
+ result = eckey_to_pkey(eckey, &pkey);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ key->keydata.pkey = pkey;
+
+ if (label != NULL) {
+ key->label = isc_mem_strdup(key->mctx, label);
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ }
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ key->key_size = DNS_KEY_ECDSA256SIZE * 4;
+ } else {
+ key->key_size = DNS_KEY_ECDSA384SIZE * 4;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dst__key_to_eckey(dst_key_t *key, EC_KEY **eckey) {
+ REQUIRE(eckey != NULL && *eckey == NULL);
+
+ int group_nid;
+ switch (key->key_alg) {
+ case DST_ALG_ECDSA256:
+ group_nid = NID_X9_62_prime256v1;
+ break;
+ case DST_ALG_ECDSA384:
+ group_nid = NID_secp384r1;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ *eckey = EC_KEY_new_by_curve_name(group_nid);
+ if (*eckey == NULL) {
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin);
+
+static isc_result_t
+opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t result = ISC_R_SUCCESS;
+ EC_KEY *eckey = NULL;
+ EC_KEY *pubeckey = NULL;
+ const char *engine = NULL;
+ const char *label = NULL;
+ int i, privkey_index = -1;
+ bool finalize_key = false;
+
+ /* read private key file */
+ result = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, key->mctx,
+ &priv);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ if (key->external) {
+ if (priv.nelements != 0 || pub == NULL) {
+ result = DST_R_INVALIDPRIVATEKEY;
+ goto end;
+ }
+ key->keydata.pkey = pub->keydata.pkey;
+ pub->keydata.pkey = NULL;
+ goto end;
+ }
+
+ for (i = 0; i < priv.nelements; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_ECDSA_ENGINE:
+ engine = (char *)priv.elements[i].data;
+ break;
+ case TAG_ECDSA_LABEL:
+ label = (char *)priv.elements[i].data;
+ break;
+ case TAG_ECDSA_PRIVATEKEY:
+ privkey_index = i;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (privkey_index < 0) {
+ result = DST_R_INVALIDPRIVATEKEY;
+ goto end;
+ }
+
+ if (label != NULL) {
+ result = opensslecdsa_fromlabel(key, engine, label, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ eckey = EVP_PKEY_get1_EC_KEY(key->keydata.pkey);
+ if (eckey == NULL) {
+ result = dst__openssl_toresult(DST_R_OPENSSLFAILURE);
+ goto end;
+ }
+
+ } else {
+ result = dst__key_to_eckey(key, &eckey);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ result = load_privkey_from_privstruct(eckey, &priv);
+ if (result != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ finalize_key = true;
+ }
+
+ if (pub != NULL && pub->keydata.pkey != NULL) {
+ pubeckey = EVP_PKEY_get1_EC_KEY(pub->keydata.pkey);
+ }
+
+ if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) {
+ result = DST_R_INVALIDPRIVATEKEY;
+ goto end;
+ }
+
+ if (finalize_key) {
+ result = finalize_eckey(key, eckey, engine, label);
+ }
+
+end:
+ if (pubeckey != NULL) {
+ EC_KEY_free(pubeckey);
+ }
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+ dst__privstruct_free(&priv, key->mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (result);
+}
+
+static isc_result_t
+opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin) {
+#if !defined(OPENSSL_NO_ENGINE)
+ isc_result_t ret = ISC_R_SUCCESS;
+ ENGINE *e;
+ EC_KEY *eckey = NULL;
+ EC_KEY *pubeckey = NULL;
+ EVP_PKEY *pkey = NULL;
+ EVP_PKEY *pubkey = NULL;
+ int group_nid = 0;
+
+ UNUSED(pin);
+
+ if (engine == NULL || label == NULL) {
+ return (DST_R_NOENGINE);
+ }
+ e = dst__openssl_getengine(engine);
+ if (e == NULL) {
+ return (DST_R_NOENGINE);
+ }
+
+ if (key->key_alg == DST_ALG_ECDSA256) {
+ group_nid = NID_X9_62_prime256v1;
+ } else {
+ group_nid = NID_secp384r1;
+ }
+
+ /* Load private key. */
+ pkey = ENGINE_load_private_key(e, label, NULL, NULL);
+ if (pkey == NULL) {
+ return (dst__openssl_toresult2("ENGINE_load_private_key",
+ DST_R_OPENSSLFAILURE));
+ }
+ /* Check base id, group nid */
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ eckey = EVP_PKEY_get1_EC_KEY(pkey);
+ if (eckey == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != group_nid) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ /* Load public key. */
+ pubkey = ENGINE_load_public_key(e, label, NULL, NULL);
+ if (pubkey == NULL) {
+ DST_RET(dst__openssl_toresult2("ENGINE_load_public_key",
+ DST_R_OPENSSLFAILURE));
+ }
+ /* Check base id, group nid */
+ if (EVP_PKEY_base_id(pubkey) != EVP_PKEY_EC) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ pubeckey = EVP_PKEY_get1_EC_KEY(pubkey);
+ if (pubeckey == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (EC_GROUP_get_curve_name(EC_KEY_get0_group(pubeckey)) != group_nid) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+
+ if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ key->label = isc_mem_strdup(key->mctx, label);
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ key->key_size = EVP_PKEY_bits(pkey);
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+
+err:
+ if (pubkey != NULL) {
+ EVP_PKEY_free(pubkey);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (pubeckey != NULL) {
+ EC_KEY_free(pubeckey);
+ }
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+
+ return (ret);
+#else
+ UNUSED(key);
+ UNUSED(engine);
+ UNUSED(label);
+ UNUSED(pin);
+ return (DST_R_NOENGINE);
+#endif
+}
+
+static dst_func_t opensslecdsa_functions = {
+ opensslecdsa_createctx,
+ NULL, /*%< createctx2 */
+ opensslecdsa_destroyctx,
+ opensslecdsa_adddata,
+ opensslecdsa_sign,
+ opensslecdsa_verify,
+ NULL, /*%< verify2 */
+ NULL, /*%< computesecret */
+ opensslecdsa_compare,
+ NULL, /*%< paramcompare */
+ opensslecdsa_generate,
+ opensslecdsa_isprivate,
+ opensslecdsa_destroy,
+ opensslecdsa_todns,
+ opensslecdsa_fromdns,
+ opensslecdsa_tofile,
+ opensslecdsa_parse,
+ NULL, /*%< cleanup */
+ opensslecdsa_fromlabel, /*%< fromlabel */
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+isc_result_t
+dst__opensslecdsa_init(dst_func_t **funcp) {
+ REQUIRE(funcp != NULL);
+ if (*funcp == NULL) {
+ *funcp = &opensslecdsa_functions;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+#endif /* !USE_PKCS11 */
diff --git a/lib/dns/openssleddsa_link.c b/lib/dns/openssleddsa_link.c
new file mode 100644
index 0000000..295b8b8
--- /dev/null
+++ b/lib/dns/openssleddsa_link.c
@@ -0,0 +1,708 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if !USE_PKCS11
+
+#if HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448
+
+#include <stdbool.h>
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/x509.h>
+#if !defined(OPENSSL_NO_ENGINE)
+#include <openssl/engine.h>
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+
+#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/result.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 (ISC_R_NOMEMORY);
+ }
+
+#if HAVE_OPENSSL_ED25519
+ if (key->key_alg == DST_ALG_ED25519) {
+ siglen = DNS_SIG_ED25519SIZE;
+ }
+#endif /* if HAVE_OPENSSL_ED25519 */
+#if HAVE_OPENSSL_ED448
+ if (key->key_alg == DST_ALG_ED448) {
+ siglen = DNS_SIG_ED448SIZE;
+ }
+#endif /* if HAVE_OPENSSL_ED448 */
+ if (siglen == 0) {
+ DST_RET(ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (sig->length != siglen) {
+ DST_RET(DST_R_VERIFYFAILURE);
+ }
+
+ isc_buffer_usedregion(buf, &tbsreg);
+
+ if (EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey) != 1) {
+ DST_RET(dst__openssl_toresult3(
+ dctx->category, "EVP_DigestVerifyInit", ISC_R_FAILURE));
+ }
+
+ status = EVP_DigestVerify(ctx, sig->base, siglen, tbsreg.base,
+ tbsreg.length);
+
+ switch (status) {
+ case 1:
+ ret = ISC_R_SUCCESS;
+ break;
+ case 0:
+ ret = dst__openssl_toresult(DST_R_VERIFYFAILURE);
+ break;
+ default:
+ ret = dst__openssl_toresult3(dctx->category, "EVP_DigestVerify",
+ DST_R_VERIFYFAILURE);
+ break;
+ }
+
+err:
+ EVP_MD_CTX_free(ctx);
+ isc_buffer_free(&buf);
+ dctx->ctxdata.generic = NULL;
+
+ return (ret);
+}
+
+static bool
+openssleddsa_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ int status;
+ EVP_PKEY *pkey1 = key1->keydata.pkey;
+ EVP_PKEY *pkey2 = key2->keydata.pkey;
+
+ if (pkey1 == NULL && pkey2 == NULL) {
+ return (true);
+ } else if (pkey1 == NULL || pkey2 == NULL) {
+ return (false);
+ }
+
+ status = EVP_PKEY_cmp(pkey1, pkey2);
+ if (status == 1) {
+ return (true);
+ }
+ return (false);
+}
+
+static isc_result_t
+openssleddsa_generate(dst_key_t *key, int unused, void (*callback)(int)) {
+ isc_result_t ret;
+ EVP_PKEY *pkey = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ int nid = 0, status;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+ UNUSED(unused);
+ UNUSED(callback);
+
+#if HAVE_OPENSSL_ED25519
+ if (key->key_alg == DST_ALG_ED25519) {
+ nid = NID_ED25519;
+ key->key_size = DNS_KEY_ED25519SIZE * 8;
+ }
+#endif /* if HAVE_OPENSSL_ED25519 */
+#if HAVE_OPENSSL_ED448
+ if (key->key_alg == DST_ALG_ED448) {
+ nid = NID_ED448;
+ key->key_size = DNS_KEY_ED448SIZE * 8;
+ }
+#endif /* if HAVE_OPENSSL_ED448 */
+ if (nid == 0) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ ctx = EVP_PKEY_CTX_new_id(nid, NULL);
+ if (ctx == NULL) {
+ return (dst__openssl_toresult2("EVP_PKEY_CTX_new_id",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ status = EVP_PKEY_keygen_init(ctx);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ status = EVP_PKEY_keygen(ctx, &pkey);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ key->keydata.pkey = pkey;
+ ret = ISC_R_SUCCESS;
+
+err:
+ EVP_PKEY_CTX_free(ctx);
+ return (ret);
+}
+
+static bool
+openssleddsa_isprivate(const dst_key_t *key) {
+ EVP_PKEY *pkey = key->keydata.pkey;
+ size_t len;
+
+ if (pkey == NULL) {
+ return (false);
+ }
+
+ if (EVP_PKEY_get_raw_private_key(pkey, NULL, &len) == 1 && len > 0) {
+ return (true);
+ }
+ /* can check if first error is EC_R_INVALID_PRIVATE_KEY */
+ while (ERR_get_error() != 0) {
+ /**/
+ }
+
+ return (false);
+}
+
+static void
+openssleddsa_destroy(dst_key_t *key) {
+ EVP_PKEY *pkey = key->keydata.pkey;
+
+ EVP_PKEY_free(pkey);
+ key->keydata.pkey = NULL;
+}
+
+static isc_result_t
+openssleddsa_todns(const dst_key_t *key, isc_buffer_t *data) {
+ EVP_PKEY *pkey = key->keydata.pkey;
+ isc_region_t r;
+ size_t len;
+
+ REQUIRE(pkey != NULL);
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ if (key->key_alg == DST_ALG_ED25519) {
+ len = DNS_KEY_ED25519SIZE;
+ } else {
+ len = DNS_KEY_ED448SIZE;
+ }
+
+ isc_buffer_availableregion(data, &r);
+ if (r.length < len) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (EVP_PKEY_get_raw_public_key(pkey, r.base, &len) != 1) {
+ return (dst__openssl_toresult(ISC_R_FAILURE));
+ }
+
+ isc_buffer_add(data, len);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+openssleddsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ isc_result_t ret;
+ isc_region_t r;
+ size_t len;
+ EVP_PKEY *pkey;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ len = r.length;
+ ret = raw_key_to_ossl(key->key_alg, 0, r.base, &len, &pkey);
+ if (ret != ISC_R_SUCCESS) {
+ return ret;
+ }
+
+ isc_buffer_forward(data, len);
+ key->keydata.pkey = pkey;
+ key->key_size = len * 8;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+openssleddsa_tofile(const dst_key_t *key, const char *directory) {
+ isc_result_t ret;
+ dst_private_t priv;
+ unsigned char *buf = NULL;
+ size_t len;
+ int i;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ if (key->keydata.pkey == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ priv.nelements = 0;
+ return (dst__privstruct_writefile(key, &priv, directory));
+ }
+
+ i = 0;
+
+ if (openssleddsa_isprivate(key)) {
+ if (key->key_alg == DST_ALG_ED25519) {
+ len = DNS_KEY_ED25519SIZE;
+ } else {
+ len = DNS_KEY_ED448SIZE;
+ }
+ buf = isc_mem_get(key->mctx, len);
+ if (EVP_PKEY_get_raw_private_key(key->keydata.pkey, buf,
+ &len) != 1)
+ {
+ DST_RET(dst__openssl_toresult(ISC_R_FAILURE));
+ }
+ priv.elements[i].tag = TAG_EDDSA_PRIVATEKEY;
+ priv.elements[i].length = len;
+ priv.elements[i].data = buf;
+ i++;
+ }
+ if (key->engine != NULL) {
+ priv.elements[i].tag = TAG_EDDSA_ENGINE;
+ priv.elements[i].length = (unsigned short)strlen(key->engine) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->engine;
+ i++;
+ }
+ if (key->label != NULL) {
+ priv.elements[i].tag = TAG_EDDSA_LABEL;
+ priv.elements[i].length = (unsigned short)strlen(key->label) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->label;
+ i++;
+ }
+
+ priv.nelements = i;
+ ret = dst__privstruct_writefile(key, &priv, directory);
+
+err:
+ if (buf != NULL) {
+ isc_mem_put(key->mctx, buf, len);
+ }
+ return (ret);
+}
+
+static isc_result_t
+eddsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) {
+ if (pubpkey == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ if (EVP_PKEY_cmp(pkey, pubpkey) == 1) {
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_FAILURE);
+}
+
+static isc_result_t
+openssleddsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t ret;
+ int i, privkey_index = -1;
+ const char *engine = NULL, *label = NULL;
+ EVP_PKEY *pkey = NULL, *pubpkey = NULL;
+ size_t len;
+ isc_mem_t *mctx = key->mctx;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ /* read private key file */
+ ret = dst__privstruct_parse(key, DST_ALG_ED25519, lexer, mctx, &priv);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ if (key->external) {
+ if (priv.nelements != 0) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ if (pub == NULL) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ key->keydata.pkey = pub->keydata.pkey;
+ pub->keydata.pkey = NULL;
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (ISC_R_SUCCESS);
+ }
+
+ if (pub != NULL) {
+ pubpkey = pub->keydata.pkey;
+ }
+
+ for (i = 0; i < priv.nelements; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_EDDSA_ENGINE:
+ engine = (char *)priv.elements[i].data;
+ break;
+ case TAG_EDDSA_LABEL:
+ label = (char *)priv.elements[i].data;
+ break;
+ case TAG_EDDSA_PRIVATEKEY:
+ privkey_index = i;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (label != NULL) {
+ ret = openssleddsa_fromlabel(key, engine, label, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ if (eddsa_check(key->keydata.pkey, pubpkey) != ISC_R_SUCCESS) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ DST_RET(ISC_R_SUCCESS);
+ }
+
+ if (privkey_index < 0) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ len = priv.elements[privkey_index].length;
+ ret = raw_key_to_ossl(key->key_alg, 1,
+ priv.elements[privkey_index].data, &len, &pkey);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ if (eddsa_check(pkey, pubpkey) != ISC_R_SUCCESS) {
+ EVP_PKEY_free(pkey);
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ key->keydata.pkey = pkey;
+ key->key_size = len * 8;
+ ret = ISC_R_SUCCESS;
+
+err:
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (ret);
+}
+
+static isc_result_t
+openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin) {
+#if !defined(OPENSSL_NO_ENGINE)
+ isc_result_t ret;
+ ENGINE *e;
+ EVP_PKEY *pkey = NULL, *pubpkey = NULL;
+ int baseid = EVP_PKEY_NONE;
+
+ UNUSED(pin);
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+#if HAVE_OPENSSL_ED25519
+ if (key->key_alg == DST_ALG_ED25519) {
+ baseid = EVP_PKEY_ED25519;
+ }
+#endif /* if HAVE_OPENSSL_ED25519 */
+#if HAVE_OPENSSL_ED448
+ if (key->key_alg == DST_ALG_ED448) {
+ baseid = EVP_PKEY_ED448;
+ }
+#endif /* if HAVE_OPENSSL_ED448 */
+ if (baseid == EVP_PKEY_NONE) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (engine == NULL) {
+ return (DST_R_NOENGINE);
+ }
+ e = dst__openssl_getengine(engine);
+ if (e == NULL) {
+ return (DST_R_NOENGINE);
+ }
+ pkey = ENGINE_load_private_key(e, label, NULL, NULL);
+ if (pkey == NULL) {
+ return (dst__openssl_toresult2("ENGINE_load_private_key",
+ ISC_R_NOTFOUND));
+ }
+ if (EVP_PKEY_base_id(pkey) != baseid) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
+ if (eddsa_check(pkey, pubpkey) != ISC_R_SUCCESS) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ key->label = isc_mem_strdup(key->mctx, label);
+ key->key_size = EVP_PKEY_bits(pkey);
+ key->keydata.pkey = pkey;
+ pkey = NULL;
+ ret = ISC_R_SUCCESS;
+
+err:
+ if (pubpkey != NULL) {
+ EVP_PKEY_free(pubpkey);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ return (ret);
+#else /* if !defined(OPENSSL_NO_ENGINE) */
+ UNUSED(key);
+ UNUSED(engine);
+ UNUSED(label);
+ UNUSED(pin);
+ return (DST_R_NOENGINE);
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+}
+
+static dst_func_t openssleddsa_functions = {
+ openssleddsa_createctx,
+ NULL, /*%< createctx2 */
+ openssleddsa_destroyctx,
+ openssleddsa_adddata,
+ openssleddsa_sign,
+ openssleddsa_verify,
+ NULL, /*%< verify2 */
+ NULL, /*%< computesecret */
+ openssleddsa_compare,
+ NULL, /*%< paramcompare */
+ openssleddsa_generate,
+ openssleddsa_isprivate,
+ openssleddsa_destroy,
+ openssleddsa_todns,
+ openssleddsa_fromdns,
+ openssleddsa_tofile,
+ openssleddsa_parse,
+ NULL, /*%< cleanup */
+ openssleddsa_fromlabel,
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+isc_result_t
+dst__openssleddsa_init(dst_func_t **funcp) {
+ REQUIRE(funcp != NULL);
+ if (*funcp == NULL) {
+ *funcp = &openssleddsa_functions;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+#endif /* HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 */
+
+#endif /* !USE_PKCS11 */
+
+/*! \file */
diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c
new file mode 100644
index 0000000..1f2a2e0
--- /dev/null
+++ b/lib/dns/opensslrsa_link.c
@@ -0,0 +1,1405 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if !USE_PKCS11
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/objects.h>
+#include <openssl/rsa.h>
+
+#include <isc/mem.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <pk11/site.h>
+
+#include <dst/result.h>
+
+#include "dst_internal.h"
+#include "dst_openssl.h"
+#include "dst_parse.h"
+#if !defined(OPENSSL_NO_ENGINE)
+#include <openssl/engine.h>
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+
+/*
+ * Limit the size of public exponents.
+ */
+#ifndef RSA_MAX_PUBEXP_BITS
+#define RSA_MAX_PUBEXP_BITS 35
+#endif /* ifndef RSA_MAX_PUBEXP_BITS */
+
+/*
+ * We don't use configure for windows so enforce the OpenSSL version
+ * here. Unlike with configure we don't support overriding this test.
+ */
+#if defined(WIN32) && (OPENSSL_VERSION_NUMBER < 0x10000000L)
+#error Please upgrade OpenSSL to 1.0.0 or greater.
+#endif /* if defined(WIN32) && (OPENSSL_VERSION_NUMBER < 0x10000000L) */
+
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+#if !HAVE_RSA_SET0_KEY
+/* From OpenSSL 1.1.0 */
+static int
+RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) {
+ /*
+ * If the fields n and e in r are NULL, the corresponding input
+ * parameters MUST be non-NULL for n and e. d may be
+ * left NULL (in case only the public key is used).
+ */
+ if ((r->n == NULL && n == NULL) || (r->e == NULL && e == NULL)) {
+ return (0);
+ }
+
+ if (n != NULL) {
+ BN_free(r->n);
+ r->n = n;
+ }
+ if (e != NULL) {
+ BN_free(r->e);
+ r->e = e;
+ }
+ if (d != NULL) {
+ BN_free(r->d);
+ r->d = d;
+ }
+
+ return (1);
+}
+
+static int
+RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) {
+ /*
+ * If the fields p and q in r are NULL, the corresponding input
+ * parameters MUST be non-NULL.
+ */
+ if ((r->p == NULL && p == NULL) || (r->q == NULL && q == NULL)) {
+ return (0);
+ }
+
+ if (p != NULL) {
+ BN_free(r->p);
+ r->p = p;
+ }
+ if (q != NULL) {
+ BN_free(r->q);
+ r->q = q;
+ }
+
+ return (1);
+}
+
+static int
+RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) {
+ /*
+ * If the fields dmp1, dmq1 and iqmp in r are NULL, the
+ * corresponding input parameters MUST be non-NULL.
+ */
+ if ((r->dmp1 == NULL && dmp1 == NULL) ||
+ (r->dmq1 == NULL && dmq1 == NULL) ||
+ (r->iqmp == NULL && iqmp == NULL))
+ {
+ return (0);
+ }
+
+ if (dmp1 != NULL) {
+ BN_free(r->dmp1);
+ r->dmp1 = dmp1;
+ }
+ if (dmq1 != NULL) {
+ BN_free(r->dmq1);
+ r->dmq1 = dmq1;
+ }
+ if (iqmp != NULL) {
+ BN_free(r->iqmp);
+ r->iqmp = iqmp;
+ }
+
+ return (1);
+}
+
+static void
+RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e,
+ const BIGNUM **d) {
+ if (n != NULL) {
+ *n = r->n;
+ }
+ if (e != NULL) {
+ *e = r->e;
+ }
+ if (d != NULL) {
+ *d = r->d;
+ }
+}
+
+static void
+RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) {
+ if (p != NULL) {
+ *p = r->p;
+ }
+ if (q != NULL) {
+ *q = r->q;
+ }
+}
+
+static void
+RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1,
+ const BIGNUM **iqmp) {
+ if (dmp1 != NULL) {
+ *dmp1 = r->dmp1;
+ }
+ if (dmq1 != NULL) {
+ *dmq1 = r->dmq1;
+ }
+ if (iqmp != NULL) {
+ *iqmp = r->iqmp;
+ }
+}
+
+static int
+RSA_test_flags(const RSA *r, int flags) {
+ return (r->flags & flags);
+}
+
+#endif /* !HAVE_RSA_SET0_KEY */
+
+static isc_result_t
+opensslrsa_createctx(dst_key_t *key, dst_context_t *dctx) {
+ EVP_MD_CTX *evp_md_ctx;
+ const EVP_MD *type = NULL;
+
+ UNUSED(key);
+ REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_RSASHA256 ||
+ dctx->key->key_alg == DST_ALG_RSASHA512);
+
+ /*
+ * Reject incorrect RSA key lengths.
+ */
+ switch (dctx->key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ /* From RFC 3110 */
+ if (dctx->key->key_size > 4096) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA256:
+ /* From RFC 5702 */
+ if ((dctx->key->key_size < 512) || (dctx->key->key_size > 4096))
+ {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA512:
+ /* From RFC 5702 */
+ if ((dctx->key->key_size < 1024) ||
+ (dctx->key->key_size > 4096))
+ {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ evp_md_ctx = EVP_MD_CTX_create();
+ if (evp_md_ctx == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ switch (dctx->key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ type = EVP_sha1(); /* SHA1 + RSA */
+ break;
+ case DST_ALG_RSASHA256:
+ type = EVP_sha256(); /* SHA256 + RSA */
+ break;
+ case DST_ALG_RSASHA512:
+ type = EVP_sha512();
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (!EVP_DigestInit_ex(evp_md_ctx, type, NULL)) {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ return (dst__openssl_toresult3(
+ dctx->category, "EVP_DigestInit_ex", ISC_R_FAILURE));
+ }
+ dctx->ctxdata.evp_md_ctx = evp_md_ctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+opensslrsa_destroyctx(dst_context_t *dctx) {
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_RSASHA256 ||
+ dctx->key->key_alg == DST_ALG_RSASHA512);
+
+ if (evp_md_ctx != NULL) {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ dctx->ctxdata.evp_md_ctx = NULL;
+ }
+}
+
+static isc_result_t
+opensslrsa_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_RSASHA256 ||
+ dctx->key->key_alg == DST_ALG_RSASHA512);
+
+ if (!EVP_DigestUpdate(evp_md_ctx, data->base, data->length)) {
+ return (dst__openssl_toresult3(
+ dctx->category, "EVP_DigestUpdate", ISC_R_FAILURE));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+opensslrsa_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ dst_key_t *key = dctx->key;
+ isc_region_t r;
+ unsigned int siglen = 0;
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+ EVP_PKEY *pkey = key->keydata.pkey;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_RSASHA256 ||
+ dctx->key->key_alg == DST_ALG_RSASHA512);
+
+ isc_buffer_availableregion(sig, &r);
+
+ if (r.length < (unsigned int)EVP_PKEY_size(pkey)) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (!EVP_SignFinal(evp_md_ctx, r.base, &siglen, pkey)) {
+ return (dst__openssl_toresult3(dctx->category, "EVP_SignFinal",
+ ISC_R_FAILURE));
+ }
+
+ isc_buffer_add(sig, siglen);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+opensslrsa_verify2(dst_context_t *dctx, int maxbits, const isc_region_t *sig) {
+ dst_key_t *key = dctx->key;
+ int status = 0;
+ const BIGNUM *e = NULL;
+ EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx;
+ EVP_PKEY *pkey = key->keydata.pkey;
+ RSA *rsa;
+ int bits;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ dctx->key->key_alg == DST_ALG_RSASHA256 ||
+ dctx->key->key_alg == DST_ALG_RSASHA512);
+
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ RSA_get0_key(rsa, NULL, &e, NULL);
+ if (e == NULL) {
+ RSA_free(rsa);
+ return (dst__openssl_toresult(DST_R_VERIFYFAILURE));
+ }
+ bits = BN_num_bits(e);
+ RSA_free(rsa);
+ if (bits > maxbits && maxbits != 0) {
+ return (DST_R_VERIFYFAILURE);
+ }
+
+ status = EVP_VerifyFinal(evp_md_ctx, sig->base, sig->length, pkey);
+ switch (status) {
+ case 1:
+ return (ISC_R_SUCCESS);
+ case 0:
+ return (dst__openssl_toresult(DST_R_VERIFYFAILURE));
+ default:
+ return (dst__openssl_toresult3(dctx->category,
+ "EVP_VerifyFinal",
+ DST_R_VERIFYFAILURE));
+ }
+}
+
+static isc_result_t
+opensslrsa_verify(dst_context_t *dctx, const isc_region_t *sig) {
+ return (opensslrsa_verify2(dctx, 0, sig));
+}
+
+static bool
+opensslrsa_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ int status;
+ RSA *rsa1 = NULL, *rsa2 = NULL;
+ const BIGNUM *n1 = NULL, *n2 = NULL;
+ const BIGNUM *e1 = NULL, *e2 = NULL;
+ const BIGNUM *d1 = NULL, *d2 = NULL;
+ const BIGNUM *p1 = NULL, *p2 = NULL;
+ const BIGNUM *q1 = NULL, *q2 = NULL;
+ EVP_PKEY *pkey1, *pkey2;
+
+ pkey1 = key1->keydata.pkey;
+ pkey2 = key2->keydata.pkey;
+ /*
+ * The pkey reference will keep these around after
+ * the RSA_free() call.
+ */
+ if (pkey1 != NULL) {
+ rsa1 = EVP_PKEY_get1_RSA(pkey1);
+ RSA_free(rsa1);
+ }
+ if (pkey2 != NULL) {
+ rsa2 = EVP_PKEY_get1_RSA(pkey2);
+ RSA_free(rsa2);
+ }
+
+ if (rsa1 == NULL && rsa2 == NULL) {
+ return (true);
+ } else if (rsa1 == NULL || rsa2 == NULL) {
+ return (false);
+ }
+
+ RSA_get0_key(rsa1, &n1, &e1, &d1);
+ RSA_get0_key(rsa2, &n2, &e2, &d2);
+ status = BN_cmp(n1, n2) || BN_cmp(e1, e2);
+
+ if (status != 0) {
+ return (false);
+ }
+
+ if (RSA_test_flags(rsa1, RSA_FLAG_EXT_PKEY) != 0 ||
+ RSA_test_flags(rsa2, RSA_FLAG_EXT_PKEY) != 0)
+ {
+ if (RSA_test_flags(rsa1, RSA_FLAG_EXT_PKEY) == 0 ||
+ RSA_test_flags(rsa2, RSA_FLAG_EXT_PKEY) == 0)
+ {
+ return (false);
+ }
+ /*
+ * Can't compare private parameters, BTW does it make sense?
+ */
+ return (true);
+ }
+
+ if (d1 != NULL || d2 != NULL) {
+ if (d1 == NULL || d2 == NULL) {
+ return (false);
+ }
+ RSA_get0_factors(rsa1, &p1, &q1);
+ RSA_get0_factors(rsa2, &p2, &q2);
+ status = BN_cmp(d1, d2) || BN_cmp(p1, p2) || BN_cmp(q1, q2);
+
+ if (status != 0) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+static int
+progress_cb(int p, int n, BN_GENCB *cb) {
+ union {
+ void *dptr;
+ void (*fptr)(int);
+ } u;
+
+ UNUSED(n);
+
+ u.dptr = BN_GENCB_get_arg(cb);
+ if (u.fptr != NULL) {
+ u.fptr(p);
+ }
+ return (1);
+}
+
+static isc_result_t
+opensslrsa_generate(dst_key_t *key, int exp, void (*callback)(int)) {
+ isc_result_t ret = DST_R_OPENSSLFAILURE;
+ union {
+ void *dptr;
+ void (*fptr)(int);
+ } u;
+ RSA *rsa = RSA_new();
+ BIGNUM *e = BN_new();
+#if !HAVE_BN_GENCB_NEW
+ BN_GENCB _cb;
+#endif /* !HAVE_BN_GENCB_NEW */
+ BN_GENCB *cb = BN_GENCB_new();
+ EVP_PKEY *pkey = EVP_PKEY_new();
+
+ /*
+ * Reject incorrect RSA key lengths.
+ */
+ switch (key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ /* From RFC 3110 */
+ if (key->key_size > 4096) {
+ goto err;
+ }
+ break;
+ case DST_ALG_RSASHA256:
+ /* From RFC 5702 */
+ if ((key->key_size < 512) || (key->key_size > 4096)) {
+ goto err;
+ }
+ break;
+ case DST_ALG_RSASHA512:
+ /* From RFC 5702 */
+ if ((key->key_size < 1024) || (key->key_size > 4096)) {
+ goto err;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (rsa == NULL || e == NULL || cb == NULL) {
+ goto err;
+ }
+ if (pkey == NULL) {
+ goto err;
+ }
+ if (!EVP_PKEY_set1_RSA(pkey, rsa)) {
+ goto err;
+ }
+
+ if (exp == 0) {
+ /* RSA_F4 0x10001 */
+ BN_set_bit(e, 0);
+ BN_set_bit(e, 16);
+ } else {
+ /* (phased-out) F5 0x100000001 */
+ BN_set_bit(e, 0);
+ BN_set_bit(e, 32);
+ }
+
+ if (callback == NULL) {
+ BN_GENCB_set_old(cb, NULL, NULL);
+ } else {
+ u.fptr = callback;
+ BN_GENCB_set(cb, progress_cb, u.dptr);
+ }
+
+ if (RSA_generate_key_ex(rsa, key->key_size, e, cb)) {
+ BN_free(e);
+ BN_GENCB_free(cb);
+ cb = NULL;
+ key->keydata.pkey = pkey;
+
+ RSA_free(rsa);
+ return (ISC_R_SUCCESS);
+ }
+ ret = dst__openssl_toresult2("RSA_generate_key_ex",
+ DST_R_OPENSSLFAILURE);
+
+err:
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+ if (e != NULL) {
+ BN_free(e);
+ e = NULL;
+ }
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ rsa = NULL;
+ }
+ if (cb != NULL) {
+ BN_GENCB_free(cb);
+ cb = NULL;
+ }
+ return (dst__openssl_toresult(ret));
+}
+
+static bool
+opensslrsa_isprivate(const dst_key_t *key) {
+ const BIGNUM *d = NULL;
+ RSA *rsa = EVP_PKEY_get1_RSA(key->keydata.pkey);
+ INSIST(rsa != NULL);
+ RSA_free(rsa);
+ /* key->keydata.pkey still has a reference so rsa is still valid. */
+ if (rsa != NULL && RSA_test_flags(rsa, RSA_FLAG_EXT_PKEY) != 0) {
+ return (true);
+ }
+ RSA_get0_key(rsa, NULL, NULL, &d);
+ return (rsa != NULL && d != NULL);
+}
+
+static void
+opensslrsa_destroy(dst_key_t *key) {
+ EVP_PKEY *pkey = key->keydata.pkey;
+ EVP_PKEY_free(pkey);
+ key->keydata.pkey = NULL;
+}
+
+static isc_result_t
+opensslrsa_todns(const dst_key_t *key, isc_buffer_t *data) {
+ isc_region_t r;
+ unsigned int e_bytes;
+ unsigned int mod_bytes;
+ isc_result_t ret;
+ RSA *rsa;
+ EVP_PKEY *pkey;
+ const BIGNUM *e = NULL, *n = NULL;
+
+ REQUIRE(key->keydata.pkey != NULL);
+
+ pkey = key->keydata.pkey;
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ RSA_get0_key(rsa, &n, &e, NULL);
+ if (e == NULL || n == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ mod_bytes = BN_num_bytes(n);
+ e_bytes = BN_num_bytes(e);
+
+ isc_buffer_availableregion(data, &r);
+
+ if (e_bytes < 256) { /*%< key exponent is <= 2040 bits */
+ if (r.length < 1) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(data, (uint8_t)e_bytes);
+ isc_region_consume(&r, 1);
+ } else {
+ if (r.length < 3) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(data, 0);
+ isc_buffer_putuint16(data, (uint16_t)e_bytes);
+ isc_region_consume(&r, 3);
+ }
+
+ if (r.length < e_bytes + mod_bytes) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+
+ RSA_get0_key(rsa, &n, &e, NULL);
+ BN_bn2bin(e, r.base);
+ isc_region_consume(&r, e_bytes);
+ BN_bn2bin(n, r.base);
+
+ isc_buffer_add(data, e_bytes + mod_bytes);
+
+ ret = ISC_R_SUCCESS;
+err:
+ RSA_free(rsa);
+ return (ret);
+}
+
+static isc_result_t
+opensslrsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ RSA *rsa;
+ isc_region_t r;
+ unsigned int e_bytes;
+ unsigned int length;
+ EVP_PKEY *pkey;
+ BIGNUM *e = NULL, *n = NULL;
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ length = r.length;
+
+ rsa = RSA_new();
+ if (rsa == NULL) {
+ return (dst__openssl_toresult(ISC_R_NOMEMORY));
+ }
+
+ if (r.length < 1) {
+ RSA_free(rsa);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ e_bytes = *r.base;
+ isc_region_consume(&r, 1);
+
+ if (e_bytes == 0) {
+ if (r.length < 2) {
+ RSA_free(rsa);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ e_bytes = (*r.base) << 8;
+ isc_region_consume(&r, 1);
+ e_bytes += *r.base;
+ isc_region_consume(&r, 1);
+ }
+
+ if (r.length < e_bytes) {
+ RSA_free(rsa);
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+ e = BN_bin2bn(r.base, e_bytes, NULL);
+ isc_region_consume(&r, e_bytes);
+ n = BN_bin2bn(r.base, r.length, NULL);
+ if (e == NULL || n == NULL) {
+ RSA_free(rsa);
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (RSA_set0_key(rsa, n, e, NULL) == 0) {
+ if (n != NULL) {
+ BN_free(n);
+ }
+ if (e != NULL) {
+ BN_free(e);
+ }
+ RSA_free(rsa);
+ return (ISC_R_NOMEMORY);
+ }
+ key->key_size = BN_num_bits(n);
+
+ isc_buffer_forward(data, length);
+
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ RSA_free(rsa);
+ return (ISC_R_NOMEMORY);
+ }
+ if (!EVP_PKEY_set1_RSA(pkey, rsa)) {
+ EVP_PKEY_free(pkey);
+ RSA_free(rsa);
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ key->keydata.pkey = pkey;
+ RSA_free(rsa);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+opensslrsa_tofile(const dst_key_t *key, const char *directory) {
+ int i;
+ RSA *rsa;
+ dst_private_t priv;
+ unsigned char *bufs[8];
+ isc_result_t result;
+ const BIGNUM *n = NULL, *e = NULL, *d = NULL;
+ const BIGNUM *p = NULL, *q = NULL;
+ const BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
+
+ if (key->external) {
+ priv.nelements = 0;
+ return (dst__privstruct_writefile(key, &priv, directory));
+ }
+
+ if (key->keydata.pkey == NULL) {
+ return (DST_R_NULLKEY);
+ }
+ rsa = EVP_PKEY_get1_RSA(key->keydata.pkey);
+ if (rsa == NULL) {
+ return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ memset(bufs, 0, sizeof(bufs));
+
+ RSA_get0_key(rsa, &n, &e, &d);
+ RSA_get0_factors(rsa, &p, &q);
+ RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
+
+ for (i = 0; i < 8; i++) {
+ bufs[i] = isc_mem_get(key->mctx, BN_num_bytes(n));
+ }
+
+ i = 0;
+
+ priv.elements[i].tag = TAG_RSA_MODULUS;
+ priv.elements[i].length = BN_num_bytes(n);
+ BN_bn2bin(n, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.elements[i].tag = TAG_RSA_PUBLICEXPONENT;
+ priv.elements[i].length = BN_num_bytes(e);
+ BN_bn2bin(e, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ if (d != NULL) {
+ priv.elements[i].tag = TAG_RSA_PRIVATEEXPONENT;
+ priv.elements[i].length = BN_num_bytes(d);
+ BN_bn2bin(d, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (p != NULL) {
+ priv.elements[i].tag = TAG_RSA_PRIME1;
+ priv.elements[i].length = BN_num_bytes(p);
+ BN_bn2bin(p, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (q != NULL) {
+ priv.elements[i].tag = TAG_RSA_PRIME2;
+ priv.elements[i].length = BN_num_bytes(q);
+ BN_bn2bin(q, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (dmp1 != NULL) {
+ priv.elements[i].tag = TAG_RSA_EXPONENT1;
+ priv.elements[i].length = BN_num_bytes(dmp1);
+ BN_bn2bin(dmp1, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (dmq1 != NULL) {
+ priv.elements[i].tag = TAG_RSA_EXPONENT2;
+ priv.elements[i].length = BN_num_bytes(dmq1);
+ BN_bn2bin(dmq1, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (iqmp != NULL) {
+ priv.elements[i].tag = TAG_RSA_COEFFICIENT;
+ priv.elements[i].length = BN_num_bytes(iqmp);
+ BN_bn2bin(iqmp, bufs[i]);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (key->engine != NULL) {
+ priv.elements[i].tag = TAG_RSA_ENGINE;
+ priv.elements[i].length = (unsigned short)strlen(key->engine) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->engine;
+ i++;
+ }
+
+ if (key->label != NULL) {
+ priv.elements[i].tag = TAG_RSA_LABEL;
+ priv.elements[i].length = (unsigned short)strlen(key->label) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->label;
+ i++;
+ }
+
+ priv.nelements = i;
+ result = dst__privstruct_writefile(key, &priv, directory);
+
+ RSA_free(rsa);
+ for (i = 0; i < 8; i++) {
+ if (bufs[i] == NULL) {
+ break;
+ }
+ isc_mem_put(key->mctx, bufs[i], BN_num_bytes(n));
+ }
+ return (result);
+}
+
+static isc_result_t
+rsa_check(RSA *rsa, RSA *pub) {
+ const BIGNUM *n1 = NULL, *n2 = NULL;
+ const BIGNUM *e1 = NULL, *e2 = NULL;
+ BIGNUM *n = NULL, *e = NULL;
+
+ /*
+ * Public parameters should be the same but if they are not set
+ * copy them from the public key.
+ */
+ RSA_get0_key(rsa, &n1, &e1, NULL);
+ if (pub != NULL) {
+ RSA_get0_key(pub, &n2, &e2, NULL);
+ if (n1 != NULL) {
+ if (BN_cmp(n1, n2) != 0) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+ } else {
+ n = BN_dup(n2);
+ if (n == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ }
+ if (e1 != NULL) {
+ if (BN_cmp(e1, e2) != 0) {
+ if (n != NULL) {
+ BN_free(n);
+ }
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+ } else {
+ e = BN_dup(e2);
+ if (e == NULL) {
+ if (n != NULL) {
+ BN_free(n);
+ }
+ return (ISC_R_NOMEMORY);
+ }
+ }
+ if (RSA_set0_key(rsa, n, e, NULL) == 0) {
+ if (n != NULL) {
+ BN_free(n);
+ }
+ if (e != NULL) {
+ BN_free(e);
+ }
+ }
+ }
+ RSA_get0_key(rsa, &n1, &e1, NULL);
+ if (n1 == NULL || e1 == NULL) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t ret;
+ int i;
+ RSA *rsa = NULL, *pubrsa = NULL;
+#if !defined(OPENSSL_NO_ENGINE)
+ ENGINE *ep = NULL;
+ const BIGNUM *ex = NULL;
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+ isc_mem_t *mctx = key->mctx;
+ const char *engine = NULL, *label = NULL;
+ EVP_PKEY *pkey = NULL;
+ BIGNUM *n = NULL, *e = NULL, *d = NULL;
+ BIGNUM *p = NULL, *q = NULL;
+ BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
+
+ /* read private key file */
+ ret = dst__privstruct_parse(key, DST_ALG_RSA, lexer, mctx, &priv);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ if (key->external) {
+ if (priv.nelements != 0) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ if (pub == NULL) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ key->keydata.pkey = pub->keydata.pkey;
+ pub->keydata.pkey = NULL;
+ key->key_size = pub->key_size;
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (ISC_R_SUCCESS);
+ }
+
+ if (pub != NULL && pub->keydata.pkey != NULL) {
+ pubrsa = EVP_PKEY_get1_RSA(pub->keydata.pkey);
+ }
+
+ for (i = 0; i < priv.nelements; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_RSA_ENGINE:
+ engine = (char *)priv.elements[i].data;
+ break;
+ case TAG_RSA_LABEL:
+ label = (char *)priv.elements[i].data;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Is this key is stored in a HSM?
+ * See if we can fetch it.
+ */
+ if (label != NULL) {
+#if !defined(OPENSSL_NO_ENGINE)
+ if (engine == NULL) {
+ DST_RET(DST_R_NOENGINE);
+ }
+ ep = dst__openssl_getengine(engine);
+ if (ep == NULL) {
+ DST_RET(DST_R_NOENGINE);
+ }
+ pkey = ENGINE_load_private_key(ep, label, NULL, NULL);
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("ENGINE_load_private_"
+ "key",
+ ISC_R_NOTFOUND));
+ }
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ key->label = isc_mem_strdup(key->mctx, label);
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ RSA_get0_key(rsa, NULL, &ex, NULL);
+ if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+ if (pubrsa != NULL) {
+ RSA_free(pubrsa);
+ }
+ key->key_size = EVP_PKEY_bits(pkey);
+ key->keydata.pkey = pkey;
+ RSA_free(rsa);
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (ISC_R_SUCCESS);
+#else /* if !defined(OPENSSL_NO_ENGINE) */
+ DST_RET(DST_R_NOENGINE);
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+ }
+
+ rsa = RSA_new();
+ if (rsa == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ if (!EVP_PKEY_set1_RSA(pkey, rsa)) {
+ DST_RET(ISC_R_FAILURE);
+ }
+ key->keydata.pkey = pkey;
+
+ for (i = 0; i < priv.nelements; i++) {
+ BIGNUM *bn;
+ switch (priv.elements[i].tag) {
+ case TAG_RSA_ENGINE:
+ continue;
+ case TAG_RSA_LABEL:
+ continue;
+ default:
+ bn = BN_bin2bn(priv.elements[i].data,
+ priv.elements[i].length, NULL);
+ if (bn == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ switch (priv.elements[i].tag) {
+ case TAG_RSA_MODULUS:
+ n = bn;
+ break;
+ case TAG_RSA_PUBLICEXPONENT:
+ e = bn;
+ break;
+ case TAG_RSA_PRIVATEEXPONENT:
+ d = bn;
+ break;
+ case TAG_RSA_PRIME1:
+ p = bn;
+ break;
+ case TAG_RSA_PRIME2:
+ q = bn;
+ break;
+ case TAG_RSA_EXPONENT1:
+ dmp1 = bn;
+ break;
+ case TAG_RSA_EXPONENT2:
+ dmq1 = bn;
+ break;
+ case TAG_RSA_COEFFICIENT:
+ iqmp = bn;
+ break;
+ }
+ }
+ }
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+
+ if (RSA_set0_key(rsa, n, e, d) == 0) {
+ if (n != NULL) {
+ BN_free(n);
+ }
+ if (e != NULL) {
+ BN_free(e);
+ }
+ if (d != NULL) {
+ BN_free(d);
+ }
+ }
+ if (RSA_set0_factors(rsa, p, q) == 0) {
+ if (p != NULL) {
+ BN_free(p);
+ }
+ if (q != NULL) {
+ BN_free(q);
+ }
+ }
+ if (RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp) == 0) {
+ if (dmp1 != NULL) {
+ BN_free(dmp1);
+ }
+ if (dmq1 != NULL) {
+ BN_free(dmq1);
+ }
+ if (iqmp != NULL) {
+ BN_free(iqmp);
+ }
+ }
+
+ if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ if (BN_num_bits(e) > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+ key->key_size = BN_num_bits(n);
+ if (pubrsa != NULL) {
+ RSA_free(pubrsa);
+ }
+ RSA_free(rsa);
+
+ return (ISC_R_SUCCESS);
+
+err:
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ }
+ if (pubrsa != NULL) {
+ RSA_free(pubrsa);
+ }
+ key->keydata.generic = NULL;
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (ret);
+}
+
+static isc_result_t
+opensslrsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin) {
+#if !defined(OPENSSL_NO_ENGINE)
+ ENGINE *e = NULL;
+ isc_result_t ret;
+ EVP_PKEY *pkey = NULL;
+ RSA *rsa = NULL, *pubrsa = NULL;
+ const BIGNUM *ex = NULL;
+
+ UNUSED(pin);
+
+ if (engine == NULL) {
+ DST_RET(DST_R_NOENGINE);
+ }
+ e = dst__openssl_getengine(engine);
+ if (e == NULL) {
+ DST_RET(DST_R_NOENGINE);
+ }
+ pkey = ENGINE_load_public_key(e, label, NULL, NULL);
+ if (pkey != NULL) {
+ pubrsa = EVP_PKEY_get1_RSA(pkey);
+ EVP_PKEY_free(pkey);
+ if (pubrsa == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ }
+ pkey = ENGINE_load_private_key(e, label, NULL, NULL);
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("ENGINE_load_private_key",
+ ISC_R_NOTFOUND));
+ }
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ key->label = isc_mem_strdup(key->mctx, label);
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ RSA_get0_key(rsa, NULL, &ex, NULL);
+ if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+ if (pubrsa != NULL) {
+ RSA_free(pubrsa);
+ }
+ key->key_size = EVP_PKEY_bits(pkey);
+ key->keydata.pkey = pkey;
+ RSA_free(rsa);
+ return (ISC_R_SUCCESS);
+
+err:
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ }
+ if (pubrsa != NULL) {
+ RSA_free(pubrsa);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ return (ret);
+#else /* if !defined(OPENSSL_NO_ENGINE) */
+ UNUSED(key);
+ UNUSED(engine);
+ UNUSED(label);
+ UNUSED(pin);
+ return (DST_R_NOENGINE);
+#endif /* if !defined(OPENSSL_NO_ENGINE) */
+}
+
+static dst_func_t opensslrsa_functions = {
+ opensslrsa_createctx,
+ NULL, /*%< createctx2 */
+ opensslrsa_destroyctx,
+ opensslrsa_adddata,
+ opensslrsa_sign,
+ opensslrsa_verify,
+ opensslrsa_verify2,
+ NULL, /*%< computesecret */
+ opensslrsa_compare,
+ NULL, /*%< paramcompare */
+ opensslrsa_generate,
+ opensslrsa_isprivate,
+ opensslrsa_destroy,
+ opensslrsa_todns,
+ opensslrsa_fromdns,
+ opensslrsa_tofile,
+ opensslrsa_parse,
+ NULL, /*%< cleanup */
+ opensslrsa_fromlabel,
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+/*
+ * An RSA public key with 2048 bits
+ */
+static const unsigned char e_bytes[] = "\x01\x00\x01";
+static const unsigned char n_bytes[] =
+ "\xc3\x90\x07\xbe\xf1\x85\xfc\x1a\x43\xb1\xa5\x15\xce\x71\x34\xfc\xc1"
+ "\x87\x27\x28\x38\xa4\xcf\x7c\x1a\x82\xa8\xdc\x04\x14\xd0\x3f\xb4\xfe"
+ "\x20\x4a\xdd\xd9\x0d\xd7\xcd\x61\x8c\xbd\x61\xa8\x10\xb5\x63\x1c\x29"
+ "\x15\xcb\x41\xee\x43\x91\x7f\xeb\xa5\x2c\xab\x81\x75\x0d\xa3\x3d\xe4"
+ "\xc8\x49\xb9\xca\x5a\x55\xa1\xbb\x09\xd1\xfb\xcd\xa2\xd2\x12\xa4\x85"
+ "\xdf\xa5\x65\xc9\x27\x2d\x8b\xd7\x8b\xfe\x6d\xc4\xd1\xd9\x83\x1c\x91"
+ "\x7d\x3d\xd0\xa4\xcd\xe1\xe7\xb9\x7a\x11\x38\xf9\x8b\x3c\xec\x30\xb6"
+ "\x36\xb9\x92\x64\x81\x56\x3c\xbc\xf9\x49\xfb\xba\x82\xb7\xa0\xfa\x65"
+ "\x79\x83\xb9\x4c\xa7\xfd\x53\x0b\x5a\xe4\xde\xf9\xfc\x38\x7e\xb5\x2c"
+ "\xa0\xc3\xb2\xfc\x7c\x38\xb0\x63\x50\xaf\x00\xaa\xb2\xad\x49\x54\x1e"
+ "\x8b\x11\x88\x9b\x6e\xae\x3b\x23\xa3\xdd\x53\x51\x80\x7a\x0b\x91\x4e"
+ "\x6d\x32\x01\xbd\x17\x81\x12\x64\x9f\x84\xae\x76\x53\x1a\x63\xa0\xda"
+ "\xcc\x45\x04\x72\xb0\xa7\xfb\xfa\x02\x39\x53\xc1\x83\x1f\x88\x54\x47"
+ "\x88\x63\x20\x71\x5d\xe2\xaa\x7c\x53\x39\x5e\x35\x25\xee\xe6\x5c\x15"
+ "\x5e\x14\xbe\x99\xde\x25\x19\xe7\x13\xdb\xce\xa3\xd3\x6c\x5c\xbb\x0e"
+ "\x6b";
+
+static const unsigned char sha1_sig[] =
+ "\x69\x99\x89\x28\xe0\x38\x34\x91\x29\xb6\xac\x4b\xe9\x51\xbd\xbe\xc8"
+ "\x1a\x2d\xb6\xca\x99\xa3\x9f\x6a\x8b\x94\x5a\x51\x37\xd5\x8d\xae\x87"
+ "\xed\xbc\x8e\xb8\xa3\x60\x6b\xf6\xe6\x72\xfc\x26\x2a\x39\x2b\xfe\x88"
+ "\x1a\xa9\xd1\x93\xc7\xb9\xf8\xb6\x45\xa1\xf9\xa1\x56\x78\x7b\x00\xec"
+ "\x33\x83\xd4\x93\x25\x48\xb3\x50\x09\xd0\xbc\x7f\xac\x67\xc7\xa2\x7f"
+ "\xfc\xf6\x5a\xef\xf8\x5a\xad\x52\x74\xf5\x71\x34\xd9\x3d\x33\x8b\x4d"
+ "\x99\x64\x7e\x14\x59\xbe\xdf\x26\x8a\x67\x96\x6c\x1f\x79\x85\x10\x0d"
+ "\x7f\xd6\xa4\xba\x57\x41\x03\x71\x4e\x8c\x17\xd5\xc4\xfb\x4a\xbe\x66"
+ "\x45\x15\x45\x0c\x02\xe0\x10\xe1\xbb\x33\x8d\x90\x34\x3c\x94\xa4\x4c"
+ "\x7c\xd0\x5e\x90\x76\x80\x59\xb2\xfa\x54\xbf\xa9\x86\xb8\x84\x1e\x28"
+ "\x48\x60\x2f\x9e\xa4\xbc\xd4\x9c\x20\x27\x16\xac\x33\xcb\xcf\xab\x93"
+ "\x7a\x3b\x74\xa0\x18\x92\xa1\x4f\xfc\x52\x19\xee\x7a\x13\x73\xba\x36"
+ "\xaf\x78\x5d\xb6\x1f\x96\x76\x15\x73\xee\x04\xa8\x70\x27\xf7\xe7\xfa"
+ "\xe8\xf6\xc8\x5f\x4a\x81\x56\x0a\x94\xf3\xc6\x98\xd2\x93\xc4\x0b\x49"
+ "\x6b\x44\xd3\x73\xa2\xe3\xef\x5d\x9e\x68\xac\xa7\x42\xb1\xbb\x65\xbe"
+ "\x59";
+
+static const unsigned char sha256_sig[] =
+ "\x0f\x8c\xdb\xe6\xb6\x21\xc8\xc5\x28\x76\x7d\xf6\xf2\x3b\x78\x47\x77"
+ "\x03\x34\xc5\x5e\xc0\xda\x42\x41\xc0\x0f\x97\xd3\xd0\x53\xa1\xd6\x87"
+ "\xe4\x16\x29\x9a\xa5\x59\xf4\x01\xad\xc9\x04\xe7\x61\xe2\xcb\x79\x73"
+ "\xce\xe0\xa6\x85\xe5\x10\x8c\x4b\xc5\x68\x3b\x96\x42\x3f\x56\xb3\x6d"
+ "\x89\xc4\xff\x72\x36\xf2\x3f\xed\xe9\xb8\xe3\xae\xab\x3c\xb7\xaa\xf7"
+ "\x1f\x8f\x26\x6b\xee\xc1\xac\x72\x89\x23\x8b\x7a\xd7\x8c\x84\xf3\xf5"
+ "\x97\xa8\x8d\xd3\xef\xb2\x5e\x06\x04\x21\xdd\x28\xa2\x28\x83\x68\x9b"
+ "\xac\x34\xdd\x36\x33\xda\xdd\xa4\x59\xc7\x5a\x4d\xf3\x83\x06\xd5\xc0"
+ "\x0d\x1f\x4f\x47\x2f\x9f\xcc\xc2\x0d\x21\x1e\x82\xb9\x3d\xf3\xa4\x1a"
+ "\xa6\xd8\x0e\x72\x1d\x71\x17\x1c\x54\xad\x37\x3e\xa4\x0e\x70\x86\x53"
+ "\xfb\x40\xad\xb9\x14\xf8\x8d\x93\xbb\xd7\xe7\x31\xce\xe0\x98\xda\x27"
+ "\x1c\x18\x8e\xd8\x85\xcb\xa7\xb1\x18\xac\x8c\xa8\x9d\xa9\xe2\xf6\x30"
+ "\x95\xa4\x81\xf4\x1c\xa0\x31\xd5\xc7\x9d\x28\x33\xee\x7f\x08\x4f\xcb"
+ "\xd1\x14\x17\xdf\xd0\x88\x78\x47\x29\xaf\x6c\xb2\x62\xa6\x30\x87\x29"
+ "\xaa\x80\x19\x7d\x2f\x05\xe3\x7e\x23\x73\x88\x08\xcc\xbd\x50\x46\x09"
+ "\x2a";
+
+static const unsigned char sha512_sig[] =
+ "\x15\xda\x87\x87\x1f\x76\x08\xd3\x9d\x3a\xb9\xd2\x6a\x0e\x3b\x7d\xdd"
+ "\xec\x7d\xc4\x6d\x26\xf5\x04\xd3\x76\xc7\x83\xc4\x81\x69\x35\xe9\x47"
+ "\xbf\x49\xd1\xc0\xf9\x01\x4e\x0a\x34\x5b\xd0\xec\x6e\xe2\x2e\xe9\x2d"
+ "\x00\xfd\xe0\xa0\x28\x54\x53\x19\x49\x6d\xd2\x58\xb9\x47\xfa\x45\xad"
+ "\xd2\x1d\x52\xac\x80\xcb\xfc\x91\x97\x84\x58\x5f\xab\x21\x62\x60\x79"
+ "\xb8\x8a\x83\xe1\xf1\xcb\x05\x4c\x92\x56\x62\xd9\xbf\xa7\x81\x34\x23"
+ "\xdf\xd7\xa7\xc4\xdf\xde\x96\x00\x57\x4b\x78\x85\xb9\x3b\xdd\x3f\x98"
+ "\x88\x59\x1d\x48\xcf\x5a\xa8\xb7\x2a\x8b\x77\x93\x8e\x38\x3a\x0c\xa7"
+ "\x8a\x5f\xe6\x9f\xcb\xf0\x9a\x6b\xb6\x91\x04\x8b\x69\x6a\x37\xee\xa2"
+ "\xad\x5f\x31\x20\x96\xd6\x51\x80\xbf\x62\x48\xb8\xe4\x94\x10\x86\x4e"
+ "\xf2\x22\x1e\xa4\xd5\x54\xfe\xe1\x35\x49\xaf\xf8\x62\xfc\x11\xeb\xf7"
+ "\x3d\xd5\x5e\xaf\x11\xbd\x3d\xa9\x3a\x9f\x7f\xe8\xb4\x0d\xa2\xbb\x1c"
+ "\xbd\x4c\xed\x9e\x81\xb1\xec\xd3\xea\xaa\x03\xe3\x14\xdf\x8c\xb3\x78"
+ "\x85\x5e\x87\xad\xec\x41\x1a\xa9\x4f\xd2\xe6\xc6\xbe\xfa\xb8\x10\xea"
+ "\x74\x25\x36\x0c\x23\xe2\x24\xb7\x21\xb7\x0d\xaf\xf6\xb4\x31\xf5\x75"
+ "\xf1";
+
+static isc_result_t
+check_algorithm(unsigned char algorithm) {
+ BIGNUM *n = NULL, *e = NULL;
+ EVP_MD_CTX *evp_md_ctx = EVP_MD_CTX_create();
+ EVP_PKEY *pkey = NULL;
+ const EVP_MD *type = NULL;
+ const unsigned char *sig = NULL;
+ int status;
+ isc_result_t ret = ISC_R_SUCCESS;
+ size_t len;
+ RSA *rsa = NULL;
+
+ if (evp_md_ctx == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+
+ switch (algorithm) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ type = EVP_sha1(); /* SHA1 + RSA */
+ sig = sha1_sig;
+ len = sizeof(sha1_sig) - 1;
+ break;
+ case DST_ALG_RSASHA256:
+ type = EVP_sha256(); /* SHA256 + RSA */
+ sig = sha256_sig;
+ len = sizeof(sha256_sig) - 1;
+ break;
+ case DST_ALG_RSASHA512:
+ type = EVP_sha512();
+ sig = sha512_sig;
+ len = sizeof(sha512_sig) - 1;
+ break;
+ default:
+ DST_RET(ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (type == NULL) {
+ DST_RET(ISC_R_NOTIMPLEMENTED);
+ }
+
+ /*
+ * Construct pkey.
+ */
+ e = BN_bin2bn(e_bytes, sizeof(e_bytes) - 1, NULL);
+ n = BN_bin2bn(n_bytes, sizeof(n_bytes) - 1, NULL);
+ if (e == NULL || n == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+
+ rsa = RSA_new();
+ if (rsa == NULL) {
+ DST_RET(dst__openssl_toresult2("RSA_new",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = RSA_set0_key(rsa, n, e, NULL);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("RSA_set0_key",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ /* These are now managed by OpenSSL. */
+ n = NULL;
+ e = NULL;
+
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_new",
+ DST_R_OPENSSLFAILURE));
+ }
+ status = EVP_PKEY_set1_RSA(pkey, rsa);
+ if (status != 1) {
+ DST_RET(dst__openssl_toresult2("EVP_PKEY_set1_RSA",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ /*
+ * Check that we can verify the signature.
+ */
+ if (EVP_DigestInit_ex(evp_md_ctx, type, NULL) != 1 ||
+ EVP_DigestUpdate(evp_md_ctx, "test", 4) != 1 ||
+ EVP_VerifyFinal(evp_md_ctx, sig, len, pkey) != 1)
+ {
+ DST_RET(ISC_R_NOTIMPLEMENTED);
+ }
+
+err:
+ BN_free(e);
+ BN_free(n);
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (evp_md_ctx != NULL) {
+ EVP_MD_CTX_destroy(evp_md_ctx);
+ }
+ ERR_clear_error();
+ return (ret);
+}
+
+isc_result_t
+dst__opensslrsa_init(dst_func_t **funcp, unsigned char algorithm) {
+ isc_result_t result;
+
+ REQUIRE(funcp != NULL);
+
+ result = check_algorithm(algorithm);
+
+ if (result == ISC_R_SUCCESS) {
+ if (*funcp == NULL) {
+ *funcp = &opensslrsa_functions;
+ }
+ } else if (result == ISC_R_NOTIMPLEMENTED) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+#endif /* !USE_PKCS11 */
+
+/*! \file */
diff --git a/lib/dns/order.c b/lib/dns/order.c
new file mode 100644
index 0000000..94e5f81
--- /dev/null
+++ b/lib/dns/order.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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_copynf(name, dns_fixedname_name(&ent->name));
+ ent->rdtype = rdtype;
+ ent->rdclass = rdclass;
+ ent->mode = mode;
+ ISC_LINK_INIT(ent, link);
+ ISC_LIST_INITANDAPPEND(order->ents, ent, link);
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+match(const dns_name_t *name1, const dns_name_t *name2) {
+ if (dns_name_iswildcard(name2)) {
+ return (dns_name_matcheswildcard(name1, name2));
+ }
+ return (dns_name_equal(name1, name2));
+}
+
+unsigned int
+dns_order_find(dns_order_t *order, const dns_name_t *name,
+ dns_rdatatype_t rdtype, dns_rdataclass_t rdclass) {
+ dns_order_ent_t *ent;
+ REQUIRE(DNS_ORDER_VALID(order));
+
+ for (ent = ISC_LIST_HEAD(order->ents); ent != NULL;
+ ent = ISC_LIST_NEXT(ent, link))
+ {
+ if (ent->rdtype != rdtype && ent->rdtype != dns_rdatatype_any) {
+ continue;
+ }
+ if (ent->rdclass != rdclass &&
+ ent->rdclass != dns_rdataclass_any)
+ {
+ continue;
+ }
+ if (match(name, dns_fixedname_name(&ent->name))) {
+ return (ent->mode);
+ }
+ }
+ return (DNS_RDATASETATTR_NONE);
+}
+
+void
+dns_order_attach(dns_order_t *source, dns_order_t **target) {
+ REQUIRE(DNS_ORDER_VALID(source));
+ REQUIRE(target != NULL && *target == NULL);
+ isc_refcount_increment(&source->references);
+ *target = source;
+}
+
+void
+dns_order_detach(dns_order_t **orderp) {
+ REQUIRE(orderp != NULL && DNS_ORDER_VALID(*orderp));
+ dns_order_t *order;
+ order = *orderp;
+ *orderp = NULL;
+
+ if (isc_refcount_decrement(&order->references) == 1) {
+ isc_refcount_destroy(&order->references);
+ order->magic = 0;
+ dns_order_ent_t *ent;
+ while ((ent = ISC_LIST_HEAD(order->ents)) != NULL) {
+ ISC_LIST_UNLINK(order->ents, ent, link);
+ isc_mem_put(order->mctx, ent, sizeof(*ent));
+ }
+ isc_mem_putanddetach(&order->mctx, order, sizeof(*order));
+ }
+}
diff --git a/lib/dns/peer.c b/lib/dns/peer.c
new file mode 100644
index 0000000..d07933d
--- /dev/null
+++ b/lib/dns/peer.c
@@ -0,0 +1,919 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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>
+
+/*%
+ * Bit positions in the dns_peer_t structure flags field
+ */
+#define BOGUS_BIT 0
+#define SERVER_TRANSFER_FORMAT_BIT 1
+#define TRANSFERS_BIT 2
+#define PROVIDE_IXFR_BIT 3
+#define REQUEST_IXFR_BIT 4
+#define SUPPORT_EDNS_BIT 5
+#define SERVER_UDPSIZE_BIT 6
+#define SERVER_MAXUDP_BIT 7
+#define REQUEST_NSID_BIT 8
+#define SEND_COOKIE_BIT 9
+#define NOTIFY_DSCP_BIT 10
+#define TRANSFER_DSCP_BIT 11
+#define QUERY_DSCP_BIT 12
+#define REQUEST_EXPIRE_BIT 13
+#define EDNS_VERSION_BIT 14
+#define FORCE_TCP_BIT 15
+#define SERVER_PADDING_BIT 16
+#define REQUEST_TCP_KEEPALIVE_BIT 17
+
+static void
+peerlist_delete(dns_peerlist_t **list);
+
+static void
+peer_delete(dns_peer_t **peer);
+
+isc_result_t
+dns_peerlist_new(isc_mem_t *mem, dns_peerlist_t **list) {
+ dns_peerlist_t *l;
+
+ REQUIRE(list != NULL);
+
+ l = isc_mem_get(mem, sizeof(*l));
+
+ ISC_LIST_INIT(l->elements);
+ l->mem = mem;
+ isc_refcount_init(&l->refs, 1);
+ l->magic = DNS_PEERLIST_MAGIC;
+
+ *list = l;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_peerlist_attach(dns_peerlist_t *source, dns_peerlist_t **target) {
+ REQUIRE(DNS_PEERLIST_VALID(source));
+ REQUIRE(target != NULL);
+ REQUIRE(*target == NULL);
+
+ isc_refcount_increment(&source->refs);
+
+ *target = source;
+}
+
+void
+dns_peerlist_detach(dns_peerlist_t **list) {
+ dns_peerlist_t *plist;
+
+ REQUIRE(list != NULL);
+ REQUIRE(*list != NULL);
+ REQUIRE(DNS_PEERLIST_VALID(*list));
+
+ plist = *list;
+ *list = NULL;
+
+ if (isc_refcount_decrement(&plist->refs) == 1) {
+ peerlist_delete(&plist);
+ }
+}
+
+static void
+peerlist_delete(dns_peerlist_t **list) {
+ dns_peerlist_t *l;
+ dns_peer_t *server, *stmp;
+
+ REQUIRE(list != NULL);
+ REQUIRE(DNS_PEERLIST_VALID(*list));
+
+ l = *list;
+ *list = NULL;
+
+ isc_refcount_destroy(&l->refs);
+
+ server = ISC_LIST_HEAD(l->elements);
+ while (server != NULL) {
+ stmp = ISC_LIST_NEXT(server, next);
+ ISC_LIST_UNLINK(l->elements, server, next);
+ dns_peer_detach(&server);
+ server = stmp;
+ }
+
+ l->magic = 0;
+ isc_mem_put(l->mem, l, sizeof(*l));
+}
+
+void
+dns_peerlist_addpeer(dns_peerlist_t *peers, dns_peer_t *peer) {
+ dns_peer_t *p = NULL;
+
+ dns_peer_attach(peer, &p);
+
+ /*
+ * More specifics to front of list.
+ */
+ for (p = ISC_LIST_HEAD(peers->elements); p != NULL;
+ p = ISC_LIST_NEXT(p, next))
+ {
+ if (p->prefixlen < peer->prefixlen) {
+ break;
+ }
+ }
+
+ if (p != NULL) {
+ ISC_LIST_INSERTBEFORE(peers->elements, p, peer, next);
+ } else {
+ ISC_LIST_APPEND(peers->elements, peer, next);
+ }
+}
+
+isc_result_t
+dns_peerlist_peerbyaddr(dns_peerlist_t *servers, const isc_netaddr_t *addr,
+ dns_peer_t **retval) {
+ dns_peer_t *server;
+ isc_result_t res;
+
+ REQUIRE(retval != NULL);
+ REQUIRE(DNS_PEERLIST_VALID(servers));
+
+ server = ISC_LIST_HEAD(servers->elements);
+ while (server != NULL) {
+ if (isc_netaddr_eqprefix(addr, &server->address,
+ server->prefixlen))
+ {
+ break;
+ }
+
+ server = ISC_LIST_NEXT(server, next);
+ }
+
+ if (server != NULL) {
+ *retval = server;
+ res = ISC_R_SUCCESS;
+ } else {
+ res = ISC_R_NOTFOUND;
+ }
+
+ return (res);
+}
+
+isc_result_t
+dns_peerlist_currpeer(dns_peerlist_t *peers, dns_peer_t **retval) {
+ dns_peer_t *p = NULL;
+
+ p = ISC_LIST_TAIL(peers->elements);
+
+ dns_peer_attach(p, retval);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_new(isc_mem_t *mem, const isc_netaddr_t *addr, dns_peer_t **peerptr) {
+ unsigned int prefixlen = 0;
+
+ REQUIRE(peerptr != NULL);
+ switch (addr->family) {
+ case AF_INET:
+ prefixlen = 32;
+ break;
+ case AF_INET6:
+ prefixlen = 128;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (dns_peer_newprefix(mem, addr, prefixlen, peerptr));
+}
+
+isc_result_t
+dns_peer_newprefix(isc_mem_t *mem, const isc_netaddr_t *addr,
+ unsigned int prefixlen, dns_peer_t **peerptr) {
+ dns_peer_t *peer;
+
+ REQUIRE(peerptr != NULL && *peerptr == NULL);
+
+ peer = isc_mem_get(mem, sizeof(*peer));
+
+ *peer = (dns_peer_t){
+ .magic = DNS_PEER_MAGIC,
+ .address = *addr,
+ .prefixlen = prefixlen,
+ .mem = mem,
+ .transfer_format = dns_one_answer,
+ };
+
+ isc_refcount_init(&peer->refs, 1);
+
+ ISC_LINK_INIT(peer, next);
+
+ *peerptr = peer;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_peer_attach(dns_peer_t *source, dns_peer_t **target) {
+ REQUIRE(DNS_PEER_VALID(source));
+ REQUIRE(target != NULL);
+ REQUIRE(*target == NULL);
+
+ isc_refcount_increment(&source->refs);
+
+ *target = source;
+}
+
+void
+dns_peer_detach(dns_peer_t **peer) {
+ dns_peer_t *p;
+
+ REQUIRE(peer != NULL);
+ REQUIRE(*peer != NULL);
+ REQUIRE(DNS_PEER_VALID(*peer));
+
+ p = *peer;
+ *peer = NULL;
+
+ if (isc_refcount_decrement(&p->refs) == 1) {
+ peer_delete(&p);
+ }
+}
+
+static void
+peer_delete(dns_peer_t **peer) {
+ dns_peer_t *p;
+ isc_mem_t *mem;
+
+ REQUIRE(peer != NULL);
+ REQUIRE(DNS_PEER_VALID(*peer));
+
+ p = *peer;
+ *peer = NULL;
+
+ isc_refcount_destroy(&p->refs);
+
+ mem = p->mem;
+ p->mem = NULL;
+ p->magic = 0;
+
+ if (p->key != NULL) {
+ dns_name_free(p->key, mem);
+ isc_mem_put(mem, p->key, sizeof(dns_name_t));
+ }
+
+ if (p->query_source != NULL) {
+ isc_mem_put(mem, p->query_source, sizeof(*p->query_source));
+ }
+
+ if (p->notify_source != NULL) {
+ isc_mem_put(mem, p->notify_source, sizeof(*p->notify_source));
+ }
+
+ if (p->transfer_source != NULL) {
+ isc_mem_put(mem, p->transfer_source,
+ sizeof(*p->transfer_source));
+ }
+
+ isc_mem_put(mem, p, sizeof(*p));
+}
+
+isc_result_t
+dns_peer_setbogus(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(BOGUS_BIT, &peer->bitflags);
+
+ peer->bogus = newval;
+ DNS_BIT_SET(BOGUS_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getbogus(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(BOGUS_BIT, &peer->bitflags)) {
+ *retval = peer->bogus;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setprovideixfr(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(PROVIDE_IXFR_BIT, &peer->bitflags);
+
+ peer->provide_ixfr = newval;
+ DNS_BIT_SET(PROVIDE_IXFR_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getprovideixfr(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(PROVIDE_IXFR_BIT, &peer->bitflags)) {
+ *retval = peer->provide_ixfr;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setrequestixfr(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(REQUEST_IXFR_BIT, &peer->bitflags);
+
+ peer->request_ixfr = newval;
+ DNS_BIT_SET(REQUEST_IXFR_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getrequestixfr(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(REQUEST_IXFR_BIT, &peer->bitflags)) {
+ *retval = peer->request_ixfr;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setsupportedns(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SUPPORT_EDNS_BIT, &peer->bitflags);
+
+ peer->support_edns = newval;
+ DNS_BIT_SET(SUPPORT_EDNS_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getsupportedns(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(SUPPORT_EDNS_BIT, &peer->bitflags)) {
+ *retval = peer->support_edns;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setrequestnsid(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(REQUEST_NSID_BIT, &peer->bitflags);
+
+ peer->request_nsid = newval;
+ DNS_BIT_SET(REQUEST_NSID_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getrequestnsid(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(REQUEST_NSID_BIT, &peer->bitflags)) {
+ *retval = peer->request_nsid;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setsendcookie(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SEND_COOKIE_BIT, &peer->bitflags);
+
+ peer->send_cookie = newval;
+ DNS_BIT_SET(SEND_COOKIE_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getsendcookie(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(SEND_COOKIE_BIT, &peer->bitflags)) {
+ *retval = peer->send_cookie;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setrequestexpire(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(REQUEST_EXPIRE_BIT, &peer->bitflags);
+
+ peer->request_expire = newval;
+ DNS_BIT_SET(REQUEST_EXPIRE_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getrequestexpire(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(REQUEST_EXPIRE_BIT, &peer->bitflags)) {
+ *retval = peer->request_expire;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setforcetcp(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(FORCE_TCP_BIT, &peer->bitflags);
+
+ peer->force_tcp = newval;
+ DNS_BIT_SET(FORCE_TCP_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getforcetcp(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(FORCE_TCP_BIT, &peer->bitflags)) {
+ *retval = peer->force_tcp;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_settcpkeepalive(dns_peer_t *peer, bool newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(REQUEST_TCP_KEEPALIVE_BIT, &peer->bitflags);
+
+ peer->tcp_keepalive = newval;
+ DNS_BIT_SET(REQUEST_TCP_KEEPALIVE_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_gettcpkeepalive(dns_peer_t *peer, bool *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(REQUEST_TCP_KEEPALIVE_BIT, &peer->bitflags)) {
+ *retval = peer->tcp_keepalive;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_settransfers(dns_peer_t *peer, uint32_t newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(TRANSFERS_BIT, &peer->bitflags);
+
+ peer->transfers = newval;
+ DNS_BIT_SET(TRANSFERS_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_gettransfers(dns_peer_t *peer, uint32_t *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(TRANSFERS_BIT, &peer->bitflags)) {
+ *retval = peer->transfers;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_settransferformat(dns_peer_t *peer, dns_transfer_format_t newval) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SERVER_TRANSFER_FORMAT_BIT, &peer->bitflags);
+
+ peer->transfer_format = newval;
+ DNS_BIT_SET(SERVER_TRANSFER_FORMAT_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_gettransferformat(dns_peer_t *peer, dns_transfer_format_t *retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (DNS_BIT_CHECK(SERVER_TRANSFER_FORMAT_BIT, &peer->bitflags)) {
+ *retval = peer->transfer_format;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_getkey(dns_peer_t *peer, dns_name_t **retval) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(retval != NULL);
+
+ if (peer->key != NULL) {
+ *retval = peer->key;
+ }
+
+ return (peer->key == NULL ? ISC_R_NOTFOUND : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_setkey(dns_peer_t *peer, dns_name_t **keyval) {
+ bool exists = false;
+
+ if (peer->key != NULL) {
+ dns_name_free(peer->key, peer->mem);
+ isc_mem_put(peer->mem, peer->key, sizeof(dns_name_t));
+ exists = true;
+ }
+
+ peer->key = *keyval;
+ *keyval = NULL;
+
+ return (exists ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_setkeybycharp(dns_peer_t *peer, const char *keyval) {
+ isc_buffer_t b;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ isc_result_t result;
+
+ dns_fixedname_init(&fname);
+ isc_buffer_constinit(&b, keyval, strlen(keyval));
+ isc_buffer_add(&b, strlen(keyval));
+ result = dns_name_fromtext(dns_fixedname_name(&fname), &b, dns_rootname,
+ 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ name = isc_mem_get(peer->mem, sizeof(dns_name_t));
+
+ dns_name_init(name, NULL);
+ dns_name_dup(dns_fixedname_name(&fname), peer->mem, name);
+
+ result = dns_peer_setkey(peer, &name);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(peer->mem, name, sizeof(dns_name_t));
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_peer_settransfersource(dns_peer_t *peer,
+ const isc_sockaddr_t *transfer_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ if (peer->transfer_source != NULL) {
+ isc_mem_put(peer->mem, peer->transfer_source,
+ sizeof(*peer->transfer_source));
+ peer->transfer_source = NULL;
+ }
+ if (transfer_source != NULL) {
+ peer->transfer_source =
+ isc_mem_get(peer->mem, sizeof(*peer->transfer_source));
+
+ *peer->transfer_source = *transfer_source;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_gettransfersource(dns_peer_t *peer, isc_sockaddr_t *transfer_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(transfer_source != NULL);
+
+ if (peer->transfer_source == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ *transfer_source = *peer->transfer_source;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_setnotifysource(dns_peer_t *peer,
+ const isc_sockaddr_t *notify_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ if (peer->notify_source != NULL) {
+ isc_mem_put(peer->mem, peer->notify_source,
+ sizeof(*peer->notify_source));
+ peer->notify_source = NULL;
+ }
+ if (notify_source != NULL) {
+ peer->notify_source = isc_mem_get(peer->mem,
+ sizeof(*peer->notify_source));
+
+ *peer->notify_source = *notify_source;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getnotifysource(dns_peer_t *peer, isc_sockaddr_t *notify_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(notify_source != NULL);
+
+ if (peer->notify_source == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ *notify_source = *peer->notify_source;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_setquerysource(dns_peer_t *peer, const isc_sockaddr_t *query_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ if (peer->query_source != NULL) {
+ isc_mem_put(peer->mem, peer->query_source,
+ sizeof(*peer->query_source));
+ peer->query_source = NULL;
+ }
+ if (query_source != NULL) {
+ peer->query_source = isc_mem_get(peer->mem,
+ sizeof(*peer->query_source));
+
+ *peer->query_source = *query_source;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getquerysource(dns_peer_t *peer, isc_sockaddr_t *query_source) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(query_source != NULL);
+
+ if (peer->query_source == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ *query_source = *peer->query_source;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_setudpsize(dns_peer_t *peer, uint16_t udpsize) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SERVER_UDPSIZE_BIT, &peer->bitflags);
+
+ peer->udpsize = udpsize;
+ DNS_BIT_SET(SERVER_UDPSIZE_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getudpsize(dns_peer_t *peer, uint16_t *udpsize) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(udpsize != NULL);
+
+ if (DNS_BIT_CHECK(SERVER_UDPSIZE_BIT, &peer->bitflags)) {
+ *udpsize = peer->udpsize;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setmaxudp(dns_peer_t *peer, uint16_t maxudp) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SERVER_MAXUDP_BIT, &peer->bitflags);
+
+ peer->maxudp = maxudp;
+ DNS_BIT_SET(SERVER_MAXUDP_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getmaxudp(dns_peer_t *peer, uint16_t *maxudp) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(maxudp != NULL);
+
+ if (DNS_BIT_CHECK(SERVER_MAXUDP_BIT, &peer->bitflags)) {
+ *maxudp = peer->maxudp;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setpadding(dns_peer_t *peer, uint16_t padding) {
+ bool existed;
+
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ existed = DNS_BIT_CHECK(SERVER_PADDING_BIT, &peer->bitflags);
+
+ if (padding > 512) {
+ padding = 512;
+ }
+ peer->padding = padding;
+ DNS_BIT_SET(SERVER_PADDING_BIT, &peer->bitflags);
+
+ return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getpadding(dns_peer_t *peer, uint16_t *padding) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(padding != NULL);
+
+ if (DNS_BIT_CHECK(SERVER_PADDING_BIT, &peer->bitflags)) {
+ *padding = peer->padding;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+isc_result_t
+dns_peer_setnotifydscp(dns_peer_t *peer, isc_dscp_t dscp) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(dscp < 64);
+
+ peer->notify_dscp = dscp;
+ DNS_BIT_SET(NOTIFY_DSCP_BIT, &peer->bitflags);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getnotifydscp(dns_peer_t *peer, isc_dscp_t *dscpp) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(dscpp != NULL);
+
+ if (DNS_BIT_CHECK(NOTIFY_DSCP_BIT, &peer->bitflags)) {
+ *dscpp = peer->notify_dscp;
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_peer_settransferdscp(dns_peer_t *peer, isc_dscp_t dscp) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(dscp < 64);
+
+ peer->transfer_dscp = dscp;
+ DNS_BIT_SET(TRANSFER_DSCP_BIT, &peer->bitflags);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_gettransferdscp(dns_peer_t *peer, isc_dscp_t *dscpp) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(dscpp != NULL);
+
+ if (DNS_BIT_CHECK(TRANSFER_DSCP_BIT, &peer->bitflags)) {
+ *dscpp = peer->transfer_dscp;
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_peer_setquerydscp(dns_peer_t *peer, isc_dscp_t dscp) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(dscp < 64);
+
+ peer->query_dscp = dscp;
+ DNS_BIT_SET(QUERY_DSCP_BIT, &peer->bitflags);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getquerydscp(dns_peer_t *peer, isc_dscp_t *dscpp) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(dscpp != NULL);
+
+ if (DNS_BIT_CHECK(QUERY_DSCP_BIT, &peer->bitflags)) {
+ *dscpp = peer->query_dscp;
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_peer_setednsversion(dns_peer_t *peer, uint8_t ednsversion) {
+ REQUIRE(DNS_PEER_VALID(peer));
+
+ peer->ednsversion = ednsversion;
+ DNS_BIT_SET(EDNS_VERSION_BIT, &peer->bitflags);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_peer_getednsversion(dns_peer_t *peer, uint8_t *ednsversion) {
+ REQUIRE(DNS_PEER_VALID(peer));
+ REQUIRE(ednsversion != NULL);
+
+ if (DNS_BIT_CHECK(EDNS_VERSION_BIT, &peer->bitflags)) {
+ *ednsversion = peer->ednsversion;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
diff --git a/lib/dns/pkcs11.c b/lib/dns/pkcs11.c
new file mode 100644
index 0000000..4eb90b9
--- /dev/null
+++ b/lib/dns/pkcs11.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if USE_PKCS11
+
+#include <isc/util.h>
+
+#include <pk11/internal.h>
+#include <pk11/pk11.h>
+
+#include <dns/log.h>
+#include <dns/result.h>
+
+#include "dst_internal.h"
+#include "dst_pkcs11.h"
+
+isc_result_t
+dst__pkcs11_toresult(const char *funcname, const char *file, int line,
+ isc_result_t fallback, CK_RV rv) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_CRYPTO,
+ ISC_LOG_WARNING, "%s:%d: %s: Error = 0x%.8lX\n", file,
+ line, funcname, rv);
+ if (rv == CKR_HOST_MEMORY) {
+ return (ISC_R_NOMEMORY);
+ }
+ return (fallback);
+}
+
+#endif /* USE_PKCS11 */
+/*! \file */
diff --git a/lib/dns/pkcs11ecdsa_link.c b/lib/dns/pkcs11ecdsa_link.c
new file mode 100644
index 0000000..2806c3b
--- /dev/null
+++ b/lib/dns/pkcs11ecdsa_link.c
@@ -0,0 +1,1153 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#if USE_PKCS11
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <pk11/constants.h>
+#include <pk11/internal.h>
+#include <pk11/pk11.h>
+#include <pkcs11/pkcs11.h>
+
+#include <dns/keyvalues.h>
+
+#include <dst/result.h>
+
+#include "dst_internal.h"
+#include "dst_parse.h"
+#include "dst_pkcs11.h"
+
+/*
+ * FIPS 186-3 ECDSA keys:
+ * mechanisms:
+ * CKM_ECDSA,
+ * CKM_EC_KEY_PAIR_GEN
+ * domain parameters:
+ * CKA_EC_PARAMS (choice with OID namedCurve)
+ * public keys:
+ * object class CKO_PUBLIC_KEY
+ * key type CKK_EC
+ * attribute CKA_EC_PARAMS (choice with OID namedCurve)
+ * attribute CKA_EC_POINT (point Q)
+ * private keys:
+ * object class CKO_PRIVATE_KEY
+ * key type CKK_EC
+ * attribute CKA_EC_PARAMS (choice with OID namedCurve)
+ * attribute CKA_VALUE (big int d)
+ * point format: 0x04 (octet-string) <2*size+1> 0x4 (uncompressed) <x> <y>
+ */
+
+#define TAG_OCTECT_STRING 0x04
+#define UNCOMPRESSED 0x04
+
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+static CK_BBOOL truevalue = TRUE;
+static CK_BBOOL falsevalue = FALSE;
+
+static void
+pkcs11ecdsa_destroy(dst_key_t *key);
+
+static isc_result_t
+pkcs11ecdsa_createctx(dst_key_t *key, dst_context_t *dctx) {
+ CK_RV rv;
+ CK_MECHANISM mech = { 0, NULL, 0 };
+ CK_SLOT_ID slotid;
+ pk11_context_t *pk11_ctx;
+ pk11_object_t *ec = key->keydata.pkey;
+ isc_result_t ret;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 ||
+ dctx->key->key_alg == DST_ALG_ECDSA384);
+ REQUIRE(ec != NULL);
+
+ if (dctx->key->key_alg == DST_ALG_ECDSA256) {
+ mech.mechanism = CKM_SHA256;
+ } else {
+ mech.mechanism = CKM_SHA384;
+ }
+
+ pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx));
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ if (ec->ontoken && (dctx->use == DO_SIGN)) {
+ slotid = ec->slot;
+ } else {
+ slotid = pk11_get_best_token(OP_ECDSA);
+ }
+ ret = pk11_get_session(pk11_ctx, OP_ECDSA, true, false, ec->reqlogon,
+ NULL, slotid);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ PK11_RET(pkcs_C_DigestInit, (pk11_ctx->session, &mech), ISC_R_FAILURE);
+ dctx->ctxdata.pk11_ctx = pk11_ctx;
+ return (ISC_R_SUCCESS);
+
+err:
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ return (ret);
+}
+
+static void
+pkcs11ecdsa_destroyctx(dst_context_t *dctx) {
+ CK_BYTE garbage[ISC_SHA384_DIGESTLENGTH];
+ CK_ULONG len = ISC_SHA384_DIGESTLENGTH;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 ||
+ dctx->key->key_alg == DST_ALG_ECDSA384);
+
+ if (pk11_ctx != NULL) {
+ (void)pkcs_C_DigestFinal(pk11_ctx->session, garbage, &len);
+ memset(garbage, 0, sizeof(garbage));
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ dctx->ctxdata.pk11_ctx = NULL;
+ }
+}
+
+static isc_result_t
+pkcs11ecdsa_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ CK_RV rv;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 ||
+ dctx->key->key_alg == DST_ALG_ECDSA384);
+
+ PK11_CALL(pkcs_C_DigestUpdate,
+ (pk11_ctx->session, (CK_BYTE_PTR)data->base,
+ (CK_ULONG)data->length),
+ ISC_R_FAILURE);
+
+ return (ret);
+}
+
+static isc_result_t
+pkcs11ecdsa_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ CK_RV rv;
+ CK_MECHANISM mech = { CKM_ECDSA, NULL, 0 };
+ CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
+ CK_KEY_TYPE keyType = CKK_EC;
+ CK_ATTRIBUTE keyTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_EC_PARAMS, NULL, 0 },
+ { CKA_VALUE, NULL, 0 }
+ };
+ CK_ATTRIBUTE *attr;
+ CK_BYTE digest[ISC_SHA384_DIGESTLENGTH];
+ CK_ULONG dgstlen;
+ CK_ULONG siglen;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+ dst_key_t *key = dctx->key;
+ pk11_object_t *ec = key->keydata.pkey;
+ isc_region_t r;
+ isc_result_t ret = ISC_R_SUCCESS;
+ unsigned int i;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+ REQUIRE(ec != NULL);
+
+ switch (key->key_alg) {
+ case DST_ALG_ECDSA256:
+ dgstlen = ISC_SHA256_DIGESTLENGTH;
+ siglen = DNS_SIG_ECDSA256SIZE;
+ break;
+ case DST_ALG_ECDSA384:
+ siglen = DNS_SIG_ECDSA384SIZE;
+ dgstlen = ISC_SHA384_DIGESTLENGTH;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ PK11_RET(pkcs_C_DigestFinal, (pk11_ctx->session, digest, &dgstlen),
+ ISC_R_FAILURE);
+
+ isc_buffer_availableregion(sig, &r);
+ if (r.length < siglen) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+
+ if (ec->ontoken && (ec->object != CK_INVALID_HANDLE)) {
+ pk11_ctx->ontoken = ec->ontoken;
+ pk11_ctx->object = ec->object;
+ goto token_key;
+ }
+
+ for (attr = pk11_attribute_first(ec); attr != NULL;
+ attr = pk11_attribute_next(ec, attr))
+ {
+ switch (attr->type) {
+ case CKA_EC_PARAMS:
+ INSIST(keyTemplate[5].type == attr->type);
+ keyTemplate[5].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[5].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[5].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_VALUE:
+ INSIST(keyTemplate[6].type == attr->type);
+ keyTemplate[6].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[6].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[6].ulValueLen = attr->ulValueLen;
+ break;
+ }
+ }
+ pk11_ctx->object = CK_INVALID_HANDLE;
+ pk11_ctx->ontoken = false;
+ PK11_RET(pkcs_C_CreateObject,
+ (pk11_ctx->session, keyTemplate, (CK_ULONG)7, &hKey),
+ ISC_R_FAILURE);
+
+token_key:
+
+ PK11_RET(pkcs_C_SignInit,
+ (pk11_ctx->session, &mech,
+ pk11_ctx->ontoken ? pk11_ctx->object : hKey),
+ ISC_R_FAILURE);
+
+ PK11_RET(pkcs_C_Sign,
+ (pk11_ctx->session, digest, dgstlen, (CK_BYTE_PTR)r.base,
+ &siglen),
+ DST_R_SIGNFAILURE);
+
+ isc_buffer_add(sig, (unsigned int)siglen);
+
+err:
+
+ if (hKey != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey);
+ }
+ for (i = 5; i <= 6; i++) {
+ if (keyTemplate[i].pValue != NULL) {
+ {
+ memset(keyTemplate[i].pValue, 0,
+ keyTemplate[i].ulValueLen);
+ isc_mem_put(dctx->mctx, keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ }
+ }
+ }
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ dctx->ctxdata.pk11_ctx = NULL;
+
+ return (ret);
+}
+
+static isc_result_t
+pkcs11ecdsa_verify(dst_context_t *dctx, const isc_region_t *sig) {
+ CK_RV rv;
+ CK_MECHANISM mech = { CKM_ECDSA, NULL, 0 };
+ CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY;
+ CK_KEY_TYPE keyType = CKK_EC;
+ CK_ATTRIBUTE keyTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_EC_PARAMS, NULL, 0 },
+ { CKA_EC_POINT, NULL, 0 }
+ };
+ CK_ATTRIBUTE *attr;
+ CK_BYTE digest[ISC_SHA384_DIGESTLENGTH];
+ CK_ULONG dgstlen;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+ dst_key_t *key = dctx->key;
+ pk11_object_t *ec = key->keydata.pkey;
+ isc_result_t ret = ISC_R_SUCCESS;
+ unsigned int i;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+ REQUIRE(ec != NULL);
+
+ switch (key->key_alg) {
+ case DST_ALG_ECDSA256:
+ dgstlen = ISC_SHA256_DIGESTLENGTH;
+ break;
+ case DST_ALG_ECDSA384:
+ dgstlen = ISC_SHA384_DIGESTLENGTH;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ PK11_RET(pkcs_C_DigestFinal, (pk11_ctx->session, digest, &dgstlen),
+ ISC_R_FAILURE);
+
+ for (attr = pk11_attribute_first(ec); attr != NULL;
+ attr = pk11_attribute_next(ec, attr))
+ {
+ switch (attr->type) {
+ case CKA_EC_PARAMS:
+ INSIST(keyTemplate[5].type == attr->type);
+ keyTemplate[5].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[5].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[5].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_EC_POINT:
+ INSIST(keyTemplate[6].type == attr->type);
+ keyTemplate[6].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[6].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[6].ulValueLen = attr->ulValueLen;
+ break;
+ }
+ }
+ pk11_ctx->object = CK_INVALID_HANDLE;
+ pk11_ctx->ontoken = false;
+ PK11_RET(pkcs_C_CreateObject,
+ (pk11_ctx->session, keyTemplate, (CK_ULONG)7, &hKey),
+ ISC_R_FAILURE);
+
+ PK11_RET(pkcs_C_VerifyInit, (pk11_ctx->session, &mech, hKey),
+ ISC_R_FAILURE);
+
+ PK11_RET(pkcs_C_Verify,
+ (pk11_ctx->session, digest, dgstlen, (CK_BYTE_PTR)sig->base,
+ (CK_ULONG)sig->length),
+ DST_R_VERIFYFAILURE);
+
+err:
+
+ if (hKey != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey);
+ }
+ for (i = 5; i <= 6; i++) {
+ if (keyTemplate[i].pValue != NULL) {
+ {
+ memset(keyTemplate[i].pValue, 0,
+ keyTemplate[i].ulValueLen);
+ isc_mem_put(dctx->mctx, keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ }
+ }
+ }
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ dctx->ctxdata.pk11_ctx = NULL;
+
+ return (ret);
+}
+
+static bool
+pkcs11ecdsa_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ pk11_object_t *ec1, *ec2;
+ CK_ATTRIBUTE *attr1, *attr2;
+
+ ec1 = key1->keydata.pkey;
+ ec2 = key2->keydata.pkey;
+
+ if ((ec1 == NULL) && (ec2 == NULL)) {
+ return (true);
+ } else if ((ec1 == NULL) || (ec2 == NULL)) {
+ return (false);
+ }
+
+ attr1 = pk11_attribute_bytype(ec1, CKA_EC_PARAMS);
+ attr2 = pk11_attribute_bytype(ec2, CKA_EC_PARAMS);
+ if ((attr1 == NULL) && (attr2 == NULL)) {
+ return (true);
+ } else if ((attr1 == NULL) || (attr2 == NULL) ||
+ (attr1->ulValueLen != attr2->ulValueLen) ||
+ !isc_safe_memequal(attr1->pValue, attr2->pValue,
+ attr1->ulValueLen))
+ {
+ return (false);
+ }
+
+ attr1 = pk11_attribute_bytype(ec1, CKA_EC_POINT);
+ attr2 = pk11_attribute_bytype(ec2, CKA_EC_POINT);
+ if ((attr1 == NULL) && (attr2 == NULL)) {
+ return (true);
+ } else if ((attr1 == NULL) || (attr2 == NULL) ||
+ (attr1->ulValueLen != attr2->ulValueLen) ||
+ !isc_safe_memequal(attr1->pValue, attr2->pValue,
+ attr1->ulValueLen))
+ {
+ return (false);
+ }
+
+ attr1 = pk11_attribute_bytype(ec1, CKA_VALUE);
+ attr2 = pk11_attribute_bytype(ec2, CKA_VALUE);
+ if (((attr1 != NULL) || (attr2 != NULL)) &&
+ ((attr1 == NULL) || (attr2 == NULL) ||
+ (attr1->ulValueLen != attr2->ulValueLen) ||
+ !isc_safe_memequal(attr1->pValue, attr2->pValue,
+ attr1->ulValueLen)))
+ {
+ return (false);
+ }
+
+ if (!ec1->ontoken && !ec2->ontoken) {
+ return (true);
+ } else if (ec1->ontoken || ec2->ontoken || (ec1->object != ec2->object))
+ {
+ return (false);
+ }
+
+ return (true);
+}
+
+#define SETCURVE() \
+ switch (key->key_alg) { \
+ case DST_ALG_ECDSA256: \
+ attr->pValue = isc_mem_get(key->mctx, \
+ sizeof(PK11_ECC_PRIME256V1)); \
+ memmove(attr->pValue, PK11_ECC_PRIME256V1, \
+ sizeof(PK11_ECC_PRIME256V1)); \
+ attr->ulValueLen = sizeof(PK11_ECC_PRIME256V1); \
+ break; \
+ case DST_ALG_ECDSA384: \
+ attr->pValue = isc_mem_get(key->mctx, \
+ sizeof(PK11_ECC_SECP384R1)); \
+ memmove(attr->pValue, PK11_ECC_SECP384R1, \
+ sizeof(PK11_ECC_SECP384R1)); \
+ attr->ulValueLen = sizeof(PK11_ECC_SECP384R1); \
+ break; \
+ default: \
+ UNREACHABLE(); \
+ }
+
+#define FREECURVE() \
+ if (attr->pValue != NULL) { \
+ memset(attr->pValue, 0, attr->ulValueLen); \
+ isc_mem_put(key->mctx, attr->pValue, attr->ulValueLen); \
+ attr->pValue = NULL; \
+ }
+
+static isc_result_t
+pkcs11ecdsa_generate(dst_key_t *key, int unused, void (*callback)(int)) {
+ CK_RV rv;
+ CK_MECHANISM mech = { CKM_EC_KEY_PAIR_GEN, NULL, 0 };
+ CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY;
+ CK_KEY_TYPE keyType = CKK_EC;
+ CK_ATTRIBUTE pubTemplate[] = {
+ { CKA_CLASS, &pubClass, (CK_ULONG)sizeof(pubClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_EC_PARAMS, NULL, 0 }
+ };
+ CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE;
+ CK_OBJECT_HANDLE privClass = CKO_PRIVATE_KEY;
+ CK_ATTRIBUTE privTemplate[] = {
+ { CKA_CLASS, &privClass, (CK_ULONG)sizeof(privClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_SENSITIVE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_EXTRACTABLE, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) }
+ };
+ CK_ATTRIBUTE *attr;
+ pk11_object_t *ec;
+ pk11_context_t *pk11_ctx;
+ isc_result_t ret;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+ UNUSED(unused);
+ UNUSED(callback);
+
+ pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx));
+ ret = pk11_get_session(pk11_ctx, OP_ECDSA, true, false, false, NULL,
+ pk11_get_best_token(OP_ECDSA));
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ ec = isc_mem_get(key->mctx, sizeof(*ec));
+ memset(ec, 0, sizeof(*ec));
+ key->keydata.pkey = ec;
+ ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 3);
+ memset(ec->repr, 0, sizeof(*attr) * 3);
+ ec->attrcnt = 3;
+
+ attr = ec->repr;
+ attr[0].type = CKA_EC_PARAMS;
+ attr[1].type = CKA_EC_POINT;
+ attr[2].type = CKA_VALUE;
+
+ attr = &pubTemplate[5];
+ SETCURVE();
+
+ PK11_RET(pkcs_C_GenerateKeyPair,
+ (pk11_ctx->session, &mech, pubTemplate, (CK_ULONG)6,
+ privTemplate, (CK_ULONG)7, &pub, &priv),
+ DST_R_CRYPTOFAILURE);
+
+ attr = &pubTemplate[5];
+ FREECURVE();
+
+ attr = ec->repr;
+ SETCURVE();
+
+ attr++;
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 1),
+ DST_R_CRYPTOFAILURE);
+ attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen);
+ memset(attr->pValue, 0, attr->ulValueLen);
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 1),
+ DST_R_CRYPTOFAILURE);
+
+ attr++;
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 1),
+ DST_R_CRYPTOFAILURE);
+ attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen);
+ memset(attr->pValue, 0, attr->ulValueLen);
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 1),
+ DST_R_CRYPTOFAILURE);
+
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, priv);
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, pub);
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ switch (key->key_alg) {
+ case DST_ALG_ECDSA256:
+ key->key_size = DNS_KEY_ECDSA256SIZE * 4;
+ break;
+ case DST_ALG_ECDSA384:
+ key->key_size = DNS_KEY_ECDSA384SIZE * 4;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+
+err:
+ pkcs11ecdsa_destroy(key);
+ if (priv != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, priv);
+ }
+ if (pub != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, pub);
+ }
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ return (ret);
+}
+
+static bool
+pkcs11ecdsa_isprivate(const dst_key_t *key) {
+ pk11_object_t *ec = key->keydata.pkey;
+ CK_ATTRIBUTE *attr;
+
+ if (ec == NULL) {
+ return (false);
+ }
+ attr = pk11_attribute_bytype(ec, CKA_VALUE);
+ return (attr != NULL || ec->ontoken);
+}
+
+static void
+pkcs11ecdsa_destroy(dst_key_t *key) {
+ pk11_object_t *ec = key->keydata.pkey;
+ CK_ATTRIBUTE *attr;
+
+ if (ec == NULL) {
+ return;
+ }
+
+ INSIST((ec->object == CK_INVALID_HANDLE) || ec->ontoken);
+
+ for (attr = pk11_attribute_first(ec); attr != NULL;
+ attr = pk11_attribute_next(ec, attr))
+ {
+ switch (attr->type) {
+ case CKA_LABEL:
+ case CKA_ID:
+ case CKA_EC_PARAMS:
+ case CKA_EC_POINT:
+ case CKA_VALUE:
+ FREECURVE();
+ break;
+ }
+ }
+ if (ec->repr != NULL) {
+ memset(ec->repr, 0, ec->attrcnt * sizeof(*attr));
+ isc_mem_put(key->mctx, ec->repr, ec->attrcnt * sizeof(*attr));
+ }
+ memset(ec, 0, sizeof(*ec));
+ isc_mem_put(key->mctx, ec, sizeof(*ec));
+ key->keydata.pkey = NULL;
+}
+
+static isc_result_t
+pkcs11ecdsa_todns(const dst_key_t *key, isc_buffer_t *data) {
+ pk11_object_t *ec;
+ isc_region_t r;
+ unsigned int len;
+ CK_ATTRIBUTE *attr;
+
+ REQUIRE(key->keydata.pkey != NULL);
+
+ switch (key->key_alg) {
+ case DST_ALG_ECDSA256:
+ len = DNS_KEY_ECDSA256SIZE;
+ break;
+ case DST_ALG_ECDSA384:
+ len = DNS_KEY_ECDSA384SIZE;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ ec = key->keydata.pkey;
+ attr = pk11_attribute_bytype(ec, CKA_EC_POINT);
+ if ((attr == NULL) || (attr->ulValueLen != len + 3) ||
+ (((CK_BYTE_PTR)attr->pValue)[0] != TAG_OCTECT_STRING) ||
+ (((CK_BYTE_PTR)attr->pValue)[1] != len + 1) ||
+ (((CK_BYTE_PTR)attr->pValue)[2] != UNCOMPRESSED))
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ isc_buffer_availableregion(data, &r);
+ if (r.length < len) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(r.base, (CK_BYTE_PTR)attr->pValue + 3, len);
+ isc_buffer_add(data, len);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+pkcs11ecdsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ pk11_object_t *ec;
+ isc_region_t r;
+ unsigned int len;
+ CK_ATTRIBUTE *attr;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+
+ switch (key->key_alg) {
+ case DST_ALG_ECDSA256:
+ len = DNS_KEY_ECDSA256SIZE;
+ break;
+ case DST_ALG_ECDSA384:
+ len = DNS_KEY_ECDSA384SIZE;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ if (r.length != len) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+
+ ec = isc_mem_get(key->mctx, sizeof(*ec));
+ memset(ec, 0, sizeof(*ec));
+ ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2);
+ ec->attrcnt = 2;
+
+ attr = ec->repr;
+ attr->type = CKA_EC_PARAMS;
+ SETCURVE();
+
+ attr++;
+ attr->type = CKA_EC_POINT;
+ attr->pValue = isc_mem_get(key->mctx, len + 3);
+ ((CK_BYTE_PTR)attr->pValue)[0] = TAG_OCTECT_STRING;
+ ((CK_BYTE_PTR)attr->pValue)[1] = len + 1;
+ ((CK_BYTE_PTR)attr->pValue)[2] = UNCOMPRESSED;
+ memmove((CK_BYTE_PTR)attr->pValue + 3, r.base, len);
+ attr->ulValueLen = len + 3;
+
+ isc_buffer_forward(data, len);
+ key->keydata.pkey = ec;
+ key->key_size = len * 4;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+pkcs11ecdsa_tofile(const dst_key_t *key, const char *directory) {
+ isc_result_t ret;
+ pk11_object_t *ec;
+ dst_private_t priv;
+ unsigned char *buf = NULL;
+ unsigned int i = 0;
+ CK_ATTRIBUTE *attr;
+
+ if (key->keydata.pkey == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ priv.nelements = 0;
+ return (dst__privstruct_writefile(key, &priv, directory));
+ }
+
+ ec = key->keydata.pkey;
+ attr = pk11_attribute_bytype(ec, CKA_VALUE);
+ if (attr != NULL) {
+ buf = isc_mem_get(key->mctx, attr->ulValueLen);
+ priv.elements[i].tag = TAG_ECDSA_PRIVATEKEY;
+ priv.elements[i].length = (unsigned short)attr->ulValueLen;
+ memmove(buf, attr->pValue, attr->ulValueLen);
+ priv.elements[i].data = buf;
+ i++;
+ }
+
+ if (key->engine != NULL) {
+ priv.elements[i].tag = TAG_ECDSA_ENGINE;
+ priv.elements[i].length = strlen(key->engine) + 1;
+ priv.elements[i].data = (unsigned char *)key->engine;
+ i++;
+ }
+
+ if (key->label != NULL) {
+ priv.elements[i].tag = TAG_ECDSA_LABEL;
+ priv.elements[i].length = strlen(key->label) + 1;
+ priv.elements[i].data = (unsigned char *)key->label;
+ i++;
+ }
+
+ priv.nelements = i;
+ ret = dst__privstruct_writefile(key, &priv, directory);
+
+ if (buf != NULL) {
+ memset(buf, 0, attr->ulValueLen);
+ isc_mem_put(key->mctx, buf, attr->ulValueLen);
+ }
+ return (ret);
+}
+
+static isc_result_t
+pkcs11ecdsa_fetch(dst_key_t *key, const char *engine, const char *label,
+ dst_key_t *pub) {
+ CK_RV rv;
+ CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
+ CK_KEY_TYPE keyType = CKK_EC;
+ CK_ATTRIBUTE searchTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_LABEL, NULL, 0 }
+ };
+ CK_ULONG cnt;
+ CK_ATTRIBUTE *attr;
+ CK_ATTRIBUTE *pubattr;
+ pk11_object_t *ec;
+ pk11_object_t *pubec;
+ pk11_context_t *pk11_ctx = NULL;
+ isc_result_t ret;
+
+ if (label == NULL) {
+ return (DST_R_NOENGINE);
+ }
+
+ ec = key->keydata.pkey;
+ pubec = pub->keydata.pkey;
+
+ ec->object = CK_INVALID_HANDLE;
+ ec->ontoken = true;
+ ec->reqlogon = true;
+ ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2);
+ memset(ec->repr, 0, sizeof(*attr) * 2);
+ ec->attrcnt = 2;
+ attr = ec->repr;
+
+ attr->type = CKA_EC_PARAMS;
+ pubattr = pk11_attribute_bytype(pubec, CKA_EC_PARAMS);
+ INSIST(pubattr != NULL);
+ attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen);
+ memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen);
+ attr->ulValueLen = pubattr->ulValueLen;
+ attr++;
+
+ attr->type = CKA_EC_POINT;
+ pubattr = pk11_attribute_bytype(pubec, CKA_EC_POINT);
+ INSIST(pubattr != NULL);
+ attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen);
+ memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen);
+ attr->ulValueLen = pubattr->ulValueLen;
+
+ ret = pk11_parse_uri(ec, label, key->mctx, OP_ECDSA);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx));
+ ret = pk11_get_session(pk11_ctx, OP_ECDSA, true, false, ec->reqlogon,
+ NULL, ec->slot);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ attr = pk11_attribute_bytype(ec, CKA_LABEL);
+ if (attr == NULL) {
+ attr = pk11_attribute_bytype(ec, CKA_ID);
+ INSIST(attr != NULL);
+ searchTemplate[3].type = CKA_ID;
+ }
+ searchTemplate[3].pValue = attr->pValue;
+ searchTemplate[3].ulValueLen = attr->ulValueLen;
+
+ PK11_RET(pkcs_C_FindObjectsInit,
+ (pk11_ctx->session, searchTemplate, (CK_ULONG)4),
+ DST_R_CRYPTOFAILURE);
+ PK11_RET(pkcs_C_FindObjects,
+ (pk11_ctx->session, &ec->object, (CK_ULONG)1, &cnt),
+ DST_R_CRYPTOFAILURE);
+ (void)pkcs_C_FindObjectsFinal(pk11_ctx->session);
+ if (cnt == 0) {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+ if (cnt > 1) {
+ DST_RET(ISC_R_EXISTS);
+ }
+
+ if (engine != NULL) {
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ }
+
+ key->label = isc_mem_strdup(key->mctx, label);
+
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ return (ISC_R_SUCCESS);
+
+err:
+ if (pk11_ctx != NULL) {
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ }
+ return (ret);
+}
+
+static isc_result_t
+pkcs11ecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t ret;
+ pk11_object_t *ec = NULL;
+ CK_ATTRIBUTE *attr, *pattr;
+ isc_mem_t *mctx = key->mctx;
+ unsigned int i;
+ const char *engine = NULL, *label = NULL;
+
+ REQUIRE(key->key_alg == DST_ALG_ECDSA256 ||
+ key->key_alg == DST_ALG_ECDSA384);
+
+ if ((pub == NULL) || (pub->keydata.pkey == NULL)) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ /* read private key file */
+ ret = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, mctx, &priv);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ if (key->external) {
+ if (priv.nelements != 0) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ key->keydata.pkey = pub->keydata.pkey;
+ pub->keydata.pkey = NULL;
+ key->key_size = pub->key_size;
+
+ dst__privstruct_free(&priv, mctx);
+ memset(&priv, 0, sizeof(priv));
+
+ return (ISC_R_SUCCESS);
+ }
+
+ for (i = 0; i < priv.nelements; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_ECDSA_ENGINE:
+ engine = (char *)priv.elements[i].data;
+ break;
+ case TAG_ECDSA_LABEL:
+ label = (char *)priv.elements[i].data;
+ break;
+ default:
+ break;
+ }
+ }
+ ec = isc_mem_get(key->mctx, sizeof(*ec));
+ memset(ec, 0, sizeof(*ec));
+ key->keydata.pkey = ec;
+
+ /* Is this key is stored in a HSM? See if we can fetch it. */
+ if ((label != NULL) || (engine != NULL)) {
+ ret = pkcs11ecdsa_fetch(key, engine, label, pub);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ dst__privstruct_free(&priv, mctx);
+ memset(&priv, 0, sizeof(priv));
+ return (ret);
+ }
+
+ ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 3);
+ memset(ec->repr, 0, sizeof(*attr) * 3);
+ ec->attrcnt = 3;
+
+ attr = ec->repr;
+ attr->type = CKA_EC_PARAMS;
+ pattr = pk11_attribute_bytype(pub->keydata.pkey, CKA_EC_PARAMS);
+ INSIST(pattr != NULL);
+ attr->pValue = isc_mem_get(key->mctx, pattr->ulValueLen);
+ memmove(attr->pValue, pattr->pValue, pattr->ulValueLen);
+ attr->ulValueLen = pattr->ulValueLen;
+
+ attr++;
+ attr->type = CKA_EC_POINT;
+ pattr = pk11_attribute_bytype(pub->keydata.pkey, CKA_EC_POINT);
+ INSIST(pattr != NULL);
+ attr->pValue = isc_mem_get(key->mctx, pattr->ulValueLen);
+ memmove(attr->pValue, pattr->pValue, pattr->ulValueLen);
+ attr->ulValueLen = pattr->ulValueLen;
+
+ attr++;
+ attr->type = CKA_VALUE;
+ attr->pValue = isc_mem_get(key->mctx, priv.elements[0].length);
+ memmove(attr->pValue, priv.elements[0].data, priv.elements[0].length);
+ attr->ulValueLen = priv.elements[0].length;
+
+ dst__privstruct_free(&priv, mctx);
+ memset(&priv, 0, sizeof(priv));
+ switch (key->key_alg) {
+ case DST_ALG_ECDSA256:
+ key->key_size = DNS_KEY_ECDSA256SIZE * 4;
+ break;
+ case DST_ALG_ECDSA384:
+ key->key_size = DNS_KEY_ECDSA384SIZE * 4;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+
+err:
+ pkcs11ecdsa_destroy(key);
+ dst__privstruct_free(&priv, mctx);
+ memset(&priv, 0, sizeof(priv));
+ return (ret);
+}
+
+static isc_result_t
+pkcs11ecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin) {
+ CK_RV rv;
+ CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY;
+ CK_KEY_TYPE keyType = CKK_EC;
+ CK_ATTRIBUTE searchTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_LABEL, NULL, 0 }
+ };
+ CK_ULONG cnt;
+ CK_ATTRIBUTE *attr;
+ pk11_object_t *ec;
+ pk11_context_t *pk11_ctx = NULL;
+ isc_result_t ret;
+ unsigned int i;
+
+ UNUSED(pin);
+
+ ec = isc_mem_get(key->mctx, sizeof(*ec));
+ memset(ec, 0, sizeof(*ec));
+ ec->object = CK_INVALID_HANDLE;
+ ec->ontoken = true;
+ ec->reqlogon = true;
+ key->keydata.pkey = ec;
+
+ ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2);
+ memset(ec->repr, 0, sizeof(*attr) * 2);
+ ec->attrcnt = 2;
+ attr = ec->repr;
+ attr[0].type = CKA_EC_PARAMS;
+ attr[1].type = CKA_EC_POINT;
+
+ ret = pk11_parse_uri(ec, label, key->mctx, OP_ECDSA);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx));
+ ret = pk11_get_session(pk11_ctx, OP_ECDSA, true, false, ec->reqlogon,
+ NULL, ec->slot);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ attr = pk11_attribute_bytype(ec, CKA_LABEL);
+ if (attr == NULL) {
+ attr = pk11_attribute_bytype(ec, CKA_ID);
+ INSIST(attr != NULL);
+ searchTemplate[3].type = CKA_ID;
+ }
+ searchTemplate[3].pValue = attr->pValue;
+ searchTemplate[3].ulValueLen = attr->ulValueLen;
+
+ PK11_RET(pkcs_C_FindObjectsInit,
+ (pk11_ctx->session, searchTemplate, (CK_ULONG)4),
+ DST_R_CRYPTOFAILURE);
+ PK11_RET(pkcs_C_FindObjects,
+ (pk11_ctx->session, &hKey, (CK_ULONG)1, &cnt),
+ DST_R_CRYPTOFAILURE);
+ (void)pkcs_C_FindObjectsFinal(pk11_ctx->session);
+ if (cnt == 0) {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+ if (cnt > 1) {
+ DST_RET(ISC_R_EXISTS);
+ }
+
+ attr = ec->repr;
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2),
+ DST_R_CRYPTOFAILURE);
+ for (i = 0; i <= 1; i++) {
+ attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen);
+ memset(attr[i].pValue, 0, attr[i].ulValueLen);
+ }
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2),
+ DST_R_CRYPTOFAILURE);
+
+ keyClass = CKO_PRIVATE_KEY;
+ PK11_RET(pkcs_C_FindObjectsInit,
+ (pk11_ctx->session, searchTemplate, (CK_ULONG)4),
+ DST_R_CRYPTOFAILURE);
+ PK11_RET(pkcs_C_FindObjects,
+ (pk11_ctx->session, &ec->object, (CK_ULONG)1, &cnt),
+ DST_R_CRYPTOFAILURE);
+ (void)pkcs_C_FindObjectsFinal(pk11_ctx->session);
+ if (cnt == 0) {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+ if (cnt > 1) {
+ DST_RET(ISC_R_EXISTS);
+ }
+
+ if (engine != NULL) {
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ }
+
+ key->label = isc_mem_strdup(key->mctx, label);
+ switch (key->key_alg) {
+ case DST_ALG_ECDSA256:
+ key->key_size = DNS_KEY_ECDSA256SIZE * 4;
+ break;
+ case DST_ALG_ECDSA384:
+ key->key_size = DNS_KEY_ECDSA384SIZE * 4;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ return (ISC_R_SUCCESS);
+
+err:
+ pkcs11ecdsa_destroy(key);
+ if (pk11_ctx != NULL) {
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ }
+ return (ret);
+}
+
+static dst_func_t pkcs11ecdsa_functions = {
+ pkcs11ecdsa_createctx,
+ NULL, /*%< createctx2 */
+ pkcs11ecdsa_destroyctx,
+ pkcs11ecdsa_adddata,
+ pkcs11ecdsa_sign,
+ pkcs11ecdsa_verify,
+ NULL, /*%< verify2 */
+ NULL, /*%< computesecret */
+ pkcs11ecdsa_compare,
+ NULL, /*%< paramcompare */
+ pkcs11ecdsa_generate,
+ pkcs11ecdsa_isprivate,
+ pkcs11ecdsa_destroy,
+ pkcs11ecdsa_todns,
+ pkcs11ecdsa_fromdns,
+ pkcs11ecdsa_tofile,
+ pkcs11ecdsa_parse,
+ NULL, /*%< cleanup */
+ pkcs11ecdsa_fromlabel,
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+isc_result_t
+dst__pkcs11ecdsa_init(dst_func_t **funcp) {
+ REQUIRE(funcp != NULL);
+ if (*funcp == NULL) {
+ *funcp = &pkcs11ecdsa_functions;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+#endif /* USE_PKCS11 */
diff --git a/lib/dns/pkcs11eddsa_link.c b/lib/dns/pkcs11eddsa_link.c
new file mode 100644
index 0000000..a6aff28
--- /dev/null
+++ b/lib/dns/pkcs11eddsa_link.c
@@ -0,0 +1,1124 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#if USE_PKCS11
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <pk11/constants.h>
+#include <pk11/internal.h>
+#include <pk11/pk11.h>
+#include <pkcs11/pkcs11.h>
+
+#include <dns/keyvalues.h>
+
+#include <dst/result.h>
+
+#include "dst_internal.h"
+#include "dst_parse.h"
+#include "dst_pkcs11.h"
+
+/*
+ * FIPS 186-3 EDDSA keys:
+ * mechanisms:
+ * CKM_EDDSA,
+ * CKM_EC_EDWARDS_KEY_PAIR_GEN
+ * domain parameters:
+ * CKA_EC_PARAMS (choice with OID namedCurve)
+ * public keys:
+ * object class CKO_PUBLIC_KEY
+ * key type CKK_EC_EDWARDS
+ * attribute CKA_EC_PARAMS (choice with OID namedCurve)
+ * attribute CKA_EC_POINT (big int A)
+ * private keys:
+ * object class CKO_PRIVATE_KEY
+ * key type CKK_EC_EDWARDS
+ * attribute CKA_EC_PARAMS (choice with OID namedCurve)
+ * attribute CKA_VALUE (big int k)
+ * point format: 0x04 (octet-string) <size> <A>
+ */
+
+#define TAG_OCTECT_STRING 0x04
+
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+static CK_BBOOL truevalue = TRUE;
+static CK_BBOOL falsevalue = FALSE;
+
+static void
+pkcs11eddsa_destroy(dst_key_t *key);
+
+static isc_result_t
+pkcs11eddsa_createctx(dst_key_t *key, dst_context_t *dctx) {
+ isc_buffer_t *buf = NULL;
+
+ UNUSED(key);
+ REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 ||
+ dctx->key->key_alg == DST_ALG_ED448);
+
+ isc_buffer_allocate(dctx->mctx, &buf, 16);
+ isc_buffer_setautorealloc(buf, true);
+ dctx->ctxdata.generic = buf;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+pkcs11eddsa_destroyctx(dst_context_t *dctx) {
+ isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 ||
+ dctx->key->key_alg == DST_ALG_ED448);
+ if (buf != NULL) {
+ isc_buffer_free(&buf);
+ }
+ dctx->ctxdata.generic = NULL;
+}
+
+static isc_result_t
+pkcs11eddsa_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic;
+ isc_result_t result;
+
+ REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 ||
+ dctx->key->key_alg == DST_ALG_ED448);
+
+ result = isc_buffer_copyregion(buf, data);
+ INSIST(result == ISC_R_SUCCESS);
+
+ return (result);
+}
+
+static isc_result_t
+pkcs11eddsa_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic;
+ CK_RV rv;
+ CK_MECHANISM mech = { CKM_EDDSA, NULL, 0 };
+ CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
+ CK_KEY_TYPE keyType = CKK_EC_EDWARDS;
+ CK_ATTRIBUTE keyTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_EC_PARAMS, NULL, 0 },
+ { CKA_VALUE, NULL, 0 }
+ };
+ CK_ATTRIBUTE *attr;
+ CK_ULONG siglen;
+ CK_SLOT_ID slotid;
+ pk11_context_t *pk11_ctx;
+ dst_key_t *key = dctx->key;
+ pk11_object_t *ec = key->keydata.pkey;
+ isc_region_t t;
+ isc_region_t r;
+ isc_result_t ret = ISC_R_SUCCESS;
+ unsigned int i;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+ REQUIRE(ec != NULL);
+
+ switch (key->key_alg) {
+ case DST_ALG_ED25519:
+ siglen = DNS_SIG_ED25519SIZE;
+ break;
+ case DST_ALG_ED448:
+ siglen = DNS_SIG_ED448SIZE;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx));
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ if (ec->ontoken && (dctx->use == DO_SIGN)) {
+ slotid = ec->slot;
+ } else {
+ slotid = pk11_get_best_token(OP_EDDSA);
+ }
+ ret = pk11_get_session(pk11_ctx, OP_EDDSA, true, false, ec->reqlogon,
+ NULL, slotid);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ isc_buffer_availableregion(sig, &r);
+ if (r.length < siglen) {
+ DST_RET(ISC_R_NOSPACE);
+ }
+
+ if (ec->ontoken && (ec->object != CK_INVALID_HANDLE)) {
+ pk11_ctx->ontoken = ec->ontoken;
+ pk11_ctx->object = ec->object;
+ goto token_key;
+ }
+
+ for (attr = pk11_attribute_first(ec); attr != NULL;
+ attr = pk11_attribute_next(ec, attr))
+ {
+ switch (attr->type) {
+ case CKA_EC_PARAMS:
+ INSIST(keyTemplate[5].type == attr->type);
+ keyTemplate[5].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[5].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[5].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_VALUE:
+ INSIST(keyTemplate[6].type == attr->type);
+ keyTemplate[6].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[6].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[6].ulValueLen = attr->ulValueLen;
+ break;
+ }
+ }
+ pk11_ctx->object = CK_INVALID_HANDLE;
+ pk11_ctx->ontoken = false;
+ PK11_RET(pkcs_C_CreateObject,
+ (pk11_ctx->session, keyTemplate, (CK_ULONG)7, &hKey),
+ ISC_R_FAILURE);
+
+token_key:
+
+ PK11_RET(pkcs_C_SignInit,
+ (pk11_ctx->session, &mech,
+ pk11_ctx->ontoken ? pk11_ctx->object : hKey),
+ ISC_R_FAILURE);
+
+ isc_buffer_usedregion(buf, &t);
+
+ PK11_RET(pkcs_C_Sign,
+ (pk11_ctx->session, (CK_BYTE_PTR)t.base, (CK_ULONG)t.length,
+ (CK_BYTE_PTR)r.base, &siglen),
+ DST_R_SIGNFAILURE);
+
+ isc_buffer_add(sig, (unsigned int)siglen);
+
+err:
+
+ if (hKey != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey);
+ }
+ for (i = 5; i <= 6; i++) {
+ if (keyTemplate[i].pValue != NULL) {
+ {
+ memset(keyTemplate[i].pValue, 0,
+ keyTemplate[i].ulValueLen);
+ isc_mem_put(dctx->mctx, keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ }
+ }
+ }
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ isc_buffer_free(&buf);
+ dctx->ctxdata.generic = NULL;
+
+ return (ret);
+}
+
+static isc_result_t
+pkcs11eddsa_verify(dst_context_t *dctx, const isc_region_t *sig) {
+ isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic;
+ CK_RV rv;
+ CK_MECHANISM mech = { CKM_EDDSA, NULL, 0 };
+ CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY;
+ CK_KEY_TYPE keyType = CKK_EC_EDWARDS;
+ CK_ATTRIBUTE keyTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_EC_PARAMS, NULL, 0 },
+ { CKA_EC_POINT, NULL, 0 }
+ };
+ CK_ATTRIBUTE *attr;
+ CK_SLOT_ID slotid;
+ pk11_context_t *pk11_ctx;
+ dst_key_t *key = dctx->key;
+ pk11_object_t *ec = key->keydata.pkey;
+ isc_region_t t;
+ isc_result_t ret = ISC_R_SUCCESS;
+ unsigned int i;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+ REQUIRE(ec != NULL);
+
+ pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx));
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ if (ec->ontoken && (dctx->use == DO_SIGN)) {
+ slotid = ec->slot;
+ } else {
+ slotid = pk11_get_best_token(OP_EDDSA);
+ }
+ ret = pk11_get_session(pk11_ctx, OP_EDDSA, true, false, ec->reqlogon,
+ NULL, slotid);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ for (attr = pk11_attribute_first(ec); attr != NULL;
+ attr = pk11_attribute_next(ec, attr))
+ {
+ switch (attr->type) {
+ case CKA_EC_PARAMS:
+ INSIST(keyTemplate[5].type == attr->type);
+ keyTemplate[5].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[5].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[5].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_EC_POINT:
+ INSIST(keyTemplate[6].type == attr->type);
+ keyTemplate[6].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[6].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[6].ulValueLen = attr->ulValueLen;
+ break;
+ }
+ }
+ pk11_ctx->object = CK_INVALID_HANDLE;
+ pk11_ctx->ontoken = false;
+ PK11_RET(pkcs_C_CreateObject,
+ (pk11_ctx->session, keyTemplate, (CK_ULONG)7, &hKey),
+ ISC_R_FAILURE);
+
+ PK11_RET(pkcs_C_VerifyInit, (pk11_ctx->session, &mech, hKey),
+ ISC_R_FAILURE);
+
+ isc_buffer_usedregion(buf, &t);
+
+ PK11_RET(pkcs_C_Verify,
+ (pk11_ctx->session, (CK_BYTE_PTR)t.base, (CK_ULONG)t.length,
+ (CK_BYTE_PTR)sig->base, (CK_ULONG)sig->length),
+ DST_R_VERIFYFAILURE);
+
+err:
+
+ if (hKey != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey);
+ }
+ for (i = 5; i <= 6; i++) {
+ if (keyTemplate[i].pValue != NULL) {
+ {
+ memset(keyTemplate[i].pValue, 0,
+ keyTemplate[i].ulValueLen);
+ isc_mem_put(dctx->mctx, keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ }
+ }
+ }
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ isc_buffer_free(&buf);
+ dctx->ctxdata.generic = NULL;
+
+ return (ret);
+}
+
+static bool
+pkcs11eddsa_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ pk11_object_t *ec1, *ec2;
+ CK_ATTRIBUTE *attr1, *attr2;
+
+ ec1 = key1->keydata.pkey;
+ ec2 = key2->keydata.pkey;
+
+ if ((ec1 == NULL) && (ec2 == NULL)) {
+ return (true);
+ } else if ((ec1 == NULL) || (ec2 == NULL)) {
+ return (false);
+ }
+
+ attr1 = pk11_attribute_bytype(ec1, CKA_EC_PARAMS);
+ attr2 = pk11_attribute_bytype(ec2, CKA_EC_PARAMS);
+ if ((attr1 == NULL) && (attr2 == NULL)) {
+ return (true);
+ } else if ((attr1 == NULL) || (attr2 == NULL) ||
+ (attr1->ulValueLen != attr2->ulValueLen) ||
+ !isc_safe_memequal(attr1->pValue, attr2->pValue,
+ attr1->ulValueLen))
+ {
+ return (false);
+ }
+
+ attr1 = pk11_attribute_bytype(ec1, CKA_EC_POINT);
+ attr2 = pk11_attribute_bytype(ec2, CKA_EC_POINT);
+ if ((attr1 == NULL) && (attr2 == NULL)) {
+ return (true);
+ } else if ((attr1 == NULL) || (attr2 == NULL) ||
+ (attr1->ulValueLen != attr2->ulValueLen) ||
+ !isc_safe_memequal(attr1->pValue, attr2->pValue,
+ attr1->ulValueLen))
+ {
+ return (false);
+ }
+
+ attr1 = pk11_attribute_bytype(ec1, CKA_VALUE);
+ attr2 = pk11_attribute_bytype(ec2, CKA_VALUE);
+ if (((attr1 != NULL) || (attr2 != NULL)) &&
+ ((attr1 == NULL) || (attr2 == NULL) ||
+ (attr1->ulValueLen != attr2->ulValueLen) ||
+ !isc_safe_memequal(attr1->pValue, attr2->pValue,
+ attr1->ulValueLen)))
+ {
+ return (false);
+ }
+
+ if (!ec1->ontoken && !ec2->ontoken) {
+ return (true);
+ } else if (ec1->ontoken || ec2->ontoken || (ec1->object != ec2->object))
+ {
+ return (false);
+ }
+
+ return (true);
+}
+
+#define SETCURVE() \
+ switch (key->key_alg) { \
+ case DST_ALG_ED25519: \
+ attr->pValue = isc_mem_get(key->mctx, \
+ sizeof(PK11_ECX_ED25519)); \
+ memmove(attr->pValue, PK11_ECX_ED25519, \
+ sizeof(PK11_ECX_ED25519)); \
+ attr->ulValueLen = sizeof(PK11_ECX_ED25519); \
+ break; \
+ case DST_ALG_ED448: \
+ attr->pValue = isc_mem_get(key->mctx, sizeof(PK11_ECX_ED448)); \
+ memmove(attr->pValue, PK11_ECX_ED448, sizeof(PK11_ECX_ED448)); \
+ attr->ulValueLen = sizeof(PK11_ECX_ED448); \
+ break; \
+ default: \
+ UNREACHABLE(); \
+ }
+
+#define FREECURVE() \
+ if (attr->pValue != NULL) { \
+ memset(attr->pValue, 0, attr->ulValueLen); \
+ isc_mem_put(key->mctx, attr->pValue, attr->ulValueLen); \
+ attr->pValue = NULL; \
+ }
+
+static isc_result_t
+pkcs11eddsa_generate(dst_key_t *key, int unused, void (*callback)(int)) {
+ CK_RV rv;
+ CK_MECHANISM mech = { CKM_EC_EDWARDS_KEY_PAIR_GEN, NULL, 0 };
+ CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY;
+ CK_KEY_TYPE keyType = CKK_EC_EDWARDS;
+ CK_ATTRIBUTE pubTemplate[] = {
+ { CKA_CLASS, &pubClass, (CK_ULONG)sizeof(pubClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_EC_PARAMS, NULL, 0 }
+ };
+ CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE;
+ CK_OBJECT_HANDLE privClass = CKO_PRIVATE_KEY;
+ CK_ATTRIBUTE privTemplate[] = {
+ { CKA_CLASS, &privClass, (CK_ULONG)sizeof(privClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_SENSITIVE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_EXTRACTABLE, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) }
+ };
+ CK_ATTRIBUTE *attr;
+ pk11_object_t *ec;
+ pk11_context_t *pk11_ctx;
+ isc_result_t ret;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+ UNUSED(unused);
+ UNUSED(callback);
+
+ pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx));
+ ret = pk11_get_session(pk11_ctx, OP_EDDSA, true, false, false, NULL,
+ pk11_get_best_token(OP_EDDSA));
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ ec = isc_mem_get(key->mctx, sizeof(*ec));
+ memset(ec, 0, sizeof(*ec));
+ key->keydata.pkey = ec;
+ ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 3);
+ memset(ec->repr, 0, sizeof(*attr) * 3);
+ ec->attrcnt = 3;
+
+ attr = ec->repr;
+ attr[0].type = CKA_EC_PARAMS;
+ attr[1].type = CKA_EC_POINT;
+ attr[2].type = CKA_VALUE;
+
+ attr = &pubTemplate[5];
+ SETCURVE();
+
+ PK11_RET(pkcs_C_GenerateKeyPair,
+ (pk11_ctx->session, &mech, pubTemplate, (CK_ULONG)6,
+ privTemplate, (CK_ULONG)7, &pub, &priv),
+ DST_R_CRYPTOFAILURE);
+
+ attr = &pubTemplate[5];
+ FREECURVE();
+
+ attr = ec->repr;
+ SETCURVE();
+
+ attr++;
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 1),
+ DST_R_CRYPTOFAILURE);
+ attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen);
+ memset(attr->pValue, 0, attr->ulValueLen);
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 1),
+ DST_R_CRYPTOFAILURE);
+
+ attr++;
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 1),
+ DST_R_CRYPTOFAILURE);
+ attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen);
+ memset(attr->pValue, 0, attr->ulValueLen);
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 1),
+ DST_R_CRYPTOFAILURE);
+
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, priv);
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, pub);
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ switch (key->key_alg) {
+ case DST_ALG_ED25519:
+ key->key_size = DNS_KEY_ED25519SIZE * 8;
+ break;
+ case DST_ALG_ED448:
+ key->key_size = DNS_KEY_ED448SIZE * 8;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+
+err:
+ pkcs11eddsa_destroy(key);
+ if (priv != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, priv);
+ }
+ if (pub != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, pub);
+ }
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ return (ret);
+}
+
+static bool
+pkcs11eddsa_isprivate(const dst_key_t *key) {
+ pk11_object_t *ec = key->keydata.pkey;
+ CK_ATTRIBUTE *attr;
+
+ if (ec == NULL) {
+ return (false);
+ }
+ attr = pk11_attribute_bytype(ec, CKA_VALUE);
+ return (attr != NULL || ec->ontoken);
+}
+
+static void
+pkcs11eddsa_destroy(dst_key_t *key) {
+ pk11_object_t *ec = key->keydata.pkey;
+ CK_ATTRIBUTE *attr;
+
+ if (ec == NULL) {
+ return;
+ }
+
+ INSIST((ec->object == CK_INVALID_HANDLE) || ec->ontoken);
+
+ for (attr = pk11_attribute_first(ec); attr != NULL;
+ attr = pk11_attribute_next(ec, attr))
+ {
+ switch (attr->type) {
+ case CKA_LABEL:
+ case CKA_ID:
+ case CKA_EC_PARAMS:
+ case CKA_EC_POINT:
+ case CKA_VALUE:
+ FREECURVE();
+ break;
+ }
+ }
+ if (ec->repr != NULL) {
+ memset(ec->repr, 0, ec->attrcnt * sizeof(*attr));
+ isc_mem_put(key->mctx, ec->repr, ec->attrcnt * sizeof(*attr));
+ }
+ memset(ec, 0, sizeof(*ec));
+ isc_mem_put(key->mctx, ec, sizeof(*ec));
+ key->keydata.pkey = NULL;
+}
+
+static isc_result_t
+pkcs11eddsa_todns(const dst_key_t *key, isc_buffer_t *data) {
+ pk11_object_t *ec;
+ isc_region_t r;
+ unsigned int len;
+ CK_ATTRIBUTE *attr;
+
+ REQUIRE(key->keydata.pkey != NULL);
+
+ switch (key->key_alg) {
+ case DST_ALG_ED25519:
+ len = DNS_KEY_ED25519SIZE;
+ break;
+ case DST_ALG_ED448:
+ len = DNS_KEY_ED448SIZE;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ ec = key->keydata.pkey;
+ attr = pk11_attribute_bytype(ec, CKA_EC_POINT);
+ if ((attr == NULL) || (attr->ulValueLen != len + 2) ||
+ (((CK_BYTE_PTR)attr->pValue)[0] != TAG_OCTECT_STRING) ||
+ (((CK_BYTE_PTR)attr->pValue)[1] != len))
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ isc_buffer_availableregion(data, &r);
+ if (r.length < len) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(r.base, (CK_BYTE_PTR)attr->pValue + 2, len);
+ isc_buffer_add(data, len);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+pkcs11eddsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ pk11_object_t *ec;
+ isc_region_t r;
+ unsigned int len;
+ CK_ATTRIBUTE *attr;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ switch (key->key_alg) {
+ case DST_ALG_ED25519:
+ len = DNS_KEY_ED25519SIZE;
+ break;
+ case DST_ALG_ED448:
+ len = DNS_KEY_ED448SIZE;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ if (r.length != len) {
+ return (DST_R_INVALIDPUBLICKEY);
+ }
+
+ ec = isc_mem_get(key->mctx, sizeof(*ec));
+ memset(ec, 0, sizeof(*ec));
+ ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2);
+ ec->attrcnt = 2;
+
+ attr = ec->repr;
+ attr->type = CKA_EC_PARAMS;
+ SETCURVE();
+
+ attr++;
+ attr->type = CKA_EC_POINT;
+ attr->pValue = isc_mem_get(key->mctx, len + 2);
+ ((CK_BYTE_PTR)attr->pValue)[0] = TAG_OCTECT_STRING;
+ ((CK_BYTE_PTR)attr->pValue)[1] = len;
+ memmove((CK_BYTE_PTR)attr->pValue + 2, r.base, len);
+ attr->ulValueLen = len + 2;
+
+ isc_buffer_forward(data, len);
+ key->keydata.pkey = ec;
+ key->key_size = len * 8;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+pkcs11eddsa_tofile(const dst_key_t *key, const char *directory) {
+ isc_result_t ret;
+ pk11_object_t *ec;
+ dst_private_t priv;
+ unsigned char *buf = NULL;
+ unsigned int i = 0;
+ CK_ATTRIBUTE *attr;
+
+ if (key->keydata.pkey == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ priv.nelements = 0;
+ return (dst__privstruct_writefile(key, &priv, directory));
+ }
+
+ ec = key->keydata.pkey;
+ attr = pk11_attribute_bytype(ec, CKA_VALUE);
+ if (attr != NULL) {
+ buf = isc_mem_get(key->mctx, attr->ulValueLen);
+ priv.elements[i].tag = TAG_EDDSA_PRIVATEKEY;
+ priv.elements[i].length = (unsigned short)attr->ulValueLen;
+ memmove(buf, attr->pValue, attr->ulValueLen);
+ priv.elements[i].data = buf;
+ i++;
+ }
+
+ if (key->engine != NULL) {
+ priv.elements[i].tag = TAG_EDDSA_ENGINE;
+ priv.elements[i].length = strlen(key->engine) + 1;
+ priv.elements[i].data = (unsigned char *)key->engine;
+ i++;
+ }
+
+ if (key->label != NULL) {
+ priv.elements[i].tag = TAG_EDDSA_LABEL;
+ priv.elements[i].length = strlen(key->label) + 1;
+ priv.elements[i].data = (unsigned char *)key->label;
+ i++;
+ }
+
+ priv.nelements = i;
+ ret = dst__privstruct_writefile(key, &priv, directory);
+
+ if (buf != NULL) {
+ memset(buf, 0, attr->ulValueLen);
+ isc_mem_put(key->mctx, buf, attr->ulValueLen);
+ }
+ return (ret);
+}
+
+static isc_result_t
+pkcs11eddsa_fetch(dst_key_t *key, const char *engine, const char *label,
+ dst_key_t *pub) {
+ CK_RV rv;
+ CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
+ CK_KEY_TYPE keyType = CKK_EC_EDWARDS;
+ CK_ATTRIBUTE searchTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_LABEL, NULL, 0 }
+ };
+ CK_ULONG cnt;
+ CK_ATTRIBUTE *attr;
+ CK_ATTRIBUTE *pubattr;
+ pk11_object_t *ec;
+ pk11_object_t *pubec;
+ pk11_context_t *pk11_ctx = NULL;
+ isc_result_t ret;
+
+ if (label == NULL) {
+ return (DST_R_NOENGINE);
+ }
+
+ ec = key->keydata.pkey;
+ pubec = pub->keydata.pkey;
+
+ ec->object = CK_INVALID_HANDLE;
+ ec->ontoken = true;
+ ec->reqlogon = true;
+ ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2);
+ memset(ec->repr, 0, sizeof(*attr) * 2);
+ ec->attrcnt = 2;
+ attr = ec->repr;
+
+ attr->type = CKA_EC_PARAMS;
+ pubattr = pk11_attribute_bytype(pubec, CKA_EC_PARAMS);
+ INSIST(pubattr != NULL);
+ attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen);
+ memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen);
+ attr->ulValueLen = pubattr->ulValueLen;
+ attr++;
+
+ attr->type = CKA_EC_POINT;
+ pubattr = pk11_attribute_bytype(pubec, CKA_EC_POINT);
+ INSIST(pubattr != NULL);
+ attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen);
+ memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen);
+ attr->ulValueLen = pubattr->ulValueLen;
+
+ ret = pk11_parse_uri(ec, label, key->mctx, OP_EDDSA);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx));
+ ret = pk11_get_session(pk11_ctx, OP_EDDSA, true, false, ec->reqlogon,
+ NULL, ec->slot);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ attr = pk11_attribute_bytype(ec, CKA_LABEL);
+ if (attr == NULL) {
+ attr = pk11_attribute_bytype(ec, CKA_ID);
+ INSIST(attr != NULL);
+ searchTemplate[3].type = CKA_ID;
+ }
+ searchTemplate[3].pValue = attr->pValue;
+ searchTemplate[3].ulValueLen = attr->ulValueLen;
+
+ PK11_RET(pkcs_C_FindObjectsInit,
+ (pk11_ctx->session, searchTemplate, (CK_ULONG)4),
+ DST_R_CRYPTOFAILURE);
+ PK11_RET(pkcs_C_FindObjects,
+ (pk11_ctx->session, &ec->object, (CK_ULONG)1, &cnt),
+ DST_R_CRYPTOFAILURE);
+ (void)pkcs_C_FindObjectsFinal(pk11_ctx->session);
+ if (cnt == 0) {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+ if (cnt > 1) {
+ DST_RET(ISC_R_EXISTS);
+ }
+
+ if (engine != NULL) {
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ }
+
+ key->label = isc_mem_strdup(key->mctx, label);
+
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ return (ISC_R_SUCCESS);
+
+err:
+ if (pk11_ctx != NULL) {
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ }
+ return (ret);
+}
+
+static isc_result_t
+pkcs11eddsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t ret;
+ pk11_object_t *ec = NULL;
+ CK_ATTRIBUTE *attr, *pattr;
+ isc_mem_t *mctx = key->mctx;
+ unsigned int i;
+ const char *engine = NULL, *label = NULL;
+
+ REQUIRE(key->key_alg == DST_ALG_ED25519 ||
+ key->key_alg == DST_ALG_ED448);
+
+ if ((pub == NULL) || (pub->keydata.pkey == NULL)) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ /* read private key file */
+ ret = dst__privstruct_parse(key, DST_ALG_ED25519, lexer, mctx, &priv);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ if (key->external) {
+ if (priv.nelements != 0) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ key->keydata.pkey = pub->keydata.pkey;
+ pub->keydata.pkey = NULL;
+ key->key_size = pub->key_size;
+
+ dst__privstruct_free(&priv, mctx);
+ memset(&priv, 0, sizeof(priv));
+
+ return (ISC_R_SUCCESS);
+ }
+
+ for (i = 0; i < priv.nelements; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_EDDSA_ENGINE:
+ engine = (char *)priv.elements[i].data;
+ break;
+ case TAG_EDDSA_LABEL:
+ label = (char *)priv.elements[i].data;
+ break;
+ default:
+ break;
+ }
+ }
+ ec = isc_mem_get(key->mctx, sizeof(*ec));
+ memset(ec, 0, sizeof(*ec));
+ key->keydata.pkey = ec;
+
+ /* Is this key is stored in a HSM? See if we can fetch it. */
+ if ((label != NULL) || (engine != NULL)) {
+ ret = pkcs11eddsa_fetch(key, engine, label, pub);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ dst__privstruct_free(&priv, mctx);
+ memset(&priv, 0, sizeof(priv));
+ return (ret);
+ }
+
+ ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 3);
+ memset(ec->repr, 0, sizeof(*attr) * 3);
+ ec->attrcnt = 3;
+
+ attr = ec->repr;
+ attr->type = CKA_EC_PARAMS;
+ pattr = pk11_attribute_bytype(pub->keydata.pkey, CKA_EC_PARAMS);
+ INSIST(pattr != NULL);
+ attr->pValue = isc_mem_get(key->mctx, pattr->ulValueLen);
+ memmove(attr->pValue, pattr->pValue, pattr->ulValueLen);
+ attr->ulValueLen = pattr->ulValueLen;
+
+ attr++;
+ attr->type = CKA_EC_POINT;
+ pattr = pk11_attribute_bytype(pub->keydata.pkey, CKA_EC_POINT);
+ INSIST(pattr != NULL);
+ attr->pValue = isc_mem_get(key->mctx, pattr->ulValueLen);
+ memmove(attr->pValue, pattr->pValue, pattr->ulValueLen);
+ attr->ulValueLen = pattr->ulValueLen;
+
+ attr++;
+ attr->type = CKA_VALUE;
+ attr->pValue = isc_mem_get(key->mctx, priv.elements[0].length);
+ memmove(attr->pValue, priv.elements[0].data, priv.elements[0].length);
+ attr->ulValueLen = priv.elements[0].length;
+
+ dst__privstruct_free(&priv, mctx);
+ memset(&priv, 0, sizeof(priv));
+ switch (key->key_alg) {
+ case DST_ALG_ED25519:
+ key->key_size = DNS_KEY_ED25519SIZE * 8;
+ break;
+ case DST_ALG_ED448:
+ key->key_size = DNS_KEY_ED448SIZE * 8;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+
+err:
+ pkcs11eddsa_destroy(key);
+ dst__privstruct_free(&priv, mctx);
+ memset(&priv, 0, sizeof(priv));
+ return (ret);
+}
+
+static isc_result_t
+pkcs11eddsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin) {
+ CK_RV rv;
+ CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY;
+ CK_KEY_TYPE keyType = CKK_EC_EDWARDS;
+ CK_ATTRIBUTE searchTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_LABEL, NULL, 0 }
+ };
+ CK_ULONG cnt;
+ CK_ATTRIBUTE *attr;
+ pk11_object_t *ec;
+ pk11_context_t *pk11_ctx = NULL;
+ isc_result_t ret;
+ unsigned int i;
+
+ UNUSED(pin);
+
+ ec = isc_mem_get(key->mctx, sizeof(*ec));
+ memset(ec, 0, sizeof(*ec));
+ ec->object = CK_INVALID_HANDLE;
+ ec->ontoken = true;
+ ec->reqlogon = true;
+ key->keydata.pkey = ec;
+
+ ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2);
+ memset(ec->repr, 0, sizeof(*attr) * 2);
+ ec->attrcnt = 2;
+ attr = ec->repr;
+ attr[0].type = CKA_EC_PARAMS;
+ attr[1].type = CKA_EC_POINT;
+
+ ret = pk11_parse_uri(ec, label, key->mctx, OP_EDDSA);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx));
+ ret = pk11_get_session(pk11_ctx, OP_EDDSA, true, false, ec->reqlogon,
+ NULL, ec->slot);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ attr = pk11_attribute_bytype(ec, CKA_LABEL);
+ if (attr == NULL) {
+ attr = pk11_attribute_bytype(ec, CKA_ID);
+ INSIST(attr != NULL);
+ searchTemplate[3].type = CKA_ID;
+ }
+ searchTemplate[3].pValue = attr->pValue;
+ searchTemplate[3].ulValueLen = attr->ulValueLen;
+
+ PK11_RET(pkcs_C_FindObjectsInit,
+ (pk11_ctx->session, searchTemplate, (CK_ULONG)4),
+ DST_R_CRYPTOFAILURE);
+ PK11_RET(pkcs_C_FindObjects,
+ (pk11_ctx->session, &hKey, (CK_ULONG)1, &cnt),
+ DST_R_CRYPTOFAILURE);
+ (void)pkcs_C_FindObjectsFinal(pk11_ctx->session);
+ if (cnt == 0) {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+ if (cnt > 1) {
+ DST_RET(ISC_R_EXISTS);
+ }
+
+ attr = ec->repr;
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2),
+ DST_R_CRYPTOFAILURE);
+ for (i = 0; i <= 1; i++) {
+ attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen);
+ memset(attr[i].pValue, 0, attr[i].ulValueLen);
+ }
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2),
+ DST_R_CRYPTOFAILURE);
+
+ keyClass = CKO_PRIVATE_KEY;
+ PK11_RET(pkcs_C_FindObjectsInit,
+ (pk11_ctx->session, searchTemplate, (CK_ULONG)4),
+ DST_R_CRYPTOFAILURE);
+ PK11_RET(pkcs_C_FindObjects,
+ (pk11_ctx->session, &ec->object, (CK_ULONG)1, &cnt),
+ DST_R_CRYPTOFAILURE);
+ (void)pkcs_C_FindObjectsFinal(pk11_ctx->session);
+ if (cnt == 0) {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+ if (cnt > 1) {
+ DST_RET(ISC_R_EXISTS);
+ }
+
+ if (engine != NULL) {
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ }
+
+ key->label = isc_mem_strdup(key->mctx, label);
+ switch (key->key_alg) {
+ case DST_ALG_ED25519:
+ key->key_size = DNS_KEY_ED25519SIZE * 8;
+ break;
+ case DST_ALG_ED448:
+ key->key_size = DNS_KEY_ED448SIZE * 8;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ return (ISC_R_SUCCESS);
+
+err:
+ pkcs11eddsa_destroy(key);
+ if (pk11_ctx != NULL) {
+ pk11_return_session(pk11_ctx);
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ }
+ return (ret);
+}
+
+static dst_func_t pkcs11eddsa_functions = {
+ pkcs11eddsa_createctx,
+ NULL, /*%< createctx2 */
+ pkcs11eddsa_destroyctx,
+ pkcs11eddsa_adddata,
+ pkcs11eddsa_sign,
+ pkcs11eddsa_verify,
+ NULL, /*%< verify2 */
+ NULL, /*%< computesecret */
+ pkcs11eddsa_compare,
+ NULL, /*%< paramcompare */
+ pkcs11eddsa_generate,
+ pkcs11eddsa_isprivate,
+ pkcs11eddsa_destroy,
+ pkcs11eddsa_todns,
+ pkcs11eddsa_fromdns,
+ pkcs11eddsa_tofile,
+ pkcs11eddsa_parse,
+ NULL, /*%< cleanup */
+ pkcs11eddsa_fromlabel,
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+isc_result_t
+dst__pkcs11eddsa_init(dst_func_t **funcp) {
+ REQUIRE(funcp != NULL);
+ if (*funcp == NULL) {
+ *funcp = &pkcs11eddsa_functions;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+#endif /* USE_PKCS11 */
diff --git a/lib/dns/pkcs11rsa_link.c b/lib/dns/pkcs11rsa_link.c
new file mode 100644
index 0000000..b439dd7
--- /dev/null
+++ b/lib/dns/pkcs11rsa_link.c
@@ -0,0 +1,2115 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#if USE_PKCS11
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <pk11/internal.h>
+#include <pk11/site.h>
+
+#include <dst/result.h>
+
+#include "dst_internal.h"
+#include "dst_parse.h"
+#include "dst_pkcs11.h"
+
+/*
+ * Limit the size of public exponents.
+ */
+#ifndef RSA_MAX_PUBEXP_BITS
+#define RSA_MAX_PUBEXP_BITS 35
+#endif /* ifndef RSA_MAX_PUBEXP_BITS */
+
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+static CK_BBOOL truevalue = TRUE;
+static CK_BBOOL falsevalue = FALSE;
+
+static void
+pkcs11rsa_destroy(dst_key_t *key);
+
+#ifndef PK11_RSA_PKCS_REPLACE
+
+static isc_result_t
+pkcs11rsa_createctx_sign(dst_key_t *key, dst_context_t *dctx) {
+ CK_RV rv;
+ CK_MECHANISM mech = { 0, NULL, 0 };
+ CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
+ CK_KEY_TYPE keyType = CKK_RSA;
+ CK_ATTRIBUTE keyTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_SENSITIVE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_MODULUS, NULL, 0 },
+ { CKA_PUBLIC_EXPONENT, NULL, 0 },
+ { CKA_PRIVATE_EXPONENT, NULL, 0 },
+ { CKA_PRIME_1, NULL, 0 },
+ { CKA_PRIME_2, NULL, 0 },
+ { CKA_EXPONENT_1, NULL, 0 },
+ { CKA_EXPONENT_2, NULL, 0 },
+ { CKA_COEFFICIENT, NULL, 0 }
+ };
+ CK_ATTRIBUTE *attr;
+ CK_SLOT_ID slotid;
+ pk11_object_t *rsa;
+ pk11_context_t *pk11_ctx;
+ isc_result_t ret;
+ unsigned int i;
+
+ REQUIRE(key->key_alg == DST_ALG_RSASHA1 ||
+ key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ key->key_alg == DST_ALG_RSASHA256 ||
+ key->key_alg == DST_ALG_RSASHA512);
+
+ /*
+ * Reject incorrect RSA key lengths.
+ */
+ switch (dctx->key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ /* From RFC 3110 */
+ if (dctx->key->key_size > 4096) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA256:
+ /* From RFC 5702 */
+ if ((dctx->key->key_size < 512) || (dctx->key->key_size > 4096))
+ {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA512:
+ /* From RFC 5702 */
+ if ((dctx->key->key_size < 1024) ||
+ (dctx->key->key_size > 4096))
+ {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ rsa = key->keydata.pkey;
+
+ pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx));
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ if (rsa->ontoken) {
+ slotid = rsa->slot;
+ } else {
+ slotid = pk11_get_best_token(OP_RSA);
+ }
+ ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, rsa->reqlogon,
+ NULL, slotid);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ if (rsa->ontoken && (rsa->object != CK_INVALID_HANDLE)) {
+ pk11_ctx->ontoken = rsa->ontoken;
+ pk11_ctx->object = rsa->object;
+ goto token_key;
+ }
+
+ for (attr = pk11_attribute_first(rsa); attr != NULL;
+ attr = pk11_attribute_next(rsa, attr))
+ {
+ switch (attr->type) {
+ case CKA_MODULUS:
+ INSIST(keyTemplate[6].type == attr->type);
+ keyTemplate[6].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[6].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[6].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_PUBLIC_EXPONENT:
+ INSIST(keyTemplate[7].type == attr->type);
+ keyTemplate[7].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[7].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[7].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_PRIVATE_EXPONENT:
+ INSIST(keyTemplate[8].type == attr->type);
+ keyTemplate[8].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[8].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[8].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_PRIME_1:
+ INSIST(keyTemplate[9].type == attr->type);
+ keyTemplate[9].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[9].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[9].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_PRIME_2:
+ INSIST(keyTemplate[10].type == attr->type);
+ keyTemplate[10].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[10].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[10].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_EXPONENT_1:
+ INSIST(keyTemplate[11].type == attr->type);
+ keyTemplate[11].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[11].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[11].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_EXPONENT_2:
+ INSIST(keyTemplate[12].type == attr->type);
+ keyTemplate[12].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[12].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[12].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_COEFFICIENT:
+ INSIST(keyTemplate[13].type == attr->type);
+ keyTemplate[13].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[13].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[13].ulValueLen = attr->ulValueLen;
+ break;
+ }
+ }
+ pk11_ctx->object = CK_INVALID_HANDLE;
+ pk11_ctx->ontoken = false;
+ PK11_RET(pkcs_C_CreateObject,
+ (pk11_ctx->session, keyTemplate, (CK_ULONG)14,
+ &pk11_ctx->object),
+ ISC_R_FAILURE);
+
+token_key:
+
+ switch (dctx->key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ mech.mechanism = CKM_SHA1_RSA_PKCS;
+ break;
+ case DST_ALG_RSASHA256:
+ mech.mechanism = CKM_SHA256_RSA_PKCS;
+ break;
+ case DST_ALG_RSASHA512:
+ mech.mechanism = CKM_SHA512_RSA_PKCS;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ PK11_RET(pkcs_C_SignInit, (pk11_ctx->session, &mech, pk11_ctx->object),
+ ISC_R_FAILURE);
+
+ dctx->ctxdata.pk11_ctx = pk11_ctx;
+
+ for (i = 6; i <= 13; i++) {
+ if (keyTemplate[i].pValue != NULL) {
+ {
+ isc_safe_memwipe(keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ isc_mem_put(dctx->mctx, keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+
+err:
+ if (!pk11_ctx->ontoken && (pk11_ctx->object != CK_INVALID_HANDLE)) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, pk11_ctx->object);
+ }
+ for (i = 6; i <= 13; i++) {
+ if (keyTemplate[i].pValue != NULL) {
+ {
+ isc_safe_memwipe(keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ isc_mem_put(dctx->mctx, keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ }
+ }
+ }
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ return (ret);
+}
+
+static isc_result_t
+pkcs11rsa_createctx_verify(dst_key_t *key, unsigned int maxbits,
+ dst_context_t *dctx) {
+ CK_RV rv;
+ CK_MECHANISM mech = { 0, NULL, 0 };
+ CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY;
+ CK_KEY_TYPE keyType = CKK_RSA;
+ CK_ATTRIBUTE keyTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_MODULUS, NULL, 0 },
+ { CKA_PUBLIC_EXPONENT, NULL, 0 },
+ };
+ CK_ATTRIBUTE *attr;
+ pk11_object_t *rsa;
+ pk11_context_t *pk11_ctx;
+ isc_result_t ret;
+ unsigned int i;
+
+ REQUIRE(key->key_alg == DST_ALG_RSASHA1 ||
+ key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ key->key_alg == DST_ALG_RSASHA256 ||
+ key->key_alg == DST_ALG_RSASHA512);
+ REQUIRE(maxbits <= RSA_MAX_PUBEXP_BITS);
+
+ /*
+ * Reject incorrect RSA key lengths.
+ */
+ switch (dctx->key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ /* From RFC 3110 */
+ if (dctx->key->key_size > 4096) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA256:
+ /* From RFC 5702 */
+ if ((dctx->key->key_size < 512) || (dctx->key->key_size > 4096))
+ {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA512:
+ /* From RFC 5702 */
+ if ((dctx->key->key_size < 1024) ||
+ (dctx->key->key_size > 4096))
+ {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ rsa = key->keydata.pkey;
+
+ pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx));
+ ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, rsa->reqlogon,
+ NULL, pk11_get_best_token(OP_RSA));
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ for (attr = pk11_attribute_first(rsa); attr != NULL;
+ attr = pk11_attribute_next(rsa, attr))
+ {
+ unsigned int bits;
+
+ switch (attr->type) {
+ case CKA_MODULUS:
+ INSIST(keyTemplate[5].type == attr->type);
+ keyTemplate[5].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[5].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[5].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_PUBLIC_EXPONENT:
+ INSIST(keyTemplate[6].type == attr->type);
+ keyTemplate[6].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[6].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[6].ulValueLen = attr->ulValueLen;
+ ret = pk11_numbits(attr->pValue, attr->ulValueLen,
+ &bits);
+ if (ret != ISC_R_SUCCESS ||
+ (bits > maxbits && maxbits != 0))
+ {
+ DST_RET(DST_R_VERIFYFAILURE);
+ }
+ break;
+ }
+ }
+ pk11_ctx->object = CK_INVALID_HANDLE;
+ pk11_ctx->ontoken = false;
+ PK11_RET(pkcs_C_CreateObject,
+ (pk11_ctx->session, keyTemplate, (CK_ULONG)7,
+ &pk11_ctx->object),
+ ISC_R_FAILURE);
+
+ switch (dctx->key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ mech.mechanism = CKM_SHA1_RSA_PKCS;
+ break;
+ case DST_ALG_RSASHA256:
+ mech.mechanism = CKM_SHA256_RSA_PKCS;
+ break;
+ case DST_ALG_RSASHA512:
+ mech.mechanism = CKM_SHA512_RSA_PKCS;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ PK11_RET(pkcs_C_VerifyInit,
+ (pk11_ctx->session, &mech, pk11_ctx->object), ISC_R_FAILURE);
+
+ dctx->ctxdata.pk11_ctx = pk11_ctx;
+
+ for (i = 5; i <= 6; i++) {
+ if (keyTemplate[i].pValue != NULL) {
+ {
+ isc_safe_memwipe(keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ isc_mem_put(dctx->mctx, keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+
+err:
+ if (!pk11_ctx->ontoken && (pk11_ctx->object != CK_INVALID_HANDLE)) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, pk11_ctx->object);
+ }
+ for (i = 5; i <= 6; i++) {
+ if (keyTemplate[i].pValue != NULL) {
+ {
+ isc_safe_memwipe(keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ isc_mem_put(dctx->mctx, keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ }
+ }
+ }
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ return (ret);
+}
+
+static isc_result_t
+pkcs11rsa_createctx(dst_key_t *key, dst_context_t *dctx) {
+ if (dctx->use == DO_SIGN) {
+ return (pkcs11rsa_createctx_sign(key, dctx));
+ } else {
+ return (pkcs11rsa_createctx_verify(key, 0U, dctx));
+ }
+}
+
+static isc_result_t
+pkcs11rsa_createctx2(dst_key_t *key, int maxbits, dst_context_t *dctx) {
+ if (dctx->use == DO_SIGN) {
+ return (pkcs11rsa_createctx_sign(key, dctx));
+ } else {
+ return (pkcs11rsa_createctx_verify(key, (unsigned)maxbits,
+ dctx));
+ }
+}
+
+static void
+pkcs11rsa_destroyctx(dst_context_t *dctx) {
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+
+ if (pk11_ctx != NULL) {
+ if (!pk11_ctx->ontoken &&
+ (pk11_ctx->object != CK_INVALID_HANDLE))
+ {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session,
+ pk11_ctx->object);
+ }
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ dctx->ctxdata.pk11_ctx = NULL;
+ }
+}
+
+static isc_result_t
+pkcs11rsa_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ CK_RV rv;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ if (dctx->use == DO_SIGN) {
+ PK11_CALL(pkcs_C_SignUpdate,
+ (pk11_ctx->session, (CK_BYTE_PTR)data->base,
+ (CK_ULONG)data->length),
+ ISC_R_FAILURE);
+ } else {
+ PK11_CALL(pkcs_C_VerifyUpdate,
+ (pk11_ctx->session, (CK_BYTE_PTR)data->base,
+ (CK_ULONG)data->length),
+ ISC_R_FAILURE);
+ }
+ return (ret);
+}
+
+static isc_result_t
+pkcs11rsa_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ CK_RV rv;
+ CK_ULONG siglen = 0;
+ isc_region_t r;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ PK11_RET(pkcs_C_SignFinal, (pk11_ctx->session, NULL, &siglen),
+ DST_R_SIGNFAILURE);
+
+ isc_buffer_availableregion(sig, &r);
+
+ if (r.length < (unsigned int)siglen) {
+ return (ISC_R_NOSPACE);
+ }
+
+ PK11_RET(pkcs_C_SignFinal,
+ (pk11_ctx->session, (CK_BYTE_PTR)r.base, &siglen),
+ DST_R_SIGNFAILURE);
+
+ isc_buffer_add(sig, (unsigned int)siglen);
+
+err:
+ return (ret);
+}
+
+static isc_result_t
+pkcs11rsa_verify(dst_context_t *dctx, const isc_region_t *sig) {
+ CK_RV rv;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ PK11_CALL(pkcs_C_VerifyFinal,
+ (pk11_ctx->session, (CK_BYTE_PTR)sig->base,
+ (CK_ULONG)sig->length),
+ DST_R_VERIFYFAILURE);
+ return (ret);
+}
+
+#else /* ifndef PK11_RSA_PKCS_REPLACE */
+
+/*
+ * CKM_<hash>_RSA_PKCS mechanisms are not available so fall back
+ * to CKM_RSA_PKCS and do the EMSA-PKCS#1-v1.5 encapsulation by hand.
+ */
+
+CK_BYTE md5_der[] = { 0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10 };
+CK_BYTE sha1_der[] = { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
+ 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
+CK_BYTE sha256_der[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02,
+ 0x01, 0x05, 0x00, 0x04, 0x20 };
+CK_BYTE sha512_der[] = { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02,
+ 0x03, 0x05, 0x00, 0x04, 0x40 };
+#define MAX_DER_SIZE 19
+#define MIN_PKCS1_PADLEN 11
+
+static isc_result_t
+pkcs11rsa_createctx(dst_key_t *key, dst_context_t *dctx) {
+ CK_RV rv;
+ CK_MECHANISM mech = { 0, NULL, 0 };
+ CK_SLOT_ID slotid;
+ pk11_object_t *rsa = key->keydata.pkey;
+ pk11_context_t *pk11_ctx;
+ isc_result_t ret;
+
+ REQUIRE(key->key_alg == DST_ALG_RSASHA1 ||
+ key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ key->key_alg == DST_ALG_RSASHA256 ||
+ key->key_alg == DST_ALG_RSASHA512);
+ REQUIRE(rsa != NULL);
+
+ /*
+ * Reject incorrect RSA key lengths.
+ */
+ switch (dctx->key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ /* From RFC 3110 */
+ if (dctx->key->key_size > 4096) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA256:
+ /* From RFC 5702 */
+ if ((dctx->key->key_size < 512) || (dctx->key->key_size > 4096))
+ {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA512:
+ /* From RFC 5702 */
+ if ((dctx->key->key_size < 1024) ||
+ (dctx->key->key_size > 4096))
+ {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ switch (key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ mech.mechanism = CKM_SHA_1;
+ break;
+ case DST_ALG_RSASHA256:
+ mech.mechanism = CKM_SHA256;
+ break;
+ case DST_ALG_RSASHA512:
+ mech.mechanism = CKM_SHA512;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx));
+ memset(pk11_ctx, 0, sizeof(*pk11_ctx));
+ if (rsa->ontoken) {
+ slotid = rsa->slot;
+ } else {
+ slotid = pk11_get_best_token(OP_RSA);
+ }
+ ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, rsa->reqlogon,
+ NULL, slotid);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ PK11_RET(pkcs_C_DigestInit, (pk11_ctx->session, &mech), ISC_R_FAILURE);
+ dctx->ctxdata.pk11_ctx = pk11_ctx;
+ return (ISC_R_SUCCESS);
+
+err:
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ return (ret);
+}
+
+static void
+pkcs11rsa_destroyctx(dst_context_t *dctx) {
+ CK_BYTE garbage[ISC_SHA512_DIGESTLENGTH];
+ CK_ULONG len = ISC_SHA512_DIGESTLENGTH;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+
+ if (pk11_ctx != NULL) {
+ (void)pkcs_C_DigestFinal(pk11_ctx->session, garbage, &len);
+ isc_safe_memwipe(garbage, sizeof(garbage));
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ dctx->ctxdata.pk11_ctx = NULL;
+ }
+}
+
+static isc_result_t
+pkcs11rsa_adddata(dst_context_t *dctx, const isc_region_t *data) {
+ CK_RV rv;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ PK11_CALL(pkcs_C_DigestUpdate,
+ (pk11_ctx->session, (CK_BYTE_PTR)data->base,
+ (CK_ULONG)data->length),
+ ISC_R_FAILURE);
+
+ return (ret);
+}
+
+static isc_result_t
+pkcs11rsa_sign(dst_context_t *dctx, isc_buffer_t *sig) {
+ CK_RV rv;
+ CK_MECHANISM mech = { CKM_RSA_PKCS, NULL, 0 };
+ CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
+ CK_KEY_TYPE keyType = CKK_RSA;
+ CK_ATTRIBUTE keyTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_SENSITIVE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_MODULUS, NULL, 0 },
+ { CKA_PUBLIC_EXPONENT, NULL, 0 },
+ { CKA_PRIVATE_EXPONENT, NULL, 0 },
+ { CKA_PRIME_1, NULL, 0 },
+ { CKA_PRIME_2, NULL, 0 },
+ { CKA_EXPONENT_1, NULL, 0 },
+ { CKA_EXPONENT_2, NULL, 0 },
+ { CKA_COEFFICIENT, NULL, 0 }
+ };
+ CK_ATTRIBUTE *attr;
+ CK_BYTE digest[MAX_DER_SIZE + ISC_SHA512_DIGESTLENGTH];
+ CK_BYTE *der;
+ CK_ULONG derlen;
+ CK_ULONG hashlen;
+ CK_ULONG dgstlen;
+ CK_ULONG siglen = 0;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+ dst_key_t *key = dctx->key;
+ pk11_object_t *rsa = key->keydata.pkey;
+ isc_region_t r;
+ isc_result_t ret = ISC_R_SUCCESS;
+ unsigned int i;
+
+ REQUIRE(key->key_alg == DST_ALG_RSASHA1 ||
+ key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ key->key_alg == DST_ALG_RSASHA256 ||
+ key->key_alg == DST_ALG_RSASHA512);
+ REQUIRE(rsa != NULL);
+
+ /*
+ * Reject incorrect RSA key lengths.
+ */
+ switch (dctx->key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ /* From RFC 3110 */
+ if (dctx->key->key_size > 4096) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA256:
+ /* From RFC 5702 */
+ if ((dctx->key->key_size < 512) || (dctx->key->key_size > 4096))
+ {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA512:
+ /* From RFC 5702 */
+ if ((dctx->key->key_size < 1024) ||
+ (dctx->key->key_size > 4096))
+ {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ switch (key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ der = sha1_der;
+ derlen = sizeof(sha1_der);
+ hashlen = ISC_SHA1_DIGESTLENGTH;
+ break;
+ case DST_ALG_RSASHA256:
+ der = sha256_der;
+ derlen = sizeof(sha256_der);
+ hashlen = ISC_SHA256_DIGESTLENGTH;
+ break;
+ case DST_ALG_RSASHA512:
+ der = sha512_der;
+ derlen = sizeof(sha512_der);
+ hashlen = ISC_SHA512_DIGESTLENGTH;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ dgstlen = derlen + hashlen;
+ INSIST(dgstlen <= sizeof(digest));
+ memmove(digest, der, derlen);
+
+ PK11_RET(pkcs_C_DigestFinal,
+ (pk11_ctx->session, digest + derlen, &hashlen),
+ DST_R_SIGNFAILURE);
+
+ isc_buffer_availableregion(sig, &r);
+ if (r.length < (unsigned int)dgstlen + MIN_PKCS1_PADLEN) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (rsa->ontoken && (rsa->object != CK_INVALID_HANDLE)) {
+ pk11_ctx->ontoken = rsa->ontoken;
+ pk11_ctx->object = rsa->object;
+ goto token_key;
+ }
+
+ for (attr = pk11_attribute_first(rsa); attr != NULL;
+ attr = pk11_attribute_next(rsa, attr))
+ {
+ switch (attr->type) {
+ case CKA_MODULUS:
+ INSIST(keyTemplate[6].type == attr->type);
+ keyTemplate[6].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[6].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[6].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_PUBLIC_EXPONENT:
+ INSIST(keyTemplate[7].type == attr->type);
+ keyTemplate[7].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[7].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[7].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_PRIVATE_EXPONENT:
+ INSIST(keyTemplate[8].type == attr->type);
+ keyTemplate[8].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[8].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[8].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_PRIME_1:
+ INSIST(keyTemplate[9].type == attr->type);
+ keyTemplate[9].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[9].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[9].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_PRIME_2:
+ INSIST(keyTemplate[10].type == attr->type);
+ keyTemplate[10].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[10].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[10].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_EXPONENT_1:
+ INSIST(keyTemplate[11].type == attr->type);
+ keyTemplate[11].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[11].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[11].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_EXPONENT_2:
+ INSIST(keyTemplate[12].type == attr->type);
+ keyTemplate[12].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[12].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[12].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_COEFFICIENT:
+ INSIST(keyTemplate[13].type == attr->type);
+ keyTemplate[13].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[13].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[13].ulValueLen = attr->ulValueLen;
+ break;
+ }
+ }
+ pk11_ctx->object = CK_INVALID_HANDLE;
+ pk11_ctx->ontoken = false;
+ PK11_RET(pkcs_C_CreateObject,
+ (pk11_ctx->session, keyTemplate, (CK_ULONG)14, &hKey),
+ ISC_R_FAILURE);
+
+token_key:
+
+ PK11_RET(pkcs_C_SignInit,
+ (pk11_ctx->session, &mech,
+ pk11_ctx->ontoken ? pk11_ctx->object : hKey),
+ ISC_R_FAILURE);
+
+ PK11_RET(pkcs_C_Sign,
+ (pk11_ctx->session, digest, dgstlen, NULL, &siglen),
+ DST_R_SIGNFAILURE);
+
+ if (r.length < (unsigned int)siglen) {
+ return (ISC_R_NOSPACE);
+ }
+
+ PK11_RET(pkcs_C_Sign,
+ (pk11_ctx->session, digest, dgstlen, (CK_BYTE_PTR)r.base,
+ &siglen),
+ DST_R_SIGNFAILURE);
+
+ isc_buffer_add(sig, (unsigned int)siglen);
+
+err:
+ if (hKey != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey);
+ }
+ for (i = 6; i <= 13; i++) {
+ if (keyTemplate[i].pValue != NULL) {
+ {
+ isc_safe_memwipe(keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ isc_mem_put(dctx->mctx, keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ }
+ }
+ }
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ dctx->ctxdata.pk11_ctx = NULL;
+
+ return (ret);
+}
+
+static isc_result_t
+pkcs11rsa_verify(dst_context_t *dctx, const isc_region_t *sig) {
+ CK_RV rv;
+ CK_MECHANISM mech = { CKM_RSA_PKCS, NULL, 0 };
+ CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY;
+ CK_KEY_TYPE keyType = CKK_RSA;
+ CK_ATTRIBUTE keyTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_MODULUS, NULL, 0 },
+ { CKA_PUBLIC_EXPONENT, NULL, 0 },
+ };
+ CK_ATTRIBUTE *attr;
+ CK_BYTE digest[MAX_DER_SIZE + ISC_SHA512_DIGESTLENGTH];
+ CK_BYTE *der;
+ CK_ULONG derlen;
+ CK_ULONG hashlen;
+ CK_ULONG dgstlen;
+ pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx;
+ dst_key_t *key = dctx->key;
+ pk11_object_t *rsa = key->keydata.pkey;
+ isc_result_t ret = ISC_R_SUCCESS;
+ unsigned int i;
+
+ REQUIRE(key->key_alg == DST_ALG_RSASHA1 ||
+ key->key_alg == DST_ALG_NSEC3RSASHA1 ||
+ key->key_alg == DST_ALG_RSASHA256 ||
+ key->key_alg == DST_ALG_RSASHA512);
+ REQUIRE(rsa != NULL);
+
+ switch (key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ der = sha1_der;
+ derlen = sizeof(sha1_der);
+ hashlen = ISC_SHA1_DIGESTLENGTH;
+ break;
+ case DST_ALG_RSASHA256:
+ der = sha256_der;
+ derlen = sizeof(sha256_der);
+ hashlen = ISC_SHA256_DIGESTLENGTH;
+ break;
+ case DST_ALG_RSASHA512:
+ der = sha512_der;
+ derlen = sizeof(sha512_der);
+ hashlen = ISC_SHA512_DIGESTLENGTH;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ dgstlen = derlen + hashlen;
+ INSIST(dgstlen <= sizeof(digest));
+ memmove(digest, der, derlen);
+
+ PK11_RET(pkcs_C_DigestFinal,
+ (pk11_ctx->session, digest + derlen, &hashlen),
+ DST_R_SIGNFAILURE);
+
+ for (attr = pk11_attribute_first(rsa); attr != NULL;
+ attr = pk11_attribute_next(rsa, attr))
+ {
+ unsigned int bits;
+
+ switch (attr->type) {
+ case CKA_MODULUS:
+ INSIST(keyTemplate[5].type == attr->type);
+ keyTemplate[5].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[5].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[5].ulValueLen = attr->ulValueLen;
+ break;
+ case CKA_PUBLIC_EXPONENT:
+ INSIST(keyTemplate[6].type == attr->type);
+ keyTemplate[6].pValue = isc_mem_get(dctx->mctx,
+ attr->ulValueLen);
+ memmove(keyTemplate[6].pValue, attr->pValue,
+ attr->ulValueLen);
+ keyTemplate[6].ulValueLen = attr->ulValueLen;
+ ret = pk11_numbits(attr->pValue, attr->ulValueLen,
+ &bits);
+ if (ret != ISC_R_SUCCESS || bits > RSA_MAX_PUBEXP_BITS)
+ {
+ DST_RET(DST_R_VERIFYFAILURE);
+ }
+ break;
+ }
+ }
+ pk11_ctx->object = CK_INVALID_HANDLE;
+ pk11_ctx->ontoken = false;
+ PK11_RET(pkcs_C_CreateObject,
+ (pk11_ctx->session, keyTemplate, (CK_ULONG)7, &hKey),
+ ISC_R_FAILURE);
+
+ PK11_RET(pkcs_C_VerifyInit, (pk11_ctx->session, &mech, hKey),
+ ISC_R_FAILURE);
+
+ PK11_RET(pkcs_C_Verify,
+ (pk11_ctx->session, digest, dgstlen, (CK_BYTE_PTR)sig->base,
+ (CK_ULONG)sig->length),
+ DST_R_VERIFYFAILURE);
+
+err:
+ if (hKey != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey);
+ }
+ for (i = 5; i <= 6; i++) {
+ if (keyTemplate[i].pValue != NULL) {
+ {
+ isc_safe_memwipe(keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ isc_mem_put(dctx->mctx, keyTemplate[i].pValue,
+ keyTemplate[i].ulValueLen);
+ }
+ }
+ }
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ dctx->ctxdata.pk11_ctx = NULL;
+
+ return (ret);
+}
+#endif /* ifndef PK11_RSA_PKCS_REPLACE */
+
+static bool
+pkcs11rsa_compare(const dst_key_t *key1, const dst_key_t *key2) {
+ pk11_object_t *rsa1, *rsa2;
+ CK_ATTRIBUTE *attr1, *attr2;
+
+ rsa1 = key1->keydata.pkey;
+ rsa2 = key2->keydata.pkey;
+
+ if ((rsa1 == NULL) && (rsa2 == NULL)) {
+ return (true);
+ } else if ((rsa1 == NULL) || (rsa2 == NULL)) {
+ return (false);
+ }
+
+ attr1 = pk11_attribute_bytype(rsa1, CKA_MODULUS);
+ attr2 = pk11_attribute_bytype(rsa2, CKA_MODULUS);
+ if ((attr1 == NULL) && (attr2 == NULL)) {
+ return (true);
+ } else if ((attr1 == NULL) || (attr2 == NULL) ||
+ (attr1->ulValueLen != attr2->ulValueLen) ||
+ !isc_safe_memequal(attr1->pValue, attr2->pValue,
+ attr1->ulValueLen))
+ {
+ return (false);
+ }
+
+ attr1 = pk11_attribute_bytype(rsa1, CKA_PUBLIC_EXPONENT);
+ attr2 = pk11_attribute_bytype(rsa2, CKA_PUBLIC_EXPONENT);
+ if ((attr1 == NULL) && (attr2 == NULL)) {
+ return (true);
+ } else if ((attr1 == NULL) || (attr2 == NULL) ||
+ (attr1->ulValueLen != attr2->ulValueLen) ||
+ !isc_safe_memequal(attr1->pValue, attr2->pValue,
+ attr1->ulValueLen))
+ {
+ return (false);
+ }
+
+ attr1 = pk11_attribute_bytype(rsa1, CKA_PRIVATE_EXPONENT);
+ attr2 = pk11_attribute_bytype(rsa2, CKA_PRIVATE_EXPONENT);
+ if (((attr1 != NULL) || (attr2 != NULL)) &&
+ ((attr1 == NULL) || (attr2 == NULL) ||
+ (attr1->ulValueLen != attr2->ulValueLen) ||
+ !isc_safe_memequal(attr1->pValue, attr2->pValue,
+ attr1->ulValueLen)))
+ {
+ return (false);
+ }
+
+ if (!rsa1->ontoken && !rsa2->ontoken) {
+ return (true);
+ } else if (rsa1->ontoken || rsa2->ontoken ||
+ (rsa1->object != rsa2->object))
+ {
+ return (false);
+ }
+
+ return (true);
+}
+
+static isc_result_t
+pkcs11rsa_generate(dst_key_t *key, int exp, void (*callback)(int)) {
+ CK_RV rv;
+ CK_MECHANISM mech = { CKM_RSA_PKCS_KEY_PAIR_GEN, NULL, 0 };
+ CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE;
+ CK_ULONG bits = 0;
+ CK_BYTE pubexp[5];
+ CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY;
+ CK_KEY_TYPE keyType = CKK_RSA;
+ CK_ATTRIBUTE pubTemplate[] = {
+ { CKA_CLASS, &pubClass, (CK_ULONG)sizeof(pubClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_MODULUS_BITS, &bits, (CK_ULONG)sizeof(bits) },
+ { CKA_PUBLIC_EXPONENT, &pubexp, (CK_ULONG)sizeof(pubexp) }
+ };
+ CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY;
+ CK_ATTRIBUTE privTemplate[] = {
+ { CKA_CLASS, &privClass, (CK_ULONG)sizeof(privClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_SENSITIVE, &falsevalue, (CK_ULONG)sizeof(falsevalue) },
+ { CKA_EXTRACTABLE, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ };
+ CK_ATTRIBUTE *attr;
+ pk11_object_t *rsa;
+ pk11_context_t *pk11_ctx;
+ isc_result_t ret;
+ unsigned int i;
+
+ UNUSED(callback);
+
+ /*
+ * Reject incorrect RSA key lengths.
+ */
+ switch (key->key_alg) {
+ case DST_ALG_RSASHA1:
+ case DST_ALG_NSEC3RSASHA1:
+ /* From RFC 3110 */
+ if (key->key_size > 4096) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA256:
+ /* From RFC 5702 */
+ if ((key->key_size < 512) || (key->key_size > 4096)) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ case DST_ALG_RSASHA512:
+ /* From RFC 5702 */
+ if ((key->key_size < 1024) || (key->key_size > 4096)) {
+ return (ISC_R_FAILURE);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx));
+ ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, false, NULL,
+ pk11_get_best_token(OP_RSA));
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ bits = key->key_size;
+ if (exp == 0) {
+ /* RSA_F4 0x10001 */
+ pubexp[0] = 1;
+ pubexp[1] = 0;
+ pubexp[2] = 1;
+ pubTemplate[6].ulValueLen = 3;
+ } else {
+ /* F5 0x100000001 */
+ pubexp[0] = 1;
+ pubexp[1] = 0;
+ pubexp[2] = 0;
+ pubexp[3] = 0;
+ pubexp[4] = 1;
+ pubTemplate[6].ulValueLen = 5;
+ }
+
+ PK11_RET(pkcs_C_GenerateKeyPair,
+ (pk11_ctx->session, &mech, pubTemplate, (CK_ULONG)7,
+ privTemplate, (CK_ULONG)7, &pub, &priv),
+ DST_R_CRYPTOFAILURE);
+
+ rsa = isc_mem_get(key->mctx, sizeof(*rsa));
+ memset(rsa, 0, sizeof(*rsa));
+ key->keydata.pkey = rsa;
+ rsa->repr = isc_mem_get(key->mctx, sizeof(*attr) * 8);
+ memset(rsa->repr, 0, sizeof(*attr) * 8);
+ rsa->attrcnt = 8;
+
+ attr = rsa->repr;
+ attr[0].type = CKA_MODULUS;
+ attr[1].type = CKA_PUBLIC_EXPONENT;
+ attr[2].type = CKA_PRIVATE_EXPONENT;
+ attr[3].type = CKA_PRIME_1;
+ attr[4].type = CKA_PRIME_2;
+ attr[5].type = CKA_EXPONENT_1;
+ attr[6].type = CKA_EXPONENT_2;
+ attr[7].type = CKA_COEFFICIENT;
+
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 2),
+ DST_R_CRYPTOFAILURE);
+ for (i = 0; i <= 1; i++) {
+ attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen);
+ memset(attr[i].pValue, 0, attr[i].ulValueLen);
+ }
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 2),
+ DST_R_CRYPTOFAILURE);
+
+ attr += 2;
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 6),
+ DST_R_CRYPTOFAILURE);
+ for (i = 0; i <= 5; i++) {
+ attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen);
+ memset(attr[i].pValue, 0, attr[i].ulValueLen);
+ }
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 6),
+ DST_R_CRYPTOFAILURE);
+
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, priv);
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, pub);
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ return (ISC_R_SUCCESS);
+
+err:
+ pkcs11rsa_destroy(key);
+ if (priv != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, priv);
+ }
+ if (pub != CK_INVALID_HANDLE) {
+ (void)pkcs_C_DestroyObject(pk11_ctx->session, pub);
+ }
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ return (ret);
+}
+
+static bool
+pkcs11rsa_isprivate(const dst_key_t *key) {
+ pk11_object_t *rsa = key->keydata.pkey;
+ CK_ATTRIBUTE *attr;
+
+ if (rsa == NULL) {
+ return (false);
+ }
+ attr = pk11_attribute_bytype(rsa, CKA_PRIVATE_EXPONENT);
+ return (attr != NULL || rsa->ontoken);
+}
+
+static void
+pkcs11rsa_destroy(dst_key_t *key) {
+ pk11_object_t *rsa = key->keydata.pkey;
+ CK_ATTRIBUTE *attr;
+
+ if (rsa == NULL) {
+ return;
+ }
+
+ INSIST((rsa->object == CK_INVALID_HANDLE) || rsa->ontoken);
+
+ for (attr = pk11_attribute_first(rsa); attr != NULL;
+ attr = pk11_attribute_next(rsa, attr))
+ {
+ switch (attr->type) {
+ case CKA_LABEL:
+ case CKA_ID:
+ case CKA_MODULUS:
+ case CKA_PUBLIC_EXPONENT:
+ case CKA_PRIVATE_EXPONENT:
+ case CKA_PRIME_1:
+ case CKA_PRIME_2:
+ case CKA_EXPONENT_1:
+ case CKA_EXPONENT_2:
+ case CKA_COEFFICIENT:
+ if (attr->pValue != NULL) {
+ isc_safe_memwipe(attr->pValue,
+ attr->ulValueLen);
+ isc_mem_put(key->mctx, attr->pValue,
+ attr->ulValueLen);
+ }
+ break;
+ }
+ }
+ if (rsa->repr != NULL) {
+ isc_safe_memwipe(rsa->repr, rsa->attrcnt * sizeof(*attr));
+ isc_mem_put(key->mctx, rsa->repr, rsa->attrcnt * sizeof(*attr));
+ }
+ isc_safe_memwipe(rsa, sizeof(*rsa));
+ isc_mem_put(key->mctx, rsa, sizeof(*rsa));
+ key->keydata.pkey = NULL;
+}
+
+static isc_result_t
+pkcs11rsa_todns(const dst_key_t *key, isc_buffer_t *data) {
+ pk11_object_t *rsa;
+ CK_ATTRIBUTE *attr;
+ isc_region_t r;
+ unsigned int e_bytes = 0, mod_bytes = 0;
+ CK_BYTE *exponent = NULL, *modulus = NULL;
+
+ REQUIRE(key->keydata.pkey != NULL);
+
+ rsa = key->keydata.pkey;
+
+ for (attr = pk11_attribute_first(rsa); attr != NULL;
+ attr = pk11_attribute_next(rsa, attr))
+ {
+ switch (attr->type) {
+ case CKA_PUBLIC_EXPONENT:
+ exponent = (CK_BYTE *)attr->pValue;
+ e_bytes = (unsigned int)attr->ulValueLen;
+ break;
+ case CKA_MODULUS:
+ modulus = (CK_BYTE *)attr->pValue;
+ mod_bytes = (unsigned int)attr->ulValueLen;
+ break;
+ }
+ }
+ REQUIRE((exponent != NULL) && (modulus != NULL));
+
+ isc_buffer_availableregion(data, &r);
+
+ if (e_bytes < 256) { /*%< key exponent is <= 2040 bits */
+ if (r.length < 1) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(data, (uint8_t)e_bytes);
+ isc_region_consume(&r, 1);
+ } else {
+ if (r.length < 3) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(data, 0);
+ isc_buffer_putuint16(data, (uint16_t)e_bytes);
+ isc_region_consume(&r, 3);
+ }
+
+ if (r.length < e_bytes + mod_bytes) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(r.base, exponent, e_bytes);
+ isc_region_consume(&r, e_bytes);
+ memmove(r.base, modulus, mod_bytes);
+
+ isc_buffer_add(data, e_bytes + mod_bytes);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+pkcs11rsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
+ pk11_object_t *rsa;
+ isc_region_t r;
+ unsigned int e_bytes, mod_bytes;
+ CK_BYTE *exponent = NULL, *modulus = NULL;
+ CK_ATTRIBUTE *attr;
+ unsigned int length;
+ unsigned int bits;
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ isc_buffer_remainingregion(data, &r);
+ if (r.length == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ length = r.length;
+
+ rsa = isc_mem_get(key->mctx, sizeof(*rsa));
+
+ memset(rsa, 0, sizeof(*rsa));
+
+ e_bytes = *r.base;
+ isc_region_consume(&r, 1);
+
+ if (e_bytes == 0) {
+ if (r.length < 2) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ e_bytes = (*r.base) << 8;
+ isc_region_consume(&r, 1);
+ e_bytes += *r.base;
+ isc_region_consume(&r, 1);
+ }
+
+ if (r.length < e_bytes) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ exponent = r.base;
+ isc_region_consume(&r, e_bytes);
+ modulus = r.base;
+ mod_bytes = r.length;
+
+ ret = pk11_numbits(modulus, mod_bytes, &bits);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ key->key_size = bits;
+
+ isc_buffer_forward(data, length);
+
+ rsa->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2);
+ memset(rsa->repr, 0, sizeof(*attr) * 2);
+ rsa->attrcnt = 2;
+ attr = rsa->repr;
+ attr[0].type = CKA_MODULUS;
+ attr[0].pValue = isc_mem_get(key->mctx, mod_bytes);
+ memmove(attr[0].pValue, modulus, mod_bytes);
+ attr[0].ulValueLen = (CK_ULONG)mod_bytes;
+ attr[1].type = CKA_PUBLIC_EXPONENT;
+ attr[1].pValue = isc_mem_get(key->mctx, e_bytes);
+ memmove(attr[1].pValue, exponent, e_bytes);
+ attr[1].ulValueLen = (CK_ULONG)e_bytes;
+
+ key->keydata.pkey = rsa;
+
+ return (ISC_R_SUCCESS);
+err:
+ isc_safe_memwipe(rsa, sizeof(*rsa));
+ isc_mem_put(key->mctx, rsa, sizeof(*rsa));
+ return (ret);
+}
+
+static isc_result_t
+pkcs11rsa_tofile(const dst_key_t *key, const char *directory) {
+ int i;
+ pk11_object_t *rsa;
+ CK_ATTRIBUTE *attr;
+ CK_ATTRIBUTE *modulus = NULL, *exponent = NULL;
+ CK_ATTRIBUTE *d = NULL, *p = NULL, *q = NULL;
+ CK_ATTRIBUTE *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
+ dst_private_t priv;
+ unsigned char *bufs[10];
+ isc_result_t result;
+
+ if (key->keydata.pkey == NULL) {
+ return (DST_R_NULLKEY);
+ }
+
+ if (key->external) {
+ priv.nelements = 0;
+ return (dst__privstruct_writefile(key, &priv, directory));
+ }
+
+ rsa = key->keydata.pkey;
+
+ for (attr = pk11_attribute_first(rsa); attr != NULL;
+ attr = pk11_attribute_next(rsa, attr))
+ {
+ switch (attr->type) {
+ case CKA_MODULUS:
+ modulus = attr;
+ break;
+ case CKA_PUBLIC_EXPONENT:
+ exponent = attr;
+ break;
+ case CKA_PRIVATE_EXPONENT:
+ d = attr;
+ break;
+ case CKA_PRIME_1:
+ p = attr;
+ break;
+ case CKA_PRIME_2:
+ q = attr;
+ break;
+ case CKA_EXPONENT_1:
+ dmp1 = attr;
+ break;
+ case CKA_EXPONENT_2:
+ dmq1 = attr;
+ break;
+ case CKA_COEFFICIENT:
+ iqmp = attr;
+ break;
+ }
+ }
+ if ((modulus == NULL) || (exponent == NULL)) {
+ return (DST_R_NULLKEY);
+ }
+
+ memset(bufs, 0, sizeof(bufs));
+
+ for (i = 0; i < 10; i++) {
+ bufs[i] = isc_mem_get(key->mctx, modulus->ulValueLen);
+ memset(bufs[i], 0, modulus->ulValueLen);
+ }
+
+ i = 0;
+
+ priv.elements[i].tag = TAG_RSA_MODULUS;
+ priv.elements[i].length = (unsigned short)modulus->ulValueLen;
+ memmove(bufs[i], modulus->pValue, modulus->ulValueLen);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ priv.elements[i].tag = TAG_RSA_PUBLICEXPONENT;
+ priv.elements[i].length = (unsigned short)exponent->ulValueLen;
+ memmove(bufs[i], exponent->pValue, exponent->ulValueLen);
+ priv.elements[i].data = bufs[i];
+ i++;
+
+ if (d != NULL) {
+ priv.elements[i].tag = TAG_RSA_PRIVATEEXPONENT;
+ priv.elements[i].length = (unsigned short)d->ulValueLen;
+ memmove(bufs[i], d->pValue, d->ulValueLen);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (p != NULL) {
+ priv.elements[i].tag = TAG_RSA_PRIME1;
+ priv.elements[i].length = (unsigned short)p->ulValueLen;
+ memmove(bufs[i], p->pValue, p->ulValueLen);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (q != NULL) {
+ priv.elements[i].tag = TAG_RSA_PRIME2;
+ priv.elements[i].length = (unsigned short)q->ulValueLen;
+ memmove(bufs[i], q->pValue, q->ulValueLen);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (dmp1 != NULL) {
+ priv.elements[i].tag = TAG_RSA_EXPONENT1;
+ priv.elements[i].length = (unsigned short)dmp1->ulValueLen;
+ memmove(bufs[i], dmp1->pValue, dmp1->ulValueLen);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (dmq1 != NULL) {
+ priv.elements[i].tag = TAG_RSA_EXPONENT2;
+ priv.elements[i].length = (unsigned short)dmq1->ulValueLen;
+ memmove(bufs[i], dmq1->pValue, dmq1->ulValueLen);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (iqmp != NULL) {
+ priv.elements[i].tag = TAG_RSA_COEFFICIENT;
+ priv.elements[i].length = (unsigned short)iqmp->ulValueLen;
+ memmove(bufs[i], iqmp->pValue, iqmp->ulValueLen);
+ priv.elements[i].data = bufs[i];
+ i++;
+ }
+
+ if (key->engine != NULL) {
+ priv.elements[i].tag = TAG_RSA_ENGINE;
+ priv.elements[i].length = (unsigned short)strlen(key->engine) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->engine;
+ i++;
+ }
+
+ if (key->label != NULL) {
+ priv.elements[i].tag = TAG_RSA_LABEL;
+ priv.elements[i].length = (unsigned short)strlen(key->label) +
+ 1;
+ priv.elements[i].data = (unsigned char *)key->label;
+ i++;
+ }
+
+ priv.nelements = i;
+ result = dst__privstruct_writefile(key, &priv, directory);
+ for (i = 0; i < 10; i++) {
+ if (bufs[i] == NULL) {
+ break;
+ }
+ isc_safe_memwipe(bufs[i], modulus->ulValueLen);
+ isc_mem_put(key->mctx, bufs[i], modulus->ulValueLen);
+ }
+ return (result);
+}
+
+static isc_result_t
+pkcs11rsa_fetch(dst_key_t *key, const char *engine, const char *label,
+ dst_key_t *pub) {
+ CK_RV rv;
+ CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
+ CK_KEY_TYPE keyType = CKK_RSA;
+ CK_ATTRIBUTE searchTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_LABEL, NULL, 0 }
+ };
+ CK_ULONG cnt;
+ CK_ATTRIBUTE *attr;
+ CK_ATTRIBUTE *pubattr;
+ pk11_object_t *rsa;
+ pk11_object_t *pubrsa;
+ pk11_context_t *pk11_ctx = NULL;
+ isc_result_t ret;
+ unsigned int bits;
+
+ if (label == NULL) {
+ return (DST_R_NOENGINE);
+ }
+
+ rsa = key->keydata.pkey;
+ pubrsa = pub->keydata.pkey;
+
+ rsa->object = CK_INVALID_HANDLE;
+ rsa->ontoken = true;
+ rsa->reqlogon = true;
+ rsa->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2);
+ memset(rsa->repr, 0, sizeof(*attr) * 2);
+ rsa->attrcnt = 2;
+ attr = rsa->repr;
+
+ attr->type = CKA_MODULUS;
+ pubattr = pk11_attribute_bytype(pubrsa, CKA_MODULUS);
+ INSIST(pubattr != NULL);
+ attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen);
+ memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen);
+ attr->ulValueLen = pubattr->ulValueLen;
+ attr++;
+
+ attr->type = CKA_PUBLIC_EXPONENT;
+ pubattr = pk11_attribute_bytype(pubrsa, CKA_PUBLIC_EXPONENT);
+ INSIST(pubattr != NULL);
+ attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen);
+ memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen);
+ attr->ulValueLen = pubattr->ulValueLen;
+
+ ret = pk11_parse_uri(rsa, label, key->mctx, OP_RSA);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx));
+ ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, rsa->reqlogon,
+ NULL, rsa->slot);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ attr = pk11_attribute_bytype(rsa, CKA_LABEL);
+ if (attr == NULL) {
+ attr = pk11_attribute_bytype(rsa, CKA_ID);
+ INSIST(attr != NULL);
+ searchTemplate[3].type = CKA_ID;
+ }
+ searchTemplate[3].pValue = attr->pValue;
+ searchTemplate[3].ulValueLen = attr->ulValueLen;
+
+ PK11_RET(pkcs_C_FindObjectsInit,
+ (pk11_ctx->session, searchTemplate, (CK_ULONG)4),
+ DST_R_CRYPTOFAILURE);
+ PK11_RET(pkcs_C_FindObjects,
+ (pk11_ctx->session, &rsa->object, (CK_ULONG)1, &cnt),
+ DST_R_CRYPTOFAILURE);
+ (void)pkcs_C_FindObjectsFinal(pk11_ctx->session);
+ if (cnt == 0) {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+ if (cnt > 1) {
+ DST_RET(ISC_R_EXISTS);
+ }
+
+ if (engine != NULL) {
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ }
+
+ key->label = isc_mem_strdup(key->mctx, label);
+
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ attr = pk11_attribute_bytype(rsa, CKA_MODULUS);
+ INSIST(attr != NULL);
+ ret = pk11_numbits(attr->pValue, attr->ulValueLen, &bits);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ key->key_size = bits;
+
+ return (ISC_R_SUCCESS);
+
+err:
+ if (pk11_ctx != NULL) {
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ }
+
+ return (ret);
+}
+
+static isc_result_t
+rsa_check(pk11_object_t *rsa, pk11_object_t *pubrsa) {
+ CK_ATTRIBUTE *pubattr, *privattr;
+ CK_BYTE *priv_exp = NULL, *priv_mod = NULL;
+ CK_BYTE *pub_exp = NULL, *pub_mod = NULL;
+ unsigned int priv_explen = 0, priv_modlen = 0;
+ unsigned int pub_explen = 0, pub_modlen = 0;
+
+ REQUIRE(rsa != NULL && pubrsa != NULL);
+
+ privattr = pk11_attribute_bytype(rsa, CKA_PUBLIC_EXPONENT);
+ INSIST(privattr != NULL);
+ priv_exp = privattr->pValue;
+ priv_explen = privattr->ulValueLen;
+
+ pubattr = pk11_attribute_bytype(pubrsa, CKA_PUBLIC_EXPONENT);
+ INSIST(pubattr != NULL);
+ pub_exp = pubattr->pValue;
+ pub_explen = pubattr->ulValueLen;
+
+ if (priv_exp != NULL) {
+ if (priv_explen != pub_explen) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+ if (!isc_safe_memequal(priv_exp, pub_exp, pub_explen)) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+ } else {
+ privattr->pValue = pub_exp;
+ privattr->ulValueLen = pub_explen;
+ pubattr->pValue = NULL;
+ pubattr->ulValueLen = 0;
+ }
+
+ if (privattr->pValue == NULL) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+
+ privattr = pk11_attribute_bytype(rsa, CKA_MODULUS);
+ INSIST(privattr != NULL);
+ priv_mod = privattr->pValue;
+ priv_modlen = privattr->ulValueLen;
+
+ pubattr = pk11_attribute_bytype(pubrsa, CKA_MODULUS);
+ INSIST(pubattr != NULL);
+ pub_mod = pubattr->pValue;
+ pub_modlen = pubattr->ulValueLen;
+
+ if (priv_mod != NULL) {
+ if (priv_modlen != pub_modlen) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+ if (!isc_safe_memequal(priv_mod, pub_mod, pub_modlen)) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+ } else {
+ privattr->pValue = pub_mod;
+ privattr->ulValueLen = pub_modlen;
+ pubattr->pValue = NULL;
+ pubattr->ulValueLen = 0;
+ }
+
+ if (privattr->pValue == NULL) {
+ return (DST_R_INVALIDPRIVATEKEY);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+pkcs11rsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
+ dst_private_t priv;
+ isc_result_t ret;
+ int i;
+ pk11_object_t *rsa;
+ CK_ATTRIBUTE *attr;
+ isc_mem_t *mctx = key->mctx;
+ const char *engine = NULL, *label = NULL;
+ unsigned int bits;
+
+ /* read private key file */
+ ret = dst__privstruct_parse(key, DST_ALG_RSA, lexer, mctx, &priv);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ if (key->external) {
+ if (priv.nelements != 0) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ if (pub == NULL) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ key->keydata.pkey = pub->keydata.pkey;
+ pub->keydata.pkey = NULL;
+ key->key_size = pub->key_size;
+
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+
+ return (ISC_R_SUCCESS);
+ }
+
+ for (i = 0; i < priv.nelements; i++) {
+ switch (priv.elements[i].tag) {
+ case TAG_RSA_ENGINE:
+ engine = (char *)priv.elements[i].data;
+ break;
+ case TAG_RSA_LABEL:
+ label = (char *)priv.elements[i].data;
+ break;
+ default:
+ break;
+ }
+ }
+ rsa = isc_mem_get(key->mctx, sizeof(*rsa));
+ memset(rsa, 0, sizeof(*rsa));
+ key->keydata.pkey = rsa;
+
+ /* Is this key is stored in a HSM? See if we can fetch it. */
+ if ((label != NULL) || (engine != NULL)) {
+ ret = pkcs11rsa_fetch(key, engine, label, pub);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (ret);
+ }
+
+ rsa->repr = isc_mem_get(key->mctx, sizeof(*attr) * 8);
+ memset(rsa->repr, 0, sizeof(*attr) * 8);
+ rsa->attrcnt = 8;
+ attr = rsa->repr;
+ attr[0].type = CKA_MODULUS;
+ attr[1].type = CKA_PUBLIC_EXPONENT;
+ attr[2].type = CKA_PRIVATE_EXPONENT;
+ attr[3].type = CKA_PRIME_1;
+ attr[4].type = CKA_PRIME_2;
+ attr[5].type = CKA_EXPONENT_1;
+ attr[6].type = CKA_EXPONENT_2;
+ attr[7].type = CKA_COEFFICIENT;
+
+ for (i = 0; i < priv.nelements; i++) {
+ CK_BYTE *bn;
+
+ switch (priv.elements[i].tag) {
+ case TAG_RSA_ENGINE:
+ continue;
+ case TAG_RSA_LABEL:
+ continue;
+ default:
+ bn = isc_mem_get(key->mctx, priv.elements[i].length);
+ memmove(bn, priv.elements[i].data,
+ priv.elements[i].length);
+ }
+
+ switch (priv.elements[i].tag) {
+ case TAG_RSA_MODULUS:
+ attr = pk11_attribute_bytype(rsa, CKA_MODULUS);
+ INSIST(attr != NULL);
+ attr->pValue = bn;
+ attr->ulValueLen = priv.elements[i].length;
+ break;
+ case TAG_RSA_PUBLICEXPONENT:
+ attr = pk11_attribute_bytype(rsa, CKA_PUBLIC_EXPONENT);
+ INSIST(attr != NULL);
+ attr->pValue = bn;
+ attr->ulValueLen = priv.elements[i].length;
+ break;
+ case TAG_RSA_PRIVATEEXPONENT:
+ attr = pk11_attribute_bytype(rsa, CKA_PRIVATE_EXPONENT);
+ INSIST(attr != NULL);
+ attr->pValue = bn;
+ attr->ulValueLen = priv.elements[i].length;
+ break;
+ case TAG_RSA_PRIME1:
+ attr = pk11_attribute_bytype(rsa, CKA_PRIME_1);
+ INSIST(attr != NULL);
+ attr->pValue = bn;
+ attr->ulValueLen = priv.elements[i].length;
+ break;
+ case TAG_RSA_PRIME2:
+ attr = pk11_attribute_bytype(rsa, CKA_PRIME_2);
+ INSIST(attr != NULL);
+ attr->pValue = bn;
+ attr->ulValueLen = priv.elements[i].length;
+ break;
+ case TAG_RSA_EXPONENT1:
+ attr = pk11_attribute_bytype(rsa, CKA_EXPONENT_1);
+ INSIST(attr != NULL);
+ attr->pValue = bn;
+ attr->ulValueLen = priv.elements[i].length;
+ break;
+ case TAG_RSA_EXPONENT2:
+ attr = pk11_attribute_bytype(rsa, CKA_EXPONENT_2);
+ INSIST(attr != NULL);
+ attr->pValue = bn;
+ attr->ulValueLen = priv.elements[i].length;
+ break;
+ case TAG_RSA_COEFFICIENT:
+ attr = pk11_attribute_bytype(rsa, CKA_COEFFICIENT);
+ INSIST(attr != NULL);
+ attr->pValue = bn;
+ attr->ulValueLen = priv.elements[i].length;
+ break;
+ }
+ }
+
+ if (rsa_check(rsa, pub->keydata.pkey) != ISC_R_SUCCESS) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ attr = pk11_attribute_bytype(rsa, CKA_MODULUS);
+ INSIST(attr != NULL);
+ ret = pk11_numbits(attr->pValue, attr->ulValueLen, &bits);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ key->key_size = bits;
+
+ attr = pk11_attribute_bytype(rsa, CKA_PUBLIC_EXPONENT);
+ INSIST(attr != NULL);
+
+ ret = pk11_numbits(attr->pValue, attr->ulValueLen, &bits);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ if (bits > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+
+ return (ISC_R_SUCCESS);
+
+err:
+ pkcs11rsa_destroy(key);
+ dst__privstruct_free(&priv, mctx);
+ isc_safe_memwipe(&priv, sizeof(priv));
+ return (ret);
+}
+
+static isc_result_t
+pkcs11rsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin) {
+ CK_RV rv;
+ CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE;
+ CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY;
+ CK_KEY_TYPE keyType = CKK_RSA;
+ CK_ATTRIBUTE searchTemplate[] = {
+ { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) },
+ { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) },
+ { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) },
+ { CKA_LABEL, NULL, 0 }
+ };
+ CK_ULONG cnt;
+ CK_ATTRIBUTE *attr;
+ pk11_object_t *rsa;
+ pk11_context_t *pk11_ctx = NULL;
+ isc_result_t ret;
+ unsigned int i;
+ unsigned int bits;
+
+ UNUSED(pin);
+
+ rsa = isc_mem_get(key->mctx, sizeof(*rsa));
+ memset(rsa, 0, sizeof(*rsa));
+ rsa->object = CK_INVALID_HANDLE;
+ rsa->ontoken = true;
+ rsa->reqlogon = true;
+ key->keydata.pkey = rsa;
+
+ rsa->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2);
+ memset(rsa->repr, 0, sizeof(*attr) * 2);
+ rsa->attrcnt = 2;
+ attr = rsa->repr;
+ attr[0].type = CKA_MODULUS;
+ attr[1].type = CKA_PUBLIC_EXPONENT;
+
+ ret = pk11_parse_uri(rsa, label, key->mctx, OP_RSA);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx));
+ ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, rsa->reqlogon,
+ NULL, rsa->slot);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+
+ attr = pk11_attribute_bytype(rsa, CKA_LABEL);
+ if (attr == NULL) {
+ attr = pk11_attribute_bytype(rsa, CKA_ID);
+ INSIST(attr != NULL);
+ searchTemplate[3].type = CKA_ID;
+ }
+ searchTemplate[3].pValue = attr->pValue;
+ searchTemplate[3].ulValueLen = attr->ulValueLen;
+
+ PK11_RET(pkcs_C_FindObjectsInit,
+ (pk11_ctx->session, searchTemplate, (CK_ULONG)4),
+ DST_R_CRYPTOFAILURE);
+ PK11_RET(pkcs_C_FindObjects,
+ (pk11_ctx->session, &hKey, (CK_ULONG)1, &cnt),
+ DST_R_CRYPTOFAILURE);
+ (void)pkcs_C_FindObjectsFinal(pk11_ctx->session);
+ if (cnt == 0) {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+ if (cnt > 1) {
+ DST_RET(ISC_R_EXISTS);
+ }
+
+ attr = rsa->repr;
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2),
+ DST_R_CRYPTOFAILURE);
+ for (i = 0; i <= 1; i++) {
+ attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen);
+ memset(attr[i].pValue, 0, attr[i].ulValueLen);
+ }
+ PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2),
+ DST_R_CRYPTOFAILURE);
+
+ keyClass = CKO_PRIVATE_KEY;
+ PK11_RET(pkcs_C_FindObjectsInit,
+ (pk11_ctx->session, searchTemplate, (CK_ULONG)4),
+ DST_R_CRYPTOFAILURE);
+ PK11_RET(pkcs_C_FindObjects,
+ (pk11_ctx->session, &rsa->object, (CK_ULONG)1, &cnt),
+ DST_R_CRYPTOFAILURE);
+ (void)pkcs_C_FindObjectsFinal(pk11_ctx->session);
+ if (cnt == 0) {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+ if (cnt > 1) {
+ DST_RET(ISC_R_EXISTS);
+ }
+
+ if (engine != NULL) {
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ }
+
+ key->label = isc_mem_strdup(key->mctx, label);
+
+ attr = pk11_attribute_bytype(rsa, CKA_PUBLIC_EXPONENT);
+ INSIST(attr != NULL);
+
+ ret = pk11_numbits(attr->pValue, attr->ulValueLen, &bits);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ if (bits > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+
+ attr = pk11_attribute_bytype(rsa, CKA_MODULUS);
+ INSIST(attr != NULL);
+ ret = pk11_numbits(attr->pValue, attr->ulValueLen, &bits);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ key->key_size = bits;
+
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+
+ return (ISC_R_SUCCESS);
+
+err:
+ pkcs11rsa_destroy(key);
+ if (pk11_ctx != NULL) {
+ pk11_return_session(pk11_ctx);
+ isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx));
+ isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx));
+ }
+
+ return (ret);
+}
+
+static dst_func_t pkcs11rsa_functions = {
+ pkcs11rsa_createctx,
+#ifndef PK11_RSA_PKCS_REPLACE
+ pkcs11rsa_createctx2,
+#else /* ifndef PK11_RSA_PKCS_REPLACE */
+ NULL, /*%< createctx2 */
+#endif /* ifndef PK11_RSA_PKCS_REPLACE */
+ pkcs11rsa_destroyctx,
+ pkcs11rsa_adddata,
+ pkcs11rsa_sign,
+ pkcs11rsa_verify,
+ NULL, /*%< verify2 */
+ NULL, /*%< computesecret */
+ pkcs11rsa_compare,
+ NULL, /*%< paramcompare */
+ pkcs11rsa_generate,
+ pkcs11rsa_isprivate,
+ pkcs11rsa_destroy,
+ pkcs11rsa_todns,
+ pkcs11rsa_fromdns,
+ pkcs11rsa_tofile,
+ pkcs11rsa_parse,
+ NULL, /*%< cleanup */
+ pkcs11rsa_fromlabel,
+ NULL, /*%< dump */
+ NULL, /*%< restore */
+};
+
+isc_result_t
+dst__pkcs11rsa_init(dst_func_t **funcp) {
+ REQUIRE(funcp != NULL);
+
+ if (*funcp == NULL) {
+ *funcp = &pkcs11rsa_functions;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+#endif /* USE_PKCS11 */
diff --git a/lib/dns/portlist.c b/lib/dns/portlist.c
new file mode 100644
index 0000000..c1d523c
--- /dev/null
+++ b/lib/dns/portlist.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/net.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/portlist.h>
+#include <dns/types.h>
+
+#define DNS_PORTLIST_MAGIC ISC_MAGIC('P', 'L', 'S', 'T')
+#define DNS_VALID_PORTLIST(p) ISC_MAGIC_VALID(p, DNS_PORTLIST_MAGIC)
+
+typedef struct dns_element {
+ in_port_t port;
+ uint16_t flags;
+} dns_element_t;
+
+struct dns_portlist {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t refcount;
+ isc_mutex_t lock;
+ dns_element_t *list;
+ unsigned int allocated;
+ unsigned int active;
+};
+
+#define DNS_PL_INET 0x0001
+#define DNS_PL_INET6 0x0002
+#define DNS_PL_ALLOCATE 16
+
+static int
+compare(const void *arg1, const void *arg2) {
+ const dns_element_t *e1 = (const dns_element_t *)arg1;
+ const dns_element_t *e2 = (const dns_element_t *)arg2;
+
+ if (e1->port < e2->port) {
+ return (-1);
+ }
+ if (e1->port > e2->port) {
+ return (1);
+ }
+ return (0);
+}
+
+isc_result_t
+dns_portlist_create(isc_mem_t *mctx, dns_portlist_t **portlistp) {
+ dns_portlist_t *portlist;
+
+ REQUIRE(portlistp != NULL && *portlistp == NULL);
+
+ portlist = isc_mem_get(mctx, sizeof(*portlist));
+ isc_mutex_init(&portlist->lock);
+ isc_refcount_init(&portlist->refcount, 1);
+ portlist->list = NULL;
+ portlist->allocated = 0;
+ portlist->active = 0;
+ portlist->mctx = NULL;
+ isc_mem_attach(mctx, &portlist->mctx);
+ portlist->magic = DNS_PORTLIST_MAGIC;
+ *portlistp = portlist;
+ return (ISC_R_SUCCESS);
+}
+
+static dns_element_t *
+find_port(dns_element_t *list, unsigned int len, in_port_t port) {
+ unsigned int xtry = len / 2;
+ unsigned int min = 0;
+ unsigned int max = len - 1;
+ unsigned int last = len;
+
+ for (;;) {
+ if (list[xtry].port == port) {
+ return (&list[xtry]);
+ }
+ if (port > list[xtry].port) {
+ if (xtry == max) {
+ break;
+ }
+ min = xtry;
+ xtry = xtry + (max - xtry + 1) / 2;
+ INSIST(xtry <= max);
+ if (xtry == last) {
+ break;
+ }
+ last = min;
+ } else {
+ if (xtry == min) {
+ break;
+ }
+ max = xtry;
+ xtry = xtry - (xtry - min + 1) / 2;
+ INSIST(xtry >= min);
+ if (xtry == last) {
+ break;
+ }
+ last = max;
+ }
+ }
+ return (NULL);
+}
+
+isc_result_t
+dns_portlist_add(dns_portlist_t *portlist, int af, in_port_t port) {
+ dns_element_t *el;
+ isc_result_t result;
+
+ REQUIRE(DNS_VALID_PORTLIST(portlist));
+ REQUIRE(af == AF_INET || af == AF_INET6);
+
+ LOCK(&portlist->lock);
+ if (portlist->active != 0) {
+ el = find_port(portlist->list, portlist->active, port);
+ if (el != NULL) {
+ if (af == AF_INET) {
+ el->flags |= DNS_PL_INET;
+ } else {
+ el->flags |= DNS_PL_INET6;
+ }
+ result = ISC_R_SUCCESS;
+ goto unlock;
+ }
+ }
+
+ if (portlist->allocated <= portlist->active) {
+ unsigned int allocated;
+ allocated = portlist->allocated + DNS_PL_ALLOCATE;
+ el = isc_mem_get(portlist->mctx, sizeof(*el) * allocated);
+ if (portlist->list != NULL) {
+ memmove(el, portlist->list,
+ portlist->allocated * sizeof(*el));
+ isc_mem_put(portlist->mctx, portlist->list,
+ portlist->allocated * sizeof(*el));
+ }
+ portlist->list = el;
+ portlist->allocated = allocated;
+ }
+ portlist->list[portlist->active].port = port;
+ if (af == AF_INET) {
+ portlist->list[portlist->active].flags = DNS_PL_INET;
+ } else {
+ portlist->list[portlist->active].flags = DNS_PL_INET6;
+ }
+ portlist->active++;
+ qsort(portlist->list, portlist->active, sizeof(*el), compare);
+ result = ISC_R_SUCCESS;
+unlock:
+ UNLOCK(&portlist->lock);
+ return (result);
+}
+
+void
+dns_portlist_remove(dns_portlist_t *portlist, int af, in_port_t port) {
+ dns_element_t *el;
+
+ REQUIRE(DNS_VALID_PORTLIST(portlist));
+ REQUIRE(af == AF_INET || af == AF_INET6);
+
+ LOCK(&portlist->lock);
+ if (portlist->active != 0) {
+ el = find_port(portlist->list, portlist->active, port);
+ if (el != NULL) {
+ if (af == AF_INET) {
+ el->flags &= ~DNS_PL_INET;
+ } else {
+ el->flags &= ~DNS_PL_INET6;
+ }
+ if (el->flags == 0) {
+ *el = portlist->list[portlist->active];
+ portlist->active--;
+ qsort(portlist->list, portlist->active,
+ sizeof(*el), compare);
+ }
+ }
+ }
+ UNLOCK(&portlist->lock);
+}
+
+bool
+dns_portlist_match(dns_portlist_t *portlist, int af, in_port_t port) {
+ dns_element_t *el;
+ bool result = false;
+
+ REQUIRE(DNS_VALID_PORTLIST(portlist));
+ REQUIRE(af == AF_INET || af == AF_INET6);
+ LOCK(&portlist->lock);
+ if (portlist->active != 0) {
+ el = find_port(portlist->list, portlist->active, port);
+ if (el != NULL) {
+ if (af == AF_INET && (el->flags & DNS_PL_INET) != 0) {
+ result = true;
+ }
+ if (af == AF_INET6 && (el->flags & DNS_PL_INET6) != 0) {
+ result = true;
+ }
+ }
+ }
+ UNLOCK(&portlist->lock);
+ return (result);
+}
+
+void
+dns_portlist_attach(dns_portlist_t *portlist, dns_portlist_t **portlistp) {
+ REQUIRE(DNS_VALID_PORTLIST(portlist));
+ REQUIRE(portlistp != NULL && *portlistp == NULL);
+
+ isc_refcount_increment(&portlist->refcount);
+ *portlistp = portlist;
+}
+
+void
+dns_portlist_detach(dns_portlist_t **portlistp) {
+ REQUIRE(portlistp != NULL && DNS_VALID_PORTLIST(*portlistp));
+ dns_portlist_t *portlist = *portlistp;
+ *portlistp = NULL;
+
+ if (isc_refcount_decrement(&portlist->refcount) == 1) {
+ portlist->magic = 0;
+ isc_refcount_destroy(&portlist->refcount);
+ if (portlist->list != NULL) {
+ isc_mem_put(portlist->mctx, portlist->list,
+ portlist->allocated *
+ sizeof(*portlist->list));
+ }
+ isc_mutex_destroy(&portlist->lock);
+ isc_mem_putanddetach(&portlist->mctx, portlist,
+ sizeof(*portlist));
+ }
+}
diff --git a/lib/dns/private.c b/lib/dns/private.c
new file mode 100644
index 0000000..56573b3
--- /dev/null
+++ b/lib/dns/private.c
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <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..d5d18b8
--- /dev/null
+++ b/lib/dns/rbt.c
@@ -0,0 +1,3808 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/platform.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/socket.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
+#define ALIGNMENT_SIZE 8U /* see lib/isc/mem.c */
+
+#include <unistd.h>
+
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/rbt.h>
+#include <dns/result.h>
+#include <dns/version.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_MIN_BITS 4
+#define RBT_HASH_MAX_BITS 32
+#define RBT_HASH_OVERCOMMIT 3
+#define RBT_HASH_BUCKETSIZE 4096 /* FIXME: What would be a good value here? */
+
+#ifdef RBT_MEM_TEST
+#undef RBT_HASH_SIZE
+#define RBT_HASH_SIZE 2 /*%< To give the reallocation code a workout. */
+#endif /* ifdef RBT_MEM_TEST */
+
+#define GOLDEN_RATIO_32 0x61C88647
+
+#define HASHSIZE(bits) (UINT64_C(1) << (bits))
+
+static uint32_t
+hash_32(uint32_t val, unsigned int bits) {
+ REQUIRE(bits <= RBT_HASH_MAX_BITS);
+ /* High bits are more random. */
+ return (val * GOLDEN_RATIO_32 >> (32 - bits));
+}
+
+struct dns_rbt {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_rbtnode_t *root;
+ void (*data_deleter)(void *, void *);
+ void *deleter_arg;
+ unsigned int nodecount;
+ uint16_t hashbits;
+ uint16_t maxhashbits;
+ dns_rbtnode_t **hashtable;
+ void *mmap_location;
+};
+
+#define RED 0
+#define BLACK 1
+
+/*
+ * This is the header for map-format RBT images. It is populated,
+ * and then written, as the LAST thing done to the file before returning.
+ * Writing this last (with zeros in the header area initially) will ensure
+ * that the header is only valid when the RBT image is also valid.
+ */
+typedef struct file_header file_header_t;
+
+/* Pad to 32 bytes */
+static char FILE_VERSION[32] = "\0";
+
+/* Header length, always the same size regardless of structure size */
+#define HEADER_LENGTH 1024
+
+struct file_header {
+ char version1[32];
+ uint64_t first_node_offset; /* usually 1024 */
+ /*
+ * information about the system on which the map file was generated
+ * will be used to tell if we can load the map file or not
+ */
+ uint32_t ptrsize;
+ unsigned int bigendian : 1; /* big or little endian system */
+ unsigned int rdataset_fixed : 1; /* compiled with
+ * --enable-rrset-fixed
+ */
+ unsigned int nodecount; /* shadow from rbt structure */
+ uint64_t crc;
+ char version2[32]; /* repeated; must match version1 */
+};
+
+/*
+ * The following declarations are for the serialization of an RBT:
+ *
+ * step one: write out a zeroed header of 1024 bytes
+ * step two: walk the tree in a depth-first, left-right-down order, writing
+ * out the nodes, reserving space as we go, correcting addresses to point
+ * at the proper offset in the file, and setting a flag for each pointer to
+ * indicate that it is a reference to a location in the file, rather than in
+ * memory.
+ * step three: write out the header, adding the information that will be
+ * needed to re-create the tree object itself.
+ *
+ * The RBTDB object will do this three times, once for each of the three
+ * RBT objects it contains.
+ *
+ * Note: 'file' must point an actual open file that can be mmapped
+ * and fseeked, not to a pipe or stream
+ */
+
+static isc_result_t
+dns_rbt_zero_header(FILE *file);
+
+static isc_result_t
+write_header(FILE *file, dns_rbt_t *rbt, uint64_t first_node_offset,
+ uint64_t crc);
+
+static bool
+match_header_version(file_header_t *header);
+
+static isc_result_t
+serialize_node(FILE *file, dns_rbtnode_t *node, uintptr_t left, uintptr_t right,
+ uintptr_t down, uintptr_t parent, uintptr_t data, uint64_t *crc);
+
+static isc_result_t
+serialize_nodes(FILE *file, dns_rbtnode_t *node, uintptr_t parent,
+ dns_rbtdatawriter_t datawriter, void *writer_arg,
+ uintptr_t *where, uint64_t *crc);
+
+#define ADJUST_ADDRESS(address, relative, header) \
+ if (address != NULL && header != NULL) { \
+ address += relative * (uintptr_t)header; \
+ }
+/*
+ * The following functions allow you to get the actual address of a pointer
+ * without having to use an if statement to check to see if that address is
+ * relative or not
+ */
+static dns_rbtnode_t *
+getparent(dns_rbtnode_t *node, file_header_t *header) {
+ char *adjusted_address = (char *)(node->parent);
+
+ ADJUST_ADDRESS(adjusted_address, node->parent_is_relative, header);
+
+ return ((dns_rbtnode_t *)adjusted_address);
+}
+
+static dns_rbtnode_t *
+getleft(dns_rbtnode_t *node, file_header_t *header) {
+ char *adjusted_address = (char *)(node->left);
+
+ ADJUST_ADDRESS(adjusted_address, node->left_is_relative, header);
+
+ return ((dns_rbtnode_t *)adjusted_address);
+}
+
+static dns_rbtnode_t *
+getright(dns_rbtnode_t *node, file_header_t *header) {
+ char *adjusted_address = (char *)(node->right);
+
+ ADJUST_ADDRESS(adjusted_address, node->right_is_relative, header);
+
+ return ((dns_rbtnode_t *)adjusted_address);
+}
+
+static dns_rbtnode_t *
+getdown(dns_rbtnode_t *node, file_header_t *header) {
+ char *adjusted_address = (char *)(node->down);
+
+ ADJUST_ADDRESS(adjusted_address, node->down_is_relative, header);
+
+ return ((dns_rbtnode_t *)adjusted_address);
+}
+
+static dns_rbtnode_t *
+getdata(dns_rbtnode_t *node, file_header_t *header) {
+ char *adjusted_address = (char *)(node->data);
+
+ ADJUST_ADDRESS(adjusted_address, node->data_is_relative, header);
+
+ return ((dns_rbtnode_t *)adjusted_address);
+}
+
+/*%
+ * Elements of the rbtnode structure.
+ */
+#define PARENT(node) ((node)->parent)
+#define LEFT(node) ((node)->left)
+#define RIGHT(node) ((node)->right)
+#define DOWN(node) ((node)->down)
+#define UPPERNODE(node) ((node)->uppernode)
+#define DATA(node) ((node)->data)
+#define IS_EMPTY(node) ((node)->data == NULL)
+#define HASHNEXT(node) ((node)->hashnext)
+#define HASHVAL(node) ((node)->hashval)
+#define COLOR(node) ((node)->color)
+#define NAMELEN(node) ((node)->namelen)
+#define OLDNAMELEN(node) ((node)->oldnamelen)
+#define OFFSETLEN(node) ((node)->offsetlen)
+#define ATTRS(node) ((node)->attributes)
+#define IS_ROOT(node) ((node)->is_root)
+#define FINDCALLBACK(node) ((node)->find_callback)
+
+#define WANTEMPTYDATA_OR_DATA(options, node) \
+ ((options & DNS_RBTFIND_EMPTYDATA) != 0 || DATA(node) != NULL)
+
+/*%
+ * Structure elements from the rbtdb.c, not
+ * used as part of the rbt.c algorithms.
+ */
+#define DIRTY(node) ((node)->dirty)
+#define WILD(node) ((node)->wild)
+#define LOCKNUM(node) ((node)->locknum)
+
+/*%
+ * The variable length stuff stored after the node has the following
+ * structure.
+ *
+ * &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
+#define inline
+/*
+ * A little something to help out in GDB.
+ */
+dns_name_t
+Name(dns_rbtnode_t *node);
+dns_name_t
+Name(dns_rbtnode_t *node) {
+ dns_name_t name;
+
+ dns_name_init(&name, NULL);
+ if (node != NULL) {
+ NODENAME(node, &name);
+ }
+
+ return (name);
+}
+
+static void
+hexdump(const char *desc, unsigned char *data, size_t size) {
+ char hexdump[BUFSIZ * 2 + 1];
+ isc_buffer_t b;
+ isc_region_t r;
+ isc_result_t result;
+ size_t bytes;
+
+ fprintf(stderr, "%s: ", desc);
+ do {
+ isc_buffer_init(&b, hexdump, sizeof(hexdump));
+ r.base = data;
+ r.length = bytes = (size > BUFSIZ) ? BUFSIZ : size;
+ result = isc_hex_totext(&r, 0, "", &b);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_buffer_putuint8(&b, 0);
+ fprintf(stderr, "%s", hexdump);
+ data += bytes;
+ size -= bytes;
+ } while (size > 0);
+ fprintf(stderr, "\n");
+}
+#endif /* DEBUG */
+
+/*
+ * Upper node is the parent of the root of the passed node's
+ * subtree. The passed node must not be NULL.
+ */
+static dns_rbtnode_t *
+get_upper_node(dns_rbtnode_t *node) {
+ return (UPPERNODE(node));
+}
+
+static void
+fixup_uppernodes_helper(dns_rbtnode_t *node, dns_rbtnode_t *uppernode) {
+ if (node == NULL) {
+ return;
+ }
+
+ UPPERNODE(node) = uppernode;
+
+ fixup_uppernodes_helper(LEFT(node), uppernode);
+ fixup_uppernodes_helper(RIGHT(node), uppernode);
+ fixup_uppernodes_helper(DOWN(node), node);
+}
+
+/*
+ * This function is used to fixup uppernode members of all dns_rbtnodes
+ * after deserialization.
+ */
+static void
+fixup_uppernodes(dns_rbt_t *rbt) {
+ fixup_uppernodes_helper(rbt->root, NULL);
+}
+
+size_t
+dns__rbtnode_getdistance(dns_rbtnode_t *node) {
+ size_t nodes = 1;
+
+ while (node != NULL) {
+ if (IS_ROOT(node)) {
+ break;
+ }
+ nodes++;
+ node = PARENT(node);
+ }
+
+ return (nodes);
+}
+
+/*
+ * Forward declarations.
+ */
+static isc_result_t
+create_node(isc_mem_t *mctx, const dns_name_t *name, dns_rbtnode_t **nodep);
+
+static isc_result_t
+inithash(dns_rbt_t *rbt);
+
+static void
+hash_node(dns_rbt_t *rbt, dns_rbtnode_t *node, const dns_name_t *name);
+
+static void
+unhash_node(dns_rbt_t *rbt, dns_rbtnode_t *node);
+
+static uint32_t
+rehash_bits(dns_rbt_t *rbt, size_t newcount);
+static void
+rehash(dns_rbt_t *rbt, uint32_t newbits);
+static void
+maybe_rehash(dns_rbt_t *rbt, size_t size);
+
+static void
+rotate_left(dns_rbtnode_t *node, dns_rbtnode_t **rootp);
+static void
+rotate_right(dns_rbtnode_t *node, dns_rbtnode_t **rootp);
+
+static void
+addonlevel(dns_rbtnode_t *node, dns_rbtnode_t *current, int order,
+ dns_rbtnode_t **rootp);
+
+static void
+deletefromlevel(dns_rbtnode_t *item, dns_rbtnode_t **rootp);
+
+static isc_result_t
+treefix(dns_rbt_t *rbt, void *base, size_t size, dns_rbtnode_t *n,
+ const dns_name_t *name, dns_rbtdatafixer_t datafixer, void *fixer_arg,
+ uint64_t *crc);
+
+static void
+deletetreeflat(dns_rbt_t *rbt, unsigned int quantum, bool unhash,
+ dns_rbtnode_t **nodep);
+
+static void
+printnodename(dns_rbtnode_t *node, bool quoted, FILE *f);
+
+static void
+freenode(dns_rbt_t *rbt, dns_rbtnode_t **nodep);
+
+static isc_result_t
+dns_rbt_zero_header(FILE *file) {
+ /*
+ * Write out a zeroed header as a placeholder. Doing this ensures
+ * that the file will not read while it is partially written, should
+ * writing fail or be interrupted.
+ */
+ char buffer[HEADER_LENGTH];
+ isc_result_t result;
+
+ memset(buffer, 0, HEADER_LENGTH);
+ result = isc_stdio_write(buffer, 1, HEADER_LENGTH, file, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = fflush(file);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_once_t once = ISC_ONCE_INIT;
+
+static void
+init_file_version(void) {
+ int n;
+
+ memset(FILE_VERSION, 0, sizeof(FILE_VERSION));
+ n = snprintf(FILE_VERSION, sizeof(FILE_VERSION), "RBT Image %s %s",
+ dns_major, dns_mapapi);
+ INSIST(n > 0 && (unsigned int)n < sizeof(FILE_VERSION));
+}
+
+/*
+ * Write out the real header, including NodeDump version information
+ * and the offset of the first node.
+ *
+ * Any information stored in the rbt object itself should be stored
+ * here.
+ */
+static isc_result_t
+write_header(FILE *file, dns_rbt_t *rbt, uint64_t first_node_offset,
+ uint64_t crc) {
+ file_header_t header;
+ isc_result_t result;
+ off_t location;
+
+ RUNTIME_CHECK(isc_once_do(&once, init_file_version) == ISC_R_SUCCESS);
+
+ memset(&header, 0, sizeof(file_header_t));
+ memmove(header.version1, FILE_VERSION, sizeof(header.version1));
+ memmove(header.version2, FILE_VERSION, sizeof(header.version2));
+ header.first_node_offset = first_node_offset;
+ header.ptrsize = (uint32_t)sizeof(void *);
+ header.bigendian = (1 == htonl(1)) ? 1 : 0;
+
+#ifdef DNS_RDATASET_FIXED
+ header.rdataset_fixed = 1;
+#else /* ifdef DNS_RDATASET_FIXED */
+ header.rdataset_fixed = 0;
+#endif /* ifdef DNS_RDATASET_FIXED */
+
+ header.nodecount = rbt->nodecount;
+
+ header.crc = crc;
+
+ CHECK(isc_stdio_tell(file, &location));
+ location = dns_rbt_serialize_align(location);
+ CHECK(isc_stdio_seek(file, location, SEEK_SET));
+ CHECK(isc_stdio_write(&header, 1, sizeof(file_header_t), file, NULL));
+ CHECK(fflush(file));
+
+ /* Ensure we are always at the end of the file. */
+ CHECK(isc_stdio_seek(file, 0, SEEK_END));
+
+cleanup:
+ return (result);
+}
+
+static bool
+match_header_version(file_header_t *header) {
+ RUNTIME_CHECK(isc_once_do(&once, init_file_version) == ISC_R_SUCCESS);
+
+ if (memcmp(header->version1, FILE_VERSION, sizeof(header->version1)) !=
+ 0 ||
+ memcmp(header->version2, FILE_VERSION, sizeof(header->version1)) !=
+ 0)
+ {
+ return (false);
+ }
+
+ return (true);
+}
+
+unsigned int
+dns__rbtnode_namelen(dns_rbtnode_t *node) {
+ dns_name_t current;
+ unsigned int len = 0;
+
+ REQUIRE(DNS_RBTNODE_VALID(node));
+
+ dns_name_init(&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);
+}
+
+static isc_result_t
+serialize_node(FILE *file, dns_rbtnode_t *node, uintptr_t left, uintptr_t right,
+ uintptr_t down, uintptr_t parent, uintptr_t data,
+ uint64_t *crc) {
+ isc_result_t result;
+ dns_rbtnode_t temp_node;
+ off_t file_position;
+ unsigned char *node_data = NULL;
+ size_t datasize;
+#ifdef DEBUG
+ dns_name_t nodename;
+#endif /* ifdef DEBUG */
+
+ INSIST(node != NULL);
+
+ CHECK(isc_stdio_tell(file, &file_position));
+ file_position = dns_rbt_serialize_align(file_position);
+ CHECK(isc_stdio_seek(file, file_position, SEEK_SET));
+
+ temp_node = *node;
+ temp_node.down_is_relative = 0;
+ temp_node.left_is_relative = 0;
+ temp_node.right_is_relative = 0;
+ temp_node.parent_is_relative = 0;
+ temp_node.data_is_relative = 0;
+ temp_node.is_mmapped = 1;
+
+ /*
+ * If the next node is not NULL, calculate the next node's location
+ * in the file. Note that this will have to change when the data
+ * structure changes, and it also assumes that we always write the
+ * nodes out in list order (which we currently do.)
+ */
+ if (temp_node.parent != NULL) {
+ temp_node.parent = (dns_rbtnode_t *)(parent);
+ temp_node.parent_is_relative = 1;
+ }
+ if (temp_node.left != NULL) {
+ temp_node.left = (dns_rbtnode_t *)(left);
+ temp_node.left_is_relative = 1;
+ }
+ if (temp_node.right != NULL) {
+ temp_node.right = (dns_rbtnode_t *)(right);
+ temp_node.right_is_relative = 1;
+ }
+ if (temp_node.down != NULL) {
+ temp_node.down = (dns_rbtnode_t *)(down);
+ temp_node.down_is_relative = 1;
+ }
+ if (temp_node.data != NULL) {
+ temp_node.data = (dns_rbtnode_t *)(data);
+ temp_node.data_is_relative = 1;
+ }
+
+ temp_node.fullnamelen = dns__rbtnode_namelen(node);
+
+ node_data = (unsigned char *)node + sizeof(dns_rbtnode_t);
+ datasize = NODE_SIZE(node) - sizeof(dns_rbtnode_t);
+
+ CHECK(isc_stdio_write(&temp_node, 1, sizeof(dns_rbtnode_t), file,
+ NULL));
+ CHECK(isc_stdio_write(node_data, 1, datasize, file, NULL));
+
+#ifdef DEBUG
+ dns_name_init(&nodename, NULL);
+ NODENAME(node, &nodename);
+ fprintf(stderr, "serialize ");
+ dns_name_print(&nodename, stderr);
+ fprintf(stderr, "\n");
+ hexdump("node header", (unsigned char *)&temp_node,
+ sizeof(dns_rbtnode_t));
+ hexdump("node data", node_data, datasize);
+#endif /* ifdef DEBUG */
+
+ isc_crc64_update(crc, (const uint8_t *)&temp_node,
+ sizeof(dns_rbtnode_t));
+ isc_crc64_update(crc, (const uint8_t *)node_data, datasize);
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+serialize_nodes(FILE *file, dns_rbtnode_t *node, uintptr_t parent,
+ dns_rbtdatawriter_t datawriter, void *writer_arg,
+ uintptr_t *where, uint64_t *crc) {
+ uintptr_t left = 0, right = 0, down = 0, data = 0;
+ off_t location = 0, offset_adjust;
+ isc_result_t result;
+
+ if (node == NULL) {
+ if (where != NULL) {
+ *where = 0;
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ /* Reserve space for current node. */
+ CHECK(isc_stdio_tell(file, &location));
+ location = dns_rbt_serialize_align(location);
+ CHECK(isc_stdio_seek(file, location, SEEK_SET));
+
+ offset_adjust = dns_rbt_serialize_align(location + NODE_SIZE(node));
+ CHECK(isc_stdio_seek(file, offset_adjust, SEEK_SET));
+
+ /*
+ * Serialize the rest of the tree.
+ *
+ * WARNING: A change in the order (from left, right, down)
+ * will break the way the crc hash is computed.
+ */
+ CHECK(serialize_nodes(file, getleft(node, NULL), location, datawriter,
+ writer_arg, &left, crc));
+ CHECK(serialize_nodes(file, getright(node, NULL), location, datawriter,
+ writer_arg, &right, crc));
+ CHECK(serialize_nodes(file, getdown(node, NULL), location, datawriter,
+ writer_arg, &down, crc));
+
+ if (node->data != NULL) {
+ off_t ret;
+
+ CHECK(isc_stdio_tell(file, &ret));
+ ret = dns_rbt_serialize_align(ret);
+ CHECK(isc_stdio_seek(file, ret, SEEK_SET));
+ data = ret;
+
+ datawriter(file, node->data, writer_arg, crc);
+ }
+
+ /* Seek back to reserved space. */
+ CHECK(isc_stdio_seek(file, location, SEEK_SET));
+
+ /* Serialize the current node. */
+ CHECK(serialize_node(file, node, left, right, down, parent, data, crc));
+
+ /* Ensure we are always at the end of the file. */
+ CHECK(isc_stdio_seek(file, 0, SEEK_END));
+
+ if (where != NULL) {
+ *where = (uintptr_t)location;
+ }
+
+cleanup:
+ return (result);
+}
+
+off_t
+dns_rbt_serialize_align(off_t target) {
+ off_t offset = target % 8;
+
+ if (offset == 0) {
+ return (target);
+ } else {
+ return (target + 8 - offset);
+ }
+}
+
+isc_result_t
+dns_rbt_serialize_tree(FILE *file, dns_rbt_t *rbt,
+ dns_rbtdatawriter_t datawriter, void *writer_arg,
+ off_t *offset) {
+ isc_result_t result;
+ off_t header_position, node_position, end_position;
+ uint64_t crc;
+
+ REQUIRE(file != NULL);
+
+ CHECK(isc_file_isplainfilefd(fileno(file)));
+
+ isc_crc64_init(&crc);
+
+ CHECK(isc_stdio_tell(file, &header_position));
+
+ /* Write dummy header */
+ CHECK(dns_rbt_zero_header(file));
+
+ /* Serialize nodes */
+ CHECK(isc_stdio_tell(file, &node_position));
+ CHECK(serialize_nodes(file, rbt->root, 0, datawriter, writer_arg, NULL,
+ &crc));
+
+ CHECK(isc_stdio_tell(file, &end_position));
+ if (node_position == end_position) {
+ CHECK(isc_stdio_seek(file, header_position, SEEK_SET));
+ *offset = 0;
+ return (ISC_R_SUCCESS);
+ }
+
+ isc_crc64_final(&crc);
+#ifdef DEBUG
+ hexdump("serializing CRC", (unsigned char *)&crc, sizeof(crc));
+#endif /* ifdef DEBUG */
+
+ /* Serialize header */
+ CHECK(isc_stdio_seek(file, header_position, SEEK_SET));
+ CHECK(write_header(file, rbt, HEADER_LENGTH, crc));
+
+ /* Ensure we are always at the end of the file. */
+ CHECK(isc_stdio_seek(file, 0, SEEK_END));
+ *offset = dns_rbt_serialize_align(header_position);
+
+cleanup:
+ return (result);
+}
+
+#define CONFIRM(a) \
+ do { \
+ if (!(a)) { \
+ result = ISC_R_INVALIDFILE; \
+ goto cleanup; \
+ } \
+ } while (0);
+
+static isc_result_t
+treefix(dns_rbt_t *rbt, void *base, size_t filesize, dns_rbtnode_t *n,
+ const dns_name_t *name, dns_rbtdatafixer_t datafixer, void *fixer_arg,
+ uint64_t *crc) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_fixedname_t fixed;
+ dns_name_t nodename, *fullname = NULL;
+ unsigned char *node_data = NULL;
+ dns_rbtnode_t header;
+ size_t nodemax = filesize - sizeof(dns_rbtnode_t);
+ size_t datasize;
+
+ if (n == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+#define CHECK_ALIGNMENT(n) \
+ (((uintptr_t)n & ~((uintptr_t)ALIGNMENT_SIZE - 1)) == (uintptr_t)n)
+
+ CONFIRM((void *)n >= base);
+ CONFIRM((size_t)((char *)n - (char *)base) <= nodemax);
+ CONFIRM(CHECK_ALIGNMENT(n));
+ CONFIRM(DNS_RBTNODE_VALID(n));
+
+ dns_name_init(&nodename, NULL);
+ NODENAME(n, &nodename);
+
+ fullname = &nodename;
+ CONFIRM(dns_name_isvalid(fullname));
+
+ if (!dns_name_isabsolute(&nodename)) {
+ fullname = dns_fixedname_initname(&fixed);
+ CHECK(dns_name_concatenate(&nodename, name, fullname, NULL));
+ }
+
+ /* memorize header contents prior to fixup */
+ memmove(&header, n, sizeof(header));
+
+ if (n->left_is_relative) {
+ CONFIRM(n->left <= (dns_rbtnode_t *)nodemax);
+ n->left = getleft(n, rbt->mmap_location);
+ n->left_is_relative = 0;
+ CONFIRM(CHECK_ALIGNMENT(n->left));
+ CONFIRM(DNS_RBTNODE_VALID(n->left));
+ } else {
+ CONFIRM(n->left == NULL);
+ }
+
+ if (n->right_is_relative) {
+ CONFIRM(n->right <= (dns_rbtnode_t *)nodemax);
+ n->right = getright(n, rbt->mmap_location);
+ n->right_is_relative = 0;
+ CONFIRM(CHECK_ALIGNMENT(n->right));
+ CONFIRM(DNS_RBTNODE_VALID(n->right));
+ } else {
+ CONFIRM(n->right == NULL);
+ }
+
+ if (n->down_is_relative) {
+ CONFIRM(n->down <= (dns_rbtnode_t *)nodemax);
+ n->down = getdown(n, rbt->mmap_location);
+ n->down_is_relative = 0;
+ CONFIRM(n->down > (dns_rbtnode_t *)n);
+ CONFIRM(CHECK_ALIGNMENT(n->down));
+ CONFIRM(DNS_RBTNODE_VALID(n->down));
+ } else {
+ CONFIRM(n->down == NULL);
+ }
+
+ if (n->parent_is_relative) {
+ CONFIRM(n->parent <= (dns_rbtnode_t *)nodemax);
+ n->parent = getparent(n, rbt->mmap_location);
+ n->parent_is_relative = 0;
+ CONFIRM(n->parent < (dns_rbtnode_t *)n);
+ CONFIRM(CHECK_ALIGNMENT(n->parent));
+ CONFIRM(DNS_RBTNODE_VALID(n->parent));
+ } else {
+ CONFIRM(n->parent == NULL);
+ }
+
+ if (n->data_is_relative) {
+ CONFIRM(n->data <= (void *)filesize);
+ n->data = getdata(n, rbt->mmap_location);
+ n->data_is_relative = 0;
+ CONFIRM(n->data > (void *)n);
+ CONFIRM(CHECK_ALIGNMENT(n->data));
+ } else {
+ CONFIRM(n->data == NULL);
+ }
+
+ hash_node(rbt, n, fullname);
+
+ /* a change in the order (from left, right, down) will break hashing*/
+ if (n->left != NULL) {
+ CHECK(treefix(rbt, base, filesize, n->left, name, datafixer,
+ fixer_arg, crc));
+ }
+ if (n->right != NULL) {
+ CHECK(treefix(rbt, base, filesize, n->right, name, datafixer,
+ fixer_arg, crc));
+ }
+ if (n->down != NULL) {
+ CHECK(treefix(rbt, base, filesize, n->down, fullname, datafixer,
+ fixer_arg, crc));
+ }
+
+ if (datafixer != NULL && n->data != NULL) {
+ CHECK(datafixer(n, base, filesize, fixer_arg, crc));
+ }
+
+ rbt->nodecount++;
+ node_data = (unsigned char *)n + sizeof(dns_rbtnode_t);
+ datasize = NODE_SIZE(n) - sizeof(dns_rbtnode_t);
+
+#ifdef DEBUG
+ fprintf(stderr, "deserialize ");
+ dns_name_print(&nodename, stderr);
+ fprintf(stderr, "\n");
+ hexdump("node header", (unsigned char *)&header, sizeof(dns_rbtnode_t));
+ hexdump("node data", node_data, datasize);
+#endif /* ifdef DEBUG */
+ isc_crc64_update(crc, (const uint8_t *)&header, sizeof(dns_rbtnode_t));
+ isc_crc64_update(crc, (const uint8_t *)node_data, datasize);
+
+cleanup:
+ return (result);
+}
+
+isc_result_t
+dns_rbt_deserialize_tree(void *base_address, size_t filesize,
+ off_t header_offset, isc_mem_t *mctx,
+ dns_rbtdeleter_t deleter, void *deleter_arg,
+ dns_rbtdatafixer_t datafixer, void *fixer_arg,
+ dns_rbtnode_t **originp, dns_rbt_t **rbtp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ file_header_t *header;
+ dns_rbt_t *rbt = NULL;
+ uint64_t crc;
+ unsigned int host_big_endian;
+
+ REQUIRE(originp == NULL || *originp == NULL);
+ REQUIRE(rbtp != NULL && *rbtp == NULL);
+
+ isc_crc64_init(&crc);
+
+ CHECK(dns_rbt_create(mctx, deleter, deleter_arg, &rbt));
+
+ rbt->mmap_location = base_address;
+
+ header = (file_header_t *)((char *)base_address + header_offset);
+ if (!match_header_version(header)) {
+ result = ISC_R_INVALIDFILE;
+ goto cleanup;
+ }
+
+#ifdef DNS_RDATASET_FIXED
+ if (header->rdataset_fixed != 1) {
+ result = ISC_R_INVALIDFILE;
+ goto cleanup;
+ }
+
+#else /* ifdef DNS_RDATASET_FIXED */
+ if (header->rdataset_fixed != 0) {
+ result = ISC_R_INVALIDFILE;
+ goto cleanup;
+ }
+#endif /* ifdef DNS_RDATASET_FIXED */
+
+ if (header->ptrsize != (uint32_t)sizeof(void *)) {
+ result = ISC_R_INVALIDFILE;
+ goto cleanup;
+ }
+
+ host_big_endian = (1 == htonl(1));
+ if (header->bigendian != host_big_endian) {
+ result = ISC_R_INVALIDFILE;
+ goto cleanup;
+ }
+
+ /* Copy other data items from the header into our rbt. */
+ rbt->root = (dns_rbtnode_t *)((char *)base_address + header_offset +
+ header->first_node_offset);
+
+ if ((header->nodecount * sizeof(dns_rbtnode_t)) > filesize) {
+ result = ISC_R_INVALIDFILE;
+ goto cleanup;
+ }
+ maybe_rehash(rbt, header->nodecount);
+
+ CHECK(treefix(rbt, base_address, filesize, rbt->root, dns_rootname,
+ datafixer, fixer_arg, &crc));
+
+ isc_crc64_final(&crc);
+#ifdef DEBUG
+ hexdump("deserializing CRC", (unsigned char *)&crc, sizeof(crc));
+#endif /* ifdef DEBUG */
+
+ /* Check file hash */
+ if (header->crc != crc) {
+ result = ISC_R_INVALIDFILE;
+ goto cleanup;
+ }
+
+ if (header->nodecount != rbt->nodecount) {
+ result = ISC_R_INVALIDFILE;
+ goto cleanup;
+ }
+
+ fixup_uppernodes(rbt);
+
+ *rbtp = rbt;
+ if (originp != NULL) {
+ *originp = rbt->root;
+ }
+
+cleanup:
+ if (result != ISC_R_SUCCESS && rbt != NULL) {
+ rbt->root = NULL;
+ rbt->nodecount = 0;
+ dns_rbt_destroy(&rbt);
+ }
+
+ return (result);
+}
+
+/*
+ * Initialize a red/black tree of trees.
+ */
+isc_result_t
+dns_rbt_create(isc_mem_t *mctx, dns_rbtdeleter_t deleter, void *deleter_arg,
+ dns_rbt_t **rbtp) {
+ isc_result_t result;
+ dns_rbt_t *rbt;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(rbtp != NULL && *rbtp == NULL);
+ REQUIRE(deleter == NULL ? deleter_arg == NULL : 1);
+
+ rbt = isc_mem_get(mctx, sizeof(*rbt));
+
+ rbt->mctx = NULL;
+ isc_mem_attach(mctx, &rbt->mctx);
+ rbt->data_deleter = deleter;
+ rbt->deleter_arg = deleter_arg;
+ rbt->root = NULL;
+ rbt->nodecount = 0;
+ rbt->hashtable = NULL;
+ rbt->hashbits = 0;
+ rbt->maxhashbits = RBT_HASH_MAX_BITS;
+ rbt->mmap_location = NULL;
+
+ result = inithash(rbt);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_putanddetach(&rbt->mctx, rbt, sizeof(*rbt));
+ return (result);
+ }
+
+ rbt->magic = RBT_MAGIC;
+
+ *rbtp = rbt;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Deallocate a red/black tree of trees.
+ */
+void
+dns_rbt_destroy(dns_rbt_t **rbtp) {
+ RUNTIME_CHECK(dns_rbt_destroy2(rbtp, 0) == ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rbt_destroy2(dns_rbt_t **rbtp, unsigned int quantum) {
+ dns_rbt_t *rbt;
+
+ REQUIRE(rbtp != NULL && VALID_RBT(*rbtp));
+
+ rbt = *rbtp;
+
+ deletetreeflat(rbt, quantum, false, &rbt->root);
+ if (rbt->root != NULL) {
+ return (ISC_R_QUOTA);
+ }
+
+ *rbtp = NULL;
+
+ INSIST(rbt->nodecount == 0);
+
+ rbt->mmap_location = NULL;
+
+ if (rbt->hashtable != NULL) {
+ size_t size = HASHSIZE(rbt->hashbits) * sizeof(dns_rbtnode_t *);
+ isc_mem_put(rbt->mctx, rbt->hashtable, size);
+ }
+
+ rbt->magic = 0;
+
+ isc_mem_putanddetach(&rbt->mctx, rbt, sizeof(*rbt));
+ return (ISC_R_SUCCESS);
+}
+
+unsigned int
+dns_rbt_nodecount(dns_rbt_t *rbt) {
+ REQUIRE(VALID_RBT(rbt));
+
+ return (rbt->nodecount);
+}
+
+size_t
+dns_rbt_hashsize(dns_rbt_t *rbt) {
+ REQUIRE(VALID_RBT(rbt));
+
+ return (1 << rbt->hashbits);
+}
+
+isc_result_t
+dns_rbt_adjusthashsize(dns_rbt_t *rbt, size_t size) {
+ REQUIRE(VALID_RBT(rbt));
+
+ if (size > 0) {
+ /*
+ * Setting a new, finite size limit was requested for the RBT.
+ * Estimate how many hash table slots are needed for the
+ * requested size and how many bits would be needed to index
+ * those hash table slots, then rehash the RBT if necessary.
+ * Note that the hash table can only grow, it is not shrunk if
+ * the requested size limit is lower than the current one.
+ */
+ size_t newsize = size / RBT_HASH_BUCKETSIZE;
+ rbt->maxhashbits = rehash_bits(rbt, newsize);
+ maybe_rehash(rbt, newsize);
+ } else {
+ /*
+ * Setting an infinite size limit was requested for the RBT.
+ * Increase the maximum allowed number of hash table slots to
+ * 2^32, which enables the hash table to grow as nodes are
+ * added to the RBT without immediately preallocating 2^32 hash
+ * table slots.
+ */
+ rbt->maxhashbits = RBT_HASH_MAX_BITS;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+chain_name(dns_rbtnodechain_t *chain, dns_name_t *name,
+ bool include_chain_end) {
+ dns_name_t nodename;
+ isc_result_t result = ISC_R_SUCCESS;
+ int i;
+
+ dns_name_init(&nodename, NULL);
+
+ if (include_chain_end && chain->end != NULL) {
+ NODENAME(chain->end, &nodename);
+ dns_name_copynf(&nodename, name);
+ } else {
+ dns_name_reset(name);
+ }
+
+ for (i = (int)chain->level_count - 1; i >= 0; i--) {
+ NODENAME(chain->levels[i], &nodename);
+ result = dns_name_concatenate(name, &nodename, name, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+move_chain_to_last(dns_rbtnodechain_t *chain, dns_rbtnode_t *node) {
+ do {
+ /*
+ * Go as far right and then down as much as possible,
+ * as long as the rightmost node has a down pointer.
+ */
+ while (RIGHT(node) != NULL) {
+ node = RIGHT(node);
+ }
+
+ if (DOWN(node) == NULL) {
+ break;
+ }
+
+ ADD_LEVEL(chain, node);
+ node = DOWN(node);
+ } while (1);
+
+ chain->end = node;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Add 'name' to tree, initializing its data pointer with 'data'.
+ */
+
+isc_result_t
+dns_rbt_addnode(dns_rbt_t *rbt, const dns_name_t *name, dns_rbtnode_t **nodep) {
+ /*
+ * Does this thing have too many variables or what?
+ */
+ dns_rbtnode_t **root, *parent, *child, *current, *new_current;
+ dns_name_t *add_name, *new_name, current_name, *prefix, *suffix;
+ dns_fixedname_t fixedcopy, fixedprefix, fixedsuffix, fnewname;
+ dns_offsets_t current_offsets;
+ dns_namereln_t compared;
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned int level_count;
+ unsigned int common_labels;
+ unsigned int nlabels, hlabels;
+ int order;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ /*
+ * Dear future BIND developer,
+ *
+ * After you have tried attempting to optimize this routine by
+ * using the hashtable and have realized your folly, please
+ * append another cross ("X") below as a warning to the next
+ * future BIND developer:
+ *
+ * Number of victim developers: X
+ *
+ * I wish the past developer had included such a notice.
+ *
+ * Long form: Unlike dns_rbt_findnode(), this function does not
+ * lend itself to be optimized using the hashtable:
+ *
+ * 1. In the subtree where the insertion occurs, this function
+ * needs to have the insertion point and the order where the
+ * lookup terminated (i.e., at the insertion point where left or
+ * right child is NULL). This cannot be determined from the
+ * hashtable, so at least in that subtree, a BST O(log N) lookup
+ * is necessary.
+ *
+ * 2. Our RBT nodes contain not only single labels but label
+ * sequences to optimize space usage. So at every level, we have
+ * to look for a match in the hashtable for all superdomains in
+ * the rest of the name we're searching. This is an O(N)
+ * operation at least, here N being the label size of name, each
+ * of which is a hashtable lookup involving dns_name_equal()
+ * comparisons.
+ */
+
+ /*
+ * Create a copy of the name so the original name structure is
+ * not modified.
+ */
+ add_name = dns_fixedname_initname(&fixedcopy);
+ INSIST(add_name != NULL);
+ dns_name_clone(name, add_name);
+
+ if (ISC_UNLIKELY(rbt->root == NULL)) {
+ result = create_node(rbt->mctx, add_name, &new_current);
+ if (result == ISC_R_SUCCESS) {
+ rbt->nodecount++;
+ new_current->is_root = 1;
+
+ UPPERNODE(new_current) = NULL;
+
+ rbt->root = new_current;
+ *nodep = new_current;
+ hash_node(rbt, new_current, name);
+ }
+ return (result);
+ }
+
+ level_count = 0;
+
+ prefix = dns_fixedname_initname(&fixedprefix);
+ suffix = dns_fixedname_initname(&fixedsuffix);
+
+ INSIST(prefix != NULL);
+ INSIST(suffix != NULL);
+
+ root = &rbt->root;
+ INSIST(IS_ROOT(*root));
+ parent = NULL;
+ current = NULL;
+ child = *root;
+ dns_name_init(&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 (ISC_LIKELY(child != NULL));
+
+ if (ISC_LIKELY(result == ISC_R_SUCCESS)) {
+ result = create_node(rbt->mctx, add_name, &new_current);
+ }
+
+ if (ISC_LIKELY(result == ISC_R_SUCCESS)) {
+ if (*root == NULL) {
+ UPPERNODE(new_current) = current;
+ } else {
+ UPPERNODE(new_current) = PARENT(*root);
+ }
+
+ addonlevel(new_current, current, order, root);
+ rbt->nodecount++;
+ *nodep = new_current;
+ hash_node(rbt, new_current, name);
+ }
+
+ return (result);
+}
+
+/*
+ * Add a name to the tree of trees, associating it with some data.
+ */
+isc_result_t
+dns_rbt_addname(dns_rbt_t *rbt, const dns_name_t *name, void *data) {
+ isc_result_t result;
+ dns_rbtnode_t *node;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(dns_name_isabsolute(name));
+
+ node = NULL;
+
+ result = dns_rbt_addnode(rbt, name, &node);
+
+ /*
+ * dns_rbt_addnode will report the node exists even when
+ * it does not have data associated with it, but the
+ * dns_rbt_*name functions all behave depending on whether
+ * there is data associated with a node.
+ */
+ if (result == ISC_R_SUCCESS ||
+ (result == ISC_R_EXISTS && DATA(node) == NULL))
+ {
+ DATA(node) = data;
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+/*
+ * Find the node for "name" in the tree of trees.
+ */
+isc_result_t
+dns_rbt_findnode(dns_rbt_t *rbt, const dns_name_t *name, dns_name_t *foundname,
+ dns_rbtnode_t **node, dns_rbtnodechain_t *chain,
+ unsigned int options, dns_rbtfindcallback_t callback,
+ void *callback_arg) {
+ dns_rbtnode_t *current, *last_compared;
+ dns_rbtnodechain_t localchain;
+ dns_name_t *search_name, current_name, *callback_name;
+ dns_fixedname_t fixedcallbackname, fixedsearchname;
+ dns_namereln_t compared;
+ isc_result_t result, saved_result;
+ unsigned int common_labels;
+ unsigned int hlabels = 0;
+ int order;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(node != NULL && *node == NULL);
+ REQUIRE((options & (DNS_RBTFIND_NOEXACT | DNS_RBTFIND_NOPREDECESSOR)) !=
+ (DNS_RBTFIND_NOEXACT | DNS_RBTFIND_NOPREDECESSOR));
+
+ /*
+ * If there is a chain it needs to appear to be in a sane state,
+ * otherwise a chain is still needed to generate foundname and
+ * callback_name.
+ */
+ if (chain == NULL) {
+ options |= DNS_RBTFIND_NOPREDECESSOR;
+ chain = &localchain;
+ dns_rbtnodechain_init(chain);
+ } else {
+ dns_rbtnodechain_reset(chain);
+ }
+
+ if (ISC_UNLIKELY(rbt->root == NULL)) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /*
+ * Appease GCC about variables it incorrectly thinks are
+ * possibly used uninitialized.
+ */
+ compared = dns_namereln_none;
+ last_compared = NULL;
+ order = 0;
+
+ callback_name = dns_fixedname_initname(&fixedcallbackname);
+
+ /*
+ * search_name is the name segment being sought in each tree level.
+ * By using a fixedname, the search_name will definitely have offsets
+ * for use by any splitting.
+ * By using dns_name_clone, no name data should be copied thanks to
+ * the lack of bitstring labels.
+ */
+ search_name = dns_fixedname_initname(&fixedsearchname);
+ INSIST(search_name != NULL);
+ dns_name_clone(name, search_name);
+
+ dns_name_init(&current_name, NULL);
+
+ saved_result = ISC_R_SUCCESS;
+ current = rbt->root;
+
+ while (ISC_LIKELY(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 hash;
+
+ /*
+ * The case of current not being a subtree root,
+ * that means a left or right pointer was
+ * followed, only happens when the algorithm
+ * fell through to the traditional binary search
+ * because of a bitstring label. Since we
+ * dropped the bitstring support, this should
+ * not happen.
+ */
+ INSIST(IS_ROOT(current));
+
+ nlabels = dns_name_countlabels(search_name);
+
+ /*
+ * current is the root of the current level, so
+ * its parent is the same as its "up" pointer.
+ */
+ up_current = PARENT(current);
+ dns_name_init(&hash_name, NULL);
+
+ hashagain:
+ /*
+ * Compute the hash over the full absolute
+ * name. Look for the smallest suffix match at
+ * this tree level (hlevel), and then at every
+ * iteration, look for the next smallest suffix
+ * match (add another subdomain label to the
+ * absolute name being hashed).
+ */
+ dns_name_getlabelsequence(name, nlabels - tlabels,
+ hlabels + tlabels,
+ &hash_name);
+ hash = dns_name_fullhash(&hash_name, false);
+ dns_name_getlabelsequence(search_name,
+ nlabels - tlabels, tlabels,
+ &hash_name);
+
+ /*
+ * Walk all the nodes in the hash bucket pointed
+ * by the computed hash value.
+ */
+ for (hnode = rbt->hashtable[hash_32(hash,
+ rbt->hashbits)];
+ hnode != NULL; hnode = hnode->hashnext)
+ {
+ dns_name_t hnode_name;
+
+ if (ISC_LIKELY(hash != HASHVAL(hnode))) {
+ continue;
+ }
+ /*
+ * This checks that the hashed label
+ * sequence being looked up is at the
+ * same tree level, so that we don't
+ * match a labelsequence from some other
+ * subdomain.
+ */
+ if (ISC_LIKELY(get_upper_node(hnode) !=
+ up_current))
+ {
+ continue;
+ }
+
+ dns_name_init(&hnode_name, NULL);
+ NODENAME(hnode, &hnode_name);
+ if (ISC_LIKELY(dns_name_equal(&hnode_name,
+ &hash_name)))
+ {
+ break;
+ }
+ }
+
+ if (hnode != NULL) {
+ current = hnode;
+ /*
+ * This is an optimization. If hashing found
+ * the right node, the next call to
+ * dns_name_fullcompare() would obviously
+ * return _equal or _subdomain. Determine
+ * which of those would be the case by
+ * checking if the full name was hashed. Then
+ * make it look like dns_name_fullcompare
+ * was called and jump to the right place.
+ */
+ if (tlabels == nlabels) {
+ compared = dns_namereln_equal;
+ break;
+ } else {
+ common_labels = tlabels;
+ compared = dns_namereln_subdomain;
+ goto subdomain;
+ }
+ }
+
+ if (tlabels++ < nlabels) {
+ goto hashagain;
+ }
+
+ /*
+ * All of the labels have been tried against the hash
+ * table. Since we dropped the support of bitstring
+ * labels, the name isn't in the table.
+ */
+ current = NULL;
+ continue;
+ } else {
+ /*
+ * The names have some common suffix labels.
+ *
+ * If the number in common are equal in length to
+ * the current node's name length, then follow the
+ * down pointer and search in the new tree.
+ */
+ if (compared == dns_namereln_subdomain) {
+ subdomain:
+ /*
+ * Whack off the current node's common parts
+ * for the name to search in the next level.
+ */
+ dns_name_split(search_name, common_labels,
+ search_name, NULL);
+ hlabels += common_labels;
+ /*
+ * This might be the closest enclosing name.
+ */
+ if (WANTEMPTYDATA_OR_DATA(options, current)) {
+ *node = current;
+ }
+
+ /*
+ * Point the chain to the next level. This
+ * needs to be done before 'current' is pointed
+ * there because the callback in the next
+ * block of code needs the current 'current',
+ * but in the event the callback requests that
+ * the search be stopped then the
+ * DNS_R_PARTIALMATCH code at the end of this
+ * function needs the chain pointed to the
+ * next level.
+ */
+ ADD_LEVEL(chain, current);
+
+ /*
+ * The caller may want to interrupt the
+ * downward search when certain special nodes
+ * are traversed. If this is a special node,
+ * the callback is used to learn what the
+ * caller wants to do.
+ */
+ if (callback != NULL && FINDCALLBACK(current)) {
+ result = chain_name(
+ chain, callback_name, false);
+ if (result != ISC_R_SUCCESS) {
+ dns_rbtnodechain_reset(chain);
+ return (result);
+ }
+
+ result = (callback)(current,
+ callback_name,
+ callback_arg);
+ if (result != DNS_R_CONTINUE) {
+ saved_result = result;
+ /*
+ * Treat this node as if it
+ * had no down pointer.
+ */
+ current = NULL;
+ break;
+ }
+ }
+
+ /*
+ * Finally, head to the next tree level.
+ */
+ current = DOWN(current);
+ } else {
+ /*
+ * Though there are labels in common, the
+ * entire name at this node is not common
+ * with the search name so the search
+ * name does not exist in the tree.
+ */
+ INSIST(compared ==
+ dns_namereln_commonancestor ||
+ compared == dns_namereln_contains);
+
+ current = NULL;
+ }
+ }
+ }
+
+ /*
+ * If current is not NULL, NOEXACT is not disallowing exact matches,
+ * and either the node has data or an empty node is ok, return
+ * ISC_R_SUCCESS to indicate an exact match.
+ */
+ if (current != NULL && (options & DNS_RBTFIND_NOEXACT) == 0 &&
+ WANTEMPTYDATA_OR_DATA(options, current))
+ {
+ /*
+ * Found an exact match.
+ */
+ chain->end = current;
+ chain->level_matches = chain->level_count;
+
+ if (foundname != NULL) {
+ result = chain_name(chain, foundname, true);
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ *node = current;
+ result = saved_result;
+ } else {
+ *node = NULL;
+ }
+ } else {
+ /*
+ * Did not find an exact match (or did not want one).
+ */
+ if (*node != NULL) {
+ /*
+ * ... but found a partially matching superdomain.
+ * Unwind the chain to the partial match node
+ * to set level_matches to the level above the node,
+ * and then to derive the name.
+ *
+ * chain->level_count is guaranteed to be at least 1
+ * here because by definition of finding a superdomain,
+ * the chain is pointed to at least the first subtree.
+ */
+ chain->level_matches = chain->level_count - 1;
+
+ while (chain->levels[chain->level_matches] != *node) {
+ INSIST(chain->level_matches > 0);
+ chain->level_matches--;
+ }
+
+ if (foundname != NULL) {
+ unsigned int saved_count = chain->level_count;
+
+ chain->level_count = chain->level_matches + 1;
+
+ result = chain_name(chain, foundname, false);
+
+ chain->level_count = saved_count;
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_PARTIALMATCH;
+ }
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+
+ if (current != NULL) {
+ /*
+ * There was an exact match but either
+ * DNS_RBTFIND_NOEXACT was set, or
+ * DNS_RBTFIND_EMPTYDATA was set and the node had no
+ * data. A policy decision was made to set the
+ * chain to the exact match, but this is subject
+ * to change if it becomes apparent that something
+ * else would be more useful. It is important that
+ * this case is handled here, because the predecessor
+ * setting code below assumes the match was not exact.
+ */
+ INSIST(((options & DNS_RBTFIND_NOEXACT) != 0) ||
+ ((options & DNS_RBTFIND_EMPTYDATA) == 0 &&
+ DATA(current) == NULL));
+ chain->end = current;
+ } else if ((options & DNS_RBTFIND_NOPREDECESSOR) != 0) {
+ /*
+ * Ensure the chain points nowhere.
+ */
+ chain->end = NULL;
+ } else {
+ /*
+ * Since there was no exact match, the chain argument
+ * needs to be pointed at the DNSSEC predecessor of
+ * the search name.
+ */
+ if (compared == dns_namereln_subdomain) {
+ /*
+ * Attempted to follow a down pointer that was
+ * NULL, which means the searched for name was
+ * a subdomain of a terminal name in the tree.
+ * Since there are no existing subdomains to
+ * order against, the terminal name is the
+ * predecessor.
+ */
+ INSIST(chain->level_count > 0);
+ INSIST(chain->level_matches <
+ chain->level_count);
+ chain->end =
+ chain->levels[--chain->level_count];
+ } else {
+ isc_result_t result2;
+
+ /*
+ * Point current to the node that stopped
+ * the search.
+ *
+ * With the hashing modification that has been
+ * added to the algorithm, the stop node of a
+ * standard binary search is not known. So it
+ * has to be found. There is probably a more
+ * clever way of doing this.
+ *
+ * The assignment of current to NULL when
+ * the relationship is *not* dns_namereln_none,
+ * even though it later gets set to the same
+ * last_compared anyway, is simply to not push
+ * the while loop in one more level of
+ * indentation.
+ */
+ if (compared == dns_namereln_none) {
+ current = last_compared;
+ } else {
+ current = NULL;
+ }
+
+ while (current != NULL) {
+ NODENAME(current, &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>",
+ dns_result_totext(result));
+ }
+
+ return (printname);
+}
+
+static isc_result_t
+create_node(isc_mem_t *mctx, const dns_name_t *name, dns_rbtnode_t **nodep) {
+ dns_rbtnode_t *node;
+ isc_region_t region;
+ unsigned int labels;
+ size_t nodelen;
+
+ REQUIRE(name->offsets != NULL);
+
+ dns_name_toregion(name, &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->is_mmapped = 0;
+ node->down_is_relative = 0;
+ node->left_is_relative = 0;
+ node->right_is_relative = 0;
+ node->parent_is_relative = 0;
+ node->data_is_relative = 0;
+ node->rpz = 0;
+
+ HASHNEXT(node) = NULL;
+ HASHVAL(node) = 0;
+
+ ISC_LINK_INIT(node, deadlink);
+
+ LOCKNUM(node) = 0;
+ WILD(node) = 0;
+ DIRTY(node) = 0;
+ isc_refcount_init(&node->references, 0);
+ node->find_callback = 0;
+ node->nsec = DNS_RBT_NSEC_NORMAL;
+
+ MAKE_BLACK(node);
+
+ /*
+ * The following is stored to make reconstructing a name from the
+ * stored value in the node easy: the length of the name, the number
+ * of labels, whether the name is absolute or not, the name itself,
+ * and the name's offsets table.
+ *
+ * XXX RTH
+ * The offsets table could be made smaller by eliminating the
+ * first offset, which is always 0. This requires changes to
+ * lib/dns/name.c.
+ *
+ * Note: OLDOFFSETLEN *must* be assigned *after* OLDNAMELEN is assigned
+ * as it uses OLDNAMELEN.
+ */
+ OLDNAMELEN(node) = NAMELEN(node) = region.length;
+ OLDOFFSETLEN(node) = OFFSETLEN(node) = labels;
+ ATTRS(node) = name->attributes;
+
+ memmove(NAME(node), region.base, region.length);
+ memmove(OFFSETS(node), name->offsets, labels);
+
+#if DNS_RBT_USEMAGIC
+ node->magic = DNS_RBTNODE_MAGIC;
+#endif /* if DNS_RBT_USEMAGIC */
+ *nodep = node;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Add a node to the hash table
+ */
+static void
+hash_add_node(dns_rbt_t *rbt, dns_rbtnode_t *node, const dns_name_t *name) {
+ uint32_t hash;
+
+ REQUIRE(name != NULL);
+
+ HASHVAL(node) = dns_name_fullhash(name, false);
+
+ hash = hash_32(HASHVAL(node), rbt->hashbits);
+ HASHNEXT(node) = rbt->hashtable[hash];
+
+ rbt->hashtable[hash] = node;
+}
+
+/*
+ * Initialize hash table
+ */
+static isc_result_t
+inithash(dns_rbt_t *rbt) {
+ size_t size;
+
+ rbt->hashbits = RBT_HASH_MIN_BITS;
+ size = HASHSIZE(rbt->hashbits) * sizeof(dns_rbtnode_t *);
+ rbt->hashtable = isc_mem_get(rbt->mctx, size);
+ memset(rbt->hashtable, 0, size);
+
+ return (ISC_R_SUCCESS);
+}
+
+static uint32_t
+rehash_bits(dns_rbt_t *rbt, size_t newcount) {
+ uint32_t newbits = rbt->hashbits;
+
+ while (newcount >= HASHSIZE(newbits) && newbits < RBT_HASH_MAX_BITS) {
+ newbits += 1;
+ }
+
+ return (newbits);
+}
+
+/*
+ * Rebuild the hashtable to reduce the load factor
+ */
+static void
+rehash(dns_rbt_t *rbt, uint32_t newbits) {
+ uint32_t oldbits;
+ size_t oldsize;
+ dns_rbtnode_t **oldtable;
+ size_t newsize;
+
+ REQUIRE(rbt->hashbits <= rbt->maxhashbits);
+ REQUIRE(newbits <= rbt->maxhashbits);
+
+ oldbits = rbt->hashbits;
+ oldsize = HASHSIZE(oldbits);
+ oldtable = rbt->hashtable;
+
+ rbt->hashbits = newbits;
+ newsize = HASHSIZE(rbt->hashbits);
+ rbt->hashtable = isc_mem_get(rbt->mctx,
+ newsize * sizeof(dns_rbtnode_t *));
+ memset(rbt->hashtable, 0, newsize * sizeof(dns_rbtnode_t *));
+
+ for (size_t i = 0; i < oldsize; i++) {
+ dns_rbtnode_t *node;
+ dns_rbtnode_t *nextnode;
+ for (node = oldtable[i]; node != NULL; node = nextnode) {
+ uint32_t hash = hash_32(HASHVAL(node), rbt->hashbits);
+ nextnode = HASHNEXT(node);
+ HASHNEXT(node) = rbt->hashtable[hash];
+ rbt->hashtable[hash] = node;
+ }
+ }
+
+ isc_mem_put(rbt->mctx, oldtable, oldsize * sizeof(dns_rbtnode_t *));
+}
+
+static void
+maybe_rehash(dns_rbt_t *rbt, size_t newcount) {
+ uint32_t newbits = rehash_bits(rbt, newcount);
+ if (rbt->hashbits < newbits && newbits <= rbt->maxhashbits) {
+ rehash(rbt, newbits);
+ }
+}
+
+/*
+ * Add a node to the hash table. Rehash the hashtable if the node count
+ * rises above a critical level.
+ */
+static void
+hash_node(dns_rbt_t *rbt, dns_rbtnode_t *node, const dns_name_t *name) {
+ REQUIRE(DNS_RBTNODE_VALID(node));
+
+ if (rbt->nodecount >= (HASHSIZE(rbt->hashbits) * RBT_HASH_OVERCOMMIT)) {
+ maybe_rehash(rbt, rbt->nodecount);
+ }
+
+ hash_add_node(rbt, node, name);
+}
+
+/*
+ * Remove a node from the hash table
+ */
+static void
+unhash_node(dns_rbt_t *rbt, dns_rbtnode_t *node) {
+ uint32_t bucket;
+ dns_rbtnode_t *bucket_node;
+
+ REQUIRE(DNS_RBTNODE_VALID(node));
+
+ bucket = hash_32(HASHVAL(node), rbt->hashbits);
+ bucket_node = rbt->hashtable[bucket];
+
+ if (bucket_node == node) {
+ rbt->hashtable[bucket] = HASHNEXT(node);
+ } else {
+ while (HASHNEXT(bucket_node) != node) {
+ INSIST(HASHNEXT(bucket_node) != NULL);
+ bucket_node = HASHNEXT(bucket_node);
+ }
+ HASHNEXT(bucket_node) = HASHNEXT(node);
+ }
+}
+
+static void
+rotate_left(dns_rbtnode_t *node, dns_rbtnode_t **rootp) {
+ dns_rbtnode_t *child;
+
+ REQUIRE(DNS_RBTNODE_VALID(node));
+ REQUIRE(rootp != NULL);
+
+ child = RIGHT(node);
+ INSIST(child != NULL);
+
+ RIGHT(node) = LEFT(child);
+ if (LEFT(child) != NULL) {
+ PARENT(LEFT(child)) = node;
+ }
+ LEFT(child) = node;
+
+ PARENT(child) = PARENT(node);
+
+ if (IS_ROOT(node)) {
+ *rootp = child;
+ child->is_root = 1;
+ node->is_root = 0;
+ } else {
+ if (LEFT(PARENT(node)) == node) {
+ LEFT(PARENT(node)) = child;
+ } else {
+ RIGHT(PARENT(node)) = child;
+ }
+ }
+
+ PARENT(node) = child;
+}
+
+static void
+rotate_right(dns_rbtnode_t *node, dns_rbtnode_t **rootp) {
+ dns_rbtnode_t *child;
+
+ REQUIRE(DNS_RBTNODE_VALID(node));
+ REQUIRE(rootp != NULL);
+
+ child = LEFT(node);
+ INSIST(child != NULL);
+
+ LEFT(node) = RIGHT(child);
+ if (RIGHT(child) != NULL) {
+ PARENT(RIGHT(child)) = node;
+ }
+ RIGHT(child) = node;
+
+ PARENT(child) = PARENT(node);
+
+ if (IS_ROOT(node)) {
+ *rootp = child;
+ child->is_root = 1;
+ node->is_root = 0;
+ } else {
+ if (LEFT(PARENT(node)) == node) {
+ LEFT(PARENT(node)) = child;
+ } else {
+ RIGHT(PARENT(node)) = child;
+ }
+ }
+
+ PARENT(node) = child;
+}
+
+/*
+ * This is the real workhorse of the insertion code, because it does the
+ * true red/black tree on a single level.
+ */
+static void
+addonlevel(dns_rbtnode_t *node, dns_rbtnode_t *current, int order,
+ dns_rbtnode_t **rootp) {
+ dns_rbtnode_t *child, *root, *parent, *grandparent;
+ dns_name_t add_name, current_name;
+ dns_offsets_t add_offsets, current_offsets;
+
+ REQUIRE(rootp != NULL);
+ REQUIRE(DNS_RBTNODE_VALID(node) && LEFT(node) == NULL &&
+ RIGHT(node) == NULL);
+ REQUIRE(current != NULL);
+
+ root = *rootp;
+ if (root == NULL) {
+ /*
+ * First node of a level.
+ */
+ MAKE_BLACK(node);
+ node->is_root = 1;
+ PARENT(node) = current;
+ *rootp = node;
+ return;
+ }
+
+ child = root;
+ POST(child);
+
+ dns_name_init(&add_name, add_offsets);
+ NODENAME(node, &add_name);
+
+ dns_name_init(&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)) {
+ /* cppcheck-suppress nullPointerRedundantCheck symbolName=item
+ */
+ parent = PARENT(item);
+
+ while (child != *rootp && IS_BLACK(child)) {
+ INSIST(child == NULL || !IS_ROOT(child));
+
+ if (LEFT(parent) == child) {
+ sibling = RIGHT(parent);
+
+ if (IS_RED(sibling)) {
+ MAKE_BLACK(sibling);
+ MAKE_RED(parent);
+ rotate_left(parent, rootp);
+ sibling = RIGHT(parent);
+ }
+
+ INSIST(sibling != NULL);
+
+ /* cppcheck-suppress nullPointerRedundantCheck
+ * symbolName=sibling */
+ if (IS_BLACK(LEFT(sibling)) &&
+ IS_BLACK(RIGHT(sibling)))
+ {
+ MAKE_RED(sibling);
+ child = parent;
+ } else {
+ if (IS_BLACK(RIGHT(sibling))) {
+ MAKE_BLACK(LEFT(sibling));
+ MAKE_RED(sibling);
+ rotate_right(sibling, rootp);
+ sibling = RIGHT(parent);
+ }
+
+ COLOR(sibling) = COLOR(parent);
+ MAKE_BLACK(parent);
+ INSIST(RIGHT(sibling) != NULL);
+ MAKE_BLACK(RIGHT(sibling));
+ rotate_left(parent, rootp);
+ child = *rootp;
+ }
+ } else {
+ /*
+ * Child is parent's right child.
+ * Everything is done the same as above,
+ * except mirrored.
+ */
+ sibling = LEFT(parent);
+
+ if (IS_RED(sibling)) {
+ MAKE_BLACK(sibling);
+ MAKE_RED(parent);
+ rotate_right(parent, rootp);
+ sibling = LEFT(parent);
+ }
+
+ INSIST(sibling != NULL);
+
+ /* cppcheck-suppress nullPointerRedundantCheck
+ * symbolName=sibling */
+ if (IS_BLACK(LEFT(sibling)) &&
+ IS_BLACK(RIGHT(sibling)))
+ {
+ MAKE_RED(sibling);
+ child = parent;
+ } else {
+ if (IS_BLACK(LEFT(sibling))) {
+ MAKE_BLACK(RIGHT(sibling));
+ MAKE_RED(sibling);
+ rotate_left(sibling, rootp);
+ sibling = LEFT(parent);
+ }
+
+ COLOR(sibling) = COLOR(parent);
+ MAKE_BLACK(parent);
+ INSIST(LEFT(sibling) != NULL);
+ MAKE_BLACK(LEFT(sibling));
+ rotate_right(parent, rootp);
+ child = *rootp;
+ }
+ }
+
+ parent = PARENT(child);
+ }
+
+ if (IS_RED(child)) {
+ MAKE_BLACK(child);
+ }
+ }
+}
+
+static void
+freenode(dns_rbt_t *rbt, dns_rbtnode_t **nodep) {
+ dns_rbtnode_t *node = *nodep;
+ *nodep = NULL;
+
+ if (node->is_mmapped == 0) {
+ isc_mem_put(rbt->mctx, node, NODE_SIZE(node));
+ }
+
+ rbt->nodecount--;
+}
+
+static void
+deletetreeflat(dns_rbt_t *rbt, unsigned int quantum, bool unhash,
+ dns_rbtnode_t **nodep) {
+ dns_rbtnode_t *root = *nodep;
+
+ while (root != NULL) {
+ /*
+ * If there is a left, right or down node, walk into it
+ * and iterate.
+ */
+ if (LEFT(root) != NULL) {
+ dns_rbtnode_t *node = root;
+ root = LEFT(root);
+ LEFT(node) = NULL;
+ } else if (RIGHT(root) != NULL) {
+ dns_rbtnode_t *node = root;
+ root = RIGHT(root);
+ RIGHT(node) = NULL;
+ } else if (DOWN(root) != NULL) {
+ dns_rbtnode_t *node = root;
+ root = DOWN(root);
+ DOWN(node) = NULL;
+ } else {
+ /*
+ * There are no left, right or down nodes, so we
+ * can free this one and go back to its parent.
+ */
+ dns_rbtnode_t *node = root;
+ root = PARENT(root);
+
+ if (rbt->data_deleter != NULL && DATA(node) != NULL) {
+ rbt->data_deleter(DATA(node), rbt->deleter_arg);
+ }
+ if (unhash) {
+ unhash_node(rbt, node);
+ }
+ /*
+ * Note: we don't call unhash_node() here as we
+ * are destroying the complete RBT tree.
+ */
+#if DNS_RBT_USEMAGIC
+ node->magic = 0;
+#endif /* if DNS_RBT_USEMAGIC */
+ freenode(rbt, &node);
+ if (quantum != 0 && --quantum == 0) {
+ break;
+ }
+ }
+ }
+
+ *nodep = root;
+}
+
+static size_t
+getheight_helper(dns_rbtnode_t *node) {
+ size_t dl, dr;
+ size_t this_height, down_height;
+
+ if (node == NULL) {
+ return (0);
+ }
+
+ dl = getheight_helper(LEFT(node));
+ dr = getheight_helper(RIGHT(node));
+
+ this_height = ISC_MAX(dl + 1, dr + 1);
+ down_height = getheight_helper(DOWN(node));
+
+ return (ISC_MAX(this_height, down_height));
+}
+
+size_t
+dns__rbt_getheight(dns_rbt_t *rbt) {
+ return (getheight_helper(rbt->root));
+}
+
+static bool
+check_properties_helper(dns_rbtnode_t *node) {
+ if (node == NULL) {
+ return (true);
+ }
+
+ if (IS_RED(node)) {
+ /* Root nodes must be BLACK. */
+ if (IS_ROOT(node)) {
+ return (false);
+ }
+
+ /* Both children of RED nodes must be BLACK. */
+ if (IS_RED(LEFT(node)) || IS_RED(RIGHT(node))) {
+ return (false);
+ }
+ }
+
+ /* cppcheck-suppress nullPointerRedundantCheck symbolName=node */
+ if ((DOWN(node) != NULL) && (!IS_ROOT(DOWN(node)))) {
+ return (false);
+ }
+
+ if (IS_ROOT(node)) {
+ if ((PARENT(node) != NULL) && (DOWN(PARENT(node)) != node)) {
+ return (false);
+ }
+
+ if (get_upper_node(node) != PARENT(node)) {
+ return (false);
+ }
+ }
+
+ /* If node is assigned to the down_ pointer of its parent, it is
+ * a subtree root and must have the flag set.
+ */
+ if (((!PARENT(node)) || (DOWN(PARENT(node)) == node)) &&
+ (!IS_ROOT(node)))
+ {
+ return (false);
+ }
+
+ /* Repeat tests with this node's children. */
+ return (check_properties_helper(LEFT(node)) &&
+ check_properties_helper(RIGHT(node)) &&
+ check_properties_helper(DOWN(node)));
+}
+
+static bool
+check_black_distance_helper(dns_rbtnode_t *node, size_t *distance) {
+ size_t dl, dr, dd;
+
+ if (node == NULL) {
+ *distance = 1;
+ return (true);
+ }
+
+ /* cppcheck-suppress nullPointerRedundantCheck symbolName=node */
+ if (!check_black_distance_helper(LEFT(node), &dl)) {
+ return (false);
+ }
+
+ /* cppcheck-suppress nullPointerRedundantCheck symbolName=node */
+ if (!check_black_distance_helper(RIGHT(node), &dr)) {
+ return (false);
+ }
+
+ /* cppcheck-suppress nullPointerRedundantCheck symbolName=node */
+ if (!check_black_distance_helper(DOWN(node), &dd)) {
+ return (false);
+ }
+
+ /* Left and right side black node counts must match. */
+ if (dl != dr) {
+ return (false);
+ }
+
+ if (IS_BLACK(node)) {
+ dl++;
+ }
+
+ *distance = dl;
+
+ return (true);
+}
+
+bool
+dns__rbt_checkproperties(dns_rbt_t *rbt) {
+ size_t dd;
+
+ if (!check_properties_helper(rbt->root)) {
+ return (false);
+ }
+
+ /* Path from a given node to all its leaves must contain the
+ * same number of BLACK child nodes. This is done separately
+ * here instead of inside check_properties_helper() as
+ * it would take (n log n) complexity otherwise.
+ */
+ return (check_black_distance_helper(rbt->root, &dd));
+}
+
+static void
+dns_rbt_indent(FILE *f, int depth) {
+ int i;
+
+ fprintf(f, "%4d ", depth);
+
+ for (i = 0; i < depth; i++) {
+ fprintf(f, "- ");
+ }
+}
+
+void
+dns_rbt_printnodeinfo(dns_rbtnode_t *n, FILE *f) {
+ if (n == NULL) {
+ fprintf(f, "Null node\n");
+ return;
+ }
+
+ fprintf(f, "Node info for nodename: ");
+ printnodename(n, true, f);
+ fprintf(f, "\n");
+
+ fprintf(f, "n = %p\n", n);
+
+ fprintf(f, "Relative pointers: %s%s%s%s%s\n",
+ (n->parent_is_relative == 1 ? " P" : ""),
+ (n->right_is_relative == 1 ? " R" : ""),
+ (n->left_is_relative == 1 ? " L" : ""),
+ (n->down_is_relative == 1 ? " D" : ""),
+ (n->data_is_relative == 1 ? " T" : ""));
+
+ fprintf(f, "node lock address = %u\n", n->locknum);
+
+ fprintf(f, "Parent: %p\n", n->parent);
+ fprintf(f, "Right: %p\n", n->right);
+ fprintf(f, "Left: %p\n", n->left);
+ fprintf(f, "Down: %p\n", n->down);
+ fprintf(f, "Data: %p\n", n->data);
+}
+
+static void
+printnodename(dns_rbtnode_t *node, bool quoted, FILE *f) {
+ isc_region_t r;
+ dns_name_t name;
+ char buffer[DNS_NAME_FORMATSIZE];
+ dns_offsets_t offsets;
+
+ r.length = NAMELEN(node);
+ r.base = NAME(node);
+
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &r);
+
+ dns_name_format(&name, buffer, sizeof(buffer));
+
+ if (quoted) {
+ fprintf(f, "\"%s\"", buffer);
+ } else {
+ fprintf(f, "%s", buffer);
+ }
+}
+
+static void
+print_text_helper(dns_rbtnode_t *root, dns_rbtnode_t *parent, int depth,
+ const char *direction, void (*data_printer)(FILE *, void *),
+ FILE *f) {
+ dns_rbt_indent(f, depth);
+
+ if (root != NULL) {
+ printnodename(root, true, f);
+ /*
+ * Don't use IS_RED(root) as it tests for 'root != NULL'
+ * and cppcheck produces false positives.
+ */
+ fprintf(f, " (%s, %s", direction,
+ COLOR(root) == RED ? "RED" : "BLACK");
+
+ if ((!IS_ROOT(root) && PARENT(root) != parent) ||
+ (IS_ROOT(root) && depth > 0 && DOWN(PARENT(root)) != root))
+ {
+ fprintf(f, " (BAD parent pointer! -> ");
+ if (PARENT(root) != NULL) {
+ printnodename(PARENT(root), true, f);
+ } else {
+ fprintf(f, "NULL");
+ }
+ fprintf(f, ")");
+ }
+
+ fprintf(f, ")");
+
+ if (root->data != NULL && data_printer != NULL) {
+ fprintf(f, " data@%p: ", root->data);
+ data_printer(f, root->data);
+ }
+ fprintf(f, "\n");
+
+ depth++;
+
+ /*
+ * Don't use IS_RED(root) as it tests for 'root != NULL'
+ * and cppcheck produces false positives.
+ */
+ if (COLOR(root) == RED && IS_RED(LEFT(root))) {
+ fprintf(f, "** Red/Red color violation on left\n");
+ }
+ print_text_helper(LEFT(root), root, depth, "left", data_printer,
+ f);
+
+ /*
+ * Don't use IS_RED(root) as cppcheck produces false positives.
+ */
+ if (COLOR(root) == RED && IS_RED(RIGHT(root))) {
+ fprintf(f, "** Red/Red color violation on right\n");
+ }
+ print_text_helper(RIGHT(root), root, depth, "right",
+ data_printer, f);
+
+ print_text_helper(DOWN(root), NULL, depth, "down", data_printer,
+ f);
+ } else {
+ fprintf(f, "NULL (%s)\n", direction);
+ }
+}
+
+void
+dns_rbt_printtext(dns_rbt_t *rbt, void (*data_printer)(FILE *, void *),
+ FILE *f) {
+ REQUIRE(VALID_RBT(rbt));
+
+ print_text_helper(rbt->root, NULL, 0, "root", data_printer, f);
+}
+
+static int
+print_dot_helper(dns_rbtnode_t *node, unsigned int *nodecount,
+ bool show_pointers, FILE *f) {
+ unsigned int l, r, d;
+
+ if (node == NULL) {
+ return (0);
+ }
+
+ l = print_dot_helper(LEFT(node), nodecount, show_pointers, f);
+ r = print_dot_helper(RIGHT(node), nodecount, show_pointers, f);
+ d = print_dot_helper(DOWN(node), nodecount, show_pointers, f);
+
+ *nodecount += 1;
+
+ fprintf(f, "node%u[label = \"<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_copynf(dns_rootname, origin);
+ }
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_prev(dns_rbtnodechain_t *chain, dns_name_t *name,
+ dns_name_t *origin) {
+ dns_rbtnode_t *current, *previous, *predecessor;
+ isc_result_t result = ISC_R_SUCCESS;
+ bool new_origin = false;
+
+ REQUIRE(VALID_CHAIN(chain) && chain->end != NULL);
+
+ predecessor = NULL;
+
+ current = chain->end;
+
+ if (LEFT(current) != NULL) {
+ /*
+ * Moving left one then right as far as possible is the
+ * previous node, at least for this level.
+ */
+ current = LEFT(current);
+
+ while (RIGHT(current) != NULL) {
+ current = RIGHT(current);
+ }
+
+ predecessor = current;
+ } else {
+ /*
+ * No left links, so move toward the root. If at any point on
+ * the way there the link from parent to child is a right
+ * link, then the parent is the previous node, at least
+ * for this level.
+ */
+ while (!IS_ROOT(current)) {
+ previous = current;
+ current = PARENT(current);
+
+ if (RIGHT(current) == previous) {
+ predecessor = current;
+ break;
+ }
+ }
+ }
+
+ if (predecessor != NULL) {
+ /*
+ * Found a predecessor node in this level. It might not
+ * really be the predecessor, however.
+ */
+ if (DOWN(predecessor) != NULL) {
+ /*
+ * The predecessor is really down at least one level.
+ * Go down and as far right as possible, and repeat
+ * as long as the rightmost node has a down pointer.
+ */
+ do {
+ /*
+ * XXX DCL Need to do something about origins
+ * here. See whether to go down, and if so
+ * whether it is truly what Bob calls a
+ * new origin.
+ */
+ ADD_LEVEL(chain, predecessor);
+ predecessor = DOWN(predecessor);
+
+ /* XXX DCL duplicated from above; clever
+ * way to unduplicate? */
+
+ while (RIGHT(predecessor) != NULL) {
+ predecessor = RIGHT(predecessor);
+ }
+ } while (DOWN(predecessor) != NULL);
+
+ /* XXX DCL probably needs work on the concept */
+ if (origin != NULL) {
+ new_origin = true;
+ }
+ }
+ } else if (chain->level_count > 0) {
+ /*
+ * Dang, didn't find a predecessor in this level.
+ * Got to the root of this level without having traversed
+ * any right links. Ascend the tree one level; the
+ * node that points to this tree is the predecessor.
+ */
+ INSIST(chain->level_count > 0 && IS_ROOT(current));
+ predecessor = chain->levels[--chain->level_count];
+
+ /* XXX DCL probably needs work on the concept */
+ /*
+ * Don't declare an origin change when the new origin is "."
+ * at the top level tree, because "." is declared as the origin
+ * for the second level tree.
+ */
+ if (origin != NULL &&
+ (chain->level_count > 0 || OFFSETLEN(predecessor) > 1))
+ {
+ new_origin = true;
+ }
+ }
+
+ if (predecessor != NULL) {
+ chain->end = predecessor;
+
+ if (new_origin) {
+ result = dns_rbtnodechain_current(chain, name, origin,
+ NULL);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NEWORIGIN;
+ }
+ } else {
+ result = dns_rbtnodechain_current(chain, name, NULL,
+ NULL);
+ }
+ } else {
+ result = ISC_R_NOMORE;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_down(dns_rbtnodechain_t *chain, dns_name_t *name,
+ dns_name_t *origin) {
+ dns_rbtnode_t *current, *successor;
+ isc_result_t result = ISC_R_SUCCESS;
+ bool new_origin = false;
+
+ REQUIRE(VALID_CHAIN(chain) && chain->end != NULL);
+
+ successor = NULL;
+
+ current = chain->end;
+
+ if (DOWN(current) != NULL) {
+ /*
+ * Don't declare an origin change when the new origin is "."
+ * at the second level tree, because "." is already declared
+ * as the origin for the top level tree.
+ */
+ if (chain->level_count > 0 || OFFSETLEN(current) > 1) {
+ new_origin = true;
+ }
+
+ ADD_LEVEL(chain, current);
+ current = DOWN(current);
+
+ while (LEFT(current) != NULL) {
+ current = LEFT(current);
+ }
+
+ successor = current;
+ }
+
+ if (successor != NULL) {
+ chain->end = successor;
+
+ /*
+ * It is not necessary to use dns_rbtnodechain_current like
+ * the other functions because this function will never
+ * find a node in the topmost level. This is because the
+ * root level will never be more than one name, and everything
+ * in the megatree is a successor to that node, down at
+ * the second level or below.
+ */
+
+ if (name != NULL) {
+ NODENAME(chain->end, name);
+ }
+
+ if (new_origin) {
+ if (origin != NULL) {
+ result = chain_name(chain, origin, false);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NEWORIGIN;
+ }
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ } else {
+ result = ISC_R_NOMORE;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_nextflat(dns_rbtnodechain_t *chain, dns_name_t *name) {
+ dns_rbtnode_t *current, *previous, *successor;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(VALID_CHAIN(chain) && chain->end != NULL);
+
+ successor = NULL;
+
+ current = chain->end;
+
+ if (RIGHT(current) == NULL) {
+ while (!IS_ROOT(current)) {
+ previous = current;
+ current = PARENT(current);
+
+ if (LEFT(current) == previous) {
+ successor = current;
+ break;
+ }
+ }
+ } else {
+ current = RIGHT(current);
+
+ while (LEFT(current) != NULL) {
+ current = LEFT(current);
+ }
+
+ successor = current;
+ }
+
+ if (successor != NULL) {
+ chain->end = successor;
+
+ if (name != NULL) {
+ NODENAME(chain->end, name);
+ }
+
+ result = ISC_R_SUCCESS;
+ } else {
+ result = ISC_R_NOMORE;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_next(dns_rbtnodechain_t *chain, dns_name_t *name,
+ dns_name_t *origin) {
+ dns_rbtnode_t *current, *previous, *successor;
+ isc_result_t result = ISC_R_SUCCESS;
+ bool new_origin = false;
+
+ REQUIRE(VALID_CHAIN(chain) && chain->end != NULL);
+
+ successor = NULL;
+
+ current = chain->end;
+
+ /*
+ * If there is a level below this node, the next node is the leftmost
+ * node of the next level.
+ */
+ if (DOWN(current) != NULL) {
+ /*
+ * Don't declare an origin change when the new origin is "."
+ * at the second level tree, because "." is already declared
+ * as the origin for the top level tree.
+ */
+ if (chain->level_count > 0 || OFFSETLEN(current) > 1) {
+ new_origin = true;
+ }
+
+ ADD_LEVEL(chain, current);
+ current = DOWN(current);
+
+ while (LEFT(current) != NULL) {
+ current = LEFT(current);
+ }
+
+ successor = current;
+ } else if (RIGHT(current) == NULL) {
+ /*
+ * The successor is up, either in this level or a previous one.
+ * Head back toward the root of the tree, looking for any path
+ * that was via a left link; the successor is the node that has
+ * that left link. In the event the root of the level is
+ * reached without having traversed any left links, ascend one
+ * level and look for either a right link off the point of
+ * ascent, or search for a left link upward again, repeating
+ * ascends until either case is true.
+ */
+ do {
+ while (!IS_ROOT(current)) {
+ previous = current;
+ current = PARENT(current);
+
+ if (LEFT(current) == previous) {
+ successor = current;
+ break;
+ }
+ }
+
+ if (successor == NULL) {
+ /*
+ * Reached the root without having traversed
+ * any left pointers, so this level is done.
+ */
+ if (chain->level_count == 0) {
+ /*
+ * If the tree we are iterating over
+ * was modified since this chain was
+ * initialized in a way that caused
+ * node splits to occur, "current" may
+ * now be pointing to a root node which
+ * appears to be at level 0, but still
+ * has a parent. If that happens,
+ * abort. Otherwise, we are done
+ * looking for a successor as we really
+ * reached the root node on level 0.
+ */
+ INSIST(PARENT(current) == NULL);
+ break;
+ }
+
+ current = chain->levels[--chain->level_count];
+ new_origin = true;
+
+ if (RIGHT(current) != NULL) {
+ break;
+ }
+ }
+ } while (successor == NULL);
+ }
+
+ if (successor == NULL && RIGHT(current) != NULL) {
+ current = RIGHT(current);
+
+ while (LEFT(current) != NULL) {
+ current = LEFT(current);
+ }
+
+ successor = current;
+ }
+
+ if (successor != NULL) {
+ /*
+ * If we determine that the current node is the successor to
+ * itself, we will run into an infinite loop, so abort instead.
+ */
+ INSIST(chain->end != successor);
+
+ chain->end = successor;
+
+ /*
+ * It is not necessary to use dns_rbtnodechain_current like
+ * the other functions because this function will never
+ * find a node in the topmost level. This is because the
+ * root level will never be more than one name, and everything
+ * in the megatree is a successor to that node, down at
+ * the second level or below.
+ */
+
+ if (name != NULL) {
+ NODENAME(chain->end, name);
+ }
+
+ if (new_origin) {
+ if (origin != NULL) {
+ result = chain_name(chain, origin, false);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NEWORIGIN;
+ }
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ } else {
+ result = ISC_R_NOMORE;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_first(dns_rbtnodechain_t *chain, dns_rbt_t *rbt,
+ dns_name_t *name, dns_name_t *origin)
+
+{
+ isc_result_t result;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(VALID_CHAIN(chain));
+
+ dns_rbtnodechain_reset(chain);
+
+ chain->end = rbt->root;
+
+ result = dns_rbtnodechain_current(chain, name, origin, NULL);
+
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NEWORIGIN;
+ }
+
+ return (result);
+}
+
+isc_result_t
+dns_rbtnodechain_last(dns_rbtnodechain_t *chain, dns_rbt_t *rbt,
+ dns_name_t *name, dns_name_t *origin)
+
+{
+ isc_result_t result;
+
+ REQUIRE(VALID_RBT(rbt));
+ REQUIRE(VALID_CHAIN(chain));
+
+ dns_rbtnodechain_reset(chain);
+
+ result = move_chain_to_last(chain, rbt->root);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_rbtnodechain_current(chain, name, origin, NULL);
+
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_NEWORIGIN;
+ }
+
+ return (result);
+}
+
+void
+dns_rbtnodechain_reset(dns_rbtnodechain_t *chain) {
+ REQUIRE(VALID_CHAIN(chain));
+
+ /*
+ * Free any dynamic storage associated with 'chain', and then
+ * reinitialize 'chain'.
+ */
+ chain->end = NULL;
+ chain->level_count = 0;
+ chain->level_matches = 0;
+}
+
+void
+dns_rbtnodechain_invalidate(dns_rbtnodechain_t *chain) {
+ /*
+ * Free any dynamic storage associated with 'chain', and then
+ * invalidate 'chain'.
+ */
+
+ dns_rbtnodechain_reset(chain);
+
+ chain->magic = 0;
+}
+
+/* XXXMUKS:
+ *
+ * - worth removing inline as static functions are inlined automatically
+ * where suitable by modern compilers.
+ * - bump the size of dns_rbt.nodecount to size_t.
+ * - the dumpfile header also contains a nodecount that is unsigned
+ * int. If large files (> 2^32 nodes) are to be supported, the
+ * allocation for this field should be increased.
+ */
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
new file mode 100644
index 0000000..ee06c51
--- /dev/null
+++ b/lib/dns/rbtdb.c
@@ -0,0 +1,10667 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.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/platform.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/serial.h>
+#include <isc/socket.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/lib.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/result.h>
+#include <dns/stats.h>
+#include <dns/time.h>
+#include <dns/version.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+#include <dns/zonekey.h>
+
+#ifndef WIN32
+#include <sys/mman.h>
+#else /* ifndef WIN32 */
+#define PROT_READ 0x01
+#define PROT_WRITE 0x02
+#define MAP_PRIVATE 0x0002
+#define MAP_FAILED ((void *)-1)
+#endif /* ifndef WIN32 */
+
+#include "rbtdb.h"
+
+#define RBTDB_MAGIC ISC_MAGIC('R', 'B', 'D', '4')
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+/*
+ * This is the map file header for RBTDB images. It is populated, and then
+ * written, as the LAST thing done to the file. Writing this last (with
+ * zeros in the header area initially) will ensure that the header is only
+ * valid when the RBTDB image is also valid.
+ */
+typedef struct rbtdb_file_header rbtdb_file_header_t;
+
+/* Header length, always the same size regardless of structure size */
+#define RBTDB_HEADER_LENGTH 1024
+
+struct rbtdb_file_header {
+ char version1[32];
+ uint32_t ptrsize;
+ unsigned int bigendian : 1;
+ uint64_t tree;
+ uint64_t nsec;
+ uint64_t nsec3;
+
+ char version2[32]; /* repeated; must match version1 */
+};
+
+/*%
+ * Note that "impmagic" is not the first four bytes of the struct, so
+ * ISC_MAGIC_VALID cannot be used.
+ */
+#define VALID_RBTDB(rbtdb) \
+ ((rbtdb) != NULL && (rbtdb)->common.impmagic == RBTDB_MAGIC)
+
+typedef uint32_t rbtdb_serial_t;
+typedef uint32_t rbtdb_rdatatype_t;
+
+#define RBTDB_RDATATYPE_BASE(type) ((dns_rdatatype_t)((type)&0xFFFF))
+#define RBTDB_RDATATYPE_EXT(type) ((dns_rdatatype_t)((type) >> 16))
+#define RBTDB_RDATATYPE_VALUE(base, ext) \
+ ((rbtdb_rdatatype_t)(((uint32_t)ext) << 16) | \
+ (((uint32_t)base) & 0xffff))
+
+#define RBTDB_RDATATYPE_SIGNSEC \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_nsec)
+#define RBTDB_RDATATYPE_SIGNSEC3 \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_nsec3)
+#define RBTDB_RDATATYPE_SIGNS \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_ns)
+#define RBTDB_RDATATYPE_SIGCNAME \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_cname)
+#define RBTDB_RDATATYPE_SIGDNAME \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_dname)
+#define RBTDB_RDATATYPE_SIGDS \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_ds)
+#define RBTDB_RDATATYPE_SIGSOA \
+ RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_soa)
+#define RBTDB_RDATATYPE_NCACHEANY RBTDB_RDATATYPE_VALUE(0, dns_rdatatype_any)
+
+#define RBTDB_INITLOCK(l) isc_rwlock_init((l), 0, 0)
+#define RBTDB_DESTROYLOCK(l) isc_rwlock_destroy(l)
+#define RBTDB_LOCK(l, t) RWLOCK((l), (t))
+#define RBTDB_UNLOCK(l, t) RWUNLOCK((l), (t))
+
+/*
+ * Since node locking is sensitive to both performance and memory footprint,
+ * we need some trick here. If we have both high-performance rwlock and
+ * high performance and small-memory reference counters, we use rwlock for
+ * node lock and isc_refcount for node references. In this case, we don't have
+ * to protect the access to the counters by locks.
+ * Otherwise, we simply use ordinary mutex lock for node locking, and use
+ * simple integers as reference counters which is protected by the lock.
+ * In most cases, we can simply use wrapper macros such as NODE_LOCK and
+ * NODE_UNLOCK. In some other cases, however, we need to protect reference
+ * counters first and then protect other parts of a node as read-only data.
+ * Special additional macros, NODE_STRONGLOCK(), NODE_WEAKLOCK(), etc, are also
+ * provided for these special cases. When we can use the efficient backend
+ * routines, we should only protect the "other members" by NODE_WEAKLOCK(read).
+ * Otherwise, we should use NODE_STRONGLOCK() to protect the entire critical
+ * section including the access to the reference counter.
+ * Note that we cannot use NODE_LOCK()/NODE_UNLOCK() wherever the protected
+ * section is also protected by NODE_STRONGLOCK().
+ */
+typedef isc_rwlock_t nodelock_t;
+
+#define NODE_INITLOCK(l) isc_rwlock_init((l), 0, 0)
+#define NODE_DESTROYLOCK(l) isc_rwlock_destroy(l)
+#define NODE_LOCK(l, t) RWLOCK((l), (t))
+#define NODE_UNLOCK(l, t) RWUNLOCK((l), (t))
+#define NODE_TRYUPGRADE(l) isc_rwlock_tryupgrade(l)
+#define NODE_DOWNGRADE(l) isc_rwlock_downgrade(l)
+
+/*%
+ * Whether to rate-limit updating the LRU to avoid possible thread contention.
+ * Updating LRU requires write locking, so we don't do it every time the
+ * record is touched - only after some time passes.
+ */
+#ifndef DNS_RBTDB_LIMITLRUUPDATE
+#define DNS_RBTDB_LIMITLRUUPDATE 1
+#endif
+
+/*% Time after which we update LRU for glue records, 5 minutes */
+#define DNS_RBTDB_LRUUPDATE_GLUE 300
+/*% Time after which we update LRU for all other records, 10 minutes */
+#define DNS_RBTDB_LRUUPDATE_REGULAR 600
+
+/*
+ * Allow clients with a virtual time of up to 5 minutes in the past to see
+ * records that would have otherwise have expired.
+ */
+#define RBTDB_VIRTUAL 300
+
+struct noqname {
+ dns_name_t name;
+ void *neg;
+ void *negsig;
+ dns_rdatatype_t type;
+};
+
+typedef struct rdatasetheader {
+ /*%
+ * Locked by the owning node's lock.
+ */
+ rbtdb_serial_t serial;
+ dns_ttl_t rdh_ttl;
+ rbtdb_rdatatype_t type;
+ atomic_uint_least16_t attributes;
+ dns_trust_t trust;
+ atomic_uint_fast32_t last_refresh_fail_ts;
+ struct noqname *noqname;
+ struct noqname *closest;
+ unsigned int is_mmapped : 1;
+ unsigned int next_is_relative : 1;
+ unsigned int node_is_relative : 1;
+ unsigned int resign_lsb : 1;
+ /*%<
+ * We don't use the LIST macros, because the LIST structure has
+ * both head and tail pointers, and is doubly linked.
+ */
+
+ struct rdatasetheader *next;
+ /*%<
+ * If this is the top header for an rdataset, 'next' points
+ * to the top header for the next rdataset (i.e., the next type).
+ * Otherwise, it points up to the header whose down pointer points
+ * at this header.
+ */
+
+ struct rdatasetheader *down;
+ /*%<
+ * Points to the header for the next older version of
+ * this rdataset.
+ */
+
+ atomic_uint_fast32_t count;
+ /*%<
+ * Monotonously increased every time this rdataset is bound so that
+ * it is used as the base of the starting point in DNS responses
+ * when the "cyclic" rrset-order is required.
+ */
+
+ dns_rbtnode_t *node;
+ isc_stdtime_t last_used;
+ ISC_LINK(struct rdatasetheader) link;
+
+ unsigned int heap_index;
+ /*%<
+ * Used for TTL-based cache cleaning.
+ */
+ isc_stdtime_t resign;
+ /*%<
+ * Case vector. If the bit is set then the corresponding
+ * character in the owner name needs to be AND'd with 0x20,
+ * rendering that character upper case.
+ */
+ unsigned char upper[32];
+} rdatasetheader_t;
+
+typedef ISC_LIST(rdatasetheader_t) rdatasetheaderlist_t;
+typedef ISC_LIST(dns_rbtnode_t) rbtnodelist_t;
+
+#define RDATASET_ATTR_NONEXISTENT 0x0001
+/*%< May be potentially served as stale data. */
+#define RDATASET_ATTR_STALE 0x0002
+#define RDATASET_ATTR_IGNORE 0x0004
+#define RDATASET_ATTR_RETAIN 0x0008
+#define RDATASET_ATTR_NXDOMAIN 0x0010
+#define RDATASET_ATTR_RESIGN 0x0020
+#define RDATASET_ATTR_STATCOUNT 0x0040
+#define RDATASET_ATTR_OPTOUT 0x0080
+#define RDATASET_ATTR_NEGATIVE 0x0100
+#define RDATASET_ATTR_PREFETCH 0x0200
+#define RDATASET_ATTR_CASESET 0x0400
+#define RDATASET_ATTR_ZEROTTL 0x0800
+#define RDATASET_ATTR_CASEFULLYLOWER 0x1000
+/*%< Ancient - awaiting cleanup. */
+#define RDATASET_ATTR_ANCIENT 0x2000
+#define RDATASET_ATTR_STALE_WINDOW 0x4000
+
+/*
+ * XXX
+ * When the cache will pre-expire data (due to memory low or other
+ * situations) before the rdataset's TTL has expired, it MUST
+ * respect the RETAIN bit and not expire the data until its TTL is
+ * expired.
+ */
+
+#undef IGNORE /* WIN32 winbase.h defines this. */
+
+#define EXISTS(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_NONEXISTENT) == 0)
+#define NONEXISTENT(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_NONEXISTENT) != 0)
+#define IGNORE(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_IGNORE) != 0)
+#define RETAIN(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_RETAIN) != 0)
+#define NXDOMAIN(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_NXDOMAIN) != 0)
+#define STALE(header) \
+ ((atomic_load_acquire(&(header)->attributes) & RDATASET_ATTR_STALE) != \
+ 0)
+#define STALE_WINDOW(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_STALE_WINDOW) != 0)
+#define RESIGN(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_RESIGN) != 0)
+#define OPTOUT(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_OPTOUT) != 0)
+#define NEGATIVE(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_NEGATIVE) != 0)
+#define PREFETCH(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_PREFETCH) != 0)
+#define CASESET(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_CASESET) != 0)
+#define ZEROTTL(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_ZEROTTL) != 0)
+#define CASEFULLYLOWER(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_CASEFULLYLOWER) != 0)
+#define ANCIENT(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_ANCIENT) != 0)
+#define STATCOUNT(header) \
+ ((atomic_load_acquire(&(header)->attributes) & \
+ RDATASET_ATTR_STATCOUNT) != 0)
+
+#define RDATASET_ATTR_GET(header, attribute) \
+ (atomic_load_acquire(&(header)->attributes) & attribute)
+#define RDATASET_ATTR_SET(header, attribute) \
+ atomic_fetch_or_release(&(header)->attributes, attribute)
+#define RDATASET_ATTR_CLR(header, attribute) \
+ atomic_fetch_and_release(&(header)->attributes, ~(attribute))
+
+#define ACTIVE(header, now) \
+ (((header)->rdh_ttl > (now)) || \
+ ((header)->rdh_ttl == (now) && ZEROTTL(header)))
+
+#define DEFAULT_NODE_LOCK_COUNT 7 /*%< Should be prime. */
+#define RBTDB_GLUE_TABLE_INIT_BITS 2U
+#define RBTDB_GLUE_TABLE_MAX_BITS 32U
+#define RBTDB_GLUE_TABLE_OVERCOMMIT 3
+
+#define GOLDEN_RATIO_32 0x61C88647
+#define HASHSIZE(bits) (UINT64_C(1) << (bits))
+
+static uint32_t
+hash_32(uint32_t val, unsigned int bits) {
+ REQUIRE(bits <= RBTDB_GLUE_TABLE_MAX_BITS);
+ /* High bits are more random. */
+ return (val * GOLDEN_RATIO_32 >> (32 - bits));
+}
+
+#define EXPIREDOK(rbtiterator) \
+ (((rbtiterator)->common.options & DNS_DB_EXPIREDOK) != 0)
+
+#define STALEOK(rbtiterator) \
+ (((rbtiterator)->common.options & DNS_DB_STALEOK) != 0)
+
+/*%
+ * Number of buckets for cache DB entries (locks, LRU lists, TTL heaps).
+ * There is a tradeoff issue about configuring this value: if this is too
+ * small, it may cause heavier contention between threads; if this is too large,
+ * LRU purge algorithm won't work well (entries tend to be purged prematurely).
+ * The default value should work well for most environments, but this can
+ * also be configurable at compilation time via the
+ * DNS_RBTDB_CACHE_NODE_LOCK_COUNT variable. This value must be larger than
+ * 1 due to the assumption of overmem_purge().
+ */
+#ifdef DNS_RBTDB_CACHE_NODE_LOCK_COUNT
+#if DNS_RBTDB_CACHE_NODE_LOCK_COUNT <= 1
+#error "DNS_RBTDB_CACHE_NODE_LOCK_COUNT must be larger than 1"
+#else /* if DNS_RBTDB_CACHE_NODE_LOCK_COUNT <= 1 */
+#define DEFAULT_CACHE_NODE_LOCK_COUNT DNS_RBTDB_CACHE_NODE_LOCK_COUNT
+#endif /* if DNS_RBTDB_CACHE_NODE_LOCK_COUNT <= 1 */
+#else /* ifdef DNS_RBTDB_CACHE_NODE_LOCK_COUNT */
+#define DEFAULT_CACHE_NODE_LOCK_COUNT 17
+#endif /* DNS_RBTDB_CACHE_NODE_LOCK_COUNT */
+
+typedef struct {
+ nodelock_t lock;
+ /* Protected in the refcount routines. */
+ isc_refcount_t references;
+ /* Locked by lock. */
+ bool exiting;
+} rbtdb_nodelock_t;
+
+typedef struct rbtdb_changed {
+ dns_rbtnode_t *node;
+ bool dirty;
+ ISC_LINK(struct rbtdb_changed) link;
+} rbtdb_changed_t;
+
+typedef ISC_LIST(rbtdb_changed_t) rbtdb_changedlist_t;
+
+typedef enum { dns_db_insecure, dns_db_partial, dns_db_secure } dns_db_secure_t;
+
+typedef struct dns_rbtdb dns_rbtdb_t;
+
+/* Reason for expiring a record from cache */
+typedef enum { expire_lru, expire_ttl, expire_flush } expire_t;
+
+typedef struct rbtdb_glue rbtdb_glue_t;
+
+typedef struct rbtdb_glue_table_node {
+ struct rbtdb_glue_table_node *next;
+ dns_rbtnode_t *node;
+ rbtdb_glue_t *glue_list;
+} rbtdb_glue_table_node_t;
+
+typedef enum {
+ rdataset_ttl_fresh,
+ rdataset_ttl_stale,
+ rdataset_ttl_ancient
+} rdataset_ttl_t;
+
+typedef struct rbtdb_version {
+ /* Not locked */
+ rbtdb_serial_t serial;
+ dns_rbtdb_t *rbtdb;
+ /*
+ * Protected in the refcount routines.
+ * XXXJT: should we change the lock policy based on the refcount
+ * performance?
+ */
+ isc_refcount_t references;
+ /* Locked by database lock. */
+ bool writer;
+ bool commit_ok;
+ rbtdb_changedlist_t changed_list;
+ rdatasetheaderlist_t resigned_list;
+ ISC_LINK(struct rbtdb_version) link;
+ dns_db_secure_t secure;
+ bool havensec3;
+ /* NSEC3 parameters */
+ dns_hash_t hash;
+ uint8_t flags;
+ uint16_t iterations;
+ uint8_t salt_length;
+ unsigned char salt[DNS_NSEC3_SALTSIZE];
+
+ /*
+ * records and xfrsize are covered by rwlock.
+ */
+ isc_rwlock_t rwlock;
+ uint64_t records;
+ uint64_t xfrsize;
+
+ isc_rwlock_t glue_rwlock;
+ size_t glue_table_bits;
+ size_t glue_table_nodecount;
+ rbtdb_glue_table_node_t **glue_table;
+} rbtdb_version_t;
+
+typedef ISC_LIST(rbtdb_version_t) rbtdb_versionlist_t;
+
+struct dns_rbtdb {
+ /* Unlocked. */
+ dns_db_t common;
+ /* Locks the data in this struct */
+ isc_rwlock_t lock;
+ /* Locks the tree structure (prevents nodes appearing/disappearing) */
+ isc_rwlock_t tree_lock;
+ /* Locks for individual tree nodes */
+ unsigned int node_lock_count;
+ rbtdb_nodelock_t *node_locks;
+ dns_rbtnode_t *origin_node;
+ dns_rbtnode_t *nsec3_origin_node;
+ dns_stats_t *rrsetstats; /* cache DB only */
+ isc_stats_t *cachestats; /* cache DB only */
+ isc_stats_t *gluecachestats; /* zone DB only */
+ /* Locked by lock. */
+ unsigned int active;
+ isc_refcount_t references;
+ unsigned int attributes;
+ rbtdb_serial_t current_serial;
+ rbtdb_serial_t least_serial;
+ rbtdb_serial_t next_serial;
+ rbtdb_version_t *current_version;
+ rbtdb_version_t *future_version;
+ rbtdb_versionlist_t open_versions;
+ isc_task_t *task;
+ dns_dbnode_t *soanode;
+ dns_dbnode_t *nsnode;
+
+ /*
+ * Maximum length of time to keep using a stale answer past its
+ * normal TTL expiry.
+ */
+ dns_ttl_t serve_stale_ttl;
+
+ /*
+ * The time after a failed lookup, where stale answers from cache
+ * may be used directly in a DNS response without attempting a
+ * new iterative lookup.
+ */
+ uint32_t serve_stale_refresh;
+
+ /*
+ * This is a linked list used to implement the LRU cache. There will
+ * be node_lock_count linked lists here. Nodes in bucket 1 will be
+ * placed on the linked list rdatasets[1].
+ */
+ rdatasetheaderlist_t *rdatasets;
+
+ /*%
+ * Temporary storage for stale cache nodes and dynamically deleted
+ * nodes that await being cleaned up.
+ */
+ rbtnodelist_t *deadnodes;
+
+ /*
+ * Heaps. These are used for TTL based expiry in a cache,
+ * or for zone resigning in a zone DB. hmctx is the memory
+ * context to use for the heap (which differs from the main
+ * database memory context in the case of a cache).
+ */
+ isc_mem_t *hmctx;
+ isc_heap_t **heaps;
+
+ /*
+ * Base values for the mmap() code.
+ */
+ void *mmap_location;
+ size_t mmap_size;
+
+ /* Locked by tree_lock. */
+ dns_rbt_t *tree;
+ dns_rbt_t *nsec;
+ dns_rbt_t *nsec3;
+
+ /* Unlocked */
+ unsigned int quantum;
+};
+
+#define RBTDB_ATTR_LOADED 0x01
+#define RBTDB_ATTR_LOADING 0x02
+
+#define KEEPSTALE(rbtdb) ((rbtdb)->serve_stale_ttl > 0)
+
+/*%
+ * Search Context
+ */
+typedef struct {
+ dns_rbtdb_t *rbtdb;
+ rbtdb_version_t *rbtversion;
+ rbtdb_serial_t serial;
+ unsigned int options;
+ dns_rbtnodechain_t chain;
+ bool copy_name;
+ bool need_cleanup;
+ bool wild;
+ dns_rbtnode_t *zonecut;
+ rdatasetheader_t *zonecut_rdataset;
+ rdatasetheader_t *zonecut_sigrdataset;
+ dns_fixedname_t zonecut_name;
+ isc_stdtime_t now;
+} rbtdb_search_t;
+
+/*%
+ * Load Context
+ */
+typedef struct {
+ dns_rbtdb_t *rbtdb;
+ isc_stdtime_t now;
+} rbtdb_load_t;
+
+static void
+delete_callback(void *data, void *arg);
+static void
+rdataset_disassociate(dns_rdataset_t *rdataset);
+static isc_result_t
+rdataset_first(dns_rdataset_t *rdataset);
+static isc_result_t
+rdataset_next(dns_rdataset_t *rdataset);
+static void
+rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata);
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target);
+static unsigned int
+rdataset_count(dns_rdataset_t *rdataset);
+static isc_result_t
+rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig);
+static isc_result_t
+rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig);
+static bool
+need_headerupdate(rdatasetheader_t *header, isc_stdtime_t now);
+static void
+update_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, isc_stdtime_t now);
+static void
+expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked,
+ expire_t reason);
+static void
+overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize,
+ bool tree_locked);
+static void
+resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader);
+static void
+resign_delete(dns_rbtdb_t *rbtdb, rbtdb_version_t *version,
+ rdatasetheader_t *header);
+static void
+prune_tree(isc_task_t *task, isc_event_t *event);
+static void
+rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust);
+static void
+rdataset_expire(dns_rdataset_t *rdataset);
+static void
+rdataset_clearprefetch(dns_rdataset_t *rdataset);
+static void
+rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name);
+static void
+rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name);
+static isc_result_t
+rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version,
+ dns_message_t *msg);
+static void
+free_gluetable(rbtdb_version_t *version);
+static isc_result_t
+nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name);
+
+static dns_rdatasetmethods_t rdataset_methods = { rdataset_disassociate,
+ rdataset_first,
+ rdataset_next,
+ rdataset_current,
+ rdataset_clone,
+ rdataset_count,
+ NULL, /* addnoqname */
+ rdataset_getnoqname,
+ NULL, /* addclosest */
+ rdataset_getclosest,
+ rdataset_settrust,
+ rdataset_expire,
+ rdataset_clearprefetch,
+ rdataset_setownercase,
+ rdataset_getownercase,
+ rdataset_addglue };
+
+static dns_rdatasetmethods_t slab_methods = {
+ rdataset_disassociate,
+ rdataset_first,
+ rdataset_next,
+ rdataset_current,
+ rdataset_clone,
+ rdataset_count,
+ NULL, /* addnoqname */
+ NULL, /* getnoqname */
+ NULL, /* addclosest */
+ NULL, /* getclosest */
+ NULL, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL, /* setownercase */
+ NULL, /* getownercase */
+ NULL /* addglue */
+};
+
+static void
+rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp);
+static isc_result_t
+rdatasetiter_first(dns_rdatasetiter_t *iterator);
+static isc_result_t
+rdatasetiter_next(dns_rdatasetiter_t *iterator);
+static void
+rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset);
+
+static dns_rdatasetitermethods_t rdatasetiter_methods = {
+ rdatasetiter_destroy, rdatasetiter_first, rdatasetiter_next,
+ rdatasetiter_current
+};
+
+typedef struct rbtdb_rdatasetiter {
+ dns_rdatasetiter_t common;
+ rdatasetheader_t *current;
+} rbtdb_rdatasetiter_t;
+
+/*
+ * Note that these iterators, unless created with either DNS_DB_NSEC3ONLY or
+ * DNS_DB_NONSEC3, will transparently move between the last node of the
+ * "regular" RBT ("chain" field) and the root node of the NSEC3 RBT
+ * ("nsec3chain" field) of the database in question, as if the latter was a
+ * successor to the former in lexical order. The "current" field always holds
+ * the address of either "chain" or "nsec3chain", depending on which RBT is
+ * being traversed at given time.
+ */
+static void
+dbiterator_destroy(dns_dbiterator_t **iteratorp);
+static isc_result_t
+dbiterator_first(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_last(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name);
+static isc_result_t
+dbiterator_prev(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_next(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name);
+static isc_result_t
+dbiterator_pause(dns_dbiterator_t *iterator);
+static isc_result_t
+dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name);
+
+static dns_dbiteratormethods_t dbiterator_methods = {
+ dbiterator_destroy, dbiterator_first, dbiterator_last,
+ dbiterator_seek, dbiterator_prev, dbiterator_next,
+ dbiterator_current, dbiterator_pause, dbiterator_origin
+};
+
+#define DELETION_BATCH_MAX 64
+
+/*
+ * If 'paused' is true, then the tree lock is not being held.
+ */
+typedef struct rbtdb_dbiterator {
+ dns_dbiterator_t common;
+ bool paused;
+ bool new_origin;
+ isc_rwlocktype_t tree_locked;
+ isc_result_t result;
+ dns_fixedname_t name;
+ dns_fixedname_t origin;
+ dns_rbtnodechain_t chain;
+ dns_rbtnodechain_t nsec3chain;
+ dns_rbtnodechain_t *current;
+ dns_rbtnode_t *node;
+ dns_rbtnode_t *deletions[DELETION_BATCH_MAX];
+ int delcnt;
+ bool nsec3only;
+ bool nonsec3;
+} rbtdb_dbiterator_t;
+
+#define IS_STUB(rbtdb) (((rbtdb)->common.attributes & DNS_DBATTR_STUB) != 0)
+#define IS_CACHE(rbtdb) (((rbtdb)->common.attributes & DNS_DBATTR_CACHE) != 0)
+
+static void
+free_rbtdb(dns_rbtdb_t *rbtdb, bool log, isc_event_t *event);
+static void
+overmem(dns_db_t *db, bool over);
+static void
+setnsec3parameters(dns_db_t *db, rbtdb_version_t *version);
+static void
+setownercase(rdatasetheader_t *header, const dns_name_t *name);
+
+static bool
+match_header_version(rbtdb_file_header_t *header);
+
+/* Pad to 32 bytes */
+static char FILE_VERSION[32] = "\0";
+
+/*%
+ * 'init_count' is used to initialize 'newheader->count' which inturn
+ * is used to determine where in the cycle rrset-order cyclic starts.
+ * We don't lock this as we don't care about simultaneous updates.
+ *
+ * Note:
+ * Both init_count and header->count can be UINT32_MAX.
+ * The count on the returned rdataset however can't be as
+ * that indicates that the database does not implement cyclic
+ * processing.
+ */
+static atomic_uint_fast32_t init_count = 0;
+
+/*
+ * Locking
+ *
+ * If a routine is going to lock more than one lock in this module, then
+ * the locking must be done in the following order:
+ *
+ * Tree Lock
+ *
+ * Node Lock (Only one from the set may be locked at one time by
+ * any caller)
+ *
+ * Database Lock
+ *
+ * Failure to follow this hierarchy can result in deadlock.
+ */
+
+/*
+ * Deleting Nodes
+ *
+ * For zone databases the node for the origin of the zone MUST NOT be deleted.
+ */
+
+/*
+ * Debugging routines
+ */
+#ifdef DEBUG
+static void
+hexdump(const char *desc, unsigned char *data, size_t size) {
+ char hexdump[BUFSIZ * 2 + 1];
+ isc_buffer_t b;
+ isc_region_t r;
+ isc_result_t result;
+ size_t bytes;
+
+ fprintf(stderr, "%s: ", desc);
+ do {
+ isc_buffer_init(&b, hexdump, sizeof(hexdump));
+ r.base = data;
+ r.length = bytes = (size > BUFSIZ) ? BUFSIZ : size;
+ result = isc_hex_totext(&r, 0, "", &b);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_buffer_putuint8(&b, 0);
+ fprintf(stderr, "%s", hexdump);
+ data += bytes;
+ size -= bytes;
+ } while (size > 0);
+ fprintf(stderr, "\n");
+}
+#endif /* ifdef DEBUG */
+
+/* Fixed RRSet helper macros */
+
+#define DNS_RDATASET_LENGTH 2;
+
+#if DNS_RDATASET_FIXED
+#define DNS_RDATASET_ORDER 2
+#define DNS_RDATASET_COUNT (count * 4)
+#else /* !DNS_RDATASET_FIXED */
+#define DNS_RDATASET_ORDER 0
+#define DNS_RDATASET_COUNT 0
+#endif /* DNS_RDATASET_FIXED */
+
+/*
+ * DB Routines
+ */
+
+static void
+attach(dns_db_t *source, dns_db_t **targetp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)source;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ isc_refcount_increment(&rbtdb->references);
+
+ *targetp = source;
+}
+
+static void
+free_rbtdb_callback(isc_task_t *task, isc_event_t *event) {
+ dns_rbtdb_t *rbtdb = event->ev_arg;
+
+ UNUSED(task);
+
+ free_rbtdb(rbtdb, true, event);
+}
+
+static void
+update_cachestats(dns_rbtdb_t *rbtdb, isc_result_t result) {
+ INSIST(IS_CACHE(rbtdb));
+
+ if (rbtdb->cachestats == NULL) {
+ return;
+ }
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ case DNS_R_DELEGATION:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ isc_stats_increment(rbtdb->cachestats,
+ dns_cachestatscounter_hits);
+ break;
+ default:
+ isc_stats_increment(rbtdb->cachestats,
+ dns_cachestatscounter_misses);
+ }
+}
+
+static bool
+do_stats(rdatasetheader_t *header) {
+ return (EXISTS(header) && STATCOUNT(header));
+}
+
+static void
+update_rrsetstats(dns_rbtdb_t *rbtdb, const rbtdb_rdatatype_t htype,
+ const uint_least16_t hattributes, const bool increment) {
+ dns_rdatastatstype_t statattributes = 0;
+ dns_rdatastatstype_t base = 0;
+ dns_rdatastatstype_t type;
+ rdatasetheader_t *header = &(rdatasetheader_t){
+ .type = htype,
+ .attributes = hattributes,
+ };
+
+ if (!do_stats(header)) {
+ return;
+ }
+
+ /* At the moment we count statistics only for cache DB */
+ INSIST(IS_CACHE(rbtdb));
+
+ if (NEGATIVE(header)) {
+ if (NXDOMAIN(header)) {
+ statattributes = DNS_RDATASTATSTYPE_ATTR_NXDOMAIN;
+ } else {
+ statattributes = DNS_RDATASTATSTYPE_ATTR_NXRRSET;
+ base = RBTDB_RDATATYPE_EXT(header->type);
+ }
+ } else {
+ base = RBTDB_RDATATYPE_BASE(header->type);
+ }
+
+ if (STALE(header)) {
+ statattributes |= DNS_RDATASTATSTYPE_ATTR_STALE;
+ }
+ if (ANCIENT(header)) {
+ statattributes |= DNS_RDATASTATSTYPE_ATTR_ANCIENT;
+ }
+
+ type = DNS_RDATASTATSTYPE_VALUE(base, statattributes);
+ if (increment) {
+ dns_rdatasetstats_increment(rbtdb->rrsetstats, type);
+ } else {
+ dns_rdatasetstats_decrement(rbtdb->rrsetstats, type);
+ }
+}
+
+static void
+set_ttl(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, dns_ttl_t newttl) {
+ int idx;
+ isc_heap_t *heap;
+ dns_ttl_t oldttl;
+
+ if (!IS_CACHE(rbtdb)) {
+ header->rdh_ttl = newttl;
+ return;
+ }
+
+ oldttl = header->rdh_ttl;
+ header->rdh_ttl = newttl;
+
+ /*
+ * It's possible the rbtdb is not a cache. If this is the case,
+ * we will not have a heap, and we move on. If we do, though,
+ * we might need to adjust things.
+ */
+ if (header->heap_index == 0 || newttl == oldttl) {
+ return;
+ }
+ idx = header->node->locknum;
+ if (rbtdb->heaps == NULL || rbtdb->heaps[idx] == NULL) {
+ return;
+ }
+ heap = rbtdb->heaps[idx];
+
+ if (newttl < oldttl) {
+ isc_heap_increased(heap, header->heap_index);
+ } else {
+ isc_heap_decreased(heap, header->heap_index);
+ }
+}
+
+/*%
+ * These functions allow the heap code to rank the priority of each
+ * element. It returns true if v1 happens "sooner" than v2.
+ */
+static bool
+ttl_sooner(void *v1, void *v2) {
+ rdatasetheader_t *h1 = v1;
+ rdatasetheader_t *h2 = v2;
+
+ return (h1->rdh_ttl < h2->rdh_ttl);
+}
+
+/*%
+ * Return which RRset should be resigned sooner. If the RRsets have the
+ * same signing time, prefer the other RRset over the SOA RRset.
+ */
+static bool
+resign_sooner(void *v1, void *v2) {
+ rdatasetheader_t *h1 = v1;
+ rdatasetheader_t *h2 = v2;
+
+ return (h1->resign < h2->resign ||
+ (h1->resign == h2->resign && h1->resign_lsb < h2->resign_lsb) ||
+ (h1->resign == h2->resign && h1->resign_lsb == h2->resign_lsb &&
+ h2->type == RBTDB_RDATATYPE_SIGSOA));
+}
+
+/*%
+ * This function sets the heap index into the header.
+ */
+static void
+set_index(void *what, unsigned int idx) {
+ rdatasetheader_t *h = what;
+
+ h->heap_index = idx;
+}
+
+/*%
+ * Work out how many nodes can be deleted in the time between two
+ * requests to the nameserver. Smooth the resulting number and use it
+ * as a estimate for the number of nodes to be deleted in the next
+ * iteration.
+ */
+static unsigned int
+adjust_quantum(unsigned int old, isc_time_t *start) {
+ unsigned int pps = dns_pps; /* packets per second */
+ unsigned int interval;
+ uint64_t usecs;
+ isc_time_t end;
+ unsigned int nodes;
+
+ if (pps < 100) {
+ pps = 100;
+ }
+ isc_time_now(&end);
+
+ interval = 1000000 / pps; /* interval in usec */
+ if (interval == 0) {
+ interval = 1;
+ }
+ usecs = isc_time_microdiff(&end, start);
+ if (usecs == 0) {
+ /*
+ * We were unable to measure the amount of time taken.
+ * Double the nodes deleted next time.
+ */
+ old *= 2;
+ if (old > 1000) {
+ old = 1000;
+ }
+ return (old);
+ }
+ nodes = old * interval;
+ nodes /= (unsigned int)usecs;
+ if (nodes == 0) {
+ nodes = 1;
+ } else if (nodes > 1000) {
+ nodes = 1000;
+ }
+
+ /* Smooth */
+ nodes = (nodes + old * 3) / 4;
+
+ if (nodes != old) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1),
+ "adjust_quantum: old=%d, new=%d", old, nodes);
+ }
+
+ return (nodes);
+}
+
+static void
+free_rbtdb(dns_rbtdb_t *rbtdb, bool log, isc_event_t *event) {
+ unsigned int i;
+ isc_result_t result;
+ char buf[DNS_NAME_FORMATSIZE];
+ dns_rbt_t **treep;
+ isc_time_t start;
+
+ if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) {
+ overmem((dns_db_t *)rbtdb, (bool)-1);
+ }
+
+ REQUIRE(rbtdb->current_version != NULL || EMPTY(rbtdb->open_versions));
+ REQUIRE(rbtdb->future_version == NULL);
+
+ if (rbtdb->current_version != NULL) {
+ isc_refcount_decrementz(&rbtdb->current_version->references);
+ UNLINK(rbtdb->open_versions, rbtdb->current_version, link);
+ isc_rwlock_destroy(&rbtdb->current_version->glue_rwlock);
+ isc_refcount_destroy(&rbtdb->current_version->references);
+ isc_rwlock_destroy(&rbtdb->current_version->rwlock);
+ isc_mem_put(rbtdb->common.mctx, rbtdb->current_version,
+ sizeof(rbtdb_version_t));
+ }
+
+ /*
+ * We assume the number of remaining dead nodes is reasonably small;
+ * the overhead of unlinking all nodes here should be negligible.
+ */
+ for (i = 0; i < rbtdb->node_lock_count; i++) {
+ dns_rbtnode_t *node;
+
+ node = ISC_LIST_HEAD(rbtdb->deadnodes[i]);
+ while (node != NULL) {
+ ISC_LIST_UNLINK(rbtdb->deadnodes[i], node, deadlink);
+ node = ISC_LIST_HEAD(rbtdb->deadnodes[i]);
+ }
+ }
+
+ if (event == NULL) {
+ rbtdb->quantum = (rbtdb->task != NULL) ? 100 : 0;
+ }
+
+ for (;;) {
+ /*
+ * pick the next tree to (start to) destroy
+ */
+ treep = &rbtdb->tree;
+ if (*treep == NULL) {
+ treep = &rbtdb->nsec;
+ if (*treep == NULL) {
+ treep = &rbtdb->nsec3;
+ /*
+ * we're finished after clear cutting
+ */
+ if (*treep == NULL) {
+ break;
+ }
+ }
+ }
+
+ isc_time_now(&start);
+ result = dns_rbt_destroy2(treep, rbtdb->quantum);
+ if (result == ISC_R_QUOTA) {
+ INSIST(rbtdb->task != NULL);
+ if (rbtdb->quantum != 0) {
+ rbtdb->quantum = adjust_quantum(rbtdb->quantum,
+ &start);
+ }
+ if (event == NULL) {
+ event = isc_event_allocate(
+ rbtdb->common.mctx, NULL,
+ DNS_EVENT_FREESTORAGE,
+ free_rbtdb_callback, rbtdb,
+ sizeof(isc_event_t));
+ }
+ isc_task_send(rbtdb->task, &event);
+ return;
+ }
+ INSIST(result == ISC_R_SUCCESS && *treep == NULL);
+ }
+
+ if (event != NULL) {
+ isc_event_free(&event);
+ }
+ if (log) {
+ if (dns_name_dynamic(&rbtdb->common.origin)) {
+ dns_name_format(&rbtdb->common.origin, buf,
+ sizeof(buf));
+ } else {
+ strlcpy(buf, "<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);
+
+ if (rbtdb->mmap_location != NULL) {
+ isc_file_munmap(rbtdb->mmap_location, (size_t)rbtdb->mmap_size);
+ }
+
+ INSIST(ISC_LIST_EMPTY(rbtdb->common.update_listeners));
+
+ isc_mem_putanddetach(&rbtdb->common.mctx, rbtdb, sizeof(*rbtdb));
+}
+
+static void
+maybe_free_rbtdb(dns_rbtdb_t *rbtdb) {
+ bool want_free = false;
+ unsigned int i;
+ unsigned int inactive = 0;
+
+ /* XXX check for open versions here */
+
+ if (rbtdb->soanode != NULL) {
+ dns_db_detachnode((dns_db_t *)rbtdb, &rbtdb->soanode);
+ }
+ if (rbtdb->nsnode != NULL) {
+ dns_db_detachnode((dns_db_t *)rbtdb, &rbtdb->nsnode);
+ }
+
+ /*
+ * The current version's glue table needs to be freed early
+ * so the nodes are dereferenced before we check the active
+ * node count below.
+ */
+ if (rbtdb->current_version != NULL) {
+ free_gluetable(rbtdb->current_version);
+ }
+
+ /*
+ * Even though there are no external direct references, there still
+ * may be nodes in use.
+ */
+ for (i = 0; i < rbtdb->node_lock_count; i++) {
+ NODE_LOCK(&rbtdb->node_locks[i].lock, isc_rwlocktype_write);
+ rbtdb->node_locks[i].exiting = true;
+ if (isc_refcount_current(&rbtdb->node_locks[i].references) == 0)
+ {
+ inactive++;
+ }
+ NODE_UNLOCK(&rbtdb->node_locks[i].lock, isc_rwlocktype_write);
+ }
+
+ if (inactive != 0) {
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+ rbtdb->active -= inactive;
+ if (rbtdb->active == 0) {
+ want_free = true;
+ }
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+ if (want_free) {
+ char buf[DNS_NAME_FORMATSIZE];
+ if (dns_name_dynamic(&rbtdb->common.origin)) {
+ dns_name_format(&rbtdb->common.origin, buf,
+ sizeof(buf));
+ } else {
+ strlcpy(buf, "<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;
+ h->is_mmapped = 0;
+ h->next_is_relative = 0;
+ h->node_is_relative = 0;
+ atomic_init(&h->attributes, 0);
+ atomic_init(&h->last_refresh_fail_ts, 0);
+
+ STATIC_ASSERT((sizeof(h->attributes) == 2),
+ "The .attributes field of rdatasetheader_t needs to be "
+ "16-bit int type exactly.");
+
+#if TRACE_HEADER
+ if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) {
+ fprintf(stderr, "initialized header: %p\n", h);
+ }
+#else /* if TRACE_HEADER */
+ UNUSED(rbtdb);
+#endif /* if TRACE_HEADER */
+}
+
+/*
+ * Update the copied values of 'next' and 'node' if they are relative.
+ */
+static void
+update_newheader(rdatasetheader_t *newh, rdatasetheader_t *old) {
+ char *p;
+
+ if (old->next_is_relative) {
+ p = (char *)old;
+ p += (uintptr_t)old->next;
+ newh->next = (rdatasetheader_t *)p;
+ }
+ if (old->node_is_relative) {
+ p = (char *)old;
+ p += (uintptr_t)old->node;
+ newh->node = (dns_rbtnode_t *)p;
+ }
+ if (CASESET(old)) {
+ uint_least16_t attr = RDATASET_ATTR_GET(
+ old,
+ (RDATASET_ATTR_CASESET | RDATASET_ATTR_CASEFULLYLOWER));
+ RDATASET_ATTR_SET(newh, attr);
+ memmove(newh->upper, old->upper, sizeof(old->upper));
+ }
+}
+
+static rdatasetheader_t *
+new_rdataset(dns_rbtdb_t *rbtdb, isc_mem_t *mctx) {
+ rdatasetheader_t *h;
+
+ h = isc_mem_get(mctx, sizeof(*h));
+
+#if TRACE_HEADER
+ if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) {
+ fprintf(stderr, "allocated header: %p\n", h);
+ }
+#endif /* if TRACE_HEADER */
+ memset(h->upper, 0xeb, sizeof(h->upper));
+ init_rdataset(rbtdb, h);
+ h->rdh_ttl = 0;
+ return (h);
+}
+
+static void
+free_rdataset(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *rdataset) {
+ unsigned int size;
+ int idx;
+
+ update_rrsetstats(rbtdb, rdataset->type,
+ atomic_load_acquire(&rdataset->attributes), false);
+
+ idx = rdataset->node->locknum;
+ if (ISC_LINK_LINKED(rdataset, link)) {
+ INSIST(IS_CACHE(rbtdb));
+ ISC_LIST_UNLINK(rbtdb->rdatasets[idx], rdataset, link);
+ }
+
+ if (rdataset->heap_index != 0) {
+ isc_heap_delete(rbtdb->heaps[idx], rdataset->heap_index);
+ }
+ rdataset->heap_index = 0;
+
+ if (rdataset->noqname != NULL) {
+ free_noqname(mctx, &rdataset->noqname);
+ }
+ if (rdataset->closest != NULL) {
+ free_noqname(mctx, &rdataset->closest);
+ }
+
+ if (NONEXISTENT(rdataset)) {
+ size = sizeof(*rdataset);
+ } else {
+ size = dns_rdataslab_size((unsigned char *)rdataset,
+ sizeof(*rdataset));
+ }
+
+ if (rdataset->is_mmapped == 1) {
+ return;
+ }
+
+ isc_mem_put(mctx, rdataset, size);
+}
+
+static void
+rollback_node(dns_rbtnode_t *node, rbtdb_serial_t serial) {
+ rdatasetheader_t *header, *dcurrent;
+ bool make_dirty = false;
+
+ /*
+ * Caller must hold the node lock.
+ */
+
+ /*
+ * We set the IGNORE attribute on rdatasets with serial number
+ * 'serial'. When the reference count goes to zero, these rdatasets
+ * will be cleaned up; until that time, they will be ignored.
+ */
+ for (header = node->data; header != NULL; header = header->next) {
+ if (header->serial == serial) {
+ RDATASET_ATTR_SET(header, RDATASET_ATTR_IGNORE);
+ make_dirty = true;
+ }
+ for (dcurrent = header->down; dcurrent != NULL;
+ dcurrent = dcurrent->down)
+ {
+ if (dcurrent->serial == serial) {
+ RDATASET_ATTR_SET(dcurrent,
+ RDATASET_ATTR_IGNORE);
+ make_dirty = true;
+ }
+ }
+ }
+ if (make_dirty) {
+ node->dirty = 1;
+ }
+}
+
+static void
+mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
+ uint_least16_t attributes = atomic_load_acquire(&header->attributes);
+ uint_least16_t newattributes = 0;
+
+ /*
+ * If we are already ancient there is nothing to do.
+ */
+ do {
+ if ((attributes & RDATASET_ATTR_ANCIENT) != 0) {
+ return;
+ }
+ newattributes = attributes | RDATASET_ATTR_ANCIENT;
+ } while (!atomic_compare_exchange_weak_acq_rel(
+ &header->attributes, &attributes, newattributes));
+
+ /*
+ * Decrement the stats counter for the appropriate RRtype.
+ * If the STALE attribute is set, this will decrement the
+ * stale type counter, otherwise it decrements the active
+ * stats type counter.
+ */
+ update_rrsetstats(rbtdb, header->type, attributes, false);
+ header->node->dirty = 1;
+
+ /* Increment the stats counter for the ancient RRtype. */
+ update_rrsetstats(rbtdb, header->type, newattributes, true);
+}
+
+static void
+mark_header_stale(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
+ uint_least16_t attributes = atomic_load_acquire(&header->attributes);
+ uint_least16_t newattributes = 0;
+
+ INSIST((attributes & RDATASET_ATTR_ZEROTTL) == 0);
+
+ /*
+ * If we are already stale there is nothing to do.
+ */
+ do {
+ if ((attributes & RDATASET_ATTR_STALE) != 0) {
+ return;
+ }
+ newattributes = attributes | RDATASET_ATTR_STALE;
+ } while (!atomic_compare_exchange_weak_acq_rel(
+ &header->attributes, &attributes, newattributes));
+
+ /* Decrement the stats counter for the appropriate RRtype.
+ * If the ANCIENT attribute is set (although it is very
+ * unlikely that an RRset goes from ANCIENT to STALE), this
+ * will decrement the ancient stale type counter, otherwise it
+ * decrements the active stats type counter.
+ */
+
+ update_rrsetstats(rbtdb, header->type, attributes, false);
+ update_rrsetstats(rbtdb, header->type, newattributes, true);
+}
+
+static void
+clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx,
+ rdatasetheader_t *top) {
+ rdatasetheader_t *d, *down_next;
+
+ for (d = top->down; d != NULL; d = down_next) {
+ down_next = d->down;
+ free_rdataset(rbtdb, mctx, d);
+ }
+ top->down = NULL;
+}
+
+static void
+clean_cache_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
+ rdatasetheader_t *current, *top_prev, *top_next;
+ isc_mem_t *mctx = rbtdb->common.mctx;
+
+ /*
+ * Caller must be holding the node lock.
+ */
+
+ top_prev = NULL;
+ for (current = node->data; current != NULL; current = top_next) {
+ top_next = current->next;
+ clean_stale_headers(rbtdb, mctx, current);
+ /*
+ * If current is nonexistent, ancient, or stale and
+ * we are not keeping stale, we can clean it up.
+ */
+ if (NONEXISTENT(current) || ANCIENT(current) ||
+ (STALE(current) && !KEEPSTALE(rbtdb)))
+ {
+ if (top_prev != NULL) {
+ top_prev->next = current->next;
+ } else {
+ node->data = current->next;
+ }
+ free_rdataset(rbtdb, mctx, current);
+ } else {
+ top_prev = current;
+ }
+ }
+ node->dirty = 0;
+}
+
+static void
+clean_zone_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ rbtdb_serial_t least_serial) {
+ rdatasetheader_t *current, *dcurrent, *down_next, *dparent;
+ rdatasetheader_t *top_prev, *top_next;
+ isc_mem_t *mctx = rbtdb->common.mctx;
+ bool still_dirty = false;
+
+ /*
+ * Caller must be holding the node lock.
+ */
+ REQUIRE(least_serial != 0);
+
+ top_prev = NULL;
+ for (current = node->data; current != NULL; current = top_next) {
+ top_next = current->next;
+
+ /*
+ * First, we clean up any instances of multiple rdatasets
+ * with the same serial number, or that have the IGNORE
+ * attribute.
+ */
+ dparent = current;
+ for (dcurrent = current->down; dcurrent != NULL;
+ dcurrent = down_next)
+ {
+ down_next = dcurrent->down;
+ INSIST(dcurrent->serial <= dparent->serial);
+ if (dcurrent->serial == dparent->serial ||
+ IGNORE(dcurrent))
+ {
+ if (down_next != NULL) {
+ down_next->next = dparent;
+ }
+ dparent->down = down_next;
+ free_rdataset(rbtdb, mctx, dcurrent);
+ } else {
+ dparent = dcurrent;
+ }
+ }
+
+ /*
+ * We've now eliminated all IGNORE datasets with the possible
+ * exception of current, which we now check.
+ */
+ if (IGNORE(current)) {
+ down_next = current->down;
+ if (down_next == NULL) {
+ if (top_prev != NULL) {
+ top_prev->next = current->next;
+ } else {
+ node->data = current->next;
+ }
+ free_rdataset(rbtdb, mctx, current);
+ /*
+ * current no longer exists, so we can
+ * just continue with the loop.
+ */
+ continue;
+ } else {
+ /*
+ * Pull up current->down, making it the new
+ * current.
+ */
+ if (top_prev != NULL) {
+ top_prev->next = down_next;
+ } else {
+ node->data = down_next;
+ }
+ down_next->next = top_next;
+ free_rdataset(rbtdb, mctx, current);
+ current = down_next;
+ }
+ }
+
+ /*
+ * We now try to find the first down node less than the
+ * least serial.
+ */
+ dparent = current;
+ for (dcurrent = current->down; dcurrent != NULL;
+ dcurrent = down_next)
+ {
+ down_next = dcurrent->down;
+ if (dcurrent->serial < least_serial) {
+ break;
+ }
+ dparent = dcurrent;
+ }
+
+ /*
+ * If there is a such an rdataset, delete it and any older
+ * versions.
+ */
+ if (dcurrent != NULL) {
+ do {
+ down_next = dcurrent->down;
+ INSIST(dcurrent->serial <= least_serial);
+ free_rdataset(rbtdb, mctx, dcurrent);
+ dcurrent = down_next;
+ } while (dcurrent != NULL);
+ dparent->down = NULL;
+ }
+
+ /*
+ * Note. The serial number of 'current' might be less than
+ * least_serial too, but we cannot delete it because it is
+ * the most recent version, unless it is a NONEXISTENT
+ * rdataset.
+ */
+ if (current->down != NULL) {
+ still_dirty = true;
+ top_prev = current;
+ } else {
+ /*
+ * If this is a NONEXISTENT rdataset, we can delete it.
+ */
+ if (NONEXISTENT(current)) {
+ if (top_prev != NULL) {
+ top_prev->next = current->next;
+ } else {
+ node->data = current->next;
+ }
+ free_rdataset(rbtdb, mctx, current);
+ } else {
+ top_prev = current;
+ }
+ }
+ }
+ if (!still_dirty) {
+ node->dirty = 0;
+ }
+}
+
+/*
+ * tree_lock(write) must be held.
+ */
+static void
+delete_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
+ dns_rbtnode_t *nsecnode;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ isc_result_t result = ISC_R_UNEXPECTED;
+
+ INSIST(!ISC_LINK_LINKED(node, deadlink));
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
+ char printname[DNS_NAME_FORMATSIZE];
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1),
+ "delete_node(): %p %s (bucket %d)", node,
+ dns_rbt_formatnodename(node, printname,
+ sizeof(printname)),
+ node->locknum);
+ }
+
+ switch (node->nsec) {
+ case DNS_RBT_NSEC_NORMAL:
+ /*
+ * Though this may be wasteful, it has to be done before
+ * node is deleted.
+ */
+ name = dns_fixedname_initname(&fname);
+ dns_rbt_fullnamefromnode(node, name);
+
+ result = dns_rbt_deletenode(rbtdb->tree, node, false);
+ break;
+ case DNS_RBT_NSEC_HAS_NSEC:
+ name = dns_fixedname_initname(&fname);
+ dns_rbt_fullnamefromnode(node, name);
+ /*
+ * Delete the corresponding node from the auxiliary NSEC
+ * tree before deleting from the main tree.
+ */
+ nsecnode = NULL;
+ result = dns_rbt_findnode(rbtdb->nsec, name, NULL, &nsecnode,
+ NULL, DNS_RBTFIND_EMPTYDATA, NULL,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "delete_node: "
+ "dns_rbt_findnode(nsec): %s",
+ isc_result_totext(result));
+ } else {
+ result = dns_rbt_deletenode(rbtdb->nsec, nsecnode,
+ false);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "delete_node(): "
+ "dns_rbt_deletenode(nsecnode): %s",
+ isc_result_totext(result));
+ }
+ }
+ result = dns_rbt_deletenode(rbtdb->tree, node, false);
+ break;
+ case DNS_RBT_NSEC_NSEC:
+ result = dns_rbt_deletenode(rbtdb->nsec, node, false);
+ break;
+ case DNS_RBT_NSEC_NSEC3:
+ result = dns_rbt_deletenode(rbtdb->nsec3, node, false);
+ break;
+ }
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
+ "delete_node(): "
+ "dns_rbt_deletenode: %s",
+ isc_result_totext(result));
+ }
+}
+
+/*
+ * Caller must be holding the node lock.
+ */
+static void
+new_reference(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ isc_rwlocktype_t locktype) {
+ if (locktype == isc_rwlocktype_write && ISC_LINK_LINKED(node, deadlink))
+ {
+ ISC_LIST_UNLINK(rbtdb->deadnodes[node->locknum], node,
+ deadlink);
+ }
+ if (isc_refcount_increment0(&node->references) == 0) {
+ /* this is the first reference to the node */
+ isc_refcount_increment0(
+ &rbtdb->node_locks[node->locknum].references);
+ }
+}
+
+/*%
+ * The tree lock must be held for the result to be valid.
+ */
+static bool
+is_leaf(dns_rbtnode_t *node) {
+ return (node->parent != NULL && node->parent->down == node &&
+ node->left == NULL && node->right == NULL);
+}
+
+static void
+send_to_prune_tree(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ isc_rwlocktype_t locktype) {
+ isc_event_t *ev;
+ dns_db_t *db;
+
+ ev = isc_event_allocate(rbtdb->common.mctx, NULL, DNS_EVENT_RBTPRUNE,
+ prune_tree, node, sizeof(isc_event_t));
+ new_reference(rbtdb, node, locktype);
+ db = NULL;
+ attach((dns_db_t *)rbtdb, &db);
+ ev->ev_sender = db;
+ isc_task_send(rbtdb->task, &ev);
+}
+
+/*%
+ * Clean up dead nodes. These are nodes which have no references, and
+ * have no data. They are dead but we could not or chose not to delete
+ * them when we deleted all the data at that node because we did not want
+ * to wait for the tree write lock.
+ *
+ * The caller must hold a tree write lock and bucketnum'th node (write) lock.
+ */
+static void
+cleanup_dead_nodes(dns_rbtdb_t *rbtdb, int bucketnum) {
+ dns_rbtnode_t *node;
+ int count = 10; /* XXXJT: should be adjustable */
+
+ node = ISC_LIST_HEAD(rbtdb->deadnodes[bucketnum]);
+ while (node != NULL && count > 0) {
+ ISC_LIST_UNLINK(rbtdb->deadnodes[bucketnum], node, deadlink);
+
+ /*
+ * We might have reactivated this node without a tree write
+ * lock, so we couldn't remove this node from deadnodes then
+ * and we have to do it now.
+ */
+ if (isc_refcount_current(&node->references) != 0 ||
+ node->data != NULL)
+ {
+ node = ISC_LIST_HEAD(rbtdb->deadnodes[bucketnum]);
+ count--;
+ continue;
+ }
+
+ if (is_leaf(node) && rbtdb->task != NULL) {
+ send_to_prune_tree(rbtdb, node, isc_rwlocktype_write);
+ } else if (node->down == NULL && node->data == NULL) {
+ /*
+ * Not a interior node and not needing to be
+ * reactivated.
+ */
+ delete_node(rbtdb, node);
+ } else if (node->data == NULL) {
+ /*
+ * A interior node without data. Leave linked to
+ * to be cleaned up when node->down becomes NULL.
+ */
+ ISC_LIST_APPEND(rbtdb->deadnodes[bucketnum], node,
+ deadlink);
+ }
+ node = ISC_LIST_HEAD(rbtdb->deadnodes[bucketnum]);
+ count--;
+ }
+}
+
+/*
+ * This function is assumed to be called when a node is newly referenced
+ * and can be in the deadnode list. In that case the node must be retrieved
+ * from the list because it is going to be used. In addition, if the caller
+ * happens to hold a write lock on the tree, it's a good chance to purge dead
+ * nodes.
+ * Note: while a new reference is gained in multiple places, there are only very
+ * few cases where the node can be in the deadnode list (only empty nodes can
+ * have been added to the list).
+ */
+static void
+reactivate_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ isc_rwlocktype_t treelocktype) {
+ isc_rwlocktype_t locktype = isc_rwlocktype_read;
+ nodelock_t *nodelock = &rbtdb->node_locks[node->locknum].lock;
+ bool maybe_cleanup = false;
+
+ POST(locktype);
+
+ NODE_LOCK(nodelock, locktype);
+
+ /*
+ * Check if we can possibly cleanup the dead node. If so, upgrade
+ * the node lock below to perform the cleanup.
+ */
+ if (!ISC_LIST_EMPTY(rbtdb->deadnodes[node->locknum]) &&
+ treelocktype == isc_rwlocktype_write)
+ {
+ maybe_cleanup = true;
+ }
+
+ if (ISC_LINK_LINKED(node, deadlink) || maybe_cleanup) {
+ /*
+ * Upgrade the lock and test if we still need to unlink.
+ */
+ NODE_UNLOCK(nodelock, locktype);
+ locktype = isc_rwlocktype_write;
+ POST(locktype);
+ NODE_LOCK(nodelock, locktype);
+ if (ISC_LINK_LINKED(node, deadlink)) {
+ ISC_LIST_UNLINK(rbtdb->deadnodes[node->locknum], node,
+ deadlink);
+ }
+ if (maybe_cleanup) {
+ cleanup_dead_nodes(rbtdb, node->locknum);
+ }
+ }
+
+ new_reference(rbtdb, node, locktype);
+
+ NODE_UNLOCK(nodelock, locktype);
+}
+
+/*
+ * Caller must be holding the node lock; either the "strong", read or write
+ * lock. Note that the lock must be held even when node references are
+ * atomically modified; in that case the decrement operation itself does not
+ * have to be protected, but we must avoid a race condition where multiple
+ * threads are decreasing the reference to zero simultaneously and at least
+ * one of them is going to free the node.
+ *
+ * This function returns true if and only if the node reference decreases
+ * to zero.
+ *
+ * NOTE: Decrementing the reference count of a node to zero does not mean it
+ * will be immediately freed.
+ */
+static bool
+decrement_reference(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ rbtdb_serial_t least_serial, isc_rwlocktype_t nlock,
+ isc_rwlocktype_t tlock, bool pruning) {
+ isc_result_t result;
+ bool write_locked;
+ bool locked = tlock != isc_rwlocktype_none;
+ rbtdb_nodelock_t *nodelock;
+ int bucket = node->locknum;
+ bool no_reference = true;
+ uint_fast32_t refs;
+
+ nodelock = &rbtdb->node_locks[bucket];
+
+#define KEEP_NODE(n, r, l) \
+ ((n)->data != NULL || ((l) && (n)->down != NULL) || \
+ (n) == (r)->origin_node || (n) == (r)->nsec3_origin_node)
+
+ /* Handle easy and typical case first. */
+ if (!node->dirty && KEEP_NODE(node, rbtdb, locked)) {
+ if (isc_refcount_decrement(&node->references) == 1) {
+ refs = isc_refcount_decrement(&nodelock->references);
+ INSIST(refs > 0);
+ return (true);
+ } else {
+ return (false);
+ }
+ }
+
+ /* Upgrade the lock? */
+ if (nlock == isc_rwlocktype_read) {
+ NODE_UNLOCK(&nodelock->lock, isc_rwlocktype_read);
+ NODE_LOCK(&nodelock->lock, isc_rwlocktype_write);
+ }
+
+ if (isc_refcount_decrement(&node->references) > 1) {
+ /* Restore the lock? */
+ if (nlock == isc_rwlocktype_read) {
+ NODE_DOWNGRADE(&nodelock->lock);
+ }
+ return (false);
+ }
+
+ if (node->dirty) {
+ if (IS_CACHE(rbtdb)) {
+ clean_cache_node(rbtdb, node);
+ } else {
+ if (least_serial == 0) {
+ /*
+ * Caller doesn't know the least serial.
+ * Get it.
+ */
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ least_serial = rbtdb->least_serial;
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+ }
+ clean_zone_node(rbtdb, node, least_serial);
+ }
+ }
+
+ /*
+ * Attempt to switch to a write lock on the tree. If this fails,
+ * we will add this node to a linked list of nodes in this locking
+ * bucket which we will free later.
+ */
+ if (tlock != isc_rwlocktype_write) {
+ /*
+ * Locking hierarchy notwithstanding, we don't need to free
+ * the node lock before acquiring the tree write lock because
+ * we only do a trylock.
+ */
+ if (tlock == isc_rwlocktype_read) {
+ result = isc_rwlock_tryupgrade(&rbtdb->tree_lock);
+ } else {
+ result = isc_rwlock_trylock(&rbtdb->tree_lock,
+ isc_rwlocktype_write);
+ }
+ RUNTIME_CHECK(result == ISC_R_SUCCESS ||
+ result == ISC_R_LOCKBUSY);
+
+ write_locked = (result == ISC_R_SUCCESS);
+ } else {
+ write_locked = true;
+ }
+
+ refs = isc_refcount_decrement(&nodelock->references);
+ INSIST(refs > 0);
+
+ if (KEEP_NODE(node, rbtdb, locked || write_locked)) {
+ goto restore_locks;
+ }
+
+#undef KEEP_NODE
+
+ if (write_locked) {
+ /*
+ * We can now delete the node.
+ */
+
+ /*
+ * If this node is the only one in the level it's in, deleting
+ * this node may recursively make its parent the only node in
+ * the parent level; if so, and if no one is currently using
+ * the parent node, this is almost the only opportunity to
+ * clean it up. But the recursive cleanup is not that trivial
+ * since the child and parent may be in different lock buckets,
+ * which would cause a lock order reversal problem. To avoid
+ * the trouble, we'll dispatch a separate event for batch
+ * cleaning. We need to check whether we're deleting the node
+ * as a result of pruning to avoid infinite dispatching.
+ * Note: pruning happens only when a task has been set for the
+ * rbtdb. If the user of the rbtdb chooses not to set a task,
+ * it's their responsibility to purge stale leaves (e.g. by
+ * periodic walk-through).
+ */
+ if (!pruning && is_leaf(node) && rbtdb->task != NULL) {
+ send_to_prune_tree(rbtdb, node, isc_rwlocktype_write);
+ no_reference = false;
+ } else {
+ delete_node(rbtdb, node);
+ }
+ } else {
+ INSIST(node->data == NULL);
+ if (!ISC_LINK_LINKED(node, deadlink)) {
+ ISC_LIST_APPEND(rbtdb->deadnodes[bucket], node,
+ deadlink);
+ }
+ }
+
+restore_locks:
+ /* Restore the lock? */
+ if (nlock == isc_rwlocktype_read) {
+ NODE_DOWNGRADE(&nodelock->lock);
+ }
+
+ /*
+ * Relock a read lock, or unlock the write lock if no lock was held.
+ */
+ if (tlock == isc_rwlocktype_none) {
+ if (write_locked) {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ }
+ }
+
+ if (tlock == isc_rwlocktype_read) {
+ if (write_locked) {
+ isc_rwlock_downgrade(&rbtdb->tree_lock);
+ }
+ }
+
+ return (no_reference);
+}
+
+/*
+ * Prune the tree by recursively cleaning-up single leaves. In the worst
+ * case, the number of iteration is the number of tree levels, which is at
+ * most the maximum number of domain name labels, i.e, 127. In practice, this
+ * should be much smaller (only a few times), and even the worst case would be
+ * acceptable for a single event.
+ */
+static void
+prune_tree(isc_task_t *task, isc_event_t *event) {
+ dns_rbtdb_t *rbtdb = event->ev_sender;
+ dns_rbtnode_t *node = event->ev_arg;
+ dns_rbtnode_t *parent;
+ unsigned int locknum;
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ locknum = node->locknum;
+ NODE_LOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write);
+ do {
+ parent = node->parent;
+ decrement_reference(rbtdb, node, 0, isc_rwlocktype_write,
+ isc_rwlocktype_write, true);
+
+ if (parent != NULL && parent->down == NULL) {
+ /*
+ * node was the only down child of the parent and has
+ * just been removed. We'll then need to examine the
+ * parent. Keep the lock if possible; otherwise,
+ * release the old lock and acquire one for the parent.
+ */
+ if (parent->locknum != locknum) {
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_write);
+ locknum = parent->locknum;
+ NODE_LOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_write);
+ }
+
+ /*
+ * We need to gain a reference to the node before
+ * decrementing it in the next iteration.
+ */
+ if (ISC_LINK_LINKED(parent, deadlink)) {
+ ISC_LIST_UNLINK(rbtdb->deadnodes[locknum],
+ parent, deadlink);
+ }
+ new_reference(rbtdb, parent, isc_rwlocktype_write);
+ } else {
+ parent = NULL;
+ }
+
+ node = parent;
+ } while (node != NULL);
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+
+ detach((dns_db_t **)&rbtdb);
+}
+
+static void
+make_least_version(dns_rbtdb_t *rbtdb, rbtdb_version_t *version,
+ rbtdb_changedlist_t *cleanup_list) {
+ /*
+ * Caller must be holding the database lock.
+ */
+
+ rbtdb->least_serial = version->serial;
+ *cleanup_list = version->changed_list;
+ ISC_LIST_INIT(version->changed_list);
+}
+
+static void
+cleanup_nondirty(rbtdb_version_t *version, rbtdb_changedlist_t *cleanup_list) {
+ rbtdb_changed_t *changed, *next_changed;
+
+ /*
+ * If the changed record is dirty, then
+ * an update created multiple versions of
+ * a given rdataset. We keep this list
+ * until we're the least open version, at
+ * which point it's safe to get rid of any
+ * older versions.
+ *
+ * If the changed record isn't dirty, then
+ * we don't need it anymore since we're
+ * committing and not rolling back.
+ *
+ * The caller must be holding the database lock.
+ */
+ for (changed = HEAD(version->changed_list); changed != NULL;
+ changed = next_changed)
+ {
+ next_changed = NEXT(changed, link);
+ if (!changed->dirty) {
+ UNLINK(version->changed_list, changed, link);
+ APPEND(*cleanup_list, changed, link);
+ }
+ }
+}
+
+static void
+iszonesecure(dns_db_t *db, rbtdb_version_t *version, dns_dbnode_t *origin) {
+ dns_rdataset_t keyset;
+ dns_rdataset_t nsecset, signsecset;
+ bool haszonekey = false;
+ bool hasnsec = false;
+ isc_result_t result;
+
+ dns_rdataset_init(&keyset);
+ result = dns_db_findrdataset(db, origin, version, dns_rdatatype_dnskey,
+ 0, 0, &keyset, NULL);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_rdataset_first(&keyset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_t keyrdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&keyset, &keyrdata);
+ if (dns_zonekey_iszonekey(&keyrdata)) {
+ haszonekey = true;
+ break;
+ }
+ result = dns_rdataset_next(&keyset);
+ }
+ dns_rdataset_disassociate(&keyset);
+ }
+ if (!haszonekey) {
+ version->secure = dns_db_insecure;
+ version->havensec3 = false;
+ return;
+ }
+
+ dns_rdataset_init(&nsecset);
+ dns_rdataset_init(&signsecset);
+ result = dns_db_findrdataset(db, origin, version, dns_rdatatype_nsec, 0,
+ 0, &nsecset, &signsecset);
+ if (result == ISC_R_SUCCESS) {
+ if (dns_rdataset_isassociated(&signsecset)) {
+ hasnsec = true;
+ dns_rdataset_disassociate(&signsecset);
+ }
+ dns_rdataset_disassociate(&nsecset);
+ }
+
+ setnsec3parameters(db, version);
+
+ /*
+ * Do we have a valid NSEC/NSEC3 chain?
+ */
+ if (version->havensec3 || hasnsec) {
+ version->secure = dns_db_secure;
+ } else {
+ version->secure = dns_db_insecure;
+ }
+}
+
+/*%<
+ * Walk the origin node looking for NSEC3PARAM records.
+ * Cache the nsec3 parameters.
+ */
+static void
+setnsec3parameters(dns_db_t *db, rbtdb_version_t *version) {
+ dns_rbtnode_t *node;
+ dns_rdata_nsec3param_t nsec3param;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_region_t region;
+ isc_result_t result;
+ rdatasetheader_t *header, *header_next;
+ unsigned char *raw; /* RDATASLAB */
+ unsigned int count, length;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ version->havensec3 = false;
+ node = rbtdb->origin_node;
+ NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ do {
+ if (header->serial <= version->serial &&
+ !IGNORE(header))
+ {
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+
+ if (header != NULL &&
+ (header->type == dns_rdatatype_nsec3param))
+ {
+ /*
+ * Find A NSEC3PARAM with a supported algorithm.
+ */
+ raw = (unsigned char *)header + sizeof(*header);
+ count = raw[0] * 256 + raw[1]; /* count */
+ raw += DNS_RDATASET_COUNT + DNS_RDATASET_LENGTH;
+ while (count-- > 0U) {
+ length = raw[0] * 256 + raw[1];
+ raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH;
+ region.base = raw;
+ region.length = length;
+ raw += length;
+ dns_rdata_fromregion(
+ &rdata, rbtdb->common.rdclass,
+ dns_rdatatype_nsec3param, &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_copynf(name, zcname);
+ search->copy_name = true;
+ }
+ } else {
+ /*
+ * There is no zonecut at this node which is active in this
+ * version.
+ *
+ * If this is a "wild" node and the caller hasn't disabled
+ * wildcard matching, remember that we've seen a wild node
+ * in case we need to go searching for wildcard matches
+ * later on.
+ */
+ if (node->wild && (search->options & DNS_DBFIND_NOWILD) == 0) {
+ search->wild = true;
+ }
+ }
+
+ NODE_UNLOCK(&(search->rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+
+ return (result);
+}
+
+static void
+bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdatasetheader_t *header,
+ isc_stdtime_t now, isc_rwlocktype_t locktype,
+ dns_rdataset_t *rdataset) {
+ unsigned char *raw; /* RDATASLAB */
+ bool stale = STALE(header);
+ bool ancient = ANCIENT(header);
+
+ /*
+ * Caller must be holding the node reader lock.
+ * XXXJT: technically, we need a writer lock, since we'll increment
+ * the header count below. However, since the actual counter value
+ * doesn't matter, we prioritize performance here. (We may want to
+ * use atomic increment when available).
+ */
+
+ if (rdataset == NULL) {
+ return;
+ }
+
+ new_reference(rbtdb, node, locktype);
+
+ INSIST(rdataset->methods == NULL); /* We must be disassociated. */
+
+ /*
+ * Mark header stale or ancient if the RRset is no longer active.
+ */
+ if (!ACTIVE(header, now)) {
+ dns_ttl_t stale_ttl = header->rdh_ttl + rbtdb->serve_stale_ttl;
+ /*
+ * If this data is in the stale window keep it and if
+ * DNS_DBFIND_STALEOK is not set we tell the caller to
+ * skip this record. We skip the records with ZEROTTL
+ * (these records should not be cached anyway).
+ */
+
+ if (KEEPSTALE(rbtdb) && stale_ttl > now) {
+ stale = true;
+ } else {
+ /*
+ * We are not keeping stale, or it is outside the
+ * stale window. Mark ancient, i.e. ready for cleanup.
+ */
+ ancient = true;
+ }
+ }
+
+ rdataset->methods = &rdataset_methods;
+ rdataset->rdclass = rbtdb->common.rdclass;
+ rdataset->type = RBTDB_RDATATYPE_BASE(header->type);
+ rdataset->covers = RBTDB_RDATATYPE_EXT(header->type);
+ rdataset->ttl = header->rdh_ttl - now;
+ rdataset->trust = header->trust;
+
+ if (NEGATIVE(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_NEGATIVE;
+ }
+ if (NXDOMAIN(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_NXDOMAIN;
+ }
+ if (OPTOUT(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_OPTOUT;
+ }
+ if (PREFETCH(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_PREFETCH;
+ }
+
+ if (stale && !ancient) {
+ dns_ttl_t stale_ttl = header->rdh_ttl + rbtdb->serve_stale_ttl;
+ if (stale_ttl > now) {
+ rdataset->ttl = stale_ttl - now;
+ } else {
+ rdataset->ttl = 0;
+ }
+ if (STALE_WINDOW(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_STALE_WINDOW;
+ }
+ rdataset->attributes |= DNS_RDATASETATTR_STALE;
+ } else if (IS_CACHE(rbtdb) && !ACTIVE(header, now)) {
+ rdataset->attributes |= DNS_RDATASETATTR_ANCIENT;
+ rdataset->ttl = header->rdh_ttl;
+ }
+
+ rdataset->private1 = rbtdb;
+ rdataset->private2 = node;
+ raw = (unsigned char *)header + sizeof(*header);
+ rdataset->private3 = raw;
+ rdataset->count = atomic_fetch_add_relaxed(&header->count, 1);
+ if (rdataset->count == UINT32_MAX) {
+ rdataset->count = 0;
+ }
+
+ /*
+ * Reset iterator state.
+ */
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+
+ /*
+ * Add noqname proof.
+ */
+ rdataset->private6 = header->noqname;
+ if (rdataset->private6 != NULL) {
+ rdataset->attributes |= DNS_RDATASETATTR_NOQNAME;
+ }
+ rdataset->private7 = header->closest;
+ if (rdataset->private7 != NULL) {
+ rdataset->attributes |= DNS_RDATASETATTR_CLOSEST;
+ }
+
+ /*
+ * Copy out re-signing information.
+ */
+ if (RESIGN(header)) {
+ rdataset->attributes |= DNS_RDATASETATTR_RESIGN;
+ rdataset->resign = (header->resign << 1) | header->resign_lsb;
+ } else {
+ rdataset->resign = 0;
+ }
+}
+
+static isc_result_t
+setup_delegation(rbtdb_search_t *search, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ dns_name_t *zcname;
+ rbtdb_rdatatype_t type;
+ dns_rbtnode_t *node;
+
+ /*
+ * The caller MUST NOT be holding any node locks.
+ */
+
+ node = search->zonecut;
+ type = search->zonecut_rdataset->type;
+
+ /*
+ * If we have to set foundname, we do it before anything else.
+ * If we were to set foundname after we had set nodep or bound the
+ * rdataset, then we'd have to undo that work if dns_name_copy()
+ * failed. By setting foundname first, there's nothing to undo if
+ * we have trouble.
+ */
+ if (foundname != NULL && search->copy_name) {
+ zcname = dns_fixedname_name(&search->zonecut_name);
+ dns_name_copynf(zcname, foundname);
+ }
+ if (nodep != NULL) {
+ /*
+ * Note that we don't have to increment the node's reference
+ * count here because we're going to use the reference we
+ * already have in the search block.
+ */
+ *nodep = node;
+ search->need_cleanup = false;
+ }
+ if (rdataset != NULL) {
+ NODE_LOCK(&(search->rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ bind_rdataset(search->rbtdb, node, search->zonecut_rdataset,
+ search->now, isc_rwlocktype_read, rdataset);
+ if (sigrdataset != NULL && search->zonecut_sigrdataset != NULL)
+ {
+ bind_rdataset(search->rbtdb, node,
+ search->zonecut_sigrdataset, search->now,
+ isc_rwlocktype_read, sigrdataset);
+ }
+ NODE_UNLOCK(&(search->rbtdb->node_locks[node->locknum].lock),
+ isc_rwlocktype_read);
+ }
+
+ if (type == dns_rdatatype_dname) {
+ return (DNS_R_DNAME);
+ }
+ return (DNS_R_DELEGATION);
+}
+
+static bool
+valid_glue(rbtdb_search_t *search, dns_name_t *name, rbtdb_rdatatype_t type,
+ dns_rbtnode_t *node) {
+ unsigned char *raw; /* RDATASLAB */
+ unsigned int count, size;
+ dns_name_t ns_name;
+ bool valid = false;
+ dns_offsets_t offsets;
+ isc_region_t region;
+ rdatasetheader_t *header;
+
+ /*
+ * No additional locking is required.
+ */
+
+ /*
+ * Valid glue types are A, AAAA, A6. NS is also a valid glue type
+ * if it occurs at a zone cut, but is not valid below it.
+ */
+ if (type == dns_rdatatype_ns) {
+ if (node != search->zonecut) {
+ return (false);
+ }
+ } else if (type != dns_rdatatype_a && type != dns_rdatatype_aaaa &&
+ type != dns_rdatatype_a6)
+ {
+ return (false);
+ }
+
+ header = search->zonecut_rdataset;
+ raw = (unsigned char *)header + sizeof(*header);
+ count = raw[0] * 256 + raw[1];
+ raw += DNS_RDATASET_COUNT + DNS_RDATASET_LENGTH;
+
+ while (count > 0) {
+ count--;
+ size = raw[0] * 256 + raw[1];
+ raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH;
+ region.base = raw;
+ region.length = size;
+ raw += size;
+ /*
+ * XXX Until we have rdata structures, we have no choice but
+ * to directly access the rdata format.
+ */
+ dns_name_init(&ns_name, offsets);
+ dns_name_fromregion(&ns_name, &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_copynf(name, foundname);
+ wild = true;
+ goto found;
+ } else if (result != ISC_R_NOTFOUND) {
+ goto tree_exit;
+ }
+ }
+
+ active = false;
+ if ((options & DNS_DBFIND_FORCENSEC3) == 0) {
+ /*
+ * The NSEC3 tree won't have empty nodes,
+ * so it isn't necessary to check for them.
+ */
+ dns_rbtnodechain_t chain = search.chain;
+ active = activeempty(&search, &chain, name);
+ }
+
+ /*
+ * If we're here, then the name does not exist, is not
+ * beneath a zonecut, and there's no matching wildcard.
+ */
+ if ((search.rbtversion->secure == dns_db_secure &&
+ !search.rbtversion->havensec3) ||
+ (search.options & DNS_DBFIND_FORCENSEC) != 0 ||
+ (search.options & DNS_DBFIND_FORCENSEC3) != 0)
+ {
+ result = find_closest_nsec(&search, nodep, foundname,
+ rdataset, sigrdataset, tree,
+ search.rbtversion->secure);
+ if (result == ISC_R_SUCCESS) {
+ result = active ? DNS_R_EMPTYNAME
+ : DNS_R_NXDOMAIN;
+ }
+ } else {
+ result = active ? DNS_R_EMPTYNAME : DNS_R_NXDOMAIN;
+ }
+ goto tree_exit;
+ } else if (result != ISC_R_SUCCESS) {
+ goto tree_exit;
+ }
+
+found:
+ /*
+ * We have found a node whose name is the desired name, or we
+ * have matched a wildcard.
+ */
+
+ if (search.zonecut != NULL) {
+ /*
+ * If we're beneath a zone cut, we don't want to look for
+ * CNAMEs because they're not legitimate zone glue.
+ */
+ cname_ok = false;
+ } else {
+ /*
+ * The node may be a zone cut itself. If it might be one,
+ * make sure we check for it later.
+ *
+ * DS records live above the zone cut in ordinary zone so
+ * we want to ignore any referral.
+ *
+ * Stub zones don't have anything "above" the delegation so
+ * we always return a referral.
+ */
+ if (node->find_callback &&
+ ((node != search.rbtdb->origin_node &&
+ !dns_rdatatype_atparent(type)) ||
+ IS_STUB(search.rbtdb)))
+ {
+ maybe_zonecut = true;
+ }
+ }
+
+ /*
+ * Certain DNSSEC types are not subject to CNAME matching
+ * (RFC4035, section 2.5 and RFC3007).
+ *
+ * We don't check for RRSIG, because we don't store RRSIG records
+ * directly.
+ */
+ if (type == dns_rdatatype_key || type == dns_rdatatype_nsec) {
+ cname_ok = false;
+ }
+
+ /*
+ * We now go looking for rdata...
+ */
+
+ lock = &search.rbtdb->node_locks[node->locknum].lock;
+ NODE_LOCK(lock, isc_rwlocktype_read);
+
+ found = NULL;
+ foundsig = NULL;
+ sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type);
+ nsecheader = NULL;
+ nsecsig = NULL;
+ cnamesig = NULL;
+ empty_node = true;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ /*
+ * Look for an active, extant rdataset.
+ */
+ do {
+ if (header->serial <= search.serial && !IGNORE(header))
+ {
+ /*
+ * Is this a "this rdataset doesn't
+ * exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ /*
+ * We now know that there is at least one active
+ * rdataset at this node.
+ */
+ empty_node = false;
+
+ /*
+ * Do special zone cut handling, if requested.
+ */
+ if (maybe_zonecut && header->type == dns_rdatatype_ns) {
+ /*
+ * We increment the reference count on node to
+ * ensure that search->zonecut_rdataset will
+ * still be valid later.
+ */
+ new_reference(search.rbtdb, node,
+ isc_rwlocktype_read);
+ search.zonecut = node;
+ search.zonecut_rdataset = header;
+ search.zonecut_sigrdataset = NULL;
+ search.need_cleanup = true;
+ maybe_zonecut = false;
+ at_zonecut = true;
+ /*
+ * It is not clear if KEY should still be
+ * allowed at the parent side of the zone
+ * cut or not. It is needed for RFC3007
+ * validated updates.
+ */
+ if ((search.options & DNS_DBFIND_GLUEOK) == 0 &&
+ type != dns_rdatatype_nsec &&
+ type != dns_rdatatype_key)
+ {
+ /*
+ * Glue is not OK, but any answer we
+ * could return would be glue. Return
+ * the delegation.
+ */
+ found = NULL;
+ break;
+ }
+ if (found != NULL && foundsig != NULL) {
+ break;
+ }
+ }
+
+ /*
+ * If the NSEC3 record doesn't match the chain
+ * we are using behave as if it isn't here.
+ */
+ if (header->type == dns_rdatatype_nsec3 &&
+ !matchparams(header, &search))
+ {
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ goto partial_match;
+ }
+ /*
+ * If we found a type we were looking for,
+ * remember it.
+ */
+ if (header->type == type || type == dns_rdatatype_any ||
+ (header->type == dns_rdatatype_cname && cname_ok))
+ {
+ /*
+ * We've found the answer!
+ */
+ found = header;
+ if (header->type == dns_rdatatype_cname &&
+ cname_ok)
+ {
+ /*
+ * We may be finding a CNAME instead
+ * of the desired type.
+ *
+ * If we've already got the CNAME RRSIG,
+ * use it, otherwise change sigtype
+ * so that we find it.
+ */
+ if (cnamesig != NULL) {
+ foundsig = cnamesig;
+ } else {
+ sigtype =
+ RBTDB_RDATATYPE_SIGCNAME;
+ }
+ }
+ /*
+ * If we've got all we need, end the search.
+ */
+ if (!maybe_zonecut && foundsig != NULL) {
+ break;
+ }
+ } else if (header->type == sigtype) {
+ /*
+ * We've found the RRSIG rdataset for our
+ * target type. Remember it.
+ */
+ foundsig = header;
+ /*
+ * If we've got all we need, end the search.
+ */
+ if (!maybe_zonecut && found != NULL) {
+ break;
+ }
+ } else if (header->type == dns_rdatatype_nsec &&
+ !search.rbtversion->havensec3)
+ {
+ /*
+ * Remember a NSEC rdataset even if we're
+ * not specifically looking for it, because
+ * we might need it later.
+ */
+ nsecheader = header;
+ } else if (header->type == RBTDB_RDATATYPE_SIGNSEC &&
+ !search.rbtversion->havensec3)
+ {
+ /*
+ * If we need the NSEC rdataset, we'll also
+ * need its signature.
+ */
+ nsecsig = header;
+ } else if (cname_ok &&
+ header->type == RBTDB_RDATATYPE_SIGCNAME)
+ {
+ /*
+ * If we get a CNAME match, we'll also need
+ * its signature.
+ */
+ cnamesig = header;
+ }
+ }
+ }
+
+ if (empty_node) {
+ /*
+ * We have an exact match for the name, but there are no
+ * active rdatasets in the desired version. That means that
+ * this node doesn't exist in the desired version, and that
+ * we really have a partial match.
+ */
+ if (!wild) {
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ goto partial_match;
+ }
+ }
+
+ /*
+ * If we didn't find what we were looking for...
+ */
+ if (found == NULL) {
+ if (search.zonecut != NULL) {
+ /*
+ * We were trying to find glue at a node beneath a
+ * zone cut, but didn't.
+ *
+ * Return the delegation.
+ */
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ result = setup_delegation(&search, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ }
+ /*
+ * The desired type doesn't exist.
+ */
+ result = DNS_R_NXRRSET;
+ if (search.rbtversion->secure == dns_db_secure &&
+ !search.rbtversion->havensec3 &&
+ (nsecheader == NULL || nsecsig == NULL))
+ {
+ /*
+ * The zone is secure but there's no NSEC,
+ * or the NSEC has no signature!
+ */
+ if (!wild) {
+ result = DNS_R_BADDB;
+ goto node_exit;
+ }
+
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ result = find_closest_nsec(&search, nodep, foundname,
+ rdataset, sigrdataset,
+ search.rbtdb->tree,
+ search.rbtversion->secure);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_EMPTYWILD;
+ }
+ goto tree_exit;
+ }
+ if ((search.options & DNS_DBFIND_FORCENSEC) != 0 &&
+ nsecheader == NULL)
+ {
+ /*
+ * There's no NSEC record, and we were told
+ * to find one.
+ */
+ result = DNS_R_BADDB;
+ goto node_exit;
+ }
+ if (nodep != NULL) {
+ new_reference(search.rbtdb, node, isc_rwlocktype_read);
+ *nodep = node;
+ }
+ if ((search.rbtversion->secure == dns_db_secure &&
+ !search.rbtversion->havensec3) ||
+ (search.options & DNS_DBFIND_FORCENSEC) != 0)
+ {
+ bind_rdataset(search.rbtdb, node, nsecheader, 0,
+ isc_rwlocktype_read, rdataset);
+ if (nsecsig != NULL) {
+ bind_rdataset(search.rbtdb, node, nsecsig, 0,
+ isc_rwlocktype_read, sigrdataset);
+ }
+ }
+ if (wild) {
+ foundname->attributes |= DNS_NAMEATTR_WILDCARD;
+ }
+ goto node_exit;
+ }
+
+ /*
+ * We found what we were looking for, or we found a CNAME.
+ */
+
+ if (type != found->type && type != dns_rdatatype_any &&
+ found->type == dns_rdatatype_cname)
+ {
+ /*
+ * We weren't doing an ANY query and we found a CNAME instead
+ * of the type we were looking for, so we need to indicate
+ * that result to the caller.
+ */
+ result = DNS_R_CNAME;
+ } else if (search.zonecut != NULL) {
+ /*
+ * If we're beneath a zone cut, we must indicate that the
+ * result is glue, unless we're actually at the zone cut
+ * and the type is NSEC or KEY.
+ */
+ if (search.zonecut == node) {
+ /*
+ * It is not clear if KEY should still be
+ * allowed at the parent side of the zone
+ * cut or not. It is needed for RFC3007
+ * validated updates.
+ */
+ if (type == dns_rdatatype_nsec ||
+ type == dns_rdatatype_nsec3 ||
+ type == dns_rdatatype_key)
+ {
+ result = ISC_R_SUCCESS;
+ } else if (type == dns_rdatatype_any) {
+ result = DNS_R_ZONECUT;
+ } else {
+ result = DNS_R_GLUE;
+ }
+ } else {
+ result = DNS_R_GLUE;
+ }
+ /*
+ * We might have found data that isn't glue, but was occluded
+ * by a dynamic update. If the caller cares about this, they
+ * will have told us to validate glue.
+ *
+ * XXX We should cache the glue validity state!
+ */
+ if (result == DNS_R_GLUE &&
+ (search.options & DNS_DBFIND_VALIDATEGLUE) != 0 &&
+ !valid_glue(&search, foundname, type, node))
+ {
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ result = setup_delegation(&search, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ }
+ } else {
+ /*
+ * An ordinary successful query!
+ */
+ result = ISC_R_SUCCESS;
+ }
+
+ if (nodep != NULL) {
+ if (!at_zonecut) {
+ new_reference(search.rbtdb, node, isc_rwlocktype_read);
+ } else {
+ search.need_cleanup = false;
+ }
+ *nodep = node;
+ }
+
+ if (type != dns_rdatatype_any) {
+ bind_rdataset(search.rbtdb, node, found, 0, isc_rwlocktype_read,
+ rdataset);
+ if (foundsig != NULL) {
+ bind_rdataset(search.rbtdb, node, foundsig, 0,
+ isc_rwlocktype_read, sigrdataset);
+ }
+ }
+
+ if (wild) {
+ foundname->attributes |= DNS_NAMEATTR_WILDCARD;
+ }
+
+node_exit:
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+
+tree_exit:
+ RWUNLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ /*
+ * If we found a zonecut but aren't going to use it, we have to
+ * let go of it.
+ */
+ if (search.need_cleanup) {
+ node = search.zonecut;
+ INSIST(node != NULL);
+ lock = &(search.rbtdb->node_locks[node->locknum].lock);
+
+ NODE_LOCK(lock, isc_rwlocktype_read);
+ decrement_reference(search.rbtdb, node, 0, isc_rwlocktype_read,
+ isc_rwlocktype_none, false);
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ }
+
+ if (close_version) {
+ closeversion(db, &version, false);
+ }
+
+ dns_rbtnodechain_reset(&search.chain);
+
+ return (result);
+}
+
+static isc_result_t
+zone_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_name_t *dcname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ UNUSED(db);
+ UNUSED(name);
+ UNUSED(options);
+ UNUSED(now);
+ UNUSED(nodep);
+ UNUSED(foundname);
+ UNUSED(dcname);
+ UNUSED(rdataset);
+ UNUSED(sigrdataset);
+
+ FATAL_ERROR(__FILE__, __LINE__, "zone_findzonecut() called!");
+
+ UNREACHABLE();
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static bool
+check_stale_header(dns_rbtnode_t *node, rdatasetheader_t *header,
+ isc_rwlocktype_t *locktype, nodelock_t *lock,
+ rbtdb_search_t *search, rdatasetheader_t **header_prev) {
+ if (!ACTIVE(header, search->now)) {
+ dns_ttl_t stale = header->rdh_ttl +
+ search->rbtdb->serve_stale_ttl;
+ /*
+ * If this data is in the stale window keep it and if
+ * DNS_DBFIND_STALEOK is not set we tell the caller to
+ * skip this record. We skip the records with ZEROTTL
+ * (these records should not be cached anyway).
+ */
+
+ RDATASET_ATTR_CLR(header, RDATASET_ATTR_STALE_WINDOW);
+ if (!ZEROTTL(header) && KEEPSTALE(search->rbtdb) &&
+ stale > search->now)
+ {
+ mark_header_stale(search->rbtdb, header);
+ *header_prev = header;
+ /*
+ * If DNS_DBFIND_STALESTART is set then it means we
+ * failed to resolve the name during recursion, in
+ * this case we mark the time in which the refresh
+ * failed.
+ */
+ if ((search->options & DNS_DBFIND_STALESTART) != 0) {
+ atomic_store_release(
+ &header->last_refresh_fail_ts,
+ search->now);
+ } else if ((search->options &
+ DNS_DBFIND_STALEENABLED) != 0 &&
+ search->now <
+ (atomic_load_acquire(
+ &header->last_refresh_fail_ts) +
+ search->rbtdb->serve_stale_refresh))
+ {
+ /*
+ * If we are within interval between last
+ * refresh failure time + 'stale-refresh-time',
+ * then don't skip this stale entry but use it
+ * instead.
+ */
+ RDATASET_ATTR_SET(header,
+ RDATASET_ATTR_STALE_WINDOW);
+ return (false);
+ } else if ((search->options &
+ DNS_DBFIND_STALETIMEOUT) != 0)
+ {
+ /*
+ * We want stale RRset due to timeout, so we
+ * don't skip it.
+ */
+ return (false);
+ }
+ return ((search->options & DNS_DBFIND_STALEOK) == 0);
+ }
+
+ /*
+ * This rdataset is stale. If no one else is using the
+ * node, we can clean it up right now, otherwise we mark
+ * it as ancient, and the node as dirty, so it will get
+ * cleaned up later.
+ */
+ if ((header->rdh_ttl < search->now - RBTDB_VIRTUAL) &&
+ (*locktype == isc_rwlocktype_write ||
+ NODE_TRYUPGRADE(lock) == ISC_R_SUCCESS))
+ {
+ /*
+ * We update the node's status only when we can
+ * get write access; otherwise, we leave others
+ * to this work. Periodical cleaning will
+ * eventually take the job as the last resort.
+ * We won't downgrade the lock, since other
+ * rdatasets are probably stale, too.
+ */
+ *locktype = isc_rwlocktype_write;
+
+ if (isc_refcount_current(&node->references) == 0) {
+ isc_mem_t *mctx;
+
+ /*
+ * header->down can be non-NULL if the
+ * refcount has just decremented to 0
+ * but decrement_reference() has not
+ * performed clean_cache_node(), in
+ * which case we need to purge the stale
+ * headers first.
+ */
+ mctx = search->rbtdb->common.mctx;
+ clean_stale_headers(search->rbtdb, mctx,
+ header);
+ if (*header_prev != NULL) {
+ (*header_prev)->next = header->next;
+ } else {
+ node->data = header->next;
+ }
+ free_rdataset(search->rbtdb, mctx, header);
+ } else {
+ mark_header_ancient(search->rbtdb, header);
+ *header_prev = header;
+ }
+ } else {
+ *header_prev = header;
+ }
+ return (true);
+ }
+ return (false);
+}
+
+static isc_result_t
+cache_zonecut_callback(dns_rbtnode_t *node, dns_name_t *name, void *arg) {
+ rbtdb_search_t *search = arg;
+ rdatasetheader_t *header, *header_prev, *header_next;
+ rdatasetheader_t *dname_header, *sigdname_header;
+ isc_result_t result;
+ nodelock_t *lock;
+ isc_rwlocktype_t locktype;
+
+ /* XXX comment */
+
+ REQUIRE(search->zonecut == NULL);
+
+ /*
+ * Keep compiler silent.
+ */
+ UNUSED(name);
+
+ lock = &(search->rbtdb->node_locks[node->locknum].lock);
+ locktype = isc_rwlocktype_read;
+ NODE_LOCK(lock, locktype);
+
+ /*
+ * Look for a DNAME or RRSIG DNAME rdataset.
+ */
+ dname_header = NULL;
+ sigdname_header = NULL;
+ header_prev = NULL;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (check_stale_header(node, header, &locktype, lock, search,
+ &header_prev))
+ {
+ /* Do nothing. */
+ } else if (header->type == dns_rdatatype_dname &&
+ EXISTS(header) && !ANCIENT(header))
+ {
+ dname_header = header;
+ header_prev = header;
+ } else if (header->type == RBTDB_RDATATYPE_SIGDNAME &&
+ EXISTS(header) && !ANCIENT(header))
+ {
+ sigdname_header = header;
+ header_prev = header;
+ } else {
+ header_prev = header;
+ }
+ }
+
+ if (dname_header != NULL &&
+ (!DNS_TRUST_PENDING(dname_header->trust) ||
+ (search->options & DNS_DBFIND_PENDINGOK) != 0))
+ {
+ /*
+ * We increment the reference count on node to ensure that
+ * search->zonecut_rdataset will still be valid later.
+ */
+ new_reference(search->rbtdb, node, locktype);
+ search->zonecut = node;
+ search->zonecut_rdataset = dname_header;
+ search->zonecut_sigrdataset = sigdname_header;
+ search->need_cleanup = true;
+ result = DNS_R_PARTIALMATCH;
+ } else {
+ result = DNS_R_CONTINUE;
+ }
+
+ NODE_UNLOCK(lock, locktype);
+
+ return (result);
+}
+
+static isc_result_t
+find_deepest_zonecut(rbtdb_search_t *search, dns_rbtnode_t *node,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ unsigned int i;
+ dns_rbtnode_t *level_node;
+ rdatasetheader_t *header, *header_prev, *header_next;
+ rdatasetheader_t *found, *foundsig;
+ isc_result_t result = ISC_R_NOTFOUND;
+ dns_name_t name;
+ dns_rbtdb_t *rbtdb;
+ bool done;
+ nodelock_t *lock;
+ isc_rwlocktype_t locktype;
+
+ /*
+ * Caller must be holding the tree lock.
+ */
+
+ rbtdb = search->rbtdb;
+ i = search->chain.level_matches;
+ done = false;
+ do {
+ locktype = isc_rwlocktype_read;
+ lock = &rbtdb->node_locks[node->locknum].lock;
+ NODE_LOCK(lock, locktype);
+
+ /*
+ * Look for NS and RRSIG NS rdatasets.
+ */
+ found = NULL;
+ foundsig = NULL;
+ header_prev = NULL;
+ for (header = node->data; header != NULL; header = header_next)
+ {
+ header_next = header->next;
+ if (check_stale_header(node, header, &locktype, lock,
+ search, &header_prev))
+ {
+ /* Do nothing. */
+ } else if (EXISTS(header) && !ANCIENT(header)) {
+ /*
+ * We've found an extant rdataset. See if
+ * we're interested in it.
+ */
+ if (header->type == dns_rdatatype_ns) {
+ found = header;
+ if (foundsig != NULL) {
+ break;
+ }
+ } else if (header->type ==
+ RBTDB_RDATATYPE_SIGNS)
+ {
+ foundsig = header;
+ if (found != NULL) {
+ break;
+ }
+ }
+ header_prev = header;
+ } else {
+ header_prev = header;
+ }
+ }
+
+ if (found != NULL) {
+ /*
+ * If we have to set foundname, we do it before
+ * anything else. If we were to set foundname after
+ * we had set nodep or bound the rdataset, then we'd
+ * have to undo that work if dns_name_concatenate()
+ * failed. By setting foundname first, there's
+ * nothing to undo if we have trouble.
+ */
+ if (foundname != NULL) {
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(node, &name);
+ dns_name_copynf(&name, foundname);
+ while (i > 0) {
+ i--;
+ level_node = search->chain.levels[i];
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(level_node, &name);
+ result = dns_name_concatenate(
+ foundname, &name, foundname,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ if (nodep != NULL) {
+ *nodep = NULL;
+ }
+ goto node_exit;
+ }
+ }
+ }
+ result = DNS_R_DELEGATION;
+ if (nodep != NULL) {
+ new_reference(search->rbtdb, node, locktype);
+ *nodep = node;
+ }
+ bind_rdataset(search->rbtdb, node, found, search->now,
+ locktype, rdataset);
+ if (foundsig != NULL) {
+ bind_rdataset(search->rbtdb, node, foundsig,
+ search->now, locktype,
+ sigrdataset);
+ }
+ if (need_headerupdate(found, search->now) ||
+ (foundsig != NULL &&
+ need_headerupdate(foundsig, search->now)))
+ {
+ if (locktype != isc_rwlocktype_write) {
+ NODE_UNLOCK(lock, locktype);
+ NODE_LOCK(lock, isc_rwlocktype_write);
+ locktype = isc_rwlocktype_write;
+ POST(locktype);
+ }
+ if (need_headerupdate(found, search->now)) {
+ update_header(search->rbtdb, found,
+ search->now);
+ }
+ if (foundsig != NULL &&
+ need_headerupdate(foundsig, search->now))
+ {
+ update_header(search->rbtdb, foundsig,
+ search->now);
+ }
+ }
+ }
+
+ node_exit:
+ NODE_UNLOCK(lock, locktype);
+
+ if (found == NULL && i > 0) {
+ i--;
+ node = search->chain.levels[i];
+ } else {
+ done = true;
+ }
+ } while (!done);
+
+ return (result);
+}
+
+static isc_result_t
+find_coveringnsec(rbtdb_search_t *search, dns_dbnode_t **nodep,
+ isc_stdtime_t now, dns_name_t *foundname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ dns_rbtnode_t *node;
+ rdatasetheader_t *header, *header_next, *header_prev;
+ rdatasetheader_t *found, *foundsig;
+ bool empty_node;
+ isc_result_t result;
+ dns_fixedname_t fname, forigin;
+ dns_name_t *name, *origin;
+ rbtdb_rdatatype_t matchtype, sigmatchtype;
+ nodelock_t *lock;
+ isc_rwlocktype_t locktype;
+ dns_rbtnodechain_t chain;
+
+ chain = search->chain;
+
+ matchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_nsec, 0);
+ sigmatchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig,
+ dns_rdatatype_nsec);
+
+ do {
+ node = NULL;
+ name = dns_fixedname_initname(&fname);
+ origin = dns_fixedname_initname(&forigin);
+ result = dns_rbtnodechain_current(&chain, name, origin, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ locktype = isc_rwlocktype_read;
+ lock = &(search->rbtdb->node_locks[node->locknum].lock);
+ NODE_LOCK(lock, locktype);
+ found = NULL;
+ foundsig = NULL;
+ empty_node = true;
+ header_prev = NULL;
+ for (header = node->data; header != NULL; header = header_next)
+ {
+ header_next = header->next;
+ if (check_stale_header(node, header, &locktype, lock,
+ search, &header_prev))
+ {
+ continue;
+ }
+ if (NONEXISTENT(header) ||
+ RBTDB_RDATATYPE_BASE(header->type) == 0)
+ {
+ header_prev = header;
+ continue;
+ }
+ /*
+ * Don't stop on provable noqname / RRSIG.
+ */
+ if (header->noqname == NULL &&
+ RBTDB_RDATATYPE_BASE(header->type) !=
+ dns_rdatatype_rrsig)
+ {
+ empty_node = false;
+ }
+ if (header->type == matchtype) {
+ found = header;
+ } else if (header->type == sigmatchtype) {
+ foundsig = header;
+ }
+ header_prev = header;
+ }
+ if (found != NULL) {
+ result = dns_name_concatenate(name, origin, foundname,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock_node;
+ }
+ bind_rdataset(search->rbtdb, node, found, now, locktype,
+ rdataset);
+ if (foundsig != NULL) {
+ bind_rdataset(search->rbtdb, node, foundsig,
+ now, locktype, sigrdataset);
+ }
+ new_reference(search->rbtdb, node, locktype);
+ *nodep = node;
+ result = DNS_R_COVERINGNSEC;
+ } else if (!empty_node) {
+ result = ISC_R_NOTFOUND;
+ } else {
+ result = dns_rbtnodechain_prev(&chain, NULL, NULL);
+ }
+ unlock_node:
+ NODE_UNLOCK(lock, locktype);
+ } while (empty_node && result == ISC_R_SUCCESS);
+ return (result);
+}
+
+static isc_result_t
+cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ dns_rbtnode_t *node = NULL;
+ isc_result_t result;
+ rbtdb_search_t search;
+ bool cname_ok = true;
+ bool empty_node;
+ nodelock_t *lock;
+ isc_rwlocktype_t locktype;
+ rdatasetheader_t *header, *header_prev, *header_next;
+ rdatasetheader_t *found, *nsheader;
+ rdatasetheader_t *foundsig, *nssig, *cnamesig;
+ rdatasetheader_t *update, *updatesig;
+ rdatasetheader_t *nsecheader, *nsecsig;
+ rbtdb_rdatatype_t sigtype, negtype;
+
+ UNUSED(version);
+
+ search.rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(search.rbtdb));
+ REQUIRE(version == NULL);
+
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+
+ search.rbtversion = NULL;
+ search.serial = 1;
+ search.options = options;
+ search.copy_name = false;
+ search.need_cleanup = false;
+ search.wild = false;
+ search.zonecut = NULL;
+ dns_fixedname_init(&search.zonecut_name);
+ dns_rbtnodechain_init(&search.chain);
+ search.now = now;
+ update = NULL;
+ updatesig = NULL;
+
+ RWLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ /*
+ * Search down from the root of the tree. If, while going down, we
+ * encounter a callback node, cache_zonecut_callback() will search the
+ * rdatasets at the zone cut for a DNAME rdataset.
+ */
+ result = dns_rbt_findnode(search.rbtdb->tree, name, foundname, &node,
+ &search.chain, DNS_RBTFIND_EMPTYDATA,
+ cache_zonecut_callback, &search);
+
+ if (result == DNS_R_PARTIALMATCH) {
+ if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0) {
+ result = find_coveringnsec(&search, nodep, now,
+ foundname, rdataset,
+ sigrdataset);
+ if (result == DNS_R_COVERINGNSEC) {
+ goto tree_exit;
+ }
+ }
+ if (search.zonecut != NULL) {
+ result = setup_delegation(&search, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ } else {
+ find_ns:
+ result = find_deepest_zonecut(&search, node, nodep,
+ foundname, rdataset,
+ sigrdataset);
+ goto tree_exit;
+ }
+ } else if (result != ISC_R_SUCCESS) {
+ goto tree_exit;
+ }
+
+ /*
+ * Certain DNSSEC types are not subject to CNAME matching
+ * (RFC4035, section 2.5 and RFC3007).
+ *
+ * We don't check for RRSIG, because we don't store RRSIG records
+ * directly.
+ */
+ if (type == dns_rdatatype_key || type == dns_rdatatype_nsec) {
+ cname_ok = false;
+ }
+
+ /*
+ * We now go looking for rdata...
+ */
+
+ lock = &(search.rbtdb->node_locks[node->locknum].lock);
+ locktype = isc_rwlocktype_read;
+ NODE_LOCK(lock, locktype);
+
+ found = NULL;
+ foundsig = NULL;
+ sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type);
+ negtype = RBTDB_RDATATYPE_VALUE(0, type);
+ nsheader = NULL;
+ nsecheader = NULL;
+ nssig = NULL;
+ nsecsig = NULL;
+ cnamesig = NULL;
+ empty_node = true;
+ header_prev = NULL;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (check_stale_header(node, header, &locktype, lock, &search,
+ &header_prev))
+ {
+ /* Do nothing. */
+ } else if (EXISTS(header) && !ANCIENT(header)) {
+ /*
+ * We now know that there is at least one active
+ * non-stale rdataset at this node.
+ */
+ empty_node = false;
+
+ /*
+ * If we found a type we were looking for, remember
+ * it.
+ */
+ if (header->type == type ||
+ (type == dns_rdatatype_any &&
+ RBTDB_RDATATYPE_BASE(header->type) != 0) ||
+ (cname_ok && header->type == dns_rdatatype_cname))
+ {
+ /*
+ * We've found the answer.
+ */
+ found = header;
+ if (header->type == dns_rdatatype_cname &&
+ cname_ok && cnamesig != NULL)
+ {
+ /*
+ * If we've already got the
+ * CNAME RRSIG, use it.
+ */
+ foundsig = cnamesig;
+ }
+ } else if (header->type == sigtype) {
+ /*
+ * We've found the RRSIG rdataset for our
+ * target type. Remember it.
+ */
+ foundsig = header;
+ } else if (header->type == RBTDB_RDATATYPE_NCACHEANY ||
+ header->type == negtype)
+ {
+ /*
+ * We've found a negative cache entry.
+ */
+ found = header;
+ } else if (header->type == dns_rdatatype_ns) {
+ /*
+ * Remember a NS rdataset even if we're
+ * not specifically looking for it, because
+ * we might need it later.
+ */
+ nsheader = header;
+ } else if (header->type == RBTDB_RDATATYPE_SIGNS) {
+ /*
+ * If we need the NS rdataset, we'll also
+ * need its signature.
+ */
+ nssig = header;
+ } else if (header->type == dns_rdatatype_nsec) {
+ nsecheader = header;
+ } else if (header->type == RBTDB_RDATATYPE_SIGNSEC) {
+ nsecsig = header;
+ } else if (cname_ok &&
+ header->type == RBTDB_RDATATYPE_SIGCNAME)
+ {
+ /*
+ * If we get a CNAME match, we'll also need
+ * its signature.
+ */
+ cnamesig = header;
+ }
+ header_prev = header;
+ } else {
+ header_prev = header;
+ }
+ }
+
+ if (empty_node) {
+ /*
+ * We have an exact match for the name, but there are no
+ * extant rdatasets. That means that this node doesn't
+ * meaningfully exist, and that we really have a partial match.
+ */
+ NODE_UNLOCK(lock, locktype);
+ goto find_ns;
+ }
+
+ /*
+ * If we didn't find what we were looking for...
+ */
+ if (found == NULL ||
+ (DNS_TRUST_ADDITIONAL(found->trust) &&
+ ((options & DNS_DBFIND_ADDITIONALOK) == 0)) ||
+ (found->trust == dns_trust_glue &&
+ ((options & DNS_DBFIND_GLUEOK) == 0)) ||
+ (DNS_TRUST_PENDING(found->trust) &&
+ ((options & DNS_DBFIND_PENDINGOK) == 0)))
+ {
+ /*
+ * Return covering NODATA NSEC record.
+ */
+ if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0 &&
+ nsecheader != NULL)
+ {
+ if (nodep != NULL) {
+ new_reference(search.rbtdb, node, locktype);
+ *nodep = node;
+ }
+ bind_rdataset(search.rbtdb, node, nsecheader,
+ search.now, locktype, rdataset);
+ if (need_headerupdate(nsecheader, search.now)) {
+ update = nsecheader;
+ }
+ if (nsecsig != NULL) {
+ bind_rdataset(search.rbtdb, node, nsecsig,
+ search.now, locktype,
+ sigrdataset);
+ if (need_headerupdate(nsecsig, search.now)) {
+ updatesig = nsecsig;
+ }
+ }
+ result = DNS_R_COVERINGNSEC;
+ goto node_exit;
+ }
+
+ /*
+ * If there is an NS rdataset at this node, then this is the
+ * deepest zone cut.
+ */
+ if (nsheader != NULL) {
+ if (nodep != NULL) {
+ new_reference(search.rbtdb, node, locktype);
+ *nodep = node;
+ }
+ bind_rdataset(search.rbtdb, node, nsheader, search.now,
+ locktype, rdataset);
+ if (need_headerupdate(nsheader, search.now)) {
+ update = nsheader;
+ }
+ if (nssig != NULL) {
+ bind_rdataset(search.rbtdb, node, nssig,
+ search.now, locktype,
+ sigrdataset);
+ if (need_headerupdate(nssig, search.now)) {
+ updatesig = nssig;
+ }
+ }
+ result = DNS_R_DELEGATION;
+ goto node_exit;
+ }
+
+ /*
+ * Go find the deepest zone cut.
+ */
+ NODE_UNLOCK(lock, locktype);
+ goto find_ns;
+ }
+
+ /*
+ * We found what we were looking for, or we found a CNAME.
+ */
+
+ if (nodep != NULL) {
+ new_reference(search.rbtdb, node, locktype);
+ *nodep = node;
+ }
+
+ if (NEGATIVE(found)) {
+ /*
+ * We found a negative cache entry.
+ */
+ if (NXDOMAIN(found)) {
+ result = DNS_R_NCACHENXDOMAIN;
+ } else {
+ result = DNS_R_NCACHENXRRSET;
+ }
+ } else if (type != found->type && type != dns_rdatatype_any &&
+ found->type == dns_rdatatype_cname)
+ {
+ /*
+ * We weren't doing an ANY query and we found a CNAME instead
+ * of the type we were looking for, so we need to indicate
+ * that result to the caller.
+ */
+ result = DNS_R_CNAME;
+ } else {
+ /*
+ * An ordinary successful query!
+ */
+ result = ISC_R_SUCCESS;
+ }
+
+ if (type != dns_rdatatype_any || result == DNS_R_NCACHENXDOMAIN ||
+ result == DNS_R_NCACHENXRRSET)
+ {
+ bind_rdataset(search.rbtdb, node, found, search.now, locktype,
+ rdataset);
+ if (need_headerupdate(found, search.now)) {
+ update = found;
+ }
+ if (!NEGATIVE(found) && foundsig != NULL) {
+ bind_rdataset(search.rbtdb, node, foundsig, search.now,
+ locktype, sigrdataset);
+ if (need_headerupdate(foundsig, search.now)) {
+ updatesig = foundsig;
+ }
+ }
+ }
+
+node_exit:
+ if ((update != NULL || updatesig != NULL) &&
+ locktype != isc_rwlocktype_write)
+ {
+ NODE_UNLOCK(lock, locktype);
+ NODE_LOCK(lock, isc_rwlocktype_write);
+ locktype = isc_rwlocktype_write;
+ POST(locktype);
+ }
+ if (update != NULL && need_headerupdate(update, search.now)) {
+ update_header(search.rbtdb, update, search.now);
+ }
+ if (updatesig != NULL && need_headerupdate(updatesig, search.now)) {
+ update_header(search.rbtdb, updatesig, search.now);
+ }
+
+ NODE_UNLOCK(lock, locktype);
+
+tree_exit:
+ RWUNLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ /*
+ * If we found a zonecut but aren't going to use it, we have to
+ * let go of it.
+ */
+ if (search.need_cleanup) {
+ node = search.zonecut;
+ INSIST(node != NULL);
+ lock = &(search.rbtdb->node_locks[node->locknum].lock);
+
+ NODE_LOCK(lock, isc_rwlocktype_read);
+ decrement_reference(search.rbtdb, node, 0, isc_rwlocktype_read,
+ isc_rwlocktype_none, false);
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ }
+
+ dns_rbtnodechain_reset(&search.chain);
+
+ update_cachestats(search.rbtdb, result);
+ return (result);
+}
+
+static isc_result_t
+cache_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep,
+ dns_name_t *foundname, dns_name_t *dcname,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ dns_rbtnode_t *node = NULL;
+ nodelock_t *lock;
+ isc_result_t result;
+ rbtdb_search_t search;
+ rdatasetheader_t *header, *header_prev, *header_next;
+ rdatasetheader_t *found, *foundsig;
+ unsigned int rbtoptions = DNS_RBTFIND_EMPTYDATA;
+ isc_rwlocktype_t locktype;
+ bool dcnull = (dcname == NULL);
+
+ search.rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(search.rbtdb));
+
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+
+ search.rbtversion = NULL;
+ search.serial = 1;
+ search.options = options;
+ search.copy_name = false;
+ search.need_cleanup = false;
+ search.wild = false;
+ search.zonecut = NULL;
+ dns_fixedname_init(&search.zonecut_name);
+ dns_rbtnodechain_init(&search.chain);
+ search.now = now;
+
+ if (dcnull) {
+ dcname = foundname;
+ }
+
+ if ((options & DNS_DBFIND_NOEXACT) != 0) {
+ rbtoptions |= DNS_RBTFIND_NOEXACT;
+ }
+
+ RWLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ /*
+ * Search down from the root of the tree.
+ */
+ result = dns_rbt_findnode(search.rbtdb->tree, name, dcname, &node,
+ &search.chain, rbtoptions, NULL, &search);
+
+ if (result == DNS_R_PARTIALMATCH) {
+ result = find_deepest_zonecut(&search, node, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ } else if (result != ISC_R_SUCCESS) {
+ goto tree_exit;
+ } else if (!dcnull) {
+ dns_name_copynf(dcname, foundname);
+ }
+
+ /*
+ * We now go looking for an NS rdataset at the node.
+ */
+
+ lock = &(search.rbtdb->node_locks[node->locknum].lock);
+ locktype = isc_rwlocktype_read;
+ NODE_LOCK(lock, locktype);
+
+ found = NULL;
+ foundsig = NULL;
+ header_prev = NULL;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (check_stale_header(node, header, &locktype, lock, &search,
+ &header_prev))
+ {
+ /*
+ * The function dns_rbt_findnode found us the a matching
+ * node for 'name' and stored the result in 'dcname'.
+ * This is the deepest known zonecut in our database.
+ * However, this node may be stale and if serve-stale
+ * is not enabled (in other words 'stale-answer-enable'
+ * is set to no), this node may not be used as a
+ * zonecut we know about. If so, find the deepest
+ * zonecut from this node up and return that instead.
+ */
+ NODE_UNLOCK(lock, locktype);
+ result = find_deepest_zonecut(&search, node, nodep,
+ foundname, rdataset,
+ sigrdataset);
+ dns_name_copynf(foundname, dcname);
+ goto tree_exit;
+ } else if (EXISTS(header) && !ANCIENT(header)) {
+ /*
+ * If we found a type we were looking for, remember
+ * it.
+ */
+ if (header->type == dns_rdatatype_ns) {
+ /*
+ * Remember a NS rdataset even if we're
+ * not specifically looking for it, because
+ * we might need it later.
+ */
+ found = header;
+ } else if (header->type == RBTDB_RDATATYPE_SIGNS) {
+ /*
+ * If we need the NS rdataset, we'll also
+ * need its signature.
+ */
+ foundsig = header;
+ }
+ header_prev = header;
+ } else {
+ header_prev = header;
+ }
+ }
+
+ if (found == NULL) {
+ /*
+ * No NS records here.
+ */
+ NODE_UNLOCK(lock, locktype);
+ result = find_deepest_zonecut(&search, node, nodep, foundname,
+ rdataset, sigrdataset);
+ goto tree_exit;
+ }
+
+ if (nodep != NULL) {
+ new_reference(search.rbtdb, node, locktype);
+ *nodep = node;
+ }
+
+ bind_rdataset(search.rbtdb, node, found, search.now, locktype,
+ rdataset);
+ if (foundsig != NULL) {
+ bind_rdataset(search.rbtdb, node, foundsig, search.now,
+ locktype, sigrdataset);
+ }
+
+ if (need_headerupdate(found, search.now) ||
+ (foundsig != NULL && need_headerupdate(foundsig, search.now)))
+ {
+ if (locktype != isc_rwlocktype_write) {
+ NODE_UNLOCK(lock, locktype);
+ NODE_LOCK(lock, isc_rwlocktype_write);
+ locktype = isc_rwlocktype_write;
+ POST(locktype);
+ }
+ if (need_headerupdate(found, search.now)) {
+ update_header(search.rbtdb, found, search.now);
+ }
+ if (foundsig != NULL && need_headerupdate(foundsig, search.now))
+ {
+ update_header(search.rbtdb, foundsig, search.now);
+ }
+ }
+
+ NODE_UNLOCK(lock, locktype);
+
+tree_exit:
+ RWUNLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read);
+
+ INSIST(!search.need_cleanup);
+
+ dns_rbtnodechain_reset(&search.chain);
+
+ if (result == DNS_R_DELEGATION) {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static void
+attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *node = (dns_rbtnode_t *)source;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&node->references);
+
+ *targetp = source;
+}
+
+static void
+detachnode(dns_db_t *db, dns_dbnode_t **targetp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *node;
+ bool want_free = false;
+ bool inactive = false;
+ rbtdb_nodelock_t *nodelock;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(targetp != NULL && *targetp != NULL);
+
+ node = (dns_rbtnode_t *)(*targetp);
+ nodelock = &rbtdb->node_locks[node->locknum];
+
+ NODE_LOCK(&nodelock->lock, isc_rwlocktype_read);
+
+ if (decrement_reference(rbtdb, node, 0, isc_rwlocktype_read,
+ isc_rwlocktype_none, false))
+ {
+ if (isc_refcount_current(&nodelock->references) == 0 &&
+ nodelock->exiting)
+ {
+ inactive = true;
+ }
+ }
+
+ NODE_UNLOCK(&nodelock->lock, isc_rwlocktype_read);
+
+ *targetp = NULL;
+
+ if (inactive) {
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+ rbtdb->active--;
+ if (rbtdb->active == 0) {
+ want_free = true;
+ }
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+ if (want_free) {
+ char buf[DNS_NAME_FORMATSIZE];
+ if (dns_name_dynamic(&rbtdb->common.origin)) {
+ dns_name_format(&rbtdb->common.origin, buf,
+ sizeof(buf));
+ } else {
+ strlcpy(buf, "<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 + rbtdb->serve_stale_ttl <=
+ now - RBTDB_VIRTUAL)
+ {
+ /*
+ * We don't check if refcurrent(rbtnode) == 0 and try
+ * to free like we do in cache_find(), because
+ * refcurrent(rbtnode) must be non-zero. This is so
+ * because 'node' is an argument to the function.
+ */
+ mark_header_ancient(rbtdb, header);
+ if (log) {
+ isc_log_write(dns_lctx, category, module, level,
+ "overmem cache: ancient %s",
+ printname);
+ }
+ } else if (force_expire) {
+ if (!RETAIN(header)) {
+ set_ttl(rbtdb, header, 0);
+ mark_header_ancient(rbtdb, header);
+ } else if (log) {
+ isc_log_write(dns_lctx, category, module, level,
+ "overmem cache: "
+ "reprieve by RETAIN() %s",
+ printname);
+ }
+ } else if (isc_mem_isovermem(rbtdb->common.mctx) && log) {
+ isc_log_write(dns_lctx, category, module, level,
+ "overmem cache: saved %s", printname);
+ }
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+overmem(dns_db_t *db, bool over) {
+ /* This is an empty callback. See adb.c:water() */
+
+ UNUSED(db);
+ UNUSED(over);
+
+ return;
+}
+
+static void
+printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = node;
+ bool first;
+ uint32_t refs;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ refs = isc_refcount_current(&rbtnode->references);
+ fprintf(out, "node %p, %" PRIu32 " references, locknum = %u\n", rbtnode,
+ refs, rbtnode->locknum);
+ if (rbtnode->data != NULL) {
+ rdatasetheader_t *current, *top_next;
+
+ for (current = rbtnode->data; current != NULL;
+ current = top_next)
+ {
+ top_next = current->next;
+ first = true;
+ fprintf(out, "\ttype %u", current->type);
+ do {
+ uint_least16_t attributes = atomic_load_acquire(
+ &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 + rbtdb->serve_stale_ttl <
+ now - RBTDB_VIRTUAL) &&
+ (locktype == isc_rwlocktype_write ||
+ NODE_TRYUPGRADE(lock) == ISC_R_SUCCESS))
+ {
+ /*
+ * We update the node's status only when we
+ * can get write access.
+ */
+ locktype = isc_rwlocktype_write;
+
+ /*
+ * We don't check if refcurrent(rbtnode) == 0
+ * and try to free like we do in cache_find(),
+ * because refcurrent(rbtnode) must be
+ * non-zero. This is so because 'node' is an
+ * argument to the function.
+ */
+ mark_header_ancient(rbtdb, header);
+ }
+ } else if (EXISTS(header) && !ANCIENT(header)) {
+ if (header->type == matchtype) {
+ found = header;
+ } else if (header->type == RBTDB_RDATATYPE_NCACHEANY ||
+ header->type == negtype)
+ {
+ found = header;
+ } else if (header->type == sigmatchtype) {
+ foundsig = header;
+ }
+ }
+ }
+ if (found != NULL) {
+ bind_rdataset(rbtdb, rbtnode, found, now, locktype, rdataset);
+ if (!NEGATIVE(found) && foundsig != NULL) {
+ bind_rdataset(rbtdb, rbtnode, foundsig, now, locktype,
+ sigrdataset);
+ }
+ }
+
+ NODE_UNLOCK(lock, locktype);
+
+ if (found == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (NEGATIVE(found)) {
+ /*
+ * We found a negative cache entry.
+ */
+ if (NXDOMAIN(found)) {
+ result = DNS_R_NCACHENXDOMAIN;
+ } else {
+ result = DNS_R_NCACHENXRRSET;
+ }
+ }
+
+ update_cachestats(rbtdb, result);
+
+ return (result);
+}
+
+static isc_result_t
+allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ rbtdb_version_t *rbtversion = version;
+ rbtdb_rdatasetiter_t *iterator;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ iterator = isc_mem_get(rbtdb->common.mctx, sizeof(*iterator));
+
+ if ((db->attributes & DNS_DBATTR_CACHE) == 0) {
+ now = 0;
+ if (rbtversion == NULL) {
+ currentversion(
+ db, (dns_dbversion_t **)(void *)(&rbtversion));
+ } else {
+ INSIST(rbtversion->rbtdb == rbtdb);
+
+ (void)isc_refcount_increment(&rbtversion->references);
+ }
+ } else {
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+ rbtversion = NULL;
+ }
+
+ iterator->common.magic = DNS_RDATASETITER_MAGIC;
+ iterator->common.methods = &rdatasetiter_methods;
+ iterator->common.db = db;
+ iterator->common.node = node;
+ iterator->common.version = (dns_dbversion_t *)rbtversion;
+ iterator->common.options = options;
+ iterator->common.now = now;
+
+ isc_refcount_increment(&rbtnode->references);
+
+ iterator->current = NULL;
+
+ *iteratorp = (dns_rdatasetiter_t *)iterator;
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+cname_and_other_data(dns_rbtnode_t *node, rbtdb_serial_t serial) {
+ rdatasetheader_t *header, *header_next;
+ bool cname, other_data;
+ dns_rdatatype_t rdtype;
+
+ /*
+ * The caller must hold the node lock.
+ */
+
+ /*
+ * Look for CNAME and "other data" rdatasets active in our version.
+ */
+ cname = false;
+ other_data = false;
+ for (header = node->data; header != NULL; header = header_next) {
+ header_next = header->next;
+ if (header->type == dns_rdatatype_cname) {
+ /*
+ * Look for an active extant CNAME.
+ */
+ do {
+ if (header->serial <= serial && !IGNORE(header))
+ {
+ /*
+ * Is this a "this rdataset doesn't
+ * exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ cname = true;
+ }
+ } else {
+ /*
+ * Look for active extant "other data".
+ *
+ * "Other data" is any rdataset whose type is not
+ * KEY, NSEC, SIG or RRSIG.
+ */
+ rdtype = RBTDB_RDATATYPE_BASE(header->type);
+ if (rdtype != dns_rdatatype_key &&
+ rdtype != dns_rdatatype_sig &&
+ rdtype != dns_rdatatype_nsec &&
+ rdtype != dns_rdatatype_rrsig)
+ {
+ /*
+ * Is it active and extant?
+ */
+ do {
+ if (header->serial <= serial &&
+ !IGNORE(header))
+ {
+ /*
+ * Is this a "this rdataset
+ * doesn't exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ other_data = true;
+ }
+ }
+ }
+ }
+
+ if (cname && other_data) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static void
+resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader) {
+ INSIST(!IS_CACHE(rbtdb));
+ INSIST(newheader->heap_index == 0);
+ INSIST(!ISC_LINK_LINKED(newheader, link));
+
+ isc_heap_insert(rbtdb->heaps[idx], newheader);
+}
+
+/*
+ * node write lock must be held.
+ */
+static void
+resign_delete(dns_rbtdb_t *rbtdb, rbtdb_version_t *version,
+ rdatasetheader_t *header) {
+ /*
+ * Remove the old header from the heap
+ */
+ if (header != NULL && header->heap_index != 0) {
+ isc_heap_delete(rbtdb->heaps[header->node->locknum],
+ header->heap_index);
+ header->heap_index = 0;
+ if (version != NULL) {
+ new_reference(rbtdb, header->node,
+ isc_rwlocktype_write);
+ ISC_LIST_APPEND(version->resigned_list, header, link);
+ }
+ }
+}
+
+static uint64_t
+recordsize(rdatasetheader_t *header, unsigned int namelen) {
+ return (dns_rdataslab_rdatasize((unsigned char *)header,
+ sizeof(*header)) +
+ sizeof(dns_ttl_t) + sizeof(dns_rdatatype_t) +
+ sizeof(dns_rdataclass_t) + namelen);
+}
+
+static void
+update_recordsandxfrsize(bool add, rbtdb_version_t *rbtversion,
+ rdatasetheader_t *header, unsigned int namelen) {
+ unsigned char *hdr = (unsigned char *)header;
+ size_t hdrsize = sizeof(*header);
+
+ RWLOCK(&rbtversion->rwlock, isc_rwlocktype_write);
+ if (add) {
+ rbtversion->records += dns_rdataslab_count(hdr, hdrsize);
+ rbtversion->xfrsize += recordsize(header, namelen);
+ } else {
+ rbtversion->records -= dns_rdataslab_count(hdr, hdrsize);
+ rbtversion->xfrsize -= recordsize(header, namelen);
+ }
+ RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_write);
+}
+
+/*
+ * write lock on rbtnode must be held.
+ */
+static isc_result_t
+add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, const dns_name_t *nodename,
+ rbtdb_version_t *rbtversion, rdatasetheader_t *newheader,
+ unsigned int options, bool loading, dns_rdataset_t *addedrdataset,
+ isc_stdtime_t now) {
+ rbtdb_changed_t *changed = NULL;
+ rdatasetheader_t *topheader = NULL, *topheader_prev = NULL;
+ rdatasetheader_t *header = NULL, *sigheader = NULL;
+ unsigned char *merged = NULL;
+ isc_result_t result;
+ bool header_nx;
+ bool newheader_nx;
+ bool merge;
+ dns_rdatatype_t rdtype, covers;
+ rbtdb_rdatatype_t negtype, sigtype;
+ dns_trust_t trust;
+ int idx;
+
+ /*
+ * Add an rdatasetheader_t to a node.
+ */
+
+ /*
+ * Caller must be holding the node lock.
+ */
+
+ if ((options & DNS_DBADD_MERGE) != 0) {
+ REQUIRE(rbtversion != NULL);
+ merge = true;
+ } else {
+ merge = false;
+ }
+
+ if ((options & DNS_DBADD_FORCE) != 0) {
+ trust = dns_trust_ultimate;
+ } else {
+ trust = newheader->trust;
+ }
+
+ if (rbtversion != NULL && !loading) {
+ /*
+ * We always add a changed record, even if no changes end up
+ * being made to this node, because it's harmless and
+ * simplifies the code.
+ */
+ changed = add_changed(rbtdb, rbtversion, rbtnode);
+ if (changed == NULL) {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ return (ISC_R_NOMEMORY);
+ }
+ }
+
+ newheader_nx = NONEXISTENT(newheader) ? true : false;
+ topheader_prev = NULL;
+ sigheader = NULL;
+ negtype = 0;
+ if (rbtversion == NULL && !newheader_nx) {
+ rdtype = RBTDB_RDATATYPE_BASE(newheader->type);
+ covers = RBTDB_RDATATYPE_EXT(newheader->type);
+ sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, covers);
+ if (NEGATIVE(newheader)) {
+ /*
+ * We're adding a negative cache entry.
+ */
+ if (covers == dns_rdatatype_any) {
+ /*
+ * If we're adding an negative cache entry
+ * which covers all types (NXDOMAIN,
+ * NODATA(QTYPE=ANY)),
+ *
+ * We make all other data ancient so that the
+ * only rdataset that can be found at this
+ * node is the negative cache entry.
+ */
+ for (topheader = rbtnode->data;
+ topheader != NULL;
+ topheader = topheader->next)
+ {
+ set_ttl(rbtdb, topheader, 0);
+ mark_header_ancient(rbtdb, topheader);
+ }
+ goto find_header;
+ }
+ /*
+ * Otherwise look for any RRSIGs of the given
+ * type so they can be marked ancient later.
+ */
+ for (topheader = rbtnode->data; topheader != NULL;
+ topheader = topheader->next)
+ {
+ if (topheader->type == sigtype) {
+ sigheader = topheader;
+ }
+ }
+ negtype = RBTDB_RDATATYPE_VALUE(covers, 0);
+ } else {
+ /*
+ * We're adding something that isn't a
+ * negative cache entry. Look for an extant
+ * non-ancient NXDOMAIN/NODATA(QTYPE=ANY) negative
+ * cache entry. If we're adding an RRSIG, also
+ * check for an extant non-ancient NODATA ncache
+ * entry which covers the same type as the RRSIG.
+ */
+ for (topheader = rbtnode->data; topheader != NULL;
+ topheader = topheader->next)
+ {
+ if ((topheader->type ==
+ RBTDB_RDATATYPE_NCACHEANY) ||
+ (newheader->type == sigtype &&
+ topheader->type ==
+ RBTDB_RDATATYPE_VALUE(0, covers)))
+ {
+ break;
+ }
+ }
+ if (topheader != NULL && EXISTS(topheader) &&
+ ACTIVE(topheader, now))
+ {
+ /*
+ * Found one.
+ */
+ if (trust < topheader->trust) {
+ /*
+ * The NXDOMAIN/NODATA(QTYPE=ANY)
+ * is more trusted.
+ */
+ free_rdataset(rbtdb, rbtdb->common.mctx,
+ newheader);
+ if (addedrdataset != NULL) {
+ bind_rdataset(
+ rbtdb, rbtnode,
+ topheader, now,
+ isc_rwlocktype_write,
+ addedrdataset);
+ }
+ return (DNS_R_UNCHANGED);
+ }
+ /*
+ * The new rdataset is better. Expire the
+ * ncache entry.
+ */
+ set_ttl(rbtdb, topheader, 0);
+ mark_header_ancient(rbtdb, topheader);
+ topheader = NULL;
+ goto find_header;
+ }
+ negtype = RBTDB_RDATATYPE_VALUE(0, rdtype);
+ }
+ }
+
+ for (topheader = rbtnode->data; topheader != NULL;
+ topheader = topheader->next)
+ {
+ if (topheader->type == newheader->type ||
+ topheader->type == negtype)
+ {
+ break;
+ }
+ topheader_prev = topheader;
+ }
+
+find_header:
+ /*
+ * If header isn't NULL, we've found the right type. There may be
+ * IGNORE rdatasets between the top of the chain and the first real
+ * data. We skip over them.
+ */
+ header = topheader;
+ while (header != NULL && IGNORE(header)) {
+ header = header->down;
+ }
+ if (header != NULL) {
+ header_nx = NONEXISTENT(header) ? true : false;
+
+ /*
+ * Deleting an already non-existent rdataset has no effect.
+ */
+ if (header_nx && newheader_nx) {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ return (DNS_R_UNCHANGED);
+ }
+
+ /*
+ * Trying to add an rdataset with lower trust to a cache
+ * DB has no effect, provided that the cache data isn't
+ * stale. If the cache data is stale, new lower trust
+ * data will supersede it below. Unclear what the best
+ * policy is here.
+ */
+ if (rbtversion == NULL && trust < header->trust &&
+ (ACTIVE(header, now) || header_nx))
+ {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ if (addedrdataset != NULL) {
+ bind_rdataset(rbtdb, rbtnode, header, now,
+ isc_rwlocktype_write,
+ addedrdataset);
+ }
+ return (DNS_R_UNCHANGED);
+ }
+
+ /*
+ * Don't merge if a nonexistent rdataset is involved.
+ */
+ if (merge && (header_nx || newheader_nx)) {
+ merge = false;
+ }
+
+ /*
+ * If 'merge' is true, we'll try to create a new rdataset
+ * that is the union of 'newheader' and 'header'.
+ */
+ if (merge) {
+ unsigned int flags = 0;
+ INSIST(rbtversion->serial >= header->serial);
+ merged = NULL;
+ result = ISC_R_SUCCESS;
+
+ if ((options & DNS_DBADD_EXACT) != 0) {
+ flags |= DNS_RDATASLAB_EXACT;
+ }
+ /*
+ * TTL use here is irrelevant to the cache;
+ * merge is only done with zonedbs.
+ */
+ if ((options & DNS_DBADD_EXACTTTL) != 0 &&
+ newheader->rdh_ttl != header->rdh_ttl)
+ {
+ result = DNS_R_NOTEXACT;
+ } else if (newheader->rdh_ttl != header->rdh_ttl) {
+ flags |= DNS_RDATASLAB_FORCE;
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = dns_rdataslab_merge(
+ (unsigned char *)header,
+ (unsigned char *)newheader,
+ (unsigned int)(sizeof(*newheader)),
+ rbtdb->common.mctx,
+ rbtdb->common.rdclass,
+ (dns_rdatatype_t)header->type, flags,
+ &merged);
+ }
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * If 'header' has the same serial number as
+ * we do, we could clean it up now if we knew
+ * that our caller had no references to it.
+ * We don't know this, however, so we leave it
+ * alone. It will get cleaned up when
+ * clean_zone_node() runs.
+ */
+ free_rdataset(rbtdb, rbtdb->common.mctx,
+ newheader);
+ newheader = (rdatasetheader_t *)merged;
+ init_rdataset(rbtdb, newheader);
+ update_newheader(newheader, header);
+ if (loading && RESIGN(newheader) &&
+ RESIGN(header) &&
+ resign_sooner(header, newheader))
+ {
+ newheader->resign = header->resign;
+ newheader->resign_lsb =
+ header->resign_lsb;
+ }
+ } else {
+ free_rdataset(rbtdb, rbtdb->common.mctx,
+ newheader);
+ return (result);
+ }
+ }
+ /*
+ * Don't replace existing NS, A and AAAA RRsets in the
+ * cache if they are already exist. This prevents named
+ * being locked to old servers. Don't lower trust of
+ * existing record if the update is forced. Nothing
+ * special to be done w.r.t stale data; it gets replaced
+ * normally further down.
+ */
+ if (IS_CACHE(rbtdb) && ACTIVE(header, now) &&
+ header->type == dns_rdatatype_ns && !header_nx &&
+ !newheader_nx && header->trust >= newheader->trust &&
+ dns_rdataslab_equalx((unsigned char *)header,
+ (unsigned char *)newheader,
+ (unsigned int)(sizeof(*newheader)),
+ rbtdb->common.rdclass,
+ (dns_rdatatype_t)header->type))
+ {
+ /*
+ * Honour the new ttl if it is less than the
+ * older one.
+ */
+ if (header->rdh_ttl > newheader->rdh_ttl) {
+ set_ttl(rbtdb, header, newheader->rdh_ttl);
+ }
+ if (header->noqname == NULL &&
+ newheader->noqname != NULL)
+ {
+ header->noqname = newheader->noqname;
+ newheader->noqname = NULL;
+ }
+ if (header->closest == NULL &&
+ newheader->closest != NULL)
+ {
+ header->closest = newheader->closest;
+ newheader->closest = NULL;
+ }
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ if (addedrdataset != NULL) {
+ bind_rdataset(rbtdb, rbtnode, header, now,
+ isc_rwlocktype_write,
+ addedrdataset);
+ }
+ return (ISC_R_SUCCESS);
+ }
+ /*
+ * If we have will be replacing a NS RRset force its TTL
+ * to be no more than the current NS RRset's TTL. This
+ * ensures the delegations that are withdrawn are honoured.
+ */
+ if (IS_CACHE(rbtdb) && ACTIVE(header, now) &&
+ header->type == dns_rdatatype_ns && !header_nx &&
+ !newheader_nx && header->trust <= newheader->trust)
+ {
+ if (newheader->rdh_ttl > header->rdh_ttl) {
+ newheader->rdh_ttl = header->rdh_ttl;
+ }
+ }
+ if (IS_CACHE(rbtdb) && ACTIVE(header, now) &&
+ (options & DNS_DBADD_PREFETCH) == 0 &&
+ (header->type == dns_rdatatype_a ||
+ header->type == dns_rdatatype_aaaa ||
+ header->type == dns_rdatatype_ds ||
+ header->type == RBTDB_RDATATYPE_SIGDS) &&
+ !header_nx && !newheader_nx &&
+ header->trust >= newheader->trust &&
+ dns_rdataslab_equal((unsigned char *)header,
+ (unsigned char *)newheader,
+ (unsigned int)(sizeof(*newheader))))
+ {
+ /*
+ * Honour the new ttl if it is less than the
+ * older one.
+ */
+ if (header->rdh_ttl > newheader->rdh_ttl) {
+ set_ttl(rbtdb, header, newheader->rdh_ttl);
+ }
+ if (header->noqname == NULL &&
+ newheader->noqname != NULL)
+ {
+ header->noqname = newheader->noqname;
+ newheader->noqname = NULL;
+ }
+ if (header->closest == NULL &&
+ newheader->closest != NULL)
+ {
+ header->closest = newheader->closest;
+ newheader->closest = NULL;
+ }
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ if (addedrdataset != NULL) {
+ bind_rdataset(rbtdb, rbtnode, header, now,
+ isc_rwlocktype_write,
+ addedrdataset);
+ }
+ return (ISC_R_SUCCESS);
+ }
+ INSIST(rbtversion == NULL ||
+ rbtversion->serial >= topheader->serial);
+ if (loading) {
+ newheader->down = NULL;
+ idx = newheader->node->locknum;
+ if (IS_CACHE(rbtdb)) {
+ if (ZEROTTL(newheader)) {
+ ISC_LIST_APPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ } else {
+ ISC_LIST_PREPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ }
+ INSIST(rbtdb->heaps != NULL);
+ isc_heap_insert(rbtdb->heaps[idx], newheader);
+ } else if (RESIGN(newheader)) {
+ resign_insert(rbtdb, idx, newheader);
+ /*
+ * Don't call resign_delete as we don't need
+ * to reverse the delete. The free_rdataset
+ * call below will clean up the heap entry.
+ */
+ }
+
+ /*
+ * There are no other references to 'header' when
+ * loading, so we MAY clean up 'header' now.
+ * Since we don't generate changed records when
+ * loading, we MUST clean up 'header' now.
+ */
+ if (topheader_prev != NULL) {
+ topheader_prev->next = newheader;
+ } else {
+ rbtnode->data = newheader;
+ }
+ newheader->next = topheader->next;
+ if (rbtversion != NULL && !header_nx) {
+ update_recordsandxfrsize(false, rbtversion,
+ header,
+ nodename->length);
+ }
+ free_rdataset(rbtdb, rbtdb->common.mctx, header);
+ } else {
+ idx = newheader->node->locknum;
+ if (IS_CACHE(rbtdb)) {
+ INSIST(rbtdb->heaps != NULL);
+ isc_heap_insert(rbtdb->heaps[idx], newheader);
+ if (ZEROTTL(newheader)) {
+ ISC_LIST_APPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ } else {
+ ISC_LIST_PREPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ }
+ } else if (RESIGN(newheader)) {
+ resign_insert(rbtdb, idx, newheader);
+ resign_delete(rbtdb, rbtversion, header);
+ }
+ if (topheader_prev != NULL) {
+ topheader_prev->next = newheader;
+ } else {
+ rbtnode->data = newheader;
+ }
+ newheader->next = topheader->next;
+ newheader->down = topheader;
+ topheader->next = newheader;
+ rbtnode->dirty = 1;
+ if (changed != NULL) {
+ changed->dirty = true;
+ }
+ if (rbtversion == NULL) {
+ set_ttl(rbtdb, header, 0);
+ mark_header_ancient(rbtdb, header);
+ if (sigheader != NULL) {
+ set_ttl(rbtdb, sigheader, 0);
+ mark_header_ancient(rbtdb, sigheader);
+ }
+ }
+ if (rbtversion != NULL && !header_nx) {
+ update_recordsandxfrsize(false, rbtversion,
+ header,
+ nodename->length);
+ }
+ }
+ } else {
+ /*
+ * No non-IGNORED rdatasets of the given type exist at
+ * this node.
+ */
+
+ /*
+ * If we're trying to delete the type, don't bother.
+ */
+ if (newheader_nx) {
+ free_rdataset(rbtdb, rbtdb->common.mctx, newheader);
+ return (DNS_R_UNCHANGED);
+ }
+
+ idx = newheader->node->locknum;
+ if (IS_CACHE(rbtdb)) {
+ isc_heap_insert(rbtdb->heaps[idx], newheader);
+ if (ZEROTTL(newheader)) {
+ ISC_LIST_APPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ } else {
+ ISC_LIST_PREPEND(rbtdb->rdatasets[idx],
+ newheader, link);
+ }
+ } else if (RESIGN(newheader)) {
+ resign_insert(rbtdb, idx, newheader);
+ resign_delete(rbtdb, rbtversion, header);
+ }
+
+ if (topheader != NULL) {
+ /*
+ * We have an list of rdatasets of the given type,
+ * but they're all marked IGNORE. We simply insert
+ * the new rdataset at the head of the list.
+ *
+ * Ignored rdatasets cannot occur during loading, so
+ * we INSIST on it.
+ */
+ INSIST(!loading);
+ INSIST(rbtversion == NULL ||
+ rbtversion->serial >= topheader->serial);
+ if (topheader_prev != NULL) {
+ topheader_prev->next = newheader;
+ } else {
+ rbtnode->data = newheader;
+ }
+ newheader->next = topheader->next;
+ newheader->down = topheader;
+ topheader->next = newheader;
+ rbtnode->dirty = 1;
+ if (changed != NULL) {
+ changed->dirty = true;
+ }
+ } else {
+ /*
+ * No rdatasets of the given type exist at the node.
+ */
+ newheader->next = rbtnode->data;
+ newheader->down = NULL;
+ rbtnode->data = newheader;
+ }
+ }
+
+ if (rbtversion != NULL && !newheader_nx) {
+ update_recordsandxfrsize(true, rbtversion, newheader,
+ nodename->length);
+ }
+
+ /*
+ * Check if the node now contains CNAME and other data.
+ */
+ if (rbtversion != NULL &&
+ cname_and_other_data(rbtnode, rbtversion->serial))
+ {
+ return (DNS_R_CNAMEANDOTHER);
+ }
+
+ if (addedrdataset != NULL) {
+ bind_rdataset(rbtdb, rbtnode, newheader, now,
+ isc_rwlocktype_write, addedrdataset);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+delegating_type(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
+ rbtdb_rdatatype_t type) {
+ if (IS_CACHE(rbtdb)) {
+ if (type == dns_rdatatype_dname) {
+ return (true);
+ } else {
+ return (false);
+ }
+ } else if (type == dns_rdatatype_dname ||
+ (type == dns_rdatatype_ns &&
+ (node != rbtdb->origin_node || IS_STUB(rbtdb))))
+ {
+ return (true);
+ }
+ return (false);
+}
+
+static isc_result_t
+addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
+ dns_rdataset_t *rdataset) {
+ struct noqname *noqname;
+ isc_mem_t *mctx = rbtdb->common.mctx;
+ dns_name_t name;
+ dns_rdataset_t neg, negsig;
+ isc_result_t result;
+ isc_region_t r;
+
+ dns_name_init(&name, NULL);
+ dns_rdataset_init(&neg);
+ dns_rdataset_init(&negsig);
+
+ result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ noqname = isc_mem_get(mctx, sizeof(*noqname));
+ dns_name_init(&noqname->name, NULL);
+ noqname->neg = NULL;
+ noqname->negsig = NULL;
+ noqname->type = neg.type;
+ dns_name_dup(&name, mctx, &noqname->name);
+ result = dns_rdataslab_fromrdataset(&neg, mctx, &r, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ noqname->neg = r.base;
+ result = dns_rdataslab_fromrdataset(&negsig, mctx, &r, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ noqname->negsig = r.base;
+ dns_rdataset_disassociate(&neg);
+ dns_rdataset_disassociate(&negsig);
+ newheader->noqname = noqname;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dns_rdataset_disassociate(&neg);
+ dns_rdataset_disassociate(&negsig);
+ free_noqname(mctx, &noqname);
+ return (result);
+}
+
+static isc_result_t
+addclosest(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
+ dns_rdataset_t *rdataset) {
+ struct noqname *closest;
+ isc_mem_t *mctx = rbtdb->common.mctx;
+ dns_name_t name;
+ dns_rdataset_t neg, negsig;
+ isc_result_t result;
+ isc_region_t r;
+
+ dns_name_init(&name, NULL);
+ dns_rdataset_init(&neg);
+ dns_rdataset_init(&negsig);
+
+ result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ closest = isc_mem_get(mctx, sizeof(*closest));
+ dns_name_init(&closest->name, NULL);
+ closest->neg = NULL;
+ closest->negsig = NULL;
+ closest->type = neg.type;
+ dns_name_dup(&name, mctx, &closest->name);
+ result = dns_rdataslab_fromrdataset(&neg, mctx, &r, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ closest->neg = r.base;
+ result = dns_rdataslab_fromrdataset(&negsig, mctx, &r, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ closest->negsig = r.base;
+ dns_rdataset_disassociate(&neg);
+ dns_rdataset_disassociate(&negsig);
+ newheader->closest = closest;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dns_rdataset_disassociate(&neg);
+ dns_rdataset_disassociate(&negsig);
+ free_noqname(mctx, &closest);
+ return (result);
+}
+
+static dns_dbmethods_t zone_methods;
+
+static size_t
+rdataset_size(rdatasetheader_t *header) {
+ if (!NONEXISTENT(header)) {
+ return (dns_rdataslab_size((unsigned char *)header,
+ sizeof(*header)));
+ }
+
+ return (sizeof(*header));
+}
+
+static isc_result_t
+addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *addedrdataset) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ rbtdb_version_t *rbtversion = version;
+ isc_region_t region;
+ rdatasetheader_t *newheader;
+ rdatasetheader_t *header;
+ isc_result_t result;
+ bool delegating;
+ bool newnsec;
+ bool tree_locked = false;
+ bool cache_is_overmem = false;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
+
+ if (rbtdb->common.methods == &zone_methods) {
+ /*
+ * SOA records are only allowed at top of zone.
+ */
+ if (rdataset->type == dns_rdatatype_soa &&
+ node != rbtdb->origin_node)
+ {
+ return (DNS_R_NOTZONETOP);
+ }
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ REQUIRE(((rbtnode->nsec == DNS_RBT_NSEC_NSEC3 &&
+ (rdataset->type == dns_rdatatype_nsec3 ||
+ rdataset->covers == dns_rdatatype_nsec3)) ||
+ (rbtnode->nsec != DNS_RBT_NSEC_NSEC3 &&
+ rdataset->type != dns_rdatatype_nsec3 &&
+ rdataset->covers != dns_rdatatype_nsec3)));
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ }
+
+ if (rbtversion == NULL) {
+ if (now == 0) {
+ isc_stdtime_get(&now);
+ }
+ } else {
+ now = 0;
+ }
+
+ result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
+ &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 += rbtdb->serve_stale_ttl;
+ }
+
+ if (rdh_ttl < now - RBTDB_VIRTUAL) {
+ expire_header(rbtdb, header, tree_locked,
+ expire_ttl);
+ }
+ }
+
+ /*
+ * If we've been holding a write lock on the tree just for
+ * cleaning, we can release it now. However, we still need the
+ * node lock.
+ */
+ if (tree_locked && !delegating && !newnsec) {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ tree_locked = false;
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+ if (newnsec) {
+ dns_rbtnode_t *nsecnode;
+
+ nsecnode = NULL;
+ result = dns_rbt_addnode(rbtdb->nsec, name, &nsecnode);
+ if (result == ISC_R_SUCCESS) {
+ nsecnode->nsec = DNS_RBT_NSEC_NSEC;
+ rbtnode->nsec = DNS_RBT_NSEC_HAS_NSEC;
+ } else if (result == ISC_R_EXISTS) {
+ rbtnode->nsec = DNS_RBT_NSEC_HAS_NSEC;
+ result = ISC_R_SUCCESS;
+ }
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ result = add32(rbtdb, rbtnode, name, rbtversion, newheader,
+ options, false, addedrdataset, now);
+ }
+ if (result == ISC_R_SUCCESS && delegating) {
+ rbtnode->find_callback = 1;
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+
+ if (tree_locked) {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ }
+
+ /*
+ * Update the zone's secure status. If version is non-NULL
+ * this is deferred until closeversion() is called.
+ */
+ if (result == ISC_R_SUCCESS && version == NULL && !IS_CACHE(rbtdb)) {
+ iszonesecure(db, version, rbtdb->origin_node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *newrdataset) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ rbtdb_version_t *rbtversion = version;
+ dns_fixedname_t fname;
+ dns_name_t *nodename = dns_fixedname_initname(&fname);
+ rdatasetheader_t *topheader, *topheader_prev, *header, *newheader;
+ unsigned char *subresult;
+ isc_region_t region;
+ isc_result_t result;
+ rbtdb_changed_t *changed;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(rbtversion != NULL && rbtversion->rbtdb == rbtdb);
+
+ if (rbtdb->common.methods == &zone_methods) {
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ REQUIRE(((rbtnode->nsec == DNS_RBT_NSEC_NSEC3 &&
+ (rdataset->type == dns_rdatatype_nsec3 ||
+ rdataset->covers == dns_rdatatype_nsec3)) ||
+ (rbtnode->nsec != DNS_RBT_NSEC_NSEC3 &&
+ rdataset->type != dns_rdatatype_nsec3 &&
+ rdataset->covers != dns_rdatatype_nsec3)));
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ }
+
+ nodefullname(db, node, nodename);
+
+ result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
+ &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
+rbt_datafixer(dns_rbtnode_t *rbtnode, void *base, size_t filesize, void *arg,
+ uint64_t *crc) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)arg;
+ rdatasetheader_t *header;
+ unsigned char *limit = ((unsigned char *)base) + filesize;
+
+ REQUIRE(rbtnode != NULL);
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ for (header = rbtnode->data; header != NULL; header = header->next) {
+ unsigned char *p = (unsigned char *)header;
+ size_t size = dns_rdataslab_size(p, sizeof(*header));
+ isc_crc64_update(crc, p, size);
+#ifdef DEBUG
+ hexdump("hashing header", p, sizeof(rdatasetheader_t));
+ hexdump("hashing slab", p + sizeof(rdatasetheader_t),
+ size - sizeof(rdatasetheader_t));
+#endif /* ifdef DEBUG */
+ header->serial = 1;
+ header->is_mmapped = 1;
+ header->node = rbtnode;
+ header->node_is_relative = 0;
+
+ if (RESIGN(header) &&
+ (header->resign != 0 || header->resign_lsb != 0))
+ {
+ int idx = header->node->locknum;
+ isc_heap_insert(rbtdb->heaps[idx], header);
+ }
+
+ if (header->next != NULL) {
+ size_t cooked = dns_rbt_serialize_align(size);
+ if ((uintptr_t)header->next !=
+ (p - (unsigned char *)base) + cooked)
+ {
+ return (ISC_R_INVALIDFILE);
+ }
+ header->next = (rdatasetheader_t *)(p + cooked);
+ header->next_is_relative = 0;
+ if ((header->next < (rdatasetheader_t *)base) ||
+ (header->next > (rdatasetheader_t *)limit))
+ {
+ return (ISC_R_INVALIDFILE);
+ }
+ }
+
+ update_recordsandxfrsize(true, rbtdb->current_version, header,
+ rbtnode->fullnamelen);
+ }
+
+ /* We're done deserializing; clear fullnamelen */
+ rbtnode->fullnamelen = 0;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Load the RBT database from the image in 'f'
+ */
+static isc_result_t
+deserialize(void *arg, FILE *f, off_t offset) {
+ isc_result_t result;
+ rbtdb_load_t *loadctx = arg;
+ dns_rbtdb_t *rbtdb = loadctx->rbtdb;
+ rbtdb_file_header_t *header;
+ int fd;
+ off_t filesize = 0;
+ char *base;
+ dns_rbt_t *tree = NULL, *nsec = NULL, *nsec3 = NULL;
+ int protect, flags;
+ dns_rbtnode_t *origin_node = NULL;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ /*
+ * TODO CKB: since this is read-write (had to be to add nodes later)
+ * we will need to lock the file or the nodes in it before modifying
+ * the nodes in the file.
+ */
+
+ /* Map in the whole file in one go */
+ fd = fileno(f);
+ isc_file_getsizefd(fd, &filesize);
+ protect = PROT_READ | PROT_WRITE;
+ flags = MAP_PRIVATE;
+#ifdef MAP_FILE
+ flags |= MAP_FILE;
+#endif /* ifdef MAP_FILE */
+
+ base = isc_file_mmap(NULL, filesize, protect, flags, fd, 0);
+ if (base == NULL || base == MAP_FAILED) {
+ return (ISC_R_FAILURE);
+ }
+
+ header = (rbtdb_file_header_t *)(base + offset);
+ if (!match_header_version(header)) {
+ result = ISC_R_INVALIDFILE;
+ goto cleanup;
+ }
+
+ if (header->tree != 0) {
+ result = dns_rbt_deserialize_tree(
+ base, filesize, (off_t)header->tree, rbtdb->common.mctx,
+ delete_callback, rbtdb, rbt_datafixer, rbtdb, NULL,
+ &tree);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_rbt_findnode(tree, &rbtdb->common.origin, NULL,
+ &origin_node, NULL,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ if (header->nsec != 0) {
+ result = dns_rbt_deserialize_tree(
+ base, filesize, (off_t)header->nsec, rbtdb->common.mctx,
+ delete_callback, rbtdb, rbt_datafixer, rbtdb, NULL,
+ &nsec);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ if (header->nsec3 != 0) {
+ result = dns_rbt_deserialize_tree(
+ base, filesize, (off_t)header->nsec3,
+ rbtdb->common.mctx, delete_callback, rbtdb,
+ rbt_datafixer, rbtdb, NULL, &nsec3);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ /*
+ * We have a successfully loaded all the rbt trees now update
+ * rbtdb to use them.
+ */
+
+ rbtdb->mmap_location = base;
+ rbtdb->mmap_size = (size_t)filesize;
+
+ if (tree != NULL) {
+ dns_rbt_destroy(&rbtdb->tree);
+ rbtdb->tree = tree;
+ rbtdb->origin_node = origin_node;
+ }
+
+ if (nsec != NULL) {
+ dns_rbt_destroy(&rbtdb->nsec);
+ rbtdb->nsec = nsec;
+ }
+
+ if (nsec3 != NULL) {
+ dns_rbt_destroy(&rbtdb->nsec3);
+ rbtdb->nsec3 = nsec3;
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (tree != NULL) {
+ dns_rbt_destroy(&tree);
+ }
+ if (nsec != NULL) {
+ dns_rbt_destroy(&nsec);
+ }
+ if (nsec3 != NULL) {
+ dns_rbt_destroy(&nsec3);
+ }
+ isc_file_munmap(base, (size_t)filesize);
+ return (result);
+}
+
+static isc_result_t
+beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ rbtdb_load_t *loadctx;
+ dns_rbtdb_t *rbtdb;
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(DNS_CALLBACK_VALID(callbacks));
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ loadctx = isc_mem_get(rbtdb->common.mctx, sizeof(*loadctx));
+
+ loadctx->rbtdb = rbtdb;
+ if (IS_CACHE(rbtdb)) {
+ isc_stdtime_get(&loadctx->now);
+ } else {
+ loadctx->now = 0;
+ }
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+
+ REQUIRE((rbtdb->attributes &
+ (RBTDB_ATTR_LOADED | RBTDB_ATTR_LOADING)) == 0);
+ rbtdb->attributes |= RBTDB_ATTR_LOADING;
+
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+
+ callbacks->add = loading_addrdataset;
+ callbacks->add_private = loadctx;
+ callbacks->deserialize = deserialize;
+ callbacks->deserialize_private = loadctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) {
+ rbtdb_load_t *loadctx;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(DNS_CALLBACK_VALID(callbacks));
+ loadctx = callbacks->add_private;
+ REQUIRE(loadctx != NULL);
+ REQUIRE(loadctx->rbtdb == rbtdb);
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+
+ REQUIRE((rbtdb->attributes & RBTDB_ATTR_LOADING) != 0);
+ REQUIRE((rbtdb->attributes & RBTDB_ATTR_LOADED) == 0);
+
+ rbtdb->attributes &= ~RBTDB_ATTR_LOADING;
+ rbtdb->attributes |= RBTDB_ATTR_LOADED;
+
+ /*
+ * If there's a KEY rdataset at the zone origin containing a
+ * zone key, we consider the zone secure.
+ */
+ if (!IS_CACHE(rbtdb) && rbtdb->origin_node != NULL) {
+ dns_dbversion_t *version = rbtdb->current_version;
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+ iszonesecure(db, version, rbtdb->origin_node);
+ } else {
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+ }
+
+ callbacks->add = NULL;
+ callbacks->add_private = NULL;
+ callbacks->deserialize = NULL;
+ callbacks->deserialize_private = NULL;
+
+ isc_mem_put(rbtdb->common.mctx, loadctx, sizeof(*loadctx));
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * helper function to handle writing out the rdataset data pointed to
+ * by the void *data pointer in the dns_rbtnode
+ */
+static isc_result_t
+rbt_datawriter(FILE *rbtfile, unsigned char *data, void *arg, uint64_t *crc) {
+ rbtdb_version_t *version = (rbtdb_version_t *)arg;
+ rbtdb_serial_t serial;
+ rdatasetheader_t newheader;
+ rdatasetheader_t *header = (rdatasetheader_t *)data, *next;
+ off_t where;
+ size_t cooked, size;
+ unsigned char *p;
+ isc_result_t result = ISC_R_SUCCESS;
+ char pad[sizeof(char *)];
+ uintptr_t off;
+
+ REQUIRE(rbtfile != NULL);
+ REQUIRE(data != NULL);
+ REQUIRE(version != NULL);
+
+ serial = version->serial;
+
+ for (; header != NULL; header = next) {
+ next = header->next;
+ do {
+ if (header->serial <= serial && !IGNORE(header)) {
+ if (NONEXISTENT(header)) {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+
+ if (header == NULL) {
+ continue;
+ }
+
+ CHECK(isc_stdio_tell(rbtfile, &where));
+ size = dns_rdataslab_size((unsigned char *)header,
+ sizeof(rdatasetheader_t));
+
+ p = (unsigned char *)header;
+ memmove(&newheader, p, sizeof(rdatasetheader_t));
+ newheader.down = NULL;
+ newheader.next = NULL;
+ off = where;
+ if ((off_t)off != where) {
+ return (ISC_R_RANGE);
+ }
+ newheader.node = (dns_rbtnode_t *)off;
+ newheader.node_is_relative = 1;
+ newheader.serial = 1;
+
+ /*
+ * Round size up to the next pointer sized offset so it
+ * will be properly aligned when read back in.
+ */
+ cooked = dns_rbt_serialize_align(size);
+ if (next != NULL) {
+ newheader.next = (rdatasetheader_t *)(off + cooked);
+ newheader.next_is_relative = 1;
+ }
+
+#ifdef DEBUG
+ hexdump("writing header", (unsigned char *)&newheader,
+ sizeof(rdatasetheader_t));
+ hexdump("writing slab", p + sizeof(rdatasetheader_t),
+ size - sizeof(rdatasetheader_t));
+#endif /* ifdef DEBUG */
+ isc_crc64_update(crc, (unsigned char *)&newheader,
+ sizeof(rdatasetheader_t));
+ CHECK(isc_stdio_write(&newheader, sizeof(rdatasetheader_t), 1,
+ rbtfile, NULL));
+
+ isc_crc64_update(crc, p + sizeof(rdatasetheader_t),
+ size - sizeof(rdatasetheader_t));
+ CHECK(isc_stdio_write(p + sizeof(rdatasetheader_t),
+ size - sizeof(rdatasetheader_t), 1,
+ rbtfile, NULL));
+ /*
+ * Pad to force alignment.
+ */
+ if (size != (size_t)cooked) {
+ memset(pad, 0, sizeof(pad));
+ CHECK(isc_stdio_write(pad, cooked - size, 1, rbtfile,
+ NULL));
+ }
+ }
+
+failure:
+ return (result);
+}
+
+/*
+ * Write out a zeroed header as a placeholder. Doing this ensures
+ * that the file will not read while it is partially written, should
+ * writing fail or be interrupted.
+ */
+static isc_result_t
+rbtdb_zero_header(FILE *rbtfile) {
+ char buffer[RBTDB_HEADER_LENGTH];
+ isc_result_t result;
+
+ memset(buffer, 0, RBTDB_HEADER_LENGTH);
+ result = isc_stdio_write(buffer, 1, RBTDB_HEADER_LENGTH, rbtfile, NULL);
+ fflush(rbtfile);
+
+ return (result);
+}
+
+static isc_once_t once = ISC_ONCE_INIT;
+
+static void
+init_file_version(void) {
+ int n;
+
+ memset(FILE_VERSION, 0, sizeof(FILE_VERSION));
+ n = snprintf(FILE_VERSION, sizeof(FILE_VERSION), "RBTDB Image %s %s",
+ dns_major, dns_mapapi);
+ INSIST(n > 0 && (unsigned int)n < sizeof(FILE_VERSION));
+}
+
+/*
+ * Write the file header out, recording the locations of the three
+ * RBT's used in the rbtdb: tree, nsec, and nsec3, and including NodeDump
+ * version information and any information stored in the rbtdb object
+ * itself that should be stored here.
+ */
+static isc_result_t
+rbtdb_write_header(FILE *rbtfile, off_t tree_location, off_t nsec_location,
+ off_t nsec3_location) {
+ rbtdb_file_header_t header;
+ isc_result_t result;
+
+ RUNTIME_CHECK(isc_once_do(&once, init_file_version) == ISC_R_SUCCESS);
+
+ memset(&header, 0, sizeof(rbtdb_file_header_t));
+ memmove(header.version1, FILE_VERSION, sizeof(header.version1));
+ memmove(header.version2, FILE_VERSION, sizeof(header.version2));
+ header.ptrsize = (uint32_t)sizeof(void *);
+ header.bigendian = (1 == htonl(1)) ? 1 : 0;
+ header.tree = (uint64_t)tree_location;
+ header.nsec = (uint64_t)nsec_location;
+ header.nsec3 = (uint64_t)nsec3_location;
+ result = isc_stdio_write(&header, 1, sizeof(rbtdb_file_header_t),
+ rbtfile, NULL);
+ fflush(rbtfile);
+
+ return (result);
+}
+
+static bool
+match_header_version(rbtdb_file_header_t *header) {
+ RUNTIME_CHECK(isc_once_do(&once, init_file_version) == ISC_R_SUCCESS);
+
+ if (memcmp(header->version1, FILE_VERSION, sizeof(header->version1)) !=
+ 0 ||
+ memcmp(header->version2, FILE_VERSION, sizeof(header->version1)) !=
+ 0)
+ {
+ return (false);
+ }
+
+ return (true);
+}
+
+static isc_result_t
+serialize(dns_db_t *db, dns_dbversion_t *ver, FILE *rbtfile) {
+ rbtdb_version_t *version = (rbtdb_version_t *)ver;
+ dns_rbtdb_t *rbtdb;
+ isc_result_t result;
+ off_t tree_location, nsec_location, nsec3_location, header_location;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(rbtfile != NULL);
+
+ /* Ensure we're writing to a plain file */
+ CHECK(isc_file_isplainfilefd(fileno(rbtfile)));
+
+ /*
+ * first, write out a zeroed header to store rbtdb information
+ *
+ * then for each of the three trees, store the current position
+ * in the file and call dns_rbt_serialize_tree
+ *
+ * finally, write out the rbtdb header, storing the locations of the
+ * rbtheaders
+ *
+ * NOTE: need to do something better with the return codes, &= will
+ * not work.
+ */
+ CHECK(isc_stdio_tell(rbtfile, &header_location));
+ CHECK(rbtdb_zero_header(rbtfile));
+ CHECK(dns_rbt_serialize_tree(rbtfile, rbtdb->tree, rbt_datawriter,
+ version, &tree_location));
+ CHECK(dns_rbt_serialize_tree(rbtfile, rbtdb->nsec, rbt_datawriter,
+ version, &nsec_location));
+ CHECK(dns_rbt_serialize_tree(rbtfile, rbtdb->nsec3, rbt_datawriter,
+ version, &nsec3_location));
+
+ CHECK(isc_stdio_seek(rbtfile, header_location, SEEK_SET));
+ CHECK(rbtdb_write_header(rbtfile, tree_location, nsec_location,
+ nsec3_location));
+failure:
+ return (result);
+}
+
+static isc_result_t
+dump(dns_db_t *db, dns_dbversion_t *version, const char *filename,
+ dns_masterformat_t masterformat) {
+ dns_rbtdb_t *rbtdb;
+ rbtdb_version_t *rbtversion = version;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
+
+ return (dns_master_dump(rbtdb->common.mctx, db, version,
+ &dns_master_style_default, filename,
+ masterformat, NULL));
+}
+
+static void
+delete_callback(void *data, void *arg) {
+ dns_rbtdb_t *rbtdb = arg;
+ rdatasetheader_t *current, *next;
+ unsigned int locknum;
+
+ current = data;
+ locknum = current->node->locknum;
+ NODE_LOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write);
+ while (current != NULL) {
+ next = current->next;
+ free_rdataset(rbtdb, rbtdb->common.mctx, current);
+ current = next;
+ }
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write);
+}
+
+static bool
+issecure(dns_db_t *db) {
+ dns_rbtdb_t *rbtdb;
+ bool secure;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ secure = (rbtdb->current_version->secure == dns_db_secure);
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+
+ return (secure);
+}
+
+static bool
+isdnssec(dns_db_t *db) {
+ dns_rbtdb_t *rbtdb;
+ bool dnssec;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ dnssec = (rbtdb->current_version->secure != dns_db_insecure);
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+
+ return (dnssec);
+}
+
+static unsigned int
+nodecount(dns_db_t *db) {
+ dns_rbtdb_t *rbtdb;
+ unsigned int count;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ count = dns_rbt_nodecount(rbtdb->tree);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ return (count);
+}
+
+static size_t
+hashsize(dns_db_t *db) {
+ dns_rbtdb_t *rbtdb;
+ size_t size;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ size = dns_rbt_hashsize(rbtdb->tree);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ return (size);
+}
+
+static isc_result_t
+adjusthashsize(dns_db_t *db, size_t size) {
+ isc_result_t result;
+ dns_rbtdb_t *rbtdb;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ result = dns_rbt_adjusthashsize(rbtdb->tree, size);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+static void
+settask(dns_db_t *db, isc_task_t *task) {
+ dns_rbtdb_t *rbtdb;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write);
+ if (rbtdb->task != NULL) {
+ isc_task_detach(&rbtdb->task);
+ }
+ if (task != NULL) {
+ isc_task_attach(task, &rbtdb->task);
+ }
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write);
+}
+
+static bool
+ispersistent(dns_db_t *db) {
+ UNUSED(db);
+ return (false);
+}
+
+static isc_result_t
+getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *onode;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(nodep != NULL && *nodep == NULL);
+
+ /* Note that the access to origin_node doesn't require a DB lock */
+ onode = (dns_rbtnode_t *)rbtdb->origin_node;
+ if (onode != NULL) {
+ new_reference(rbtdb, onode, isc_rwlocktype_none);
+ *nodep = rbtdb->origin_node;
+ } else {
+ INSIST(IS_CACHE(rbtdb));
+ result = ISC_R_NOTFOUND;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, dns_hash_t *hash,
+ uint8_t *flags, uint16_t *iterations, unsigned char *salt,
+ size_t *salt_length) {
+ dns_rbtdb_t *rbtdb;
+ isc_result_t result = ISC_R_NOTFOUND;
+ rbtdb_version_t *rbtversion = version;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ if (rbtversion == NULL) {
+ rbtversion = rbtdb->current_version;
+ }
+
+ if (rbtversion->havensec3) {
+ if (hash != NULL) {
+ *hash = rbtversion->hash;
+ }
+ if (salt != NULL && salt_length != NULL) {
+ REQUIRE(*salt_length >= rbtversion->salt_length);
+ memmove(salt, rbtversion->salt,
+ rbtversion->salt_length);
+ }
+ if (salt_length != NULL) {
+ *salt_length = rbtversion->salt_length;
+ }
+ if (iterations != NULL) {
+ *iterations = rbtversion->iterations;
+ }
+ if (flags != NULL) {
+ *flags = rbtversion->flags;
+ }
+ result = ISC_R_SUCCESS;
+ }
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+static isc_result_t
+getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records,
+ uint64_t *xfrsize) {
+ dns_rbtdb_t *rbtdb;
+ isc_result_t result = ISC_R_SUCCESS;
+ rbtdb_version_t *rbtversion = version;
+
+ rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
+
+ RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read);
+ if (rbtversion == NULL) {
+ rbtversion = rbtdb->current_version;
+ }
+
+ RWLOCK(&rbtversion->rwlock, isc_rwlocktype_read);
+ if (records != NULL) {
+ *records = rbtversion->records;
+ }
+
+ if (xfrsize != NULL) {
+ *xfrsize = rbtversion->xfrsize;
+ }
+ RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_read);
+ RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+static isc_result_t
+setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, isc_stdtime_t resign) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ rdatasetheader_t *header, oldheader;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(!IS_CACHE(rbtdb));
+ REQUIRE(rdataset != NULL);
+
+ header = rdataset->private3;
+ header--;
+
+ NODE_LOCK(&rbtdb->node_locks[header->node->locknum].lock,
+ isc_rwlocktype_write);
+
+ oldheader = *header;
+ /*
+ * Only break the heap invariant (by adjusting resign and resign_lsb)
+ * if we are going to be restoring it by calling isc_heap_increased
+ * or isc_heap_decreased.
+ */
+ if (resign != 0) {
+ header->resign = (isc_stdtime_t)(dns_time64_from32(resign) >>
+ 1);
+ header->resign_lsb = resign & 0x1;
+ }
+ if (header->heap_index != 0) {
+ INSIST(RESIGN(header));
+ if (resign == 0) {
+ isc_heap_delete(rbtdb->heaps[header->node->locknum],
+ header->heap_index);
+ header->heap_index = 0;
+ } else if (resign_sooner(header, &oldheader)) {
+ isc_heap_increased(rbtdb->heaps[header->node->locknum],
+ header->heap_index);
+ } else if (resign_sooner(&oldheader, header)) {
+ isc_heap_decreased(rbtdb->heaps[header->node->locknum],
+ header->heap_index);
+ }
+ } else if (resign != 0) {
+ RDATASET_ATTR_SET(header, RDATASET_ATTR_RESIGN);
+ resign_insert(rbtdb, header->node->locknum, header);
+ }
+ NODE_UNLOCK(&rbtdb->node_locks[header->node->locknum].lock,
+ isc_rwlocktype_write);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+getsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, dns_name_t *foundname) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ rdatasetheader_t *header = NULL, *this;
+ unsigned int i;
+ isc_result_t result = ISC_R_NOTFOUND;
+ unsigned int locknum = 0;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ for (i = 0; i < rbtdb->node_lock_count; i++) {
+ NODE_LOCK(&rbtdb->node_locks[i].lock, isc_rwlocktype_read);
+
+ /*
+ * Find for the earliest signing time among all of the
+ * heaps, each of which is covered by a different bucket
+ * lock.
+ */
+ this = isc_heap_element(rbtdb->heaps[i], 1);
+ if (this == NULL) {
+ /* Nothing found; unlock and try the next heap. */
+ NODE_UNLOCK(&rbtdb->node_locks[i].lock,
+ isc_rwlocktype_read);
+ continue;
+ }
+
+ if (header == NULL) {
+ /*
+ * Found a signing time: retain the bucket lock and
+ * preserve the lock number so we can unlock it
+ * later.
+ */
+ header = this;
+ locknum = i;
+ } else if (resign_sooner(this, header)) {
+ /*
+ * Found an earlier signing time; release the
+ * previous bucket lock and retain this one instead.
+ */
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_read);
+ header = this;
+ locknum = i;
+ } else {
+ /*
+ * Earliest signing time in this heap isn't
+ * an improvement; unlock and try the next heap.
+ */
+ NODE_UNLOCK(&rbtdb->node_locks[i].lock,
+ isc_rwlocktype_read);
+ }
+ }
+
+ if (header != NULL) {
+ /*
+ * Found something; pass back the answer and unlock
+ * the bucket.
+ */
+ bind_rdataset(rbtdb, header->node, header, 0,
+ isc_rwlocktype_read, rdataset);
+
+ if (foundname != NULL) {
+ dns_rbt_fullnamefromnode(header->node, foundname);
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_read);
+
+ result = ISC_R_SUCCESS;
+ }
+
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+static void
+resigned(dns_db_t *db, dns_rdataset_t *rdataset, dns_dbversion_t *version) {
+ rbtdb_version_t *rbtversion = (rbtdb_version_t *)version;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *node;
+ rdatasetheader_t *header;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->methods == &rdataset_methods);
+ REQUIRE(rbtdb->future_version == rbtversion);
+ REQUIRE(rbtversion != NULL);
+ REQUIRE(rbtversion->writer);
+ REQUIRE(rbtversion->rbtdb == rbtdb);
+
+ node = rdataset->private2;
+ INSIST(node != NULL);
+ header = rdataset->private3;
+ INSIST(header != NULL);
+ header--;
+
+ if (header->heap_index == 0) {
+ return;
+ }
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ NODE_LOCK(&rbtdb->node_locks[node->locknum].lock, isc_rwlocktype_write);
+ /*
+ * Delete from heap and save to re-signed list so that it can
+ * be restored if we backout of this change.
+ */
+ resign_delete(rbtdb, rbtversion, header);
+ NODE_UNLOCK(&rbtdb->node_locks[node->locknum].lock,
+ isc_rwlocktype_write);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+}
+
+static isc_result_t
+setcachestats(dns_db_t *db, isc_stats_t *stats) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb)); /* current restriction */
+ REQUIRE(stats != NULL);
+
+ isc_stats_attach(stats, &rbtdb->cachestats);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+setgluecachestats(dns_db_t *db, isc_stats_t *stats) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(!IS_CACHE(rbtdb) && !IS_STUB(rbtdb));
+ REQUIRE(stats != NULL);
+
+ isc_stats_attach(stats, &rbtdb->gluecachestats);
+ return (ISC_R_SUCCESS);
+}
+
+static dns_stats_t *
+getrrsetstats(dns_db_t *db) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb)); /* current restriction */
+
+ return (rbtdb->rrsetstats);
+}
+
+static isc_result_t
+nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+ dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
+ isc_result_t result;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(node != NULL);
+ REQUIRE(name != NULL);
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ result = dns_rbt_fullnamefromnode(rbtnode, name);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+static isc_result_t
+setservestalettl(dns_db_t *db, dns_ttl_t ttl) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb));
+
+ /* currently no bounds checking. 0 means disable. */
+ rbtdb->serve_stale_ttl = ttl;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+getservestalettl(dns_db_t *db, dns_ttl_t *ttl) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb));
+
+ *ttl = rbtdb->serve_stale_ttl;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+setservestalerefresh(dns_db_t *db, uint32_t interval) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb));
+
+ /* currently no bounds checking. 0 means disable. */
+ rbtdb->serve_stale_refresh = interval;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+getservestalerefresh(dns_db_t *db, uint32_t *interval) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb));
+
+ *interval = rbtdb->serve_stale_refresh;
+ return (ISC_R_SUCCESS);
+}
+
+static dns_dbmethods_t zone_methods = { attach,
+ detach,
+ beginload,
+ endload,
+ serialize,
+ dump,
+ currentversion,
+ newversion,
+ attachversion,
+ closeversion,
+ findnode,
+ zone_find,
+ zone_findzonecut,
+ attachnode,
+ detachnode,
+ expirenode,
+ printnode,
+ createiterator,
+ zone_findrdataset,
+ allrdatasets,
+ addrdataset,
+ subtractrdataset,
+ deleterdataset,
+ issecure,
+ nodecount,
+ ispersistent,
+ overmem,
+ settask,
+ getoriginnode,
+ NULL, /* transfernode */
+ getnsec3parameters,
+ findnsec3node,
+ setsigningtime,
+ getsigningtime,
+ resigned,
+ isdnssec,
+ NULL, /* getrrsetstats */
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ NULL, /* findnodeext */
+ NULL, /* findext */
+ NULL, /* setcachestats */
+ hashsize,
+ nodefullname,
+ getsize,
+ NULL, /* setservestalettl */
+ NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
+ setgluecachestats,
+ adjusthashsize };
+
+static dns_dbmethods_t cache_methods = { attach,
+ detach,
+ beginload,
+ endload,
+ NULL, /* serialize */
+ dump,
+ currentversion,
+ newversion,
+ attachversion,
+ closeversion,
+ findnode,
+ cache_find,
+ cache_findzonecut,
+ attachnode,
+ detachnode,
+ expirenode,
+ printnode,
+ createiterator,
+ cache_findrdataset,
+ allrdatasets,
+ addrdataset,
+ subtractrdataset,
+ deleterdataset,
+ issecure,
+ nodecount,
+ ispersistent,
+ overmem,
+ settask,
+ getoriginnode,
+ NULL, /* transfernode */
+ NULL, /* getnsec3parameters */
+ NULL, /* findnsec3node */
+ NULL, /* setsigningtime */
+ NULL, /* getsigningtime */
+ NULL, /* resigned */
+ isdnssec,
+ getrrsetstats,
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ NULL, /* findnodeext */
+ NULL, /* findext */
+ setcachestats,
+ hashsize,
+ nodefullname,
+ NULL, /* getsize */
+ setservestalettl,
+ getservestalettl,
+ setservestalerefresh,
+ getservestalerefresh,
+ NULL,
+ adjusthashsize };
+
+isc_result_t
+dns_rbtdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
+ dns_rdataclass_t rdclass, unsigned int argc, char *argv[],
+ void *driverarg, dns_db_t **dbp) {
+ dns_rbtdb_t *rbtdb;
+ isc_result_t result;
+ int i;
+ dns_name_t name;
+ bool (*sooner)(void *, void *);
+ isc_mem_t *hmctx = mctx;
+
+ /* Keep the compiler happy. */
+ UNUSED(driverarg);
+
+ rbtdb = isc_mem_get(mctx, sizeof(*rbtdb));
+
+ /*
+ * If argv[0] exists, it points to a memory context to use for heap
+ */
+ if (argc != 0) {
+ hmctx = (isc_mem_t *)argv[0];
+ }
+
+ memset(rbtdb, '\0', sizeof(*rbtdb));
+ dns_name_init(&rbtdb->common.origin, NULL);
+ rbtdb->common.attributes = 0;
+ if (type == dns_dbtype_cache) {
+ rbtdb->common.methods = &cache_methods;
+ rbtdb->common.attributes |= DNS_DBATTR_CACHE;
+ } else if (type == dns_dbtype_stub) {
+ rbtdb->common.methods = &zone_methods;
+ rbtdb->common.attributes |= DNS_DBATTR_STUB;
+ } else {
+ rbtdb->common.methods = &zone_methods;
+ }
+ rbtdb->common.rdclass = rdclass;
+ rbtdb->common.mctx = NULL;
+
+ ISC_LIST_INIT(rbtdb->common.update_listeners);
+
+ RBTDB_INITLOCK(&rbtdb->lock);
+
+ isc_rwlock_init(&rbtdb->tree_lock, 0, 0);
+
+ /*
+ * Initialize node_lock_count in a generic way to support future
+ * extension which allows the user to specify this value on creation.
+ * Note that when specified for a cache DB it must be larger than 1
+ * as commented with the definition of DEFAULT_CACHE_NODE_LOCK_COUNT.
+ */
+ if (rbtdb->node_lock_count == 0) {
+ if (IS_CACHE(rbtdb)) {
+ rbtdb->node_lock_count = DEFAULT_CACHE_NODE_LOCK_COUNT;
+ } else {
+ rbtdb->node_lock_count = DEFAULT_NODE_LOCK_COUNT;
+ }
+ } else if (rbtdb->node_lock_count < 2 && IS_CACHE(rbtdb)) {
+ result = ISC_R_RANGE;
+ goto cleanup_tree_lock;
+ }
+ INSIST(rbtdb->node_lock_count < (1 << DNS_RBT_LOCKLENGTH));
+ rbtdb->node_locks = isc_mem_get(mctx, rbtdb->node_lock_count *
+ sizeof(rbtdb_nodelock_t));
+
+ rbtdb->cachestats = NULL;
+ rbtdb->gluecachestats = NULL;
+
+ rbtdb->rrsetstats = NULL;
+ if (IS_CACHE(rbtdb)) {
+ result = dns_rdatasetstats_create(mctx, &rbtdb->rrsetstats);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_node_locks;
+ }
+ rbtdb->rdatasets = isc_mem_get(
+ mctx,
+ rbtdb->node_lock_count * sizeof(rdatasetheaderlist_t));
+ for (i = 0; i < (int)rbtdb->node_lock_count; i++) {
+ ISC_LIST_INIT(rbtdb->rdatasets[i]);
+ }
+ } else {
+ rbtdb->rdatasets = NULL;
+ }
+
+ /*
+ * Create the heaps.
+ */
+ rbtdb->heaps = isc_mem_get(hmctx, rbtdb->node_lock_count *
+ sizeof(isc_heap_t *));
+ for (i = 0; i < (int)rbtdb->node_lock_count; i++) {
+ rbtdb->heaps[i] = NULL;
+ }
+ sooner = IS_CACHE(rbtdb) ? ttl_sooner : resign_sooner;
+ for (i = 0; i < (int)rbtdb->node_lock_count; i++) {
+ isc_heap_create(hmctx, sooner, set_index, 0, &rbtdb->heaps[i]);
+ }
+
+ /*
+ * Create deadnode lists.
+ */
+ rbtdb->deadnodes = isc_mem_get(mctx, rbtdb->node_lock_count *
+ sizeof(rbtnodelist_t));
+ for (i = 0; i < (int)rbtdb->node_lock_count; i++) {
+ ISC_LIST_INIT(rbtdb->deadnodes[i]);
+ }
+
+ rbtdb->active = rbtdb->node_lock_count;
+
+ for (i = 0; i < (int)(rbtdb->node_lock_count); i++) {
+ NODE_INITLOCK(&rbtdb->node_locks[i].lock);
+ isc_refcount_init(&rbtdb->node_locks[i].references, 0);
+ rbtdb->node_locks[i].exiting = false;
+ }
+
+ /*
+ * Attach to the mctx. The database will persist so long as there
+ * are references to it, and attaching to the mctx ensures that our
+ * mctx won't disappear out from under us.
+ */
+ isc_mem_attach(mctx, &rbtdb->common.mctx);
+ isc_mem_attach(hmctx, &rbtdb->hmctx);
+
+ /*
+ * Make a copy of the origin name.
+ */
+ result = dns_name_dupwithoffsets(origin, mctx, &rbtdb->common.origin);
+ if (result != ISC_R_SUCCESS) {
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+
+ /*
+ * Make the Red-Black Trees.
+ */
+ result = dns_rbt_create(mctx, delete_callback, rbtdb, &rbtdb->tree);
+ if (result != ISC_R_SUCCESS) {
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+
+ result = dns_rbt_create(mctx, delete_callback, rbtdb, &rbtdb->nsec);
+ if (result != ISC_R_SUCCESS) {
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+
+ result = dns_rbt_create(mctx, delete_callback, rbtdb, &rbtdb->nsec3);
+ if (result != ISC_R_SUCCESS) {
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+
+ /*
+ * In order to set the node callback bit correctly in zone databases,
+ * we need to know if the node has the origin name of the zone.
+ * In loading_addrdataset() we could simply compare the new name
+ * to the origin name, but this is expensive. Also, we don't know the
+ * node name in addrdataset(), so we need another way of knowing the
+ * zone's top.
+ *
+ * We now explicitly create a node for the zone's origin, and then
+ * we simply remember the node's address. This is safe, because
+ * the top-of-zone node can never be deleted, nor can its address
+ * change.
+ */
+ if (!IS_CACHE(rbtdb)) {
+ rbtdb->origin_node = NULL;
+ result = dns_rbt_addnode(rbtdb->tree, &rbtdb->common.origin,
+ &rbtdb->origin_node);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(result != ISC_R_EXISTS);
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+ INSIST(rbtdb->origin_node != NULL);
+ rbtdb->origin_node->nsec = DNS_RBT_NSEC_NORMAL;
+ /*
+ * We need to give the origin node the right locknum.
+ */
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(rbtdb->origin_node, &name);
+ rbtdb->origin_node->locknum = rbtdb->origin_node->hashval %
+ rbtdb->node_lock_count;
+ /*
+ * Add an apex node to the NSEC3 tree so that NSEC3 searches
+ * return partial matches when there is only a single NSEC3
+ * record in the tree.
+ */
+ rbtdb->nsec3_origin_node = NULL;
+ result = dns_rbt_addnode(rbtdb->nsec3, &rbtdb->common.origin,
+ &rbtdb->nsec3_origin_node);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(result != ISC_R_EXISTS);
+ free_rbtdb(rbtdb, false, NULL);
+ return (result);
+ }
+ rbtdb->nsec3_origin_node->nsec = DNS_RBT_NSEC_NSEC3;
+ /*
+ * We need to give the nsec3 origin node the right locknum.
+ */
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(rbtdb->nsec3_origin_node, &name);
+ rbtdb->nsec3_origin_node->locknum =
+ rbtdb->nsec3_origin_node->hashval %
+ rbtdb->node_lock_count;
+ }
+
+ /*
+ * Misc. Initialization.
+ */
+ isc_refcount_init(&rbtdb->references, 1);
+ rbtdb->attributes = 0;
+ rbtdb->task = NULL;
+ rbtdb->serve_stale_ttl = 0;
+
+ /*
+ * Version Initialization.
+ */
+ rbtdb->current_serial = 1;
+ rbtdb->least_serial = 1;
+ rbtdb->next_serial = 2;
+ rbtdb->current_version = allocate_version(mctx, 1, 1, false);
+ rbtdb->current_version->rbtdb = rbtdb;
+ rbtdb->current_version->secure = dns_db_insecure;
+ rbtdb->current_version->havensec3 = false;
+ rbtdb->current_version->flags = 0;
+ rbtdb->current_version->iterations = 0;
+ rbtdb->current_version->hash = 0;
+ rbtdb->current_version->salt_length = 0;
+ memset(rbtdb->current_version->salt, 0,
+ sizeof(rbtdb->current_version->salt));
+ isc_rwlock_init(&rbtdb->current_version->rwlock, 0, 0);
+ rbtdb->current_version->records = 0;
+ rbtdb->current_version->xfrsize = 0;
+ rbtdb->future_version = NULL;
+ ISC_LIST_INIT(rbtdb->open_versions);
+ /*
+ * Keep the current version in the open list so that list operation
+ * won't happen in normal lookup operations.
+ */
+ PREPEND(rbtdb->open_versions, rbtdb->current_version, link);
+
+ rbtdb->common.magic = DNS_DB_MAGIC;
+ rbtdb->common.impmagic = RBTDB_MAGIC;
+
+ *dbp = (dns_db_t *)rbtdb;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_node_locks:
+ isc_mem_put(mctx, rbtdb->node_locks,
+ rbtdb->node_lock_count * sizeof(rbtdb_nodelock_t));
+
+cleanup_tree_lock:
+ isc_rwlock_destroy(&rbtdb->tree_lock);
+ RBTDB_DESTROYLOCK(&rbtdb->lock);
+ isc_mem_put(mctx, rbtdb, sizeof(*rbtdb));
+ return (result);
+}
+
+/*
+ * Slabbed Rdataset Methods
+ */
+
+static void
+rdataset_disassociate(dns_rdataset_t *rdataset) {
+ dns_db_t *db = rdataset->private1;
+ dns_dbnode_t *node = rdataset->private2;
+
+ detachnode(db, &node);
+}
+
+static isc_result_t
+rdataset_first(dns_rdataset_t *rdataset) {
+ unsigned char *raw = rdataset->private3; /* RDATASLAB */
+ unsigned int count;
+
+ count = raw[0] * 256 + raw[1];
+ if (count == 0) {
+ rdataset->private5 = NULL;
+ return (ISC_R_NOMORE);
+ }
+
+ if ((rdataset->attributes & DNS_RDATASETATTR_LOADORDER) == 0) {
+ raw += DNS_RDATASET_COUNT;
+ }
+
+ raw += DNS_RDATASET_LENGTH;
+
+ /*
+ * The privateuint4 field is the number of rdata beyond the
+ * cursor position, so we decrement the total count by one
+ * before storing it.
+ *
+ * If DNS_RDATASETATTR_LOADORDER is not set 'raw' points to the
+ * first record. If DNS_RDATASETATTR_LOADORDER is set 'raw' points
+ * to the first entry in the offset table.
+ */
+ count--;
+ rdataset->privateuint4 = count;
+ rdataset->private5 = raw;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdataset_next(dns_rdataset_t *rdataset) {
+ unsigned int count;
+ unsigned int length;
+ unsigned char *raw; /* RDATASLAB */
+
+ count = rdataset->privateuint4;
+ if (count == 0) {
+ return (ISC_R_NOMORE);
+ }
+ count--;
+ rdataset->privateuint4 = count;
+
+ /*
+ * Skip forward one record (length + 4) or one offset (4).
+ */
+ raw = rdataset->private5;
+#if DNS_RDATASET_FIXED
+ if ((rdataset->attributes & DNS_RDATASETATTR_LOADORDER) == 0)
+#endif /* DNS_RDATASET_FIXED */
+ {
+ length = raw[0] * 256 + raw[1];
+ raw += length;
+ }
+
+ rdataset->private5 = raw + DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ unsigned char *raw = rdataset->private5; /* RDATASLAB */
+ unsigned int length;
+ isc_region_t r;
+ unsigned int flags = 0;
+
+ REQUIRE(raw != NULL);
+
+ /*
+ * Find the start of the record if not already in private5
+ * then skip the length and order fields.
+ */
+#if DNS_RDATASET_FIXED
+ if ((rdataset->attributes & DNS_RDATASETATTR_LOADORDER) != 0) {
+ unsigned int offset;
+ offset = ((unsigned int)raw[0] << 24) +
+ ((unsigned int)raw[1] << 16) +
+ ((unsigned int)raw[2] << 8) + (unsigned int)raw[3];
+ raw = rdataset->private3;
+ raw += offset;
+ }
+#endif /* if DNS_RDATASET_FIXED */
+
+ length = raw[0] * 256 + raw[1];
+
+ raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH;
+
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ if (*raw & DNS_RDATASLAB_OFFLINE) {
+ flags |= DNS_RDATA_OFFLINE;
+ }
+ length--;
+ raw++;
+ }
+ r.length = length;
+ r.base = raw;
+ dns_rdata_fromregion(rdata, rdataset->rdclass, rdataset->type, &r);
+ rdata->flags |= flags;
+}
+
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ dns_db_t *db = source->private1;
+ dns_dbnode_t *node = source->private2;
+ dns_dbnode_t *cloned_node = NULL;
+
+ attachnode(db, node, &cloned_node);
+ INSIST(!ISC_LINK_LINKED(target, link));
+ *target = *source;
+ ISC_LINK_INIT(target, link);
+
+ /*
+ * Reset iterator state.
+ */
+ target->privateuint4 = 0;
+ target->private5 = NULL;
+}
+
+static unsigned int
+rdataset_count(dns_rdataset_t *rdataset) {
+ unsigned char *raw = rdataset->private3; /* RDATASLAB */
+ unsigned int count;
+
+ count = raw[0] * 256 + raw[1];
+
+ return (count);
+}
+
+static isc_result_t
+rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *nsec, dns_rdataset_t *nsecsig) {
+ dns_db_t *db = rdataset->private1;
+ dns_dbnode_t *node = rdataset->private2;
+ dns_dbnode_t *cloned_node;
+ const struct noqname *noqname = rdataset->private6;
+
+ cloned_node = NULL;
+ attachnode(db, node, &cloned_node);
+ nsec->methods = &slab_methods;
+ nsec->rdclass = db->rdclass;
+ nsec->type = noqname->type;
+ nsec->covers = 0;
+ nsec->ttl = rdataset->ttl;
+ nsec->trust = rdataset->trust;
+ nsec->private1 = rdataset->private1;
+ nsec->private2 = rdataset->private2;
+ nsec->private3 = noqname->neg;
+ nsec->privateuint4 = 0;
+ nsec->private5 = NULL;
+ nsec->private6 = NULL;
+ nsec->private7 = NULL;
+
+ cloned_node = NULL;
+ attachnode(db, node, &cloned_node);
+ nsecsig->methods = &slab_methods;
+ nsecsig->rdclass = db->rdclass;
+ nsecsig->type = dns_rdatatype_rrsig;
+ nsecsig->covers = noqname->type;
+ nsecsig->ttl = rdataset->ttl;
+ nsecsig->trust = rdataset->trust;
+ nsecsig->private1 = rdataset->private1;
+ nsecsig->private2 = rdataset->private2;
+ nsecsig->private3 = noqname->negsig;
+ nsecsig->privateuint4 = 0;
+ nsecsig->private5 = NULL;
+ nsec->private6 = NULL;
+ nsec->private7 = NULL;
+
+ dns_name_clone(&noqname->name, name);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *nsec, dns_rdataset_t *nsecsig) {
+ dns_db_t *db = rdataset->private1;
+ dns_dbnode_t *node = rdataset->private2;
+ dns_dbnode_t *cloned_node;
+ const struct noqname *closest = rdataset->private7;
+
+ cloned_node = NULL;
+ attachnode(db, node, &cloned_node);
+ nsec->methods = &slab_methods;
+ nsec->rdclass = db->rdclass;
+ nsec->type = closest->type;
+ nsec->covers = 0;
+ nsec->ttl = rdataset->ttl;
+ nsec->trust = rdataset->trust;
+ nsec->private1 = rdataset->private1;
+ nsec->private2 = rdataset->private2;
+ nsec->private3 = closest->neg;
+ nsec->privateuint4 = 0;
+ nsec->private5 = NULL;
+ nsec->private6 = NULL;
+ nsec->private7 = NULL;
+
+ cloned_node = NULL;
+ attachnode(db, node, &cloned_node);
+ nsecsig->methods = &slab_methods;
+ nsecsig->rdclass = db->rdclass;
+ nsecsig->type = dns_rdatatype_rrsig;
+ nsecsig->covers = closest->type;
+ nsecsig->ttl = rdataset->ttl;
+ nsecsig->trust = rdataset->trust;
+ nsecsig->private1 = rdataset->private1;
+ nsecsig->private2 = rdataset->private2;
+ nsecsig->private3 = closest->negsig;
+ nsecsig->privateuint4 = 0;
+ nsecsig->private5 = NULL;
+ nsec->private6 = NULL;
+ nsec->private7 = NULL;
+
+ dns_name_clone(&closest->name, name);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *rbtnode = rdataset->private2;
+ rdatasetheader_t *header = rdataset->private3;
+
+ header--;
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+ header->trust = rdataset->trust = trust;
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+}
+
+static void
+rdataset_expire(dns_rdataset_t *rdataset) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *rbtnode = rdataset->private2;
+ rdatasetheader_t *header = rdataset->private3;
+
+ header--;
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+ expire_header(rbtdb, header, false, expire_flush);
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+}
+
+static void
+rdataset_clearprefetch(dns_rdataset_t *rdataset) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *rbtnode = rdataset->private2;
+ rdatasetheader_t *header = rdataset->private3;
+
+ header--;
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+ RDATASET_ATTR_CLR(header, RDATASET_ATTR_PREFETCH);
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+}
+
+/*
+ * Rdataset Iterator Methods
+ */
+
+static void
+rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
+ rbtdb_rdatasetiter_t *rbtiterator;
+
+ rbtiterator = (rbtdb_rdatasetiter_t *)(*iteratorp);
+
+ if (rbtiterator->common.version != NULL) {
+ closeversion(rbtiterator->common.db,
+ &rbtiterator->common.version, false);
+ }
+ detachnode(rbtiterator->common.db, &rbtiterator->common.node);
+ isc_mem_put(rbtiterator->common.db->mctx, rbtiterator,
+ sizeof(*rbtiterator));
+
+ *iteratorp = NULL;
+}
+
+static bool
+iterator_active(dns_rbtdb_t *rbtdb, rbtdb_rdatasetiter_t *rbtiterator,
+ rdatasetheader_t *header) {
+ dns_ttl_t stale_ttl = header->rdh_ttl + rbtdb->serve_stale_ttl;
+
+ /*
+ * Is this a "this rdataset doesn't exist" record?
+ */
+ if (NONEXISTENT(header)) {
+ return (false);
+ }
+
+ /*
+ * If this is a zone or this header still active then return it.
+ */
+ if (!IS_CACHE(rbtdb) || ACTIVE(header, rbtiterator->common.now)) {
+ return (true);
+ }
+
+ /*
+ * If we are not returning stale records or the rdataset is
+ * too old don't return it.
+ */
+ if (!STALEOK(rbtiterator) || (rbtiterator->common.now > stale_ttl)) {
+ return (false);
+ }
+ return (true);
+}
+
+static isc_result_t
+rdatasetiter_first(dns_rdatasetiter_t *iterator) {
+ rbtdb_rdatasetiter_t *rbtiterator = (rbtdb_rdatasetiter_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(rbtiterator->common.db);
+ dns_rbtnode_t *rbtnode = rbtiterator->common.node;
+ rbtdb_version_t *rbtversion = rbtiterator->common.version;
+ rdatasetheader_t *header, *top_next;
+ rbtdb_serial_t serial = IS_CACHE(rbtdb) ? 1 : rbtversion->serial;
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ for (header = rbtnode->data; header != NULL; header = top_next) {
+ top_next = header->next;
+ do {
+ if (EXPIREDOK(rbtiterator)) {
+ if (!NONEXISTENT(header)) {
+ break;
+ }
+ header = header->down;
+ } else if (header->serial <= serial && !IGNORE(header))
+ {
+ if (!iterator_active(rbtdb, rbtiterator,
+ header))
+ {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ break;
+ }
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ rbtiterator->current = header;
+
+ if (header == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdatasetiter_next(dns_rdatasetiter_t *iterator) {
+ rbtdb_rdatasetiter_t *rbtiterator = (rbtdb_rdatasetiter_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(rbtiterator->common.db);
+ dns_rbtnode_t *rbtnode = rbtiterator->common.node;
+ rbtdb_version_t *rbtversion = rbtiterator->common.version;
+ rdatasetheader_t *header, *top_next;
+ rbtdb_serial_t serial = IS_CACHE(rbtdb) ? 1 : rbtversion->serial;
+ rbtdb_rdatatype_t type, negtype;
+ dns_rdatatype_t rdtype, covers;
+ bool expiredok = EXPIREDOK(rbtiterator);
+
+ header = rbtiterator->current;
+ if (header == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ type = header->type;
+ rdtype = RBTDB_RDATATYPE_BASE(header->type);
+ if (NEGATIVE(header)) {
+ covers = RBTDB_RDATATYPE_EXT(header->type);
+ negtype = RBTDB_RDATATYPE_VALUE(covers, 0);
+ } else {
+ negtype = RBTDB_RDATATYPE_VALUE(0, rdtype);
+ }
+
+ /*
+ * Find the start of the header chain for the next type
+ * by walking back up the list.
+ */
+ top_next = header->next;
+ while (top_next != NULL &&
+ (top_next->type == type || top_next->type == negtype))
+ {
+ top_next = top_next->next;
+ }
+ if (expiredok) {
+ /*
+ * Keep walking down the list if possible or
+ * start the next type.
+ */
+ header = header->down != NULL ? header->down : top_next;
+ } else {
+ header = top_next;
+ }
+ for (; header != NULL; header = top_next) {
+ top_next = header->next;
+ do {
+ if (expiredok) {
+ if (!NONEXISTENT(header)) {
+ break;
+ }
+ header = header->down;
+ } else if (header->serial <= serial && !IGNORE(header))
+ {
+ if (!iterator_active(rbtdb, rbtiterator,
+ header))
+ {
+ header = NULL;
+ }
+ break;
+ } else {
+ header = header->down;
+ }
+ } while (header != NULL);
+ if (header != NULL) {
+ break;
+ }
+ /*
+ * Find the start of the header chain for the next type
+ * by walking back up the list.
+ */
+ while (top_next != NULL &&
+ (top_next->type == type || top_next->type == negtype))
+ {
+ top_next = top_next->next;
+ }
+ }
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ rbtiterator->current = header;
+
+ if (header == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset) {
+ rbtdb_rdatasetiter_t *rbtiterator = (rbtdb_rdatasetiter_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(rbtiterator->common.db);
+ dns_rbtnode_t *rbtnode = rbtiterator->common.node;
+ rdatasetheader_t *header;
+
+ header = rbtiterator->current;
+ REQUIRE(header != NULL);
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ bind_rdataset(rbtdb, rbtnode, header, rbtiterator->common.now,
+ isc_rwlocktype_read, rdataset);
+
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+}
+
+/*
+ * Database Iterator Methods
+ */
+
+static void
+reference_iter_node(rbtdb_dbiterator_t *rbtdbiter) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db;
+ dns_rbtnode_t *node = rbtdbiter->node;
+
+ if (node == NULL) {
+ return;
+ }
+
+ INSIST(rbtdbiter->tree_locked != isc_rwlocktype_none);
+ reactivate_node(rbtdb, node, rbtdbiter->tree_locked);
+}
+
+static void
+dereference_iter_node(rbtdb_dbiterator_t *rbtdbiter) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db;
+ dns_rbtnode_t *node = rbtdbiter->node;
+ nodelock_t *lock;
+
+ if (node == NULL) {
+ return;
+ }
+
+ lock = &rbtdb->node_locks[node->locknum].lock;
+ NODE_LOCK(lock, isc_rwlocktype_read);
+ decrement_reference(rbtdb, node, 0, isc_rwlocktype_read,
+ rbtdbiter->tree_locked, false);
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+
+ rbtdbiter->node = NULL;
+}
+
+static void
+flush_deletions(rbtdb_dbiterator_t *rbtdbiter) {
+ dns_rbtnode_t *node;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db;
+ bool was_read_locked = false;
+ nodelock_t *lock;
+ int i;
+
+ if (rbtdbiter->delcnt != 0) {
+ /*
+ * Note that "%d node of %d in tree" can report things like
+ * "flush_deletions: 59 nodes of 41 in tree". This means
+ * That some nodes appear on the deletions list more than
+ * once. Only the last occurrence will actually be deleted.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1),
+ "flush_deletions: %d nodes of %d in tree",
+ rbtdbiter->delcnt,
+ dns_rbt_nodecount(rbtdb->tree));
+
+ if (rbtdbiter->tree_locked == isc_rwlocktype_read) {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ was_read_locked = true;
+ }
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ rbtdbiter->tree_locked = isc_rwlocktype_write;
+
+ for (i = 0; i < rbtdbiter->delcnt; i++) {
+ node = rbtdbiter->deletions[i];
+ lock = &rbtdb->node_locks[node->locknum].lock;
+
+ NODE_LOCK(lock, isc_rwlocktype_read);
+ decrement_reference(rbtdb, node, 0, isc_rwlocktype_read,
+ rbtdbiter->tree_locked, false);
+ NODE_UNLOCK(lock, isc_rwlocktype_read);
+ }
+
+ rbtdbiter->delcnt = 0;
+
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+ if (was_read_locked) {
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ rbtdbiter->tree_locked = isc_rwlocktype_read;
+ } else {
+ rbtdbiter->tree_locked = isc_rwlocktype_none;
+ }
+ }
+}
+
+static void
+resume_iteration(rbtdb_dbiterator_t *rbtdbiter) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db;
+
+ REQUIRE(rbtdbiter->paused);
+ REQUIRE(rbtdbiter->tree_locked == isc_rwlocktype_none);
+
+ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ rbtdbiter->tree_locked = isc_rwlocktype_read;
+
+ rbtdbiter->paused = false;
+}
+
+static void
+dbiterator_destroy(dns_dbiterator_t **iteratorp) {
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)(*iteratorp);
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db;
+ dns_db_t *db = NULL;
+
+ if (rbtdbiter->tree_locked == isc_rwlocktype_read) {
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ rbtdbiter->tree_locked = isc_rwlocktype_none;
+ } else {
+ INSIST(rbtdbiter->tree_locked == isc_rwlocktype_none);
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ flush_deletions(rbtdbiter);
+
+ dns_db_attach(rbtdbiter->common.db, &db);
+ dns_db_detach(&rbtdbiter->common.db);
+
+ dns_rbtnodechain_reset(&rbtdbiter->chain);
+ dns_rbtnodechain_reset(&rbtdbiter->nsec3chain);
+ isc_mem_put(db->mctx, rbtdbiter, sizeof(*rbtdbiter));
+ dns_db_detach(&db);
+
+ *iteratorp = NULL;
+}
+
+static isc_result_t
+dbiterator_first(dns_dbiterator_t *iterator) {
+ isc_result_t result;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+ dns_name_t *name, *origin;
+
+ if (rbtdbiter->result != ISC_R_SUCCESS &&
+ rbtdbiter->result != ISC_R_NOTFOUND &&
+ rbtdbiter->result != DNS_R_PARTIALMATCH &&
+ rbtdbiter->result != ISC_R_NOMORE)
+ {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ name = dns_fixedname_name(&rbtdbiter->name);
+ origin = dns_fixedname_name(&rbtdbiter->origin);
+ dns_rbtnodechain_reset(&rbtdbiter->chain);
+ dns_rbtnodechain_reset(&rbtdbiter->nsec3chain);
+
+ if (rbtdbiter->nsec3only) {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ result = dns_rbtnodechain_first(rbtdbiter->current,
+ rbtdb->nsec3, name, origin);
+ } else {
+ rbtdbiter->current = &rbtdbiter->chain;
+ result = dns_rbtnodechain_first(rbtdbiter->current, rbtdb->tree,
+ name, origin);
+ if (!rbtdbiter->nonsec3 && result == ISC_R_NOTFOUND) {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ result = dns_rbtnodechain_first(
+ rbtdbiter->current, rbtdb->nsec3, name, origin);
+ }
+ }
+ if (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) {
+ result = dns_rbtnodechain_current(rbtdbiter->current, NULL,
+ NULL, &rbtdbiter->node);
+ if (result == ISC_R_SUCCESS) {
+ rbtdbiter->new_origin = true;
+ reference_iter_node(rbtdbiter);
+ }
+ } else {
+ INSIST(result == ISC_R_NOTFOUND);
+ result = ISC_R_NOMORE; /* The tree is empty. */
+ }
+
+ rbtdbiter->result = result;
+
+ if (result != ISC_R_SUCCESS) {
+ ENSURE(!rbtdbiter->paused);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_last(dns_dbiterator_t *iterator) {
+ isc_result_t result;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+ dns_name_t *name, *origin;
+
+ if (rbtdbiter->result != ISC_R_SUCCESS &&
+ rbtdbiter->result != ISC_R_NOTFOUND &&
+ rbtdbiter->result != DNS_R_PARTIALMATCH &&
+ rbtdbiter->result != ISC_R_NOMORE)
+ {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ name = dns_fixedname_name(&rbtdbiter->name);
+ origin = dns_fixedname_name(&rbtdbiter->origin);
+ dns_rbtnodechain_reset(&rbtdbiter->chain);
+ dns_rbtnodechain_reset(&rbtdbiter->nsec3chain);
+
+ result = ISC_R_NOTFOUND;
+ if (rbtdbiter->nsec3only && !rbtdbiter->nonsec3) {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ result = dns_rbtnodechain_last(rbtdbiter->current, rbtdb->nsec3,
+ name, origin);
+ }
+ if (!rbtdbiter->nsec3only && result == ISC_R_NOTFOUND) {
+ rbtdbiter->current = &rbtdbiter->chain;
+ result = dns_rbtnodechain_last(rbtdbiter->current, rbtdb->tree,
+ name, origin);
+ }
+ if (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) {
+ result = dns_rbtnodechain_current(rbtdbiter->current, NULL,
+ NULL, &rbtdbiter->node);
+ if (result == ISC_R_SUCCESS) {
+ rbtdbiter->new_origin = true;
+ reference_iter_node(rbtdbiter);
+ }
+ } else {
+ INSIST(result == ISC_R_NOTFOUND);
+ result = ISC_R_NOMORE; /* The tree is empty. */
+ }
+
+ rbtdbiter->result = result;
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) {
+ isc_result_t result, tresult;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+ dns_name_t *iname, *origin;
+
+ if (rbtdbiter->result != ISC_R_SUCCESS &&
+ rbtdbiter->result != ISC_R_NOTFOUND &&
+ rbtdbiter->result != DNS_R_PARTIALMATCH &&
+ rbtdbiter->result != ISC_R_NOMORE)
+ {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ iname = dns_fixedname_name(&rbtdbiter->name);
+ origin = dns_fixedname_name(&rbtdbiter->origin);
+ dns_rbtnodechain_reset(&rbtdbiter->chain);
+ dns_rbtnodechain_reset(&rbtdbiter->nsec3chain);
+
+ if (rbtdbiter->nsec3only) {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ result = dns_rbt_findnode(rbtdb->nsec3, name, NULL,
+ &rbtdbiter->node, rbtdbiter->current,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ } else if (rbtdbiter->nonsec3) {
+ rbtdbiter->current = &rbtdbiter->chain;
+ result = dns_rbt_findnode(rbtdb->tree, name, NULL,
+ &rbtdbiter->node, rbtdbiter->current,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ } else {
+ /*
+ * Stay on main chain if not found on either chain.
+ */
+ rbtdbiter->current = &rbtdbiter->chain;
+ result = dns_rbt_findnode(rbtdb->tree, name, NULL,
+ &rbtdbiter->node, rbtdbiter->current,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+ if (result == DNS_R_PARTIALMATCH) {
+ dns_rbtnode_t *node = NULL;
+ tresult = dns_rbt_findnode(
+ rbtdb->nsec3, name, NULL, &node,
+ &rbtdbiter->nsec3chain, DNS_RBTFIND_EMPTYDATA,
+ NULL, NULL);
+ if (tresult == ISC_R_SUCCESS) {
+ rbtdbiter->node = node;
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ result = tresult;
+ }
+ }
+ }
+
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ tresult = dns_rbtnodechain_current(rbtdbiter->current, iname,
+ origin, NULL);
+ if (tresult == ISC_R_SUCCESS) {
+ rbtdbiter->new_origin = true;
+ reference_iter_node(rbtdbiter);
+ } else {
+ result = tresult;
+ rbtdbiter->node = NULL;
+ }
+ } else {
+ rbtdbiter->node = NULL;
+ }
+
+ rbtdbiter->result = (result == DNS_R_PARTIALMATCH) ? ISC_R_SUCCESS
+ : result;
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_prev(dns_dbiterator_t *iterator) {
+ isc_result_t result;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_name_t *name, *origin;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+
+ REQUIRE(rbtdbiter->node != NULL);
+
+ if (rbtdbiter->result != ISC_R_SUCCESS) {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ name = dns_fixedname_name(&rbtdbiter->name);
+ origin = dns_fixedname_name(&rbtdbiter->origin);
+ result = dns_rbtnodechain_prev(rbtdbiter->current, name, origin);
+ if (result == ISC_R_NOMORE && !rbtdbiter->nsec3only &&
+ !rbtdbiter->nonsec3 && &rbtdbiter->nsec3chain == rbtdbiter->current)
+ {
+ rbtdbiter->current = &rbtdbiter->chain;
+ dns_rbtnodechain_reset(rbtdbiter->current);
+ result = dns_rbtnodechain_last(rbtdbiter->current, rbtdb->tree,
+ name, origin);
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_NOMORE;
+ }
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ if (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
+ rbtdbiter->new_origin = (result == DNS_R_NEWORIGIN);
+ result = dns_rbtnodechain_current(rbtdbiter->current, NULL,
+ NULL, &rbtdbiter->node);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ reference_iter_node(rbtdbiter);
+ }
+
+ rbtdbiter->result = result;
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_next(dns_dbiterator_t *iterator) {
+ isc_result_t result;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_name_t *name, *origin;
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+
+ REQUIRE(rbtdbiter->node != NULL);
+
+ if (rbtdbiter->result != ISC_R_SUCCESS) {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ name = dns_fixedname_name(&rbtdbiter->name);
+ origin = dns_fixedname_name(&rbtdbiter->origin);
+ result = dns_rbtnodechain_next(rbtdbiter->current, name, origin);
+ if (result == ISC_R_NOMORE && !rbtdbiter->nsec3only &&
+ !rbtdbiter->nonsec3 && &rbtdbiter->chain == rbtdbiter->current)
+ {
+ rbtdbiter->current = &rbtdbiter->nsec3chain;
+ dns_rbtnodechain_reset(rbtdbiter->current);
+ result = dns_rbtnodechain_first(rbtdbiter->current,
+ rbtdb->nsec3, name, origin);
+ if (result == ISC_R_NOTFOUND) {
+ result = ISC_R_NOMORE;
+ }
+ }
+
+ dereference_iter_node(rbtdbiter);
+
+ if (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
+ rbtdbiter->new_origin = (result == DNS_R_NEWORIGIN);
+ result = dns_rbtnodechain_current(rbtdbiter->current, NULL,
+ NULL, &rbtdbiter->node);
+ }
+ if (result == ISC_R_SUCCESS) {
+ reference_iter_node(rbtdbiter);
+ }
+
+ rbtdbiter->result = result;
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_rbtnode_t *node = rbtdbiter->node;
+ isc_result_t result;
+ dns_name_t *nodename = dns_fixedname_name(&rbtdbiter->name);
+ dns_name_t *origin = dns_fixedname_name(&rbtdbiter->origin);
+
+ REQUIRE(rbtdbiter->result == ISC_R_SUCCESS);
+ REQUIRE(rbtdbiter->node != NULL);
+
+ if (rbtdbiter->paused) {
+ resume_iteration(rbtdbiter);
+ }
+
+ if (name != NULL) {
+ if (rbtdbiter->common.relative_names) {
+ origin = NULL;
+ }
+ result = dns_name_concatenate(nodename, origin, name, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (rbtdbiter->common.relative_names && rbtdbiter->new_origin) {
+ result = DNS_R_NEWORIGIN;
+ }
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ new_reference(rbtdb, node, isc_rwlocktype_none);
+
+ *nodep = rbtdbiter->node;
+
+ if (iterator->cleaning && result == ISC_R_SUCCESS) {
+ isc_result_t expire_result;
+
+ /*
+ * If the deletion array is full, flush it before trying
+ * to expire the current node. The current node can't
+ * fully deleted while the iteration cursor is still on it.
+ */
+ if (rbtdbiter->delcnt == DELETION_BATCH_MAX) {
+ flush_deletions(rbtdbiter);
+ }
+
+ expire_result = expirenode(iterator->db, *nodep, 0);
+
+ /*
+ * expirenode() currently always returns success.
+ */
+ if (expire_result == ISC_R_SUCCESS && node->down == NULL) {
+ rbtdbiter->deletions[rbtdbiter->delcnt++] = node;
+ isc_refcount_increment(&node->references);
+ }
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dbiterator_pause(dns_dbiterator_t *iterator) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db;
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+
+ if (rbtdbiter->result != ISC_R_SUCCESS &&
+ rbtdbiter->result != ISC_R_NOTFOUND &&
+ rbtdbiter->result != DNS_R_PARTIALMATCH &&
+ rbtdbiter->result != ISC_R_NOMORE)
+ {
+ return (rbtdbiter->result);
+ }
+
+ if (rbtdbiter->paused) {
+ return (ISC_R_SUCCESS);
+ }
+
+ rbtdbiter->paused = true;
+
+ if (rbtdbiter->tree_locked != isc_rwlocktype_none) {
+ INSIST(rbtdbiter->tree_locked == isc_rwlocktype_read);
+ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+ rbtdbiter->tree_locked = isc_rwlocktype_none;
+ }
+
+ flush_deletions(rbtdbiter);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) {
+ rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator;
+ dns_name_t *origin = dns_fixedname_name(&rbtdbiter->origin);
+
+ if (rbtdbiter->result != ISC_R_SUCCESS) {
+ return (rbtdbiter->result);
+ }
+
+ dns_name_copynf(origin, name);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+setownercase(rdatasetheader_t *header, const dns_name_t *name) {
+ unsigned int i;
+ bool fully_lower;
+
+ /*
+ * We do not need to worry about label lengths as they are all
+ * less than or equal to 63.
+ */
+ memset(header->upper, 0, sizeof(header->upper));
+ fully_lower = true;
+ for (i = 0; i < name->length; i++) {
+ if (isupper(name->ndata[i])) {
+ header->upper[i / 8] |= 1 << (i % 8);
+ fully_lower = false;
+ }
+ }
+ RDATASET_ATTR_SET(header, RDATASET_ATTR_CASESET);
+ if (ISC_LIKELY(fully_lower)) {
+ RDATASET_ATTR_SET(header, RDATASET_ATTR_CASEFULLYLOWER);
+ }
+}
+
+static void
+rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *rbtnode = rdataset->private2;
+ unsigned char *raw = rdataset->private3; /* RDATASLAB */
+ rdatasetheader_t *header;
+
+ header = (struct rdatasetheader *)(raw - sizeof(*header));
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+ setownercase(header, name);
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_write);
+}
+
+static void
+rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *rbtnode = rdataset->private2;
+ unsigned char *raw = rdataset->private3; /* RDATASLAB */
+ rdatasetheader_t *header = NULL;
+ uint8_t mask = (1 << 7);
+ uint8_t bits = 0;
+
+ header = (struct rdatasetheader *)(raw - sizeof(*header));
+
+ NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+
+ if (!CASESET(header)) {
+ goto unlock;
+ }
+
+ if (ISC_LIKELY(CASEFULLYLOWER(header))) {
+ for (size_t i = 0; i < name->length; i++) {
+ name->ndata[i] = tolower(name->ndata[i]);
+ }
+ } else {
+ for (size_t i = 0; i < name->length; i++) {
+ if (mask == (1 << 7)) {
+ bits = header->upper[i / 8];
+ mask = 1;
+ } else {
+ mask <<= 1;
+ }
+
+ name->ndata[i] = ((bits & mask) != 0)
+ ? toupper(name->ndata[i])
+ : tolower(name->ndata[i]);
+ }
+ }
+
+unlock:
+ NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
+ isc_rwlocktype_read);
+}
+
+struct rbtdb_glue {
+ struct rbtdb_glue *next;
+ dns_fixedname_t fixedname;
+ dns_rdataset_t rdataset_a;
+ dns_rdataset_t sigrdataset_a;
+ dns_rdataset_t rdataset_aaaa;
+ dns_rdataset_t sigrdataset_aaaa;
+};
+
+typedef struct {
+ rbtdb_glue_t *glue_list;
+ dns_rbtdb_t *rbtdb;
+ rbtdb_version_t *rbtversion;
+} rbtdb_glue_additionaldata_ctx_t;
+
+static void
+free_gluelist(rbtdb_glue_t *glue_list, dns_rbtdb_t *rbtdb) {
+ rbtdb_glue_t *cur, *cur_next;
+
+ if (glue_list == (void *)-1) {
+ return;
+ }
+
+ cur = glue_list;
+ while (cur != NULL) {
+ cur_next = cur->next;
+
+ if (dns_rdataset_isassociated(&cur->rdataset_a)) {
+ dns_rdataset_disassociate(&cur->rdataset_a);
+ }
+ if (dns_rdataset_isassociated(&cur->sigrdataset_a)) {
+ dns_rdataset_disassociate(&cur->sigrdataset_a);
+ }
+
+ if (dns_rdataset_isassociated(&cur->rdataset_aaaa)) {
+ dns_rdataset_disassociate(&cur->rdataset_aaaa);
+ }
+ if (dns_rdataset_isassociated(&cur->sigrdataset_aaaa)) {
+ dns_rdataset_disassociate(&cur->sigrdataset_aaaa);
+ }
+
+ dns_rdataset_invalidate(&cur->rdataset_a);
+ dns_rdataset_invalidate(&cur->sigrdataset_a);
+ dns_rdataset_invalidate(&cur->rdataset_aaaa);
+ dns_rdataset_invalidate(&cur->sigrdataset_aaaa);
+
+ isc_mem_put(rbtdb->common.mctx, cur, sizeof(*cur));
+ cur = cur_next;
+ }
+}
+
+static void
+free_gluetable(rbtdb_version_t *version) {
+ dns_rbtdb_t *rbtdb;
+ size_t size, i;
+
+ RWLOCK(&version->glue_rwlock, isc_rwlocktype_write);
+
+ rbtdb = version->rbtdb;
+
+ for (i = 0; i < HASHSIZE(version->glue_table_bits); i++) {
+ rbtdb_glue_table_node_t *cur, *cur_next;
+
+ cur = version->glue_table[i];
+ while (cur != NULL) {
+ cur_next = cur->next;
+ /* isc_refcount_decrement(&cur->node->references); */
+ cur->node = NULL;
+ free_gluelist(cur->glue_list, rbtdb);
+ cur->glue_list = NULL;
+ isc_mem_put(rbtdb->common.mctx, cur, sizeof(*cur));
+ cur = cur_next;
+ }
+ version->glue_table[i] = NULL;
+ }
+
+ size = HASHSIZE(version->glue_table_bits) *
+ sizeof(*version->glue_table);
+ isc_mem_put(rbtdb->common.mctx, version->glue_table, size);
+
+ RWUNLOCK(&version->glue_rwlock, isc_rwlocktype_write);
+}
+
+static uint32_t
+rehash_bits(rbtdb_version_t *version, size_t newcount) {
+ uint32_t oldbits = version->glue_table_bits;
+ uint32_t newbits = oldbits;
+
+ while (newcount >= HASHSIZE(newbits) &&
+ newbits <= RBTDB_GLUE_TABLE_MAX_BITS)
+ {
+ newbits += 1;
+ }
+
+ return (newbits);
+}
+
+/*%
+ * Write lock (version->glue_rwlock) must be held.
+ */
+static void
+rehash_gluetable(rbtdb_version_t *version) {
+ uint32_t oldbits, newbits;
+ size_t newsize, oldcount, i;
+ rbtdb_glue_table_node_t **oldtable;
+
+ oldbits = version->glue_table_bits;
+ oldcount = HASHSIZE(oldbits);
+ oldtable = version->glue_table;
+
+ newbits = rehash_bits(version, version->glue_table_nodecount);
+ newsize = HASHSIZE(newbits) * sizeof(version->glue_table[0]);
+
+ version->glue_table = isc_mem_get(version->rbtdb->common.mctx, newsize);
+ version->glue_table_bits = newbits;
+ memset(version->glue_table, 0, newsize);
+
+ for (i = 0; i < oldcount; i++) {
+ rbtdb_glue_table_node_t *gluenode;
+ rbtdb_glue_table_node_t *nextgluenode;
+ for (gluenode = oldtable[i]; gluenode != NULL;
+ gluenode = nextgluenode)
+ {
+ uint32_t hash = isc_hash32(
+ &gluenode->node, sizeof(gluenode->node), true);
+ uint32_t idx = hash_32(hash, newbits);
+ nextgluenode = gluenode->next;
+ gluenode->next = version->glue_table[idx];
+ version->glue_table[idx] = gluenode;
+ }
+ }
+
+ isc_mem_put(version->rbtdb->common.mctx, oldtable,
+ oldcount * sizeof(*version->glue_table));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ZONE,
+ ISC_LOG_DEBUG(3),
+ "rehash_gluetable(): "
+ "resized glue table from %zu to "
+ "%zu",
+ oldcount, newsize / sizeof(version->glue_table[0]));
+}
+
+static void
+maybe_rehash_gluetable(rbtdb_version_t *version) {
+ size_t overcommit = HASHSIZE(version->glue_table_bits) *
+ RBTDB_GLUE_TABLE_OVERCOMMIT;
+ if (ISC_LIKELY(version->glue_table_nodecount < overcommit)) {
+ return;
+ }
+
+ rehash_gluetable(version);
+}
+
+static isc_result_t
+glue_nsdname_cb(void *arg, const dns_name_t *name, dns_rdatatype_t qtype) {
+ rbtdb_glue_additionaldata_ctx_t *ctx;
+ isc_result_t result;
+ dns_fixedname_t fixedname_a;
+ dns_name_t *name_a = NULL;
+ dns_rdataset_t rdataset_a, sigrdataset_a;
+ dns_rbtnode_t *node_a = NULL;
+ dns_fixedname_t fixedname_aaaa;
+ dns_name_t *name_aaaa = NULL;
+ dns_rdataset_t rdataset_aaaa, sigrdataset_aaaa;
+ dns_rbtnode_t *node_aaaa = NULL;
+ rbtdb_glue_t *glue = NULL;
+ dns_name_t *gluename = NULL;
+
+ /*
+ * NS records want addresses in additional records.
+ */
+ INSIST(qtype == dns_rdatatype_a);
+
+ ctx = (rbtdb_glue_additionaldata_ctx_t *)arg;
+
+ name_a = dns_fixedname_initname(&fixedname_a);
+ dns_rdataset_init(&rdataset_a);
+ dns_rdataset_init(&sigrdataset_a);
+
+ name_aaaa = dns_fixedname_initname(&fixedname_aaaa);
+ dns_rdataset_init(&rdataset_aaaa);
+ dns_rdataset_init(&sigrdataset_aaaa);
+
+ result = zone_find((dns_db_t *)ctx->rbtdb, name, ctx->rbtversion,
+ dns_rdatatype_a, DNS_DBFIND_GLUEOK, 0,
+ (dns_dbnode_t **)&node_a, name_a, &rdataset_a,
+ &sigrdataset_a);
+ if (result == DNS_R_GLUE) {
+ glue = isc_mem_get(ctx->rbtdb->common.mctx, sizeof(*glue));
+
+ gluename = dns_fixedname_initname(&glue->fixedname);
+ dns_name_copynf(name_a, gluename);
+
+ dns_rdataset_init(&glue->rdataset_a);
+ dns_rdataset_init(&glue->sigrdataset_a);
+ dns_rdataset_init(&glue->rdataset_aaaa);
+ dns_rdataset_init(&glue->sigrdataset_aaaa);
+
+ dns_rdataset_clone(&rdataset_a, &glue->rdataset_a);
+ if (dns_rdataset_isassociated(&sigrdataset_a)) {
+ dns_rdataset_clone(&sigrdataset_a,
+ &glue->sigrdataset_a);
+ }
+ }
+
+ result = zone_find((dns_db_t *)ctx->rbtdb, name, ctx->rbtversion,
+ dns_rdatatype_aaaa, DNS_DBFIND_GLUEOK, 0,
+ (dns_dbnode_t **)&node_aaaa, name_aaaa,
+ &rdataset_aaaa, &sigrdataset_aaaa);
+ if (result == DNS_R_GLUE) {
+ if (glue == NULL) {
+ glue = isc_mem_get(ctx->rbtdb->common.mctx,
+ sizeof(*glue));
+
+ gluename = dns_fixedname_initname(&glue->fixedname);
+ dns_name_copynf(name_aaaa, gluename);
+
+ dns_rdataset_init(&glue->rdataset_a);
+ dns_rdataset_init(&glue->sigrdataset_a);
+ dns_rdataset_init(&glue->rdataset_aaaa);
+ dns_rdataset_init(&glue->sigrdataset_aaaa);
+ } else {
+ INSIST(node_a == node_aaaa);
+ INSIST(dns_name_equal(name_a, name_aaaa));
+ }
+
+ dns_rdataset_clone(&rdataset_aaaa, &glue->rdataset_aaaa);
+ if (dns_rdataset_isassociated(&sigrdataset_aaaa)) {
+ dns_rdataset_clone(&sigrdataset_aaaa,
+ &glue->sigrdataset_aaaa);
+ }
+ }
+
+ if (glue != NULL) {
+ glue->next = ctx->glue_list;
+ ctx->glue_list = glue;
+ }
+
+ result = ISC_R_SUCCESS;
+
+ if (dns_rdataset_isassociated(&rdataset_a)) {
+ rdataset_disassociate(&rdataset_a);
+ }
+ if (dns_rdataset_isassociated(&sigrdataset_a)) {
+ rdataset_disassociate(&sigrdataset_a);
+ }
+
+ if (dns_rdataset_isassociated(&rdataset_aaaa)) {
+ rdataset_disassociate(&rdataset_aaaa);
+ }
+ if (dns_rdataset_isassociated(&sigrdataset_aaaa)) {
+ rdataset_disassociate(&sigrdataset_aaaa);
+ }
+
+ if (node_a != NULL) {
+ detachnode((dns_db_t *)ctx->rbtdb, (dns_dbnode_t *)&node_a);
+ }
+ if (node_aaaa != NULL) {
+ detachnode((dns_db_t *)ctx->rbtdb, (dns_dbnode_t *)&node_aaaa);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version,
+ dns_message_t *msg) {
+ dns_rbtdb_t *rbtdb = rdataset->private1;
+ dns_rbtnode_t *node = rdataset->private2;
+ rbtdb_version_t *rbtversion = version;
+ uint32_t idx;
+ rbtdb_glue_table_node_t *cur;
+ bool found = false;
+ bool restarted = false;
+ rbtdb_glue_t *ge;
+ rbtdb_glue_additionaldata_ctx_t ctx;
+ isc_result_t result;
+ uint64_t hash;
+
+ REQUIRE(rdataset->type == dns_rdatatype_ns);
+ REQUIRE(rbtdb == rbtversion->rbtdb);
+ REQUIRE(!IS_CACHE(rbtdb) && !IS_STUB(rbtdb));
+
+ /*
+ * The glue table cache that forms a part of the DB version
+ * structure is not explicitly bounded and there's no cache
+ * cleaning. The zone data size itself is an implicit bound.
+ *
+ * The key into the glue hashtable is the node pointer. This is
+ * because the glue hashtable is a property of the DB version,
+ * and the glue is keyed for the ownername/NS tuple. We don't
+ * bother with using an expensive dns_name_t comparison here as
+ * the node pointer is a fixed value that won't change for a DB
+ * version and can be compared directly.
+ */
+ hash = isc_hash_function(&node, sizeof(node), true);
+
+restart:
+ /*
+ * First, check if we have the additional entries already cached
+ * in the glue table.
+ */
+ RWLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_read);
+
+ idx = hash_32(hash, rbtversion->glue_table_bits);
+
+ for (cur = rbtversion->glue_table[idx]; cur != NULL; cur = cur->next) {
+ if (cur->node == node) {
+ break;
+ }
+ }
+
+ if (cur == NULL) {
+ goto no_glue;
+ }
+ /*
+ * We found a cached result. Add it to the message and
+ * return.
+ */
+ found = true;
+ ge = cur->glue_list;
+
+ /*
+ * (void *) -1 is a special value that means no glue is
+ * present in the zone.
+ */
+ if (ge == (void *)-1) {
+ if (!restarted && (rbtdb->gluecachestats != NULL)) {
+ isc_stats_increment(
+ rbtdb->gluecachestats,
+ dns_gluecachestatscounter_hits_absent);
+ }
+ goto no_glue;
+ } else {
+ if (!restarted && (rbtdb->gluecachestats != NULL)) {
+ isc_stats_increment(
+ rbtdb->gluecachestats,
+ dns_gluecachestatscounter_hits_present);
+ }
+ }
+
+ for (; ge != NULL; ge = ge->next) {
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset_a = NULL;
+ dns_rdataset_t *sigrdataset_a = NULL;
+ dns_rdataset_t *rdataset_aaaa = NULL;
+ dns_rdataset_t *sigrdataset_aaaa = NULL;
+ dns_name_t *gluename = dns_fixedname_name(&ge->fixedname);
+
+ result = dns_message_gettempname(msg, &name);
+ if (ISC_UNLIKELY(result != ISC_R_SUCCESS)) {
+ goto no_glue;
+ }
+
+ dns_name_copynf(gluename, name);
+
+ if (dns_rdataset_isassociated(&ge->rdataset_a)) {
+ result = dns_message_gettemprdataset(msg, &rdataset_a);
+ if (ISC_UNLIKELY(result != ISC_R_SUCCESS)) {
+ dns_message_puttempname(msg, &name);
+ goto no_glue;
+ }
+ }
+
+ if (dns_rdataset_isassociated(&ge->sigrdataset_a)) {
+ result = dns_message_gettemprdataset(msg,
+ &sigrdataset_a);
+ if (ISC_UNLIKELY(result != ISC_R_SUCCESS)) {
+ if (rdataset_a != NULL) {
+ dns_message_puttemprdataset(
+ msg, &rdataset_a);
+ }
+ dns_message_puttempname(msg, &name);
+ goto no_glue;
+ }
+ }
+
+ if (dns_rdataset_isassociated(&ge->rdataset_aaaa)) {
+ result = dns_message_gettemprdataset(msg,
+ &rdataset_aaaa);
+ if (ISC_UNLIKELY(result != ISC_R_SUCCESS)) {
+ dns_message_puttempname(msg, &name);
+ if (rdataset_a != NULL) {
+ dns_message_puttemprdataset(
+ msg, &rdataset_a);
+ }
+ if (sigrdataset_a != NULL) {
+ dns_message_puttemprdataset(
+ msg, &sigrdataset_a);
+ }
+ goto no_glue;
+ }
+ }
+
+ if (dns_rdataset_isassociated(&ge->sigrdataset_aaaa)) {
+ result = dns_message_gettemprdataset(msg,
+ &sigrdataset_aaaa);
+ if (ISC_UNLIKELY(result != ISC_R_SUCCESS)) {
+ dns_message_puttempname(msg, &name);
+ if (rdataset_a != NULL) {
+ dns_message_puttemprdataset(
+ msg, &rdataset_a);
+ }
+ if (sigrdataset_a != NULL) {
+ dns_message_puttemprdataset(
+ msg, &sigrdataset_a);
+ }
+ if (rdataset_aaaa != NULL) {
+ dns_message_puttemprdataset(
+ msg, &rdataset_aaaa);
+ }
+ goto no_glue;
+ }
+ }
+
+ if (ISC_LIKELY(rdataset_a != NULL)) {
+ dns_rdataset_clone(&ge->rdataset_a, rdataset_a);
+ ISC_LIST_APPEND(name->list, rdataset_a, link);
+ }
+
+ if (sigrdataset_a != NULL) {
+ dns_rdataset_clone(&ge->sigrdataset_a, sigrdataset_a);
+ ISC_LIST_APPEND(name->list, sigrdataset_a, link);
+ }
+
+ if (rdataset_aaaa != NULL) {
+ dns_rdataset_clone(&ge->rdataset_aaaa, rdataset_aaaa);
+ ISC_LIST_APPEND(name->list, rdataset_aaaa, link);
+ }
+ if (sigrdataset_aaaa != NULL) {
+ dns_rdataset_clone(&ge->sigrdataset_aaaa,
+ sigrdataset_aaaa);
+ ISC_LIST_APPEND(name->list, sigrdataset_aaaa, link);
+ }
+
+ dns_message_addname(msg, name, DNS_SECTION_ADDITIONAL);
+ }
+
+no_glue:
+ RWUNLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_read);
+
+ if (found) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (restarted) {
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * No cached glue was found in the table. Cache it and restart
+ * this function.
+ *
+ * Due to the gap between the read lock and the write lock, it's
+ * possible that we may cache a duplicate glue table entry, but
+ * we don't care.
+ */
+
+ ctx.glue_list = NULL;
+ ctx.rbtdb = rbtdb;
+ ctx.rbtversion = rbtversion;
+
+ RWLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_write);
+
+ maybe_rehash_gluetable(rbtversion);
+ idx = hash_32(hash, rbtversion->glue_table_bits);
+
+ (void)dns_rdataset_additionaldata(rdataset, glue_nsdname_cb, &ctx);
+
+ cur = isc_mem_get(rbtdb->common.mctx, sizeof(*cur));
+
+ /*
+ * XXXMUKS: it looks like the dns_dbversion is not destroyed
+ * when named is terminated by a keyboard break. This doesn't
+ * cleanup the node reference and keeps the process dangling.
+ */
+ /* isc_refcount_increment0(&node->references); */
+ cur->node = node;
+
+ if (ctx.glue_list == NULL) {
+ /*
+ * No glue was found. Cache it so.
+ */
+ cur->glue_list = (void *)-1;
+ if (rbtdb->gluecachestats != NULL) {
+ isc_stats_increment(
+ rbtdb->gluecachestats,
+ dns_gluecachestatscounter_inserts_absent);
+ }
+ } else {
+ cur->glue_list = ctx.glue_list;
+ if (rbtdb->gluecachestats != NULL) {
+ isc_stats_increment(
+ rbtdb->gluecachestats,
+ dns_gluecachestatscounter_inserts_present);
+ }
+ }
+
+ cur->next = rbtversion->glue_table[idx];
+ rbtversion->glue_table[idx] = cur;
+ rbtversion->glue_table_nodecount++;
+
+ RWUNLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_write);
+
+ restarted = true;
+ goto restart;
+
+ /* UNREACHABLE */
+}
+
+/*%
+ * Routines for LRU-based cache management.
+ */
+
+/*%
+ * See if a given cache entry that is being reused needs to be updated
+ * in the LRU-list. From the LRU management point of view, this function is
+ * expected to return true for almost all cases. When used with threads,
+ * however, this may cause a non-negligible performance penalty because a
+ * writer lock will have to be acquired before updating the list.
+ * If DNS_RBTDB_LIMITLRUUPDATE is defined to be non 0 at compilation time, this
+ * function returns true if the entry has not been updated for some period of
+ * time. We differentiate the NS or glue address case and the others since
+ * experiments have shown that the former tends to be accessed relatively
+ * infrequently and the cost of cache miss is higher (e.g., a missing NS records
+ * may cause external queries at a higher level zone, involving more
+ * transactions).
+ *
+ * Caller must hold the node (read or write) lock.
+ */
+static bool
+need_headerupdate(rdatasetheader_t *header, isc_stdtime_t now) {
+ if (RDATASET_ATTR_GET(header, (RDATASET_ATTR_NONEXISTENT |
+ RDATASET_ATTR_ANCIENT |
+ RDATASET_ATTR_ZEROTTL)) != 0)
+ {
+ return (false);
+ }
+
+#if DNS_RBTDB_LIMITLRUUPDATE
+ if (header->type == dns_rdatatype_ns ||
+ (header->trust == dns_trust_glue &&
+ (header->type == dns_rdatatype_a ||
+ header->type == dns_rdatatype_aaaa)))
+ {
+ /*
+ * Glue records are updated if at least DNS_RBTDB_LRUUPDATE_GLUE
+ * seconds have passed since the previous update time.
+ */
+ return (header->last_used + DNS_RBTDB_LRUUPDATE_GLUE <= now);
+ }
+
+ /*
+ * Other records are updated if DNS_RBTDB_LRUUPDATE_REGULAR seconds
+ * have passed.
+ */
+ return (header->last_used + DNS_RBTDB_LRUUPDATE_REGULAR <= now);
+#else
+ UNUSED(now);
+
+ return (true);
+#endif /* if DNS_RBTDB_LIMITLRUUPDATE */
+}
+
+/*%
+ * Update the timestamp of a given cache entry and move it to the head
+ * of the corresponding LRU list.
+ *
+ * Caller must hold the node (write) lock.
+ *
+ * Note that the we do NOT touch the heap here, as the TTL has not changed.
+ */
+static void
+update_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, isc_stdtime_t now) {
+ INSIST(IS_CACHE(rbtdb));
+
+ /* To be checked: can we really assume this? XXXMLG */
+ INSIST(ISC_LINK_LINKED(header, link));
+
+ ISC_LIST_UNLINK(rbtdb->rdatasets[header->node->locknum], header, link);
+ header->last_used = now;
+ ISC_LIST_PREPEND(rbtdb->rdatasets[header->node->locknum], header, link);
+}
+
+static size_t
+expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, size_t purgesize,
+ bool tree_locked) {
+ rdatasetheader_t *header, *header_prev;
+ size_t purged = 0;
+
+ for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]);
+ header != NULL && purged <= purgesize; header = header_prev)
+ {
+ header_prev = ISC_LIST_PREV(header, link);
+ /*
+ * Unlink the entry at this point to avoid checking it
+ * again even if it's currently used someone else and
+ * cannot be purged at this moment. This entry won't be
+ * referenced any more (so unlinking is safe) since the
+ * TTL was reset to 0.
+ */
+ ISC_LIST_UNLINK(rbtdb->rdatasets[locknum], header, link);
+ size_t header_size = rdataset_size(header);
+ expire_header(rbtdb, header, tree_locked, expire_lru);
+ purged += header_size;
+ }
+
+ return (purged);
+}
+
+/*%
+ * Purge some stale (i.e. unused for some period - LRU based cleaning) cache
+ * entries under the overmem condition. To recover from this condition quickly,
+ * we cleanup entries up to the size of newly added rdata (passed as purgesize).
+ *
+ * This process is triggered while adding a new entry, and we specifically avoid
+ * purging entries in the same LRU bucket as the one to which the new entry will
+ * belong. Otherwise, we might purge entries of the same name of different RR
+ * types while adding RRsets from a single response (consider the case where
+ * we're adding A and AAAA glue records of the same NS name).
+ */
+static void
+overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize,
+ bool tree_locked) {
+ unsigned int locknum;
+ size_t purged = 0;
+
+ for (locknum = (locknum_start + 1) % rbtdb->node_lock_count;
+ locknum != locknum_start && purged <= purgesize;
+ locknum = (locknum + 1) % rbtdb->node_lock_count)
+ {
+ NODE_LOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_write);
+
+ purged += expire_lru_headers(rbtdb, locknum, purgesize - purged,
+ tree_locked);
+
+ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock,
+ isc_rwlocktype_write);
+ }
+}
+
+static void
+expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked,
+ expire_t reason) {
+ set_ttl(rbtdb, header, 0);
+ mark_header_ancient(rbtdb, header);
+
+ /*
+ * Caller must hold the node (write) lock.
+ */
+
+ if (isc_refcount_current(&header->node->references) == 0) {
+ /*
+ * If no one else is using the node, we can clean it up now.
+ * We first need to gain a new reference to the node to meet a
+ * requirement of decrement_reference().
+ */
+ new_reference(rbtdb, header->node, isc_rwlocktype_write);
+ decrement_reference(rbtdb, header->node, 0,
+ isc_rwlocktype_write,
+ tree_locked ? isc_rwlocktype_write
+ : isc_rwlocktype_none,
+ false);
+
+ if (rbtdb->cachestats == NULL) {
+ return;
+ }
+
+ switch (reason) {
+ case expire_ttl:
+ isc_stats_increment(rbtdb->cachestats,
+ dns_cachestatscounter_deletettl);
+ break;
+ case expire_lru:
+ isc_stats_increment(rbtdb->cachestats,
+ dns_cachestatscounter_deletelru);
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/lib/dns/rbtdb.h b/lib/dns/rbtdb.h
new file mode 100644
index 0000000..54f0b44
--- /dev/null
+++ b/lib/dns/rbtdb.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RBTDB_H
+#define DNS_RBTDB_H 1
+
+#include <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
+
+#endif /* DNS_RBTDB_H */
diff --git a/lib/dns/rcode.c b/lib/dns/rcode.c
new file mode 100644
index 0000000..4beb5a2
--- /dev/null
+++ b/lib/dns/rcode.c
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <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 <pk11/site.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/result.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..a283831
--- /dev/null
+++ b/lib/dns/rdata.c
@@ -0,0 +1,2365 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/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/keyflags.h>
+#include <dns/keyvalues.h>
+#include <dns/message.h>
+#include <dns/rcode.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/result.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, dns_additionaldatafunc_t add, void *arg
+
+#define CALL_ADDLDATA rdata, add, arg
+
+#define ARGS_DIGEST dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg
+
+#define CALL_DIGEST rdata, digest, arg
+
+#define ARGS_CHECKOWNER \
+ const dns_name_t *name, dns_rdataclass_t rdclass, \
+ dns_rdatatype_t type, bool wildcard
+
+#define CALL_CHECKOWNER name, rdclass, type, wildcard
+
+#define ARGS_CHECKNAMES \
+ dns_rdata_t *rdata, const dns_name_t *owner, dns_name_t *bad
+
+#define CALL_CHECKNAMES rdata, owner, bad
+
+/*%
+ * Context structure for the totext_ functions.
+ * Contains formatting options for rdata-to-text
+ * conversion.
+ */
+typedef struct dns_rdata_textctx {
+ const dns_name_t *origin; /*%< Current origin, or NULL. */
+ dns_masterstyle_flags_t flags; /*%< DNS_STYLEFLAG_* */
+ unsigned int width; /*%< Width of rdata column. */
+ const char *linebreak; /*%< Line break string. */
+} dns_rdata_textctx_t;
+
+static isc_result_t
+txt_totext(isc_region_t *source, bool quote, isc_buffer_t *target);
+
+static isc_result_t
+txt_fromtext(isc_textregion_t *source, isc_buffer_t *target);
+
+static isc_result_t
+txt_fromwire(isc_buffer_t *source, isc_buffer_t *target);
+
+static isc_result_t
+commatxt_fromtext(isc_textregion_t *source, bool comma, isc_buffer_t *target);
+
+static isc_result_t
+commatxt_totext(isc_region_t *source, bool quote, bool comma,
+ isc_buffer_t *target);
+
+static isc_result_t
+multitxt_totext(isc_region_t *source, isc_buffer_t *target);
+
+static isc_result_t
+multitxt_fromtext(isc_textregion_t *source, isc_buffer_t *target);
+
+static bool
+name_prefix(dns_name_t *name, const dns_name_t *origin, dns_name_t *target);
+
+static unsigned int
+name_length(const dns_name_t *name);
+
+static isc_result_t
+str_totext(const char *source, isc_buffer_t *target);
+
+static isc_result_t
+inet_totext(int af, uint32_t flags, isc_region_t *src, isc_buffer_t *target);
+
+static bool
+buffer_empty(isc_buffer_t *source);
+
+static void
+buffer_fromregion(isc_buffer_t *buffer, isc_region_t *region);
+
+static isc_result_t
+uint32_tobuffer(uint32_t, isc_buffer_t *target);
+
+static isc_result_t
+uint16_tobuffer(uint32_t, isc_buffer_t *target);
+
+static isc_result_t
+uint8_tobuffer(uint32_t, isc_buffer_t *target);
+
+static isc_result_t
+name_tobuffer(const dns_name_t *name, isc_buffer_t *target);
+
+static uint32_t
+uint32_fromregion(isc_region_t *region);
+
+static uint16_t
+uint16_fromregion(isc_region_t *region);
+
+static uint8_t
+uint8_fromregion(isc_region_t *region);
+
+static uint8_t
+uint8_consume_fromregion(isc_region_t *region);
+
+static isc_result_t
+mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length);
+
+static int
+hexvalue(char value);
+
+static int
+decvalue(char value);
+
+static void
+default_fromtext_callback(dns_rdatacallbacks_t *callbacks, const char *, ...)
+ ISC_FORMAT_PRINTF(2, 3);
+
+static void
+fromtext_error(void (*callback)(dns_rdatacallbacks_t *, const char *, ...),
+ dns_rdatacallbacks_t *callbacks, const char *name,
+ unsigned long line, isc_token_t *token, isc_result_t result);
+
+static void
+fromtext_warneof(isc_lex_t *lexer, dns_rdatacallbacks_t *callbacks);
+
+static isc_result_t
+rdata_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx,
+ isc_buffer_t *target);
+
+static void
+warn_badname(const dns_name_t *name, isc_lex_t *lexer,
+ dns_rdatacallbacks_t *callbacks);
+
+static void
+warn_badmx(isc_token_t *token, isc_lex_t *lexer,
+ dns_rdatacallbacks_t *callbacks);
+
+static uint16_t
+uint16_consume_fromregion(isc_region_t *region);
+
+static isc_result_t
+unknown_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx,
+ isc_buffer_t *target);
+
+static isc_result_t generic_fromtext_key(ARGS_FROMTEXT);
+
+static isc_result_t generic_totext_key(ARGS_TOTEXT);
+
+static isc_result_t generic_fromwire_key(ARGS_FROMWIRE);
+
+static isc_result_t generic_fromstruct_key(ARGS_FROMSTRUCT);
+
+static isc_result_t generic_tostruct_key(ARGS_TOSTRUCT);
+
+static void generic_freestruct_key(ARGS_FREESTRUCT);
+
+static isc_result_t generic_fromtext_txt(ARGS_FROMTEXT);
+
+static isc_result_t generic_totext_txt(ARGS_TOTEXT);
+
+static isc_result_t generic_fromwire_txt(ARGS_FROMWIRE);
+
+static isc_result_t generic_fromstruct_txt(ARGS_FROMSTRUCT);
+
+static isc_result_t generic_tostruct_txt(ARGS_TOSTRUCT);
+
+static void generic_freestruct_txt(ARGS_FREESTRUCT);
+
+static isc_result_t
+generic_txt_first(dns_rdata_txt_t *txt);
+
+static isc_result_t
+generic_txt_next(dns_rdata_txt_t *txt);
+
+static isc_result_t
+generic_txt_current(dns_rdata_txt_t *txt, dns_rdata_txt_string_t *string);
+
+static isc_result_t generic_totext_ds(ARGS_TOTEXT);
+
+static isc_result_t generic_tostruct_ds(ARGS_TOSTRUCT);
+
+static isc_result_t generic_fromtext_ds(ARGS_FROMTEXT);
+
+static isc_result_t generic_fromwire_ds(ARGS_FROMWIRE);
+
+static isc_result_t generic_fromstruct_ds(ARGS_FROMSTRUCT);
+
+static isc_result_t generic_fromtext_tlsa(ARGS_FROMTEXT);
+
+static isc_result_t generic_totext_tlsa(ARGS_TOTEXT);
+
+static isc_result_t generic_fromwire_tlsa(ARGS_FROMWIRE);
+
+static isc_result_t generic_fromstruct_tlsa(ARGS_FROMSTRUCT);
+
+static isc_result_t generic_tostruct_tlsa(ARGS_TOSTRUCT);
+
+static void generic_freestruct_tlsa(ARGS_FREESTRUCT);
+
+static isc_result_t generic_fromtext_in_svcb(ARGS_FROMTEXT);
+static isc_result_t generic_totext_in_svcb(ARGS_TOTEXT);
+static isc_result_t generic_fromwire_in_svcb(ARGS_FROMWIRE);
+static isc_result_t generic_towire_in_svcb(ARGS_TOWIRE);
+static isc_result_t generic_fromstruct_in_svcb(ARGS_FROMSTRUCT);
+static isc_result_t generic_tostruct_in_svcb(ARGS_TOSTRUCT);
+static void generic_freestruct_in_svcb(ARGS_FREESTRUCT);
+static isc_result_t generic_additionaldata_in_svcb(ARGS_ADDLDATA);
+static bool generic_checknames_in_svcb(ARGS_CHECKNAMES);
+static isc_result_t
+generic_rdata_in_svcb_first(dns_rdata_in_svcb_t *);
+static isc_result_t
+generic_rdata_in_svcb_next(dns_rdata_in_svcb_t *);
+static void
+generic_rdata_in_svcb_current(dns_rdata_in_svcb_t *, isc_region_t *);
+
+/*% INT16 Size */
+#define NS_INT16SZ 2
+/*% IPv6 Address Size */
+#define NS_LOCATORSZ 8
+
+/*
+ * Active Directory gc._msdcs.<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 isc_result_t
+name_duporclone(const dns_name_t *source, isc_mem_t *mctx, dns_name_t *target) {
+ if (mctx != NULL) {
+ dns_name_dup(source, mctx, target);
+ } else {
+ dns_name_clone(source, target);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static void *
+mem_maybedup(isc_mem_t *mctx, void *source, size_t length) {
+ void *copy;
+
+ if (mctx == NULL) {
+ return (source);
+ }
+ copy = isc_mem_allocate(mctx, length);
+ memmove(copy, source, length);
+
+ return (copy);
+}
+
+static isc_result_t
+typemap_fromtext(isc_lex_t *lexer, isc_buffer_t *target, bool allow_empty) {
+ isc_token_t token;
+ unsigned char bm[8 * 1024]; /* 64k bits */
+ dns_rdatatype_t covered, max_used;
+ int octet;
+ unsigned int max_octet, newend, end;
+ int window;
+ bool first = true;
+
+ max_used = 0;
+ bm[0] = 0;
+ end = 0;
+
+ do {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, true));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+ RETTOK(dns_rdatatype_fromtext(&covered,
+ &token.value.as_textregion));
+ if (covered > max_used) {
+ newend = covered / 8;
+ if (newend > end) {
+ memset(&bm[end + 1], 0, newend - end);
+ end = newend;
+ }
+ max_used = covered;
+ }
+ bm[covered / 8] |= (0x80 >> (covered % 8));
+ first = false;
+ } while (1);
+ isc_lex_ungettoken(lexer, &token);
+ if (!allow_empty && first) {
+ return (DNS_R_FORMERR);
+ }
+
+ for (window = 0; window < 256; window++) {
+ if (max_used < window * 256) {
+ break;
+ }
+
+ max_octet = max_used - (window * 256);
+ if (max_octet >= 256) {
+ max_octet = 31;
+ } else {
+ max_octet /= 8;
+ }
+
+ /*
+ * Find if we have a type in this window.
+ */
+ for (octet = max_octet; octet >= 0; octet--) {
+ if (bm[window * 32 + octet] != 0) {
+ break;
+ }
+ }
+ if (octet < 0) {
+ continue;
+ }
+ RETERR(uint8_tobuffer(window, target));
+ RETERR(uint8_tobuffer(octet + 1, target));
+ RETERR(mem_tobuffer(target, &bm[window * 32], octet + 1));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+typemap_totext(isc_region_t *sr, dns_rdata_textctx_t *tctx,
+ isc_buffer_t *target) {
+ unsigned int i, j, k;
+ unsigned int window, len;
+ bool first = true;
+
+ for (i = 0; i < sr->length; i += len) {
+ if (tctx != NULL &&
+ (tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0)
+ {
+ RETERR(str_totext(tctx->linebreak, target));
+ first = true;
+ }
+ INSIST(i + 2 <= sr->length);
+ window = sr->base[i];
+ len = sr->base[i + 1];
+ INSIST(len > 0 && len <= 32);
+ i += 2;
+ INSIST(i + len <= sr->length);
+ for (j = 0; j < len; j++) {
+ dns_rdatatype_t t;
+ if (sr->base[i + j] == 0) {
+ continue;
+ }
+ for (k = 0; k < 8; k++) {
+ if ((sr->base[i + j] & (0x80 >> k)) == 0) {
+ continue;
+ }
+ t = window * 256 + j * 8 + k;
+ if (!first) {
+ RETERR(str_totext(" ", target));
+ }
+ first = false;
+ if (dns_rdatatype_isknown(t)) {
+ RETERR(dns_rdatatype_totext(t, target));
+ } else {
+ char buf[sizeof("TYPE65535")];
+ snprintf(buf, sizeof(buf), "TYPE%u", t);
+ RETERR(str_totext(buf, target));
+ }
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+typemap_test(isc_region_t *sr, bool allow_empty) {
+ unsigned int window, lastwindow = 0;
+ unsigned int len;
+ bool first = true;
+ unsigned int i;
+
+ for (i = 0; i < sr->length; i += len) {
+ /*
+ * Check for overflow.
+ */
+ if (i + 2 > sr->length) {
+ RETERR(DNS_R_FORMERR);
+ }
+ window = sr->base[i];
+ len = sr->base[i + 1];
+ i += 2;
+ /*
+ * Check that bitmap windows are in the correct order.
+ */
+ if (!first && window <= lastwindow) {
+ RETERR(DNS_R_FORMERR);
+ }
+ /*
+ * Check for legal lengths.
+ */
+ if (len < 1 || len > 32) {
+ RETERR(DNS_R_FORMERR);
+ }
+ /*
+ * Check for overflow.
+ */
+ if (i + len > sr->length) {
+ RETERR(DNS_R_FORMERR);
+ }
+ /*
+ * The last octet of the bitmap must be non zero.
+ */
+ if (sr->base[i + len - 1] == 0) {
+ RETERR(DNS_R_FORMERR);
+ }
+ lastwindow = window;
+ first = false;
+ }
+ if (i != sr->length) {
+ return (DNS_R_EXTRADATA);
+ }
+ if (!allow_empty && first) {
+ RETERR(DNS_R_FORMERR);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static const char hexdigits[] = "0123456789abcdef";
+static const char decdigits[] = "0123456789";
+
+#include "code.h"
+
+#define META 0x0001
+#define RESERVED 0x0002
+
+/***
+ *** Initialization
+ ***/
+
+void
+dns_rdata_init(dns_rdata_t *rdata) {
+ REQUIRE(rdata != NULL);
+
+ rdata->data = NULL;
+ rdata->length = 0;
+ rdata->rdclass = 0;
+ rdata->type = 0;
+ rdata->flags = 0;
+ ISC_LINK_INIT(rdata, link);
+ /* ISC_LIST_INIT(rdata->list); */
+}
+
+void
+dns_rdata_reset(dns_rdata_t *rdata) {
+ REQUIRE(rdata != NULL);
+
+ REQUIRE(!ISC_LINK_LINKED(rdata, link));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ rdata->data = NULL;
+ rdata->length = 0;
+ rdata->rdclass = 0;
+ rdata->type = 0;
+ rdata->flags = 0;
+}
+
+/***
+ ***
+ ***/
+
+void
+dns_rdata_clone(const dns_rdata_t *src, dns_rdata_t *target) {
+ REQUIRE(src != NULL);
+ REQUIRE(target != NULL);
+
+ REQUIRE(DNS_RDATA_INITIALIZED(target));
+
+ REQUIRE(DNS_RDATA_VALIDFLAGS(src));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(target));
+
+ target->data = src->data;
+ target->length = src->length;
+ target->rdclass = src->rdclass;
+ target->type = src->type;
+ target->flags = src->flags;
+}
+
+/***
+ *** Comparisons
+ ***/
+
+int
+dns_rdata_compare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2) {
+ int result = 0;
+ bool use_default = false;
+
+ REQUIRE(rdata1 != NULL);
+ REQUIRE(rdata2 != NULL);
+ REQUIRE(rdata1->length == 0 || rdata1->data != NULL);
+ REQUIRE(rdata2->length == 0 || rdata2->data != NULL);
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata1));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata2));
+
+ if (rdata1->rdclass != rdata2->rdclass) {
+ return (rdata1->rdclass < rdata2->rdclass ? -1 : 1);
+ }
+
+ if (rdata1->type != rdata2->type) {
+ return (rdata1->type < rdata2->type ? -1 : 1);
+ }
+
+ COMPARESWITCH
+
+ if (use_default) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ result = isc_region_compare(&r1, &r2);
+ }
+ return (result);
+}
+
+int
+dns_rdata_casecompare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2) {
+ int result = 0;
+ bool use_default = false;
+
+ REQUIRE(rdata1 != NULL);
+ REQUIRE(rdata2 != NULL);
+ REQUIRE(rdata1->length == 0 || rdata1->data != NULL);
+ REQUIRE(rdata2->length == 0 || rdata2->data != NULL);
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata1));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata2));
+
+ if (rdata1->rdclass != rdata2->rdclass) {
+ return (rdata1->rdclass < rdata2->rdclass ? -1 : 1);
+ }
+
+ if (rdata1->type != rdata2->type) {
+ return (rdata1->type < rdata2->type ? -1 : 1);
+ }
+
+ CASECOMPARESWITCH
+
+ if (use_default) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ result = isc_region_compare(&r1, &r2);
+ }
+ return (result);
+}
+
+/***
+ *** Conversions
+ ***/
+
+void
+dns_rdata_fromregion(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, isc_region_t *r) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+ REQUIRE(r != NULL);
+
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ rdata->data = r->base;
+ rdata->length = r->length;
+ rdata->rdclass = rdclass;
+ rdata->type = type;
+ rdata->flags = 0;
+}
+
+void
+dns_rdata_toregion(const dns_rdata_t *rdata, isc_region_t *r) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(r != NULL);
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+
+ r->base = rdata->data;
+ r->length = rdata->length;
+}
+
+isc_result_t
+dns_rdata_fromwire(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, isc_buffer_t *source,
+ dns_decompress_t *dctx, unsigned int options,
+ isc_buffer_t *target) {
+ isc_result_t result = ISC_R_NOTIMPLEMENTED;
+ isc_region_t region;
+ isc_buffer_t ss;
+ isc_buffer_t st;
+ bool use_default = false;
+ uint32_t activelength;
+ unsigned int length;
+
+ REQUIRE(dctx != NULL);
+ if (rdata != NULL) {
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+ REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
+ }
+ REQUIRE(source != NULL);
+ REQUIRE(target != NULL);
+
+ if (type == 0) {
+ return (DNS_R_FORMERR);
+ }
+
+ ss = *source;
+ st = *target;
+
+ activelength = isc_buffer_activelength(source);
+ INSIST(activelength < 65536);
+
+ FROMWIRESWITCH
+
+ if (use_default) {
+ if (activelength > isc_buffer_availablelength(target)) {
+ result = ISC_R_NOSPACE;
+ } else {
+ isc_buffer_putmem(target, isc_buffer_current(source),
+ activelength);
+ isc_buffer_forward(source, activelength);
+ result = ISC_R_SUCCESS;
+ }
+ }
+
+ /*
+ * Reject any rdata that expands out to more than DNS_RDATA_MAXLENGTH
+ * as we cannot transmit it.
+ */
+ length = isc_buffer_usedlength(target) - isc_buffer_usedlength(&st);
+ if (result == ISC_R_SUCCESS && length > DNS_RDATA_MAXLENGTH) {
+ result = DNS_R_FORMERR;
+ }
+
+ /*
+ * We should have consumed all of our buffer.
+ */
+ if (result == ISC_R_SUCCESS && !buffer_empty(source)) {
+ result = DNS_R_EXTRADATA;
+ }
+
+ if (rdata != NULL && result == ISC_R_SUCCESS) {
+ region.base = isc_buffer_used(&st);
+ region.length = length;
+ dns_rdata_fromregion(rdata, rdclass, type, &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, 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),
+ dns_result_totext(DNS_R_MXISADDRESS));
+ }
+}
+
+static void
+warn_badname(const dns_name_t *name, isc_lex_t *lexer,
+ dns_rdatacallbacks_t *callbacks) {
+ const char *file;
+ unsigned long line;
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ if (lexer != NULL) {
+ file = isc_lex_getsourcename(lexer);
+ line = isc_lex_getsourceline(lexer);
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ (*callbacks->warn)(callbacks, "%s:%u: warning: %s: %s", file,
+ line, namebuf,
+ dns_result_totext(DNS_R_BADNAME));
+ }
+}
+
+static void
+fromtext_error(void (*callback)(dns_rdatacallbacks_t *, const char *, ...),
+ dns_rdatacallbacks_t *callbacks, const char *name,
+ unsigned long line, isc_token_t *token, isc_result_t result) {
+ if (name == NULL) {
+ name = "UNKNOWN";
+ }
+
+ if (token != NULL) {
+ switch (token->type) {
+ case isc_tokentype_eol:
+ (*callback)(callbacks, "%s: %s:%lu: near eol: %s",
+ "dns_rdata_fromtext", name, line,
+ dns_result_totext(result));
+ break;
+ case isc_tokentype_eof:
+ (*callback)(callbacks, "%s: %s:%lu: near eof: %s",
+ "dns_rdata_fromtext", name, line,
+ dns_result_totext(result));
+ break;
+ case isc_tokentype_number:
+ (*callback)(callbacks, "%s: %s:%lu: near %lu: %s",
+ "dns_rdata_fromtext", name, line,
+ token->value.as_ulong,
+ dns_result_totext(result));
+ break;
+ case isc_tokentype_string:
+ case isc_tokentype_qstring:
+ (*callback)(callbacks, "%s: %s:%lu: near '%s': %s",
+ "dns_rdata_fromtext", name, line,
+ DNS_AS_STR(*token),
+ dns_result_totext(result));
+ break;
+ default:
+ (*callback)(callbacks, "%s: %s:%lu: %s",
+ "dns_rdata_fromtext", name, line,
+ dns_result_totext(result));
+ break;
+ }
+ } else {
+ (*callback)(callbacks, "dns_rdata_fromtext: %s:%lu: %s", name,
+ line, dns_result_totext(result));
+ }
+}
+
+dns_rdatatype_t
+dns_rdata_covers(dns_rdata_t *rdata) {
+ if (rdata->type == dns_rdatatype_rrsig) {
+ return (covers_rrsig(rdata));
+ }
+ return (covers_sig(rdata));
+}
+
+bool
+dns_rdatatype_ismeta(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_META) != 0) {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_issingleton(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_SINGLETON) != 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_notquestion(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_NOTQUESTION) !=
+ 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_questiononly(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_QUESTIONONLY) !=
+ 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_atcname(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATCNAME) != 0) {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_atparent(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATPARENT) != 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_followadditional(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) &
+ DNS_RDATATYPEATTR_FOLLOWADDITIONAL) != 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdataclass_ismeta(dns_rdataclass_t rdclass) {
+ if (rdclass == dns_rdataclass_reserved0 ||
+ rdclass == dns_rdataclass_none || rdclass == dns_rdataclass_any)
+ {
+ return (true);
+ }
+
+ return (false); /* Assume it is not a meta class. */
+}
+
+bool
+dns_rdatatype_isdnssec(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_DNSSEC) != 0) {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_iszonecutauth(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ZONECUTAUTH) !=
+ 0)
+ {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+dns_rdatatype_isknown(dns_rdatatype_t type) {
+ if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_UNKNOWN) == 0) {
+ return (true);
+ }
+ return (false);
+}
+
+void
+dns_rdata_exists(dns_rdata_t *rdata, dns_rdatatype_t type) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+
+ rdata->data = NULL;
+ rdata->length = 0;
+ rdata->flags = DNS_RDATA_UPDATE;
+ rdata->type = type;
+ rdata->rdclass = dns_rdataclass_any;
+}
+
+void
+dns_rdata_notexist(dns_rdata_t *rdata, dns_rdatatype_t type) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+
+ rdata->data = NULL;
+ rdata->length = 0;
+ rdata->flags = DNS_RDATA_UPDATE;
+ rdata->type = type;
+ rdata->rdclass = dns_rdataclass_none;
+}
+
+void
+dns_rdata_deleterrset(dns_rdata_t *rdata, dns_rdatatype_t type) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+
+ rdata->data = NULL;
+ rdata->length = 0;
+ rdata->flags = DNS_RDATA_UPDATE;
+ rdata->type = type;
+ rdata->rdclass = dns_rdataclass_any;
+}
+
+void
+dns_rdata_makedelete(dns_rdata_t *rdata) {
+ REQUIRE(rdata != NULL);
+
+ rdata->rdclass = dns_rdataclass_none;
+}
+
+const char *
+dns_rdata_updateop(dns_rdata_t *rdata, dns_section_t section) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+
+ switch (section) {
+ case DNS_SECTION_PREREQUISITE:
+ switch (rdata->rdclass) {
+ case dns_rdataclass_none:
+ switch (rdata->type) {
+ case dns_rdatatype_any:
+ return ("domain doesn't exist");
+ default:
+ return ("rrset doesn't exist");
+ }
+ case dns_rdataclass_any:
+ switch (rdata->type) {
+ case dns_rdatatype_any:
+ return ("domain exists");
+ default:
+ return ("rrset exists (value independent)");
+ }
+ default:
+ return ("rrset exists (value dependent)");
+ }
+ case DNS_SECTION_UPDATE:
+ switch (rdata->rdclass) {
+ case dns_rdataclass_none:
+ return ("delete");
+ case dns_rdataclass_any:
+ switch (rdata->type) {
+ case dns_rdatatype_any:
+ return ("delete all rrsets");
+ default:
+ return ("delete rrset");
+ }
+ default:
+ return ("add");
+ }
+ }
+ return ("invalid");
+}
diff --git a/lib/dns/rdata/any_255/tsig_250.c b/lib/dns/rdata/any_255/tsig_250.c
new file mode 100644
index 0000000..4c6b715
--- /dev/null
+++ b/lib/dns/rdata/any_255/tsig_250.c
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_ANY_255_TSIG_250_C
+#define RDATA_ANY_255_TSIG_250_C
+
+#define RRTYPE_TSIG_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_META | DNS_RDATATYPEATTR_NOTQUESTION)
+
+static isc_result_t
+fromtext_any_tsig(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ uint64_t sigtime;
+ isc_buffer_t buffer;
+ dns_rcode_t rcode;
+ long i;
+ char *e;
+
+ REQUIRE(type == dns_rdatatype_tsig);
+ REQUIRE(rdclass == dns_rdataclass_any);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Algorithm Name.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ /*
+ * Time Signed: 48 bits.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ sigtime = strtoull(DNS_AS_STR(token), &e, 10);
+ if (*e != 0) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ if ((sigtime >> 48) != 0) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer((uint16_t)(sigtime >> 32), target));
+ RETERR(uint32_tobuffer((uint32_t)(sigtime & 0xffffffffU), target));
+
+ /*
+ * Fudge.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signature Size.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signature.
+ */
+ RETERR(isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong));
+
+ /*
+ * Original ID.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Error.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (dns_tsigrcode_fromtext(&rcode, &token.value.as_textregion) !=
+ ISC_R_SUCCESS)
+ {
+ i = strtol(DNS_AS_STR(token), &e, 10);
+ if (*e != 0) {
+ RETTOK(DNS_R_UNKNOWN);
+ }
+ if (i < 0 || i > 0xffff) {
+ RETTOK(ISC_R_RANGE);
+ }
+ rcode = (dns_rcode_t)i;
+ }
+ RETERR(uint16_tobuffer(rcode, target));
+
+ /*
+ * Other Len.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Other Data.
+ */
+ return (isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong));
+}
+
+static isc_result_t
+totext_any_tsig(ARGS_TOTEXT) {
+ isc_region_t sr;
+ isc_region_t sigr;
+ char buf[sizeof(" 281474976710655 ")];
+ char *bufp;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ uint64_t sigtime;
+ unsigned short n;
+
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+ /*
+ * Algorithm Name.
+ */
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+ dns_name_fromregion(&name, &sr);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+ RETERR(str_totext(" ", target));
+ isc_region_consume(&sr, name_length(&name));
+
+ /*
+ * Time Signed.
+ */
+ sigtime = ((uint64_t)sr.base[0] << 40) | ((uint64_t)sr.base[1] << 32) |
+ ((uint64_t)sr.base[2] << 24) | ((uint64_t)sr.base[3] << 16) |
+ ((uint64_t)sr.base[4] << 8) | (uint64_t)sr.base[5];
+ isc_region_consume(&sr, 6);
+ bufp = &buf[sizeof(buf) - 1];
+ *bufp-- = 0;
+ *bufp-- = ' ';
+ do {
+ *bufp-- = decdigits[sigtime % 10];
+ sigtime /= 10;
+ } while (sigtime != 0);
+ bufp++;
+ RETERR(str_totext(bufp, target));
+
+ /*
+ * Fudge.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Signature Size.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Signature.
+ */
+ if (n != 0U) {
+ REQUIRE(n <= sr.length);
+ sigr = sr;
+ sigr.length = n;
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sigr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sigr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" ) ", target));
+ } else {
+ RETERR(str_totext(" ", target));
+ }
+ isc_region_consume(&sr, n);
+ } else {
+ RETERR(str_totext(" ", target));
+ }
+
+ /*
+ * Original ID.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Error.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ RETERR(dns_tsigrcode_totext((dns_rcode_t)n, target));
+
+ /*
+ * Other Size.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), " %u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Other.
+ */
+ if (tctx->width == 0) { /* No splitting */
+ return (isc_base64_totext(&sr, 60, "", target));
+ } else {
+ return (isc_base64_totext(&sr, 60, " ", target));
+ }
+}
+
+static isc_result_t
+fromwire_any_tsig(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ unsigned long n;
+
+ REQUIRE(type == dns_rdatatype_tsig);
+ REQUIRE(rdclass == dns_rdataclass_any);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ /*
+ * Algorithm Name.
+ */
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ isc_buffer_activeregion(source, &sr);
+ /*
+ * Time Signed + Fudge.
+ */
+ if (sr.length < 8) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, 8));
+ isc_region_consume(&sr, 8);
+ isc_buffer_forward(source, 8);
+
+ /*
+ * Signature Length + Signature.
+ */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ n = uint16_fromregion(&sr);
+ if (sr.length < n + 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, n + 2));
+ isc_region_consume(&sr, n + 2);
+ isc_buffer_forward(source, n + 2);
+
+ /*
+ * Original ID + Error.
+ */
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, 4));
+ isc_region_consume(&sr, 4);
+ isc_buffer_forward(source, 4);
+
+ /*
+ * Other Length + Other.
+ */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ n = uint16_fromregion(&sr);
+ if (sr.length < n + 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, n + 2);
+ return (mem_tobuffer(target, sr.base, n + 2));
+}
+
+static isc_result_t
+towire_any_tsig(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ RETERR(dns_name_towire(&name, cctx, target));
+ isc_region_consume(&sr, name_length(&name));
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_any_tsig(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_tsig);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_any);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_any_tsig(ARGS_FROMSTRUCT) {
+ dns_rdata_any_tsig_t *tsig = source;
+ isc_region_t tr;
+
+ REQUIRE(type == dns_rdatatype_tsig);
+ REQUIRE(rdclass == dns_rdataclass_any);
+ REQUIRE(tsig != NULL);
+ REQUIRE(tsig->common.rdclass == rdclass);
+ REQUIRE(tsig->common.rdtype == type);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Algorithm Name.
+ */
+ RETERR(name_tobuffer(&tsig->algorithm, target));
+
+ isc_buffer_availableregion(target, &tr);
+ if (tr.length < 6 + 2 + 2) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Time Signed: 48 bits.
+ */
+ RETERR(uint16_tobuffer((uint16_t)(tsig->timesigned >> 32), target));
+ RETERR(uint32_tobuffer((uint32_t)(tsig->timesigned & 0xffffffffU),
+ target));
+
+ /*
+ * Fudge.
+ */
+ RETERR(uint16_tobuffer(tsig->fudge, target));
+
+ /*
+ * Signature Size.
+ */
+ RETERR(uint16_tobuffer(tsig->siglen, target));
+
+ /*
+ * Signature.
+ */
+ RETERR(mem_tobuffer(target, tsig->signature, tsig->siglen));
+
+ isc_buffer_availableregion(target, &tr);
+ if (tr.length < 2 + 2 + 2) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Original ID.
+ */
+ RETERR(uint16_tobuffer(tsig->originalid, target));
+
+ /*
+ * Error.
+ */
+ RETERR(uint16_tobuffer(tsig->error, target));
+
+ /*
+ * Other Len.
+ */
+ RETERR(uint16_tobuffer(tsig->otherlen, target));
+
+ /*
+ * Other Data.
+ */
+ return (mem_tobuffer(target, tsig->other, tsig->otherlen));
+}
+
+static isc_result_t
+tostruct_any_tsig(ARGS_TOSTRUCT) {
+ dns_rdata_any_tsig_t *tsig;
+ dns_name_t alg;
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+ REQUIRE(rdata->length != 0);
+
+ tsig = (dns_rdata_any_tsig_t *)target;
+ tsig->common.rdclass = rdata->rdclass;
+ tsig->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&tsig->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Algorithm Name.
+ */
+ dns_name_init(&alg, NULL);
+ dns_name_fromregion(&alg, &sr);
+ dns_name_init(&tsig->algorithm, NULL);
+ RETERR(name_duporclone(&alg, mctx, &tsig->algorithm));
+
+ isc_region_consume(&sr, name_length(&tsig->algorithm));
+
+ /*
+ * Time Signed.
+ */
+ INSIST(sr.length >= 6);
+ tsig->timesigned = ((uint64_t)sr.base[0] << 40) |
+ ((uint64_t)sr.base[1] << 32) |
+ ((uint64_t)sr.base[2] << 24) |
+ ((uint64_t)sr.base[3] << 16) |
+ ((uint64_t)sr.base[4] << 8) | (uint64_t)sr.base[5];
+ isc_region_consume(&sr, 6);
+
+ /*
+ * Fudge.
+ */
+ tsig->fudge = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Signature Size.
+ */
+ tsig->siglen = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Signature.
+ */
+ INSIST(sr.length >= tsig->siglen);
+ tsig->signature = mem_maybedup(mctx, sr.base, tsig->siglen);
+ if (tsig->signature == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&sr, tsig->siglen);
+
+ /*
+ * Original ID.
+ */
+ tsig->originalid = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Error.
+ */
+ tsig->error = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Other Size.
+ */
+ tsig->otherlen = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Other.
+ */
+ INSIST(sr.length == tsig->otherlen);
+ tsig->other = mem_maybedup(mctx, sr.base, tsig->otherlen);
+ if (tsig->other == NULL) {
+ goto cleanup;
+ }
+
+ tsig->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&tsig->algorithm, tsig->mctx);
+ }
+ if (mctx != NULL && tsig->signature != NULL) {
+ isc_mem_free(mctx, tsig->signature);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_any_tsig(ARGS_FREESTRUCT) {
+ dns_rdata_any_tsig_t *tsig = (dns_rdata_any_tsig_t *)source;
+
+ REQUIRE(tsig != NULL);
+ REQUIRE(tsig->common.rdtype == dns_rdatatype_tsig);
+ REQUIRE(tsig->common.rdclass == dns_rdataclass_any);
+
+ if (tsig->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&tsig->algorithm, tsig->mctx);
+ if (tsig->signature != NULL) {
+ isc_mem_free(tsig->mctx, tsig->signature);
+ }
+ if (tsig->other != NULL) {
+ isc_mem_free(tsig->mctx, tsig->other);
+ }
+ tsig->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_any_tsig(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_any_tsig(ARGS_DIGEST) {
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+
+ UNUSED(rdata);
+ UNUSED(digest);
+ UNUSED(arg);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static bool
+checkowner_any_tsig(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_tsig);
+ REQUIRE(rdclass == dns_rdataclass_any);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_any_tsig(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_tsig);
+ REQUIRE(rdata->rdclass == dns_rdataclass_any);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_any_tsig(ARGS_COMPARE) {
+ return (compare_any_tsig(rdata1, rdata2));
+}
+
+#endif /* RDATA_ANY_255_TSIG_250_C */
diff --git a/lib/dns/rdata/any_255/tsig_250.h b/lib/dns/rdata/any_255/tsig_250.h
new file mode 100644
index 0000000..3df1d82
--- /dev/null
+++ b/lib/dns/rdata/any_255/tsig_250.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ANY_255_TSIG_250_H
+#define ANY_255_TSIG_250_H 1
+
+/*% RFC2845 */
+typedef struct dns_rdata_any_tsig {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t algorithm;
+ uint64_t timesigned;
+ uint16_t fudge;
+ uint16_t siglen;
+ unsigned char *signature;
+ uint16_t originalid;
+ uint16_t error;
+ uint16_t otherlen;
+ unsigned char *other;
+} dns_rdata_any_tsig_t;
+
+#endif /* ANY_255_TSIG_250_H */
diff --git a/lib/dns/rdata/ch_3/a_1.c b/lib/dns/rdata/ch_3/a_1.c
new file mode 100644
index 0000000..4814a02
--- /dev/null
+++ b/lib/dns/rdata/ch_3/a_1.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* by Bjorn.Victor@it.uu.se, 2005-05-07 */
+/* Based on generic/soa_6.c and generic/mx_15.c */
+
+#ifndef RDATA_CH_3_A_1_C
+#define RDATA_CH_3_A_1_C
+
+#include <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);
+ RETERR(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(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..231665d
--- /dev/null
+++ b/lib/dns/rdata/ch_3/a_1.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* by Bjorn.Victor@it.uu.se, 2005-05-07 */
+/* Based on generic/mx_15.h */
+
+#ifndef CH_3_A_1_H
+#define CH_3_A_1_H 1
+
+typedef uint16_t ch_addr_t;
+
+typedef struct dns_rdata_ch_a {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t ch_addr_dom; /* ch-addr domain for back mapping
+ * */
+ ch_addr_t ch_addr; /* chaos address (16 bit) network
+ * order */
+} dns_rdata_ch_a_t;
+
+#endif /* CH_3_A_1_H */
diff --git a/lib/dns/rdata/generic/afsdb_18.c b/lib/dns/rdata/generic/afsdb_18.c
new file mode 100644
index 0000000..2c4eb27
--- /dev/null
+++ b/lib/dns/rdata/generic/afsdb_18.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1183 */
+
+#ifndef RDATA_GENERIC_AFSDB_18_C
+#define RDATA_GENERIC_AFSDB_18_C
+
+#define RRTYPE_AFSDB_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_afsdb(ARGS_FROMTEXT) {
+ isc_token_t token;
+ isc_buffer_t buffer;
+ dns_name_t name;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_afsdb);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Subtype.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Hostname.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_afsdb(ARGS_TOTEXT) {
+ dns_name_t name;
+ dns_name_t prefix;
+ isc_region_t region;
+ char buf[sizeof("64000 ")];
+ bool sub;
+ unsigned int num;
+
+ REQUIRE(rdata->type == dns_rdatatype_afsdb);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+
+ RETERR(name_duporclone(&name, mctx, &afsdb->server));
+ afsdb->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_afsdb(ARGS_FREESTRUCT) {
+ dns_rdata_afsdb_t *afsdb = source;
+
+ REQUIRE(afsdb != NULL);
+ REQUIRE(afsdb->common.rdtype == dns_rdatatype_afsdb);
+
+ if (afsdb->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&afsdb->server, afsdb->mctx);
+ afsdb->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_afsdb(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_afsdb);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a));
+}
+
+static isc_result_t
+digest_afsdb(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_afsdb);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 2);
+ r1.length = 2;
+ RETERR((digest)(arg, &r1));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_afsdb(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_afsdb);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_afsdb(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_afsdb);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &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..878ca9d
--- /dev/null
+++ b/lib/dns/rdata/generic/afsdb_18.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_AFSDB_18_H
+#define GENERIC_AFSDB_18_H 1
+
+/*!
+ * \brief Per RFC1183 */
+
+typedef struct dns_rdata_afsdb {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t subtype;
+ dns_name_t server;
+} dns_rdata_afsdb_t;
+
+#endif /* GENERIC_AFSDB_18_H */
diff --git a/lib/dns/rdata/generic/amtrelay_260.c b/lib/dns/rdata/generic/amtrelay_260.c
new file mode 100644
index 0000000..6938c43
--- /dev/null
+++ b/lib/dns/rdata/generic/amtrelay_260.c
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_AMTRELAY_260_C
+#define RDATA_GENERIC_AMTRELAY_260_C
+
+#include <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);
+ RETERR(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(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..48a3fbb
--- /dev/null
+++ b/lib/dns/rdata/generic/amtrelay_260.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_AMTRELAY_260_H
+#define GENERIC_AMTRELAY_260_H 1
+
+typedef struct dns_rdata_amtrelay {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t precedence;
+ bool discovery;
+ uint8_t gateway_type;
+ struct in_addr in_addr; /* gateway type 1 */
+ struct in6_addr in6_addr; /* gateway type 2 */
+ dns_name_t gateway; /* gateway type 3 */
+ unsigned char *data; /* gateway type > 3 */
+ uint16_t length;
+} dns_rdata_amtrelay_t;
+
+#endif /* GENERIC_AMTRELAY_260_H */
diff --git a/lib/dns/rdata/generic/avc_258.c b/lib/dns/rdata/generic/avc_258.c
new file mode 100644
index 0000000..34d0048
--- /dev/null
+++ b/lib/dns/rdata/generic/avc_258.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_AVC_258_C
+#define RDATA_GENERIC_AVC_258_C
+
+#define RRTYPE_AVC_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_avc(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_avc);
+
+ return (generic_fromtext_txt(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_avc(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+
+ return (generic_totext_txt(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_avc(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_avc);
+
+ return (generic_fromwire_txt(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_avc(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_avc(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_avc);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_avc(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_avc);
+
+ return (generic_fromstruct_txt(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_avc(ARGS_TOSTRUCT) {
+ dns_rdata_avc_t *avc = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+ REQUIRE(avc != NULL);
+
+ avc->common.rdclass = rdata->rdclass;
+ avc->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&avc->common, link);
+
+ return (generic_tostruct_txt(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_avc(ARGS_FREESTRUCT) {
+ dns_rdata_avc_t *avc = source;
+
+ REQUIRE(avc != NULL);
+ REQUIRE(avc->common.rdtype == dns_rdatatype_avc);
+
+ generic_freestruct_txt(source);
+}
+
+static isc_result_t
+additionaldata_avc(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_avc(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_avc(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_avc);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_avc(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_avc);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_avc(ARGS_COMPARE) {
+ return (compare_avc(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_AVC_258_C */
diff --git a/lib/dns/rdata/generic/avc_258.h b/lib/dns/rdata/generic/avc_258.h
new file mode 100644
index 0000000..c158764
--- /dev/null
+++ b/lib/dns/rdata/generic/avc_258.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_AVC_258_H
+#define GENERIC_AVC_258_H 1
+
+typedef dns_rdata_txt_string_t dns_rdata_avc_string_t;
+
+typedef struct dns_rdata_avc {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *data;
+ uint16_t length;
+ /* private */
+ uint16_t offset;
+} dns_rdata_avc_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
+#endif /* GENERIC_AVC_258_H */
diff --git a/lib/dns/rdata/generic/caa_257.c b/lib/dns/rdata/generic/caa_257.c
new file mode 100644
index 0000000..0e6170b
--- /dev/null
+++ b/lib/dns/rdata/generic/caa_257.c
@@ -0,0 +1,627 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_CAA_257_C
+#define GENERIC_CAA_257_C 1
+
+#define RRTYPE_CAA_ATTRIBUTES (0)
+
+static unsigned char const alphanumeric[256] = {
+ /* 0x00-0x0f */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x10-0x1f */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x20-0x2f */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x30-0x3f */ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x40-0x4f */ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ /* 0x50-0x5f */ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x60-0x6f */ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ /* 0x70-0x7f */ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x80-0x8f */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0x90-0x9f */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xa0-0xaf */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xb0-0xbf */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xc0-0xcf */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xd0-0xdf */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xe0-0xef */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* 0xf0-0xff */ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+static isc_result_t
+fromtext_caa(ARGS_FROMTEXT) {
+ isc_token_t token;
+ isc_textregion_t tr;
+ uint8_t flags;
+ unsigned int i;
+
+ REQUIRE(type == dns_rdatatype_caa);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /* Flags. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 255U) {
+ RETTOK(ISC_R_RANGE);
+ }
+ flags = (uint8_t)(token.value.as_ulong & 255U);
+ RETERR(uint8_tobuffer(flags, target));
+
+ /*
+ * Tag
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ tr = token.value.as_textregion;
+ for (i = 0; i < tr.length; i++) {
+ if (!alphanumeric[(unsigned char)tr.base[i]]) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ }
+ RETERR(uint8_tobuffer(tr.length, target));
+ RETERR(mem_tobuffer(target, tr.base, tr.length));
+
+ /*
+ * Value
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ if (token.type != isc_tokentype_qstring &&
+ token.type != isc_tokentype_string)
+ {
+ RETERR(DNS_R_SYNTAX);
+ }
+ RETERR(multitxt_fromtext(&token.value.as_textregion, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_caa(ARGS_TOTEXT) {
+ isc_region_t region;
+ uint8_t flags;
+ char buf[256];
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_caa);
+ REQUIRE(rdata->length >= 3U);
+ REQUIRE(rdata->data != NULL);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_caa(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_caa);
+ REQUIRE(rdata->data != NULL);
+ REQUIRE(rdata->length >= 3U);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_caa(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_caa);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_caa(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_caa);
+ REQUIRE(rdata->data != NULL);
+ REQUIRE(rdata->length >= 3U);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_caa(ARGS_COMPARE) {
+ return (compare_caa(rdata1, rdata2));
+}
+
+#endif /* GENERIC_CAA_257_C */
diff --git a/lib/dns/rdata/generic/caa_257.h b/lib/dns/rdata/generic/caa_257.h
new file mode 100644
index 0000000..ff1c605
--- /dev/null
+++ b/lib/dns/rdata/generic/caa_257.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_CAA_257_H
+#define GENERIC_CAA_257_H 1
+
+typedef struct dns_rdata_caa {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t flags;
+ unsigned char *tag;
+ uint8_t tag_len;
+ unsigned char *value;
+ uint16_t value_len;
+} dns_rdata_caa_t;
+
+#endif /* GENERIC_CAA_257_H */
diff --git a/lib/dns/rdata/generic/cdnskey_60.c b/lib/dns/rdata/generic/cdnskey_60.c
new file mode 100644
index 0000000..672bf12
--- /dev/null
+++ b/lib/dns/rdata/generic/cdnskey_60.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsop-delegation-trust-maintainance-14 */
+
+#ifndef RDATA_GENERIC_CDNSKEY_60_C
+#define RDATA_GENERIC_CDNSKEY_60_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_cdnskey(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_cdnskey);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_cdnskey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_cdnskey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_cdnskey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_cdnskey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_cdnskey(ARGS_COMPARE) {
+ /*
+ * Treat ALG 253 (private DNS) subtype name case sensitively.
+ */
+ return (compare_cdnskey(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_CDNSKEY_60_C */
diff --git a/lib/dns/rdata/generic/cdnskey_60.h b/lib/dns/rdata/generic/cdnskey_60.h
new file mode 100644
index 0000000..a1a6e57
--- /dev/null
+++ b/lib/dns/rdata/generic/cdnskey_60.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_CDNSKEY_60_H
+#define GENERIC_CDNSKEY_60_H 1
+
+/* CDNSKEY records have the same RDATA fields as DNSKEY records. */
+typedef struct dns_rdata_key dns_rdata_cdnskey_t;
+
+#endif /* GENERIC_CDNSKEY_60_H */
diff --git a/lib/dns/rdata/generic/cds_59.c b/lib/dns/rdata/generic/cds_59.c
new file mode 100644
index 0000000..4b3a79b
--- /dev/null
+++ b/lib/dns/rdata/generic/cds_59.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsop-delegation-trust-maintainance-14 */
+
+#ifndef RDATA_GENERIC_CDS_59_C
+#define RDATA_GENERIC_CDS_59_C
+
+#define RRTYPE_CDS_ATTRIBUTES 0
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_cds(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_cds);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_cds(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_cds);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_cds(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_cds);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_cds(ARGS_COMPARE) {
+ return (compare_cds(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_CDS_59_C */
diff --git a/lib/dns/rdata/generic/cds_59.h b/lib/dns/rdata/generic/cds_59.h
new file mode 100644
index 0000000..31996b1
--- /dev/null
+++ b/lib/dns/rdata/generic/cds_59.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_CDS_59_H
+#define GENERIC_CDS_59_H 1
+
+/* CDS records have the same RDATA fields as DS records. */
+typedef struct dns_rdata_ds dns_rdata_cds_t;
+
+#endif /* GENERIC_CDS_59_H */
diff --git a/lib/dns/rdata/generic/cert_37.c b/lib/dns/rdata/generic/cert_37.c
new file mode 100644
index 0000000..4bd8c0a
--- /dev/null
+++ b/lib/dns/rdata/generic/cert_37.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2538 */
+
+#ifndef RDATA_GENERIC_CERT_37_C
+#define RDATA_GENERIC_CERT_37_C
+
+#define RRTYPE_CERT_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_cert(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_secalg_t secalg;
+ dns_cert_t cert;
+
+ REQUIRE(type == dns_rdatatype_cert);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Cert type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_cert_fromtext(&cert, &token.value.as_textregion));
+ RETERR(uint16_tobuffer(cert, target));
+
+ /*
+ * Key tag.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secalg_fromtext(&secalg, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &secalg, 1));
+
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_cert(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("64000 ")];
+ unsigned int n;
+
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Type.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ RETERR(dns_cert_totext((dns_cert_t)n, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Key tag.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(dns_secalg_totext(sr.base[0], target));
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Cert.
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_cert(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_cert);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 6) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_cert(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_cert(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_cert);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_cert(ARGS_FROMSTRUCT) {
+ dns_rdata_cert_t *cert = source;
+
+ REQUIRE(type == dns_rdatatype_cert);
+ REQUIRE(cert != NULL);
+ REQUIRE(cert->common.rdtype == type);
+ REQUIRE(cert->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint16_tobuffer(cert->type, target));
+ RETERR(uint16_tobuffer(cert->key_tag, target));
+ RETERR(uint8_tobuffer(cert->algorithm, target));
+
+ return (mem_tobuffer(target, cert->certificate, cert->length));
+}
+
+static isc_result_t
+tostruct_cert(ARGS_TOSTRUCT) {
+ dns_rdata_cert_t *cert = target;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+ REQUIRE(cert != NULL);
+ REQUIRE(rdata->length != 0);
+
+ cert->common.rdclass = rdata->rdclass;
+ cert->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&cert->common, link);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_cert(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_cert(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_cert);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_cert(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_cert);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_cert(ARGS_COMPARE) {
+ return (compare_cert(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_CERT_37_C */
diff --git a/lib/dns/rdata/generic/cert_37.h b/lib/dns/rdata/generic/cert_37.h
new file mode 100644
index 0000000..3fd305e
--- /dev/null
+++ b/lib/dns/rdata/generic/cert_37.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_CERT_37_H
+#define GENERIC_CERT_37_H 1
+
+/*% RFC2538 */
+typedef struct dns_rdata_cert {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t type;
+ uint16_t key_tag;
+ uint8_t algorithm;
+ uint16_t length;
+ unsigned char *certificate;
+} dns_rdata_cert_t;
+
+#endif /* GENERIC_CERT_37_H */
diff --git a/lib/dns/rdata/generic/cname_5.c b/lib/dns/rdata/generic/cname_5.c
new file mode 100644
index 0000000..a7248cd
--- /dev/null
+++ b/lib/dns/rdata/generic/cname_5.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_CNAME_5_C
+#define RDATA_GENERIC_CNAME_5_C
+
+#define RRTYPE_CNAME_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_EXCLUSIVE | DNS_RDATATYPEATTR_SINGLETON)
+
+static isc_result_t
+fromtext_cname(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_cname);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_cname(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_cname);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &cname->cname));
+ cname->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_cname(ARGS_FREESTRUCT) {
+ dns_rdata_cname_t *cname = source;
+
+ REQUIRE(cname != NULL);
+
+ if (cname->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&cname->cname, cname->mctx);
+ cname->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_cname(ARGS_ADDLDATA) {
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_cname);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_cname(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_cname);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_cname(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_cname);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_cname(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_cname);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_cname(ARGS_COMPARE) {
+ return (compare_cname(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_CNAME_5_C */
diff --git a/lib/dns/rdata/generic/cname_5.h b/lib/dns/rdata/generic/cname_5.h
new file mode 100644
index 0000000..a44dfbe
--- /dev/null
+++ b/lib/dns/rdata/generic/cname_5.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_CNAME_5_H
+#define GENERIC_CNAME_5_H 1
+
+typedef struct dns_rdata_cname {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t cname;
+} dns_rdata_cname_t;
+
+#endif /* GENERIC_CNAME_5_H */
diff --git a/lib/dns/rdata/generic/csync_62.c b/lib/dns/rdata/generic/csync_62.c
new file mode 100644
index 0000000..f988729
--- /dev/null
+++ b/lib/dns/rdata/generic/csync_62.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 7477 */
+
+#ifndef RDATA_GENERIC_CSYNC_62_C
+#define RDATA_GENERIC_CSYNC_62_C
+
+#define RRTYPE_CSYNC_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_csync(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_csync);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /* Serial. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /* Flags. */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /* Type Map */
+ return (typemap_fromtext(lexer, target, true));
+}
+
+static isc_result_t
+totext_csync(ARGS_TOTEXT) {
+ unsigned long num;
+ char buf[sizeof("0123456789")]; /* Also TYPE65535 */
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_csync);
+ REQUIRE(rdata->length >= 6);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ num = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ num = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu", num);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Don't leave a trailing space when there's no typemap present.
+ */
+ if (sr.length > 0) {
+ RETERR(str_totext(" ", target));
+ }
+ return (typemap_totext(&sr, NULL, target));
+}
+
+static isc_result_t
+fromwire_csync(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_csync);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(options);
+ UNUSED(dctx);
+
+ /*
+ * Serial + Flags
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 6) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ RETERR(mem_tobuffer(target, sr.base, 6));
+ isc_buffer_forward(source, 6);
+ isc_region_consume(&sr, 6);
+
+ RETERR(typemap_test(&sr, true));
+
+ RETERR(mem_tobuffer(target, sr.base, sr.length));
+ isc_buffer_forward(source, sr.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_csync(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_csync);
+ REQUIRE(rdata->length >= 6);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_csync(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_csync);
+ REQUIRE(rdata1->length >= 6);
+ REQUIRE(rdata2->length >= 6);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_csync(ARGS_FROMSTRUCT) {
+ dns_rdata_csync_t *csync = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_csync);
+ REQUIRE(csync != NULL);
+ REQUIRE(csync->common.rdtype == type);
+ REQUIRE(csync->common.rdclass == rdclass);
+ REQUIRE(csync->typebits != NULL || csync->len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint32_tobuffer(csync->serial, target));
+ RETERR(uint16_tobuffer(csync->flags, target));
+
+ region.base = csync->typebits;
+ region.length = csync->len;
+ RETERR(typemap_test(&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(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..ecc2202
--- /dev/null
+++ b/lib/dns/rdata/generic/csync_62.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_CSYNC_62_H
+#define GENERIC_CSYNC_62_H 1
+
+/*!
+ * \brief Per RFC 7477
+ */
+
+typedef struct dns_rdata_csync {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint32_t serial;
+ uint16_t flags;
+ unsigned char *typebits;
+ uint16_t len;
+} dns_rdata_csync_t;
+
+#endif /* GENERIC_CSYNC_62_H */
diff --git a/lib/dns/rdata/generic/dlv_32769.c b/lib/dns/rdata/generic/dlv_32769.c
new file mode 100644
index 0000000..f45b196
--- /dev/null
+++ b/lib/dns/rdata/generic/dlv_32769.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC3658 */
+
+#ifndef RDATA_GENERIC_DLV_32769_C
+#define RDATA_GENERIC_DLV_32769_C
+
+#define RRTYPE_DLV_ATTRIBUTES 0
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_dlv(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_dlv);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_dlv(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_dlv);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_dlv(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_dlv);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_dlv(ARGS_COMPARE) {
+ return (compare_dlv(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_DLV_32769_C */
diff --git a/lib/dns/rdata/generic/dlv_32769.h b/lib/dns/rdata/generic/dlv_32769.h
new file mode 100644
index 0000000..bf3734e
--- /dev/null
+++ b/lib/dns/rdata/generic/dlv_32769.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsext-delegation-signer-05.txt */
+#ifndef GENERIC_DLV_32769_H
+#define GENERIC_DLV_32769_H 1
+
+typedef struct dns_rdata_ds dns_rdata_dlv_t;
+
+#endif /* GENERIC_DLV_32769_H */
diff --git a/lib/dns/rdata/generic/dname_39.c b/lib/dns/rdata/generic/dname_39.c
new file mode 100644
index 0000000..87f417c
--- /dev/null
+++ b/lib/dns/rdata/generic/dname_39.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2672 */
+
+#ifndef RDATA_GENERIC_DNAME_39_C
+#define RDATA_GENERIC_DNAME_39_C
+
+#define RRTYPE_DNAME_ATTRIBUTES (DNS_RDATATYPEATTR_SINGLETON)
+
+static isc_result_t
+fromtext_dname(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_dname);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_dname(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_dname);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &dname->dname));
+ dname->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_dname(ARGS_FREESTRUCT) {
+ dns_rdata_dname_t *dname = source;
+
+ REQUIRE(dname != NULL);
+ REQUIRE(dname->common.rdtype == dns_rdatatype_dname);
+
+ if (dname->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&dname->dname, dname->mctx);
+ dname->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_dname(ARGS_ADDLDATA) {
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_dname);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_dname(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_dname);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_dname(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_dname);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_dname(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_dname);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_dname(ARGS_COMPARE) {
+ return (compare_dname(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_DNAME_39_C */
diff --git a/lib/dns/rdata/generic/dname_39.h b/lib/dns/rdata/generic/dname_39.h
new file mode 100644
index 0000000..b1cc198
--- /dev/null
+++ b/lib/dns/rdata/generic/dname_39.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_DNAME_39_H
+#define GENERIC_DNAME_39_H 1
+
+/*!
+ * \brief per RFC2672 */
+
+typedef struct dns_rdata_dname {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t dname;
+} dns_rdata_dname_t;
+
+#endif /* GENERIC_DNAME_39_H */
diff --git a/lib/dns/rdata/generic/dnskey_48.c b/lib/dns/rdata/generic/dnskey_48.c
new file mode 100644
index 0000000..c609ff0
--- /dev/null
+++ b/lib/dns/rdata/generic/dnskey_48.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2535 */
+
+#ifndef RDATA_GENERIC_DNSKEY_48_C
+#define RDATA_GENERIC_DNSKEY_48_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_dnskey(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_dnskey);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_dnskey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_dnskey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_dnskey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_dnskey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_dnskey(ARGS_COMPARE) {
+ /*
+ * Treat ALG 253 (private DNS) subtype name case sensitively.
+ */
+ return (compare_dnskey(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_DNSKEY_48_C */
diff --git a/lib/dns/rdata/generic/dnskey_48.h b/lib/dns/rdata/generic/dnskey_48.h
new file mode 100644
index 0000000..385d5e0
--- /dev/null
+++ b/lib/dns/rdata/generic/dnskey_48.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_DNSKEY_48_H
+#define GENERIC_DNSKEY_48_H 1
+
+/*!
+ * \brief per RFC2535
+ */
+
+typedef struct dns_rdata_key dns_rdata_dnskey_t;
+
+#endif /* GENERIC_DNSKEY_48_H */
diff --git a/lib/dns/rdata/generic/doa_259.c b/lib/dns/rdata/generic/doa_259.c
new file mode 100644
index 0000000..e35f2ca
--- /dev/null
+++ b/lib/dns/rdata/generic/doa_259.c
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_DOA_259_C
+#define RDATA_GENERIC_DOA_259_C
+
+#define RRTYPE_DOA_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_doa(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_doa);
+
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * DOA-ENTERPRISE
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * DOA-TYPE
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * DOA-LOCATION
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * DOA-MEDIA-TYPE
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+
+ /*
+ * DOA-DATA
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (strcmp(DNS_AS_STR(token), "-") == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ isc_lex_ungettoken(lexer, &token);
+ return (isc_base64_tobuffer(lexer, target, -1));
+ }
+}
+
+static isc_result_t
+totext_doa(ARGS_TOTEXT) {
+ char buf[sizeof("4294967295 ")];
+ isc_region_t region;
+ uint32_t n;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_doa);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &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) {
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_doa);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_doa(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_doa);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_doa(ARGS_CHECKOWNER) {
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ REQUIRE(type == dns_rdatatype_doa);
+
+ return (true);
+}
+
+static bool
+checknames_doa(ARGS_CHECKNAMES) {
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ REQUIRE(rdata->type == dns_rdatatype_doa);
+
+ return (true);
+}
+
+static int
+casecompare_doa(ARGS_COMPARE) {
+ return (compare_doa(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_DOA_259_C */
diff --git a/lib/dns/rdata/generic/doa_259.h b/lib/dns/rdata/generic/doa_259.h
new file mode 100644
index 0000000..92999a1
--- /dev/null
+++ b/lib/dns/rdata/generic/doa_259.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_DOA_259_H
+#define GENERIC_DOA_259_H 1
+
+typedef struct dns_rdata_doa {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *mediatype;
+ unsigned char *data;
+ uint32_t enterprise;
+ uint32_t type;
+ uint16_t data_len;
+ uint8_t location;
+ uint8_t mediatype_len;
+} dns_rdata_doa_t;
+
+#endif /* GENERIC_DOA_259_H */
diff --git a/lib/dns/rdata/generic/ds_43.c b/lib/dns/rdata/generic/ds_43.c
new file mode 100644
index 0000000..0b6fa0a
--- /dev/null
+++ b/lib/dns/rdata/generic/ds_43.c
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC3658 */
+
+#ifndef RDATA_GENERIC_DS_43_C
+#define RDATA_GENERIC_DS_43_C
+
+#define RRTYPE_DS_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_DNSSEC | DNS_RDATATYPEATTR_ZONECUTAUTH | \
+ DNS_RDATATYPEATTR_ATPARENT)
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_ds(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_ds);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_ds(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ds);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_ds(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_ds);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_ds(ARGS_COMPARE) {
+ return (compare_ds(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_DS_43_C */
diff --git a/lib/dns/rdata/generic/ds_43.h b/lib/dns/rdata/generic/ds_43.h
new file mode 100644
index 0000000..26ef13e
--- /dev/null
+++ b/lib/dns/rdata/generic/ds_43.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_DS_43_H
+#define GENERIC_DS_43_H 1
+
+/*!
+ * \brief per draft-ietf-dnsext-delegation-signer-05.txt */
+typedef struct dns_rdata_ds {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t key_tag;
+ dns_secalg_t algorithm;
+ dns_dsdigest_t digest_type;
+ uint16_t length;
+ unsigned char *digest;
+} dns_rdata_ds_t;
+
+#endif /* GENERIC_DS_43_H */
diff --git a/lib/dns/rdata/generic/eui48_108.c b/lib/dns/rdata/generic/eui48_108.c
new file mode 100644
index 0000000..7926708
--- /dev/null
+++ b/lib/dns/rdata/generic/eui48_108.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_EUI48_108_C
+#define RDATA_GENERIC_EUI48_108_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_eui48(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_eui48);
+ REQUIRE(rdata->length == 6);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_eui48(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_eui48);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_eui48(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_eui48);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_eui48(ARGS_COMPARE) {
+ return (compare_eui48(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_EUI48_108_C */
diff --git a/lib/dns/rdata/generic/eui48_108.h b/lib/dns/rdata/generic/eui48_108.h
new file mode 100644
index 0000000..2ce2706
--- /dev/null
+++ b/lib/dns/rdata/generic/eui48_108.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_EUI48_108_H
+#define GENERIC_EUI48_108_H 1
+
+typedef struct dns_rdata_eui48 {
+ dns_rdatacommon_t common;
+ unsigned char eui48[6];
+} dns_rdata_eui48_t;
+
+#endif /* GENERIC_EUI48_10k_H */
diff --git a/lib/dns/rdata/generic/eui64_109.c b/lib/dns/rdata/generic/eui64_109.c
new file mode 100644
index 0000000..5cb788a
--- /dev/null
+++ b/lib/dns/rdata/generic/eui64_109.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_EUI64_109_C
+#define RDATA_GENERIC_EUI64_109_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_eui64(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_eui64);
+ REQUIRE(rdata->length == 8);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_eui64(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_eui64);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_eui64(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_eui64);
+ REQUIRE(rdata->length == 8);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_eui64(ARGS_COMPARE) {
+ return (compare_eui64(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_EUI64_109_C */
diff --git a/lib/dns/rdata/generic/eui64_109.h b/lib/dns/rdata/generic/eui64_109.h
new file mode 100644
index 0000000..0783197
--- /dev/null
+++ b/lib/dns/rdata/generic/eui64_109.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_EUI64_109_H
+#define GENERIC_EUI64_109_H 1
+
+typedef struct dns_rdata_eui64 {
+ dns_rdatacommon_t common;
+ unsigned char eui64[8];
+} dns_rdata_eui64_t;
+
+#endif /* GENERIC_EUI64_10k_H */
diff --git a/lib/dns/rdata/generic/gpos_27.c b/lib/dns/rdata/generic/gpos_27.c
new file mode 100644
index 0000000..9d02779
--- /dev/null
+++ b/lib/dns/rdata/generic/gpos_27.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1712 */
+
+#ifndef RDATA_GENERIC_GPOS_27_C
+#define RDATA_GENERIC_GPOS_27_C
+
+#define RRTYPE_GPOS_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_gpos(ARGS_FROMTEXT) {
+ isc_token_t token;
+ int i;
+
+ REQUIRE(type == dns_rdatatype_gpos);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ for (i = 0; i < 3; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_qstring, false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_gpos(ARGS_TOTEXT) {
+ isc_region_t region;
+ int i;
+
+ REQUIRE(rdata->type == dns_rdatatype_gpos);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_gpos(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_gpos);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_gpos(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_gpos);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_gpos(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_gpos);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_gpos(ARGS_COMPARE) {
+ return (compare_gpos(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_GPOS_27_C */
diff --git a/lib/dns/rdata/generic/gpos_27.h b/lib/dns/rdata/generic/gpos_27.h
new file mode 100644
index 0000000..08b6b84
--- /dev/null
+++ b/lib/dns/rdata/generic/gpos_27.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_GPOS_27_H
+#define GENERIC_GPOS_27_H 1
+
+/*!
+ * \brief per RFC1712 */
+
+typedef struct dns_rdata_gpos {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ char *longitude;
+ char *latitude;
+ char *altitude;
+ uint8_t long_len;
+ uint8_t lat_len;
+ uint8_t alt_len;
+} dns_rdata_gpos_t;
+
+#endif /* GENERIC_GPOS_27_H */
diff --git a/lib/dns/rdata/generic/hinfo_13.c b/lib/dns/rdata/generic/hinfo_13.c
new file mode 100644
index 0000000..d483839
--- /dev/null
+++ b/lib/dns/rdata/generic/hinfo_13.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_HINFO_13_C
+#define RDATA_GENERIC_HINFO_13_C
+
+#define RRTYPE_HINFO_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_hinfo(ARGS_FROMTEXT) {
+ isc_token_t token;
+ int i;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ REQUIRE(type == dns_rdatatype_hinfo);
+
+ for (i = 0; i < 2; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_qstring, false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_hinfo(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_hinfo);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+ UNUSED(rdata);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_hinfo(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_hinfo);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_hinfo(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_hinfo);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_hinfo(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_hinfo);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_hinfo(ARGS_COMPARE) {
+ return (compare_hinfo(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_HINFO_13_C */
diff --git a/lib/dns/rdata/generic/hinfo_13.h b/lib/dns/rdata/generic/hinfo_13.h
new file mode 100644
index 0000000..294b4bf
--- /dev/null
+++ b/lib/dns/rdata/generic/hinfo_13.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_HINFO_13_H
+#define GENERIC_HINFO_13_H 1
+
+typedef struct dns_rdata_hinfo {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ char *cpu;
+ char *os;
+ uint8_t cpu_len;
+ uint8_t os_len;
+} dns_rdata_hinfo_t;
+
+#endif /* GENERIC_HINFO_13_H */
diff --git a/lib/dns/rdata/generic/hip_55.c b/lib/dns/rdata/generic/hip_55.c
new file mode 100644
index 0000000..5c9041a
--- /dev/null
+++ b/lib/dns/rdata/generic/hip_55.c
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 5205 */
+
+#ifndef RDATA_GENERIC_HIP_5_C
+#define RDATA_GENERIC_HIP_5_C
+
+#define RRTYPE_HIP_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_hip(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ isc_buffer_t hit_len;
+ isc_buffer_t key_len;
+ unsigned char *start;
+ size_t len;
+
+ REQUIRE(type == dns_rdatatype_hip);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Dummy HIT len.
+ */
+ hit_len = *target;
+ RETERR(uint8_tobuffer(0, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Dummy KEY len.
+ */
+ key_len = *target;
+ RETERR(uint16_tobuffer(0, target));
+
+ /*
+ * HIT (base16).
+ */
+ start = isc_buffer_used(target);
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(isc_hex_decodestring(DNS_AS_STR(token), target));
+
+ /*
+ * Fill in HIT len.
+ */
+ len = (unsigned char *)isc_buffer_used(target) - start;
+ if (len > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer((uint32_t)len, &hit_len));
+
+ /*
+ * Public key (base64).
+ */
+ start = isc_buffer_used(target);
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(isc_base64_decodestring(DNS_AS_STR(token), target));
+
+ /*
+ * Fill in KEY len.
+ */
+ len = (unsigned char *)isc_buffer_used(target) - start;
+ if (len > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer((uint32_t)len, &key_len));
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ /*
+ * Rendezvous Servers.
+ */
+ dns_name_init(&name, NULL);
+ do {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, true));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ } while (1);
+
+ /*
+ * Let upper layer handle eol/eof.
+ */
+ isc_lex_ungettoken(lexer, &token);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_hip(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ unsigned int length, key_len, hit_len;
+ unsigned char algorithm;
+ char buf[sizeof("225 ")];
+
+ REQUIRE(rdata->type == dns_rdatatype_hip);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &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) {
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_hip);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_hip(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_hip);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_hip(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_hip);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_hip(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_hip);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+isc_result_t
+dns_rdata_hip_first(dns_rdata_hip_t *hip) {
+ if (hip->servers_len == 0) {
+ return (ISC_R_NOMORE);
+ }
+ hip->offset = 0;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdata_hip_next(dns_rdata_hip_t *hip) {
+ isc_region_t region;
+ dns_name_t name;
+
+ if (hip->offset >= hip->servers_len) {
+ return (ISC_R_NOMORE);
+ }
+
+ region.base = hip->servers + hip->offset;
+ region.length = hip->servers_len - hip->offset;
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &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));
+}
+
+#endif /* RDATA_GENERIC_HIP_5_C */
diff --git a/lib/dns/rdata/generic/hip_55.h b/lib/dns/rdata/generic/hip_55.h
new file mode 100644
index 0000000..4b852fa
--- /dev/null
+++ b/lib/dns/rdata/generic/hip_55.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_HIP_5_H
+#define GENERIC_HIP_5_H 1
+
+/* RFC 5205 */
+
+typedef struct dns_rdata_hip {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *hit;
+ unsigned char *key;
+ unsigned char *servers;
+ uint8_t algorithm;
+ uint8_t hit_len;
+ uint16_t key_len;
+ uint16_t servers_len;
+ /* Private */
+ uint16_t offset;
+} dns_rdata_hip_t;
+
+isc_result_t
+dns_rdata_hip_first(dns_rdata_hip_t *);
+
+isc_result_t
+dns_rdata_hip_next(dns_rdata_hip_t *);
+
+void
+dns_rdata_hip_current(dns_rdata_hip_t *, dns_name_t *);
+
+#endif /* GENERIC_HIP_5_H */
diff --git a/lib/dns/rdata/generic/ipseckey_45.c b/lib/dns/rdata/generic/ipseckey_45.c
new file mode 100644
index 0000000..bc9d457
--- /dev/null
+++ b/lib/dns/rdata/generic/ipseckey_45.c
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_IPSECKEY_45_C
+#define RDATA_GENERIC_IPSECKEY_45_C
+
+#include <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);
+ RETERR(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(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..bdace0e
--- /dev/null
+++ b/lib/dns/rdata/generic/ipseckey_45.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_IPSECKEY_45_H
+#define GENERIC_IPSECKEY_45_H 1
+
+typedef struct dns_rdata_ipseckey {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t precedence;
+ uint8_t gateway_type;
+ uint8_t algorithm;
+ struct in_addr in_addr; /* gateway type 1 */
+ struct in6_addr in6_addr; /* gateway type 2 */
+ dns_name_t gateway; /* gateway type 3 */
+ unsigned char *key;
+ uint16_t keylength;
+} dns_rdata_ipseckey_t;
+
+#endif /* GENERIC_IPSECKEY_45_H */
diff --git a/lib/dns/rdata/generic/isdn_20.c b/lib/dns/rdata/generic/isdn_20.c
new file mode 100644
index 0000000..3fd756a
--- /dev/null
+++ b/lib/dns/rdata/generic/isdn_20.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1183 */
+
+#ifndef RDATA_GENERIC_ISDN_20_C
+#define RDATA_GENERIC_ISDN_20_C
+
+#define RRTYPE_ISDN_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_isdn(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_isdn);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /* ISDN-address */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+
+ /* sa: optional */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ true));
+ if (token.type != isc_tokentype_string &&
+ token.type != isc_tokentype_qstring)
+ {
+ isc_lex_ungettoken(lexer, &token);
+ return (ISC_R_SUCCESS);
+ }
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_isdn(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_isdn);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_isdn(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_isdn);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_isdn(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_isdn);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_isdn(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_isdn);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_isdn(ARGS_COMPARE) {
+ return (compare_isdn(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_ISDN_20_C */
diff --git a/lib/dns/rdata/generic/isdn_20.h b/lib/dns/rdata/generic/isdn_20.h
new file mode 100644
index 0000000..17bb155
--- /dev/null
+++ b/lib/dns/rdata/generic/isdn_20.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_ISDN_20_H
+#define GENERIC_ISDN_20_H 1
+
+/*!
+ * \brief Per RFC1183 */
+
+typedef struct dns_rdata_isdn {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ char *isdn;
+ char *subaddress;
+ uint8_t isdn_len;
+ uint8_t subaddress_len;
+} dns_rdata_isdn_t;
+
+#endif /* GENERIC_ISDN_20_H */
diff --git a/lib/dns/rdata/generic/key_25.c b/lib/dns/rdata/generic/key_25.c
new file mode 100644
index 0000000..a94ebc2
--- /dev/null
+++ b/lib/dns/rdata/generic/key_25.c
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2535 */
+
+#ifndef RDATA_GENERIC_KEY_25_C
+#define RDATA_GENERIC_KEY_25_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_key(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_key);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_key(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_key);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_key(ARGS_CHECKNAMES) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_key);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_key(ARGS_COMPARE) {
+ return (compare_key(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_KEY_25_C */
diff --git a/lib/dns/rdata/generic/key_25.h b/lib/dns/rdata/generic/key_25.h
new file mode 100644
index 0000000..c528224
--- /dev/null
+++ b/lib/dns/rdata/generic/key_25.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_KEY_25_H
+#define GENERIC_KEY_25_H 1
+
+/*!
+ * \brief Per RFC2535 */
+
+typedef struct dns_rdata_key {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t flags;
+ dns_secproto_t protocol;
+ dns_secalg_t algorithm;
+ uint16_t datalen;
+ unsigned char *data;
+} dns_rdata_key_t;
+
+#endif /* GENERIC_KEY_25_H */
diff --git a/lib/dns/rdata/generic/keydata_65533.c b/lib/dns/rdata/generic/keydata_65533.c
new file mode 100644
index 0000000..46ae5dd
--- /dev/null
+++ b/lib/dns/rdata/generic/keydata_65533.c
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_KEYDATA_65533_C
+#define GENERIC_KEYDATA_65533_C 1
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_keydata(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_keydata);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_keydata(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_keydata);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_keydata(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_keydata);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_keydata(ARGS_COMPARE) {
+ return (compare_keydata(rdata1, rdata2));
+}
+
+#endif /* GENERIC_KEYDATA_65533_C */
diff --git a/lib/dns/rdata/generic/keydata_65533.h b/lib/dns/rdata/generic/keydata_65533.h
new file mode 100644
index 0000000..5ad0715
--- /dev/null
+++ b/lib/dns/rdata/generic/keydata_65533.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_KEYDATA_65533_H
+#define GENERIC_KEYDATA_65533_H 1
+
+typedef struct dns_rdata_keydata {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint32_t refresh; /* Timer for refreshing data */
+ uint32_t addhd; /* Hold-down timer for adding */
+ uint32_t removehd; /* Hold-down timer for removing */
+ uint16_t flags; /* Copy of DNSKEY_48 */
+ dns_secproto_t protocol;
+ dns_secalg_t algorithm;
+ uint16_t datalen;
+ unsigned char *data;
+} dns_rdata_keydata_t;
+
+#endif /* GENERIC_KEYDATA_65533_H */
diff --git a/lib/dns/rdata/generic/l32_105.c b/lib/dns/rdata/generic/l32_105.c
new file mode 100644
index 0000000..07cbc0f
--- /dev/null
+++ b/lib/dns/rdata/generic/l32_105.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_L32_105_C
+#define RDATA_GENERIC_L32_105_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_l32(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_l32);
+ REQUIRE(rdata->length == 6);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_l32(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_l32);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_l32(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_l32);
+ REQUIRE(rdata->length == 6);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_l32(ARGS_COMPARE) {
+ return (compare_l32(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_L32_105_C */
diff --git a/lib/dns/rdata/generic/l32_105.h b/lib/dns/rdata/generic/l32_105.h
new file mode 100644
index 0000000..8bf2a5c
--- /dev/null
+++ b/lib/dns/rdata/generic/l32_105.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_L32_105_H
+#define GENERIC_L32_105_H 1
+
+typedef struct dns_rdata_l32 {
+ dns_rdatacommon_t common;
+ uint16_t pref;
+ struct in_addr l32;
+} dns_rdata_l32_t;
+
+#endif /* GENERIC_L32_105_H */
diff --git a/lib/dns/rdata/generic/l64_106.c b/lib/dns/rdata/generic/l64_106.c
new file mode 100644
index 0000000..c5daf07
--- /dev/null
+++ b/lib/dns/rdata/generic/l64_106.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_L64_106_C
+#define RDATA_GENERIC_L64_106_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_l64(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_l64);
+ REQUIRE(rdata->length == 10);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_l64(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_l64);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_l64(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_l64);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_l64(ARGS_COMPARE) {
+ return (compare_l64(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_L64_106_C */
diff --git a/lib/dns/rdata/generic/l64_106.h b/lib/dns/rdata/generic/l64_106.h
new file mode 100644
index 0000000..314e583
--- /dev/null
+++ b/lib/dns/rdata/generic/l64_106.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_L64_106_H
+#define GENERIC_L64_106_H 1
+
+typedef struct dns_rdata_l64 {
+ dns_rdatacommon_t common;
+ uint16_t pref;
+ unsigned char l64[8];
+} dns_rdata_l64_t;
+
+#endif /* GENERIC_L64_106_H */
diff --git a/lib/dns/rdata/generic/loc_29.c b/lib/dns/rdata/generic/loc_29.c
new file mode 100644
index 0000000..f2a08b5
--- /dev/null
+++ b/lib/dns/rdata/generic/loc_29.c
@@ -0,0 +1,838 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1876 */
+
+#ifndef RDATA_GENERIC_LOC_29_C
+#define RDATA_GENERIC_LOC_29_C
+
+#define RRTYPE_LOC_ATTRIBUTES (0)
+
+static isc_result_t
+loc_getdecimal(const char *str, unsigned long max, size_t precision, char units,
+ unsigned long *valuep) {
+ bool ok;
+ char *e;
+ size_t i;
+ long tmp;
+ unsigned long value;
+
+ value = strtoul(str, &e, 10);
+ if (*e != 0 && *e != '.' && *e != units) {
+ return (DNS_R_SYNTAX);
+ }
+ if (value > max) {
+ return (ISC_R_RANGE);
+ }
+ ok = e != str;
+ if (*e == '.') {
+ e++;
+ for (i = 0; i < precision; i++) {
+ if (*e == 0 || *e == units) {
+ break;
+ }
+ if ((tmp = decvalue(*e++)) < 0) {
+ return (DNS_R_SYNTAX);
+ }
+ ok = true;
+ value *= 10;
+ value += tmp;
+ }
+ for (; i < precision; i++) {
+ value *= 10;
+ }
+ } else {
+ for (i = 0; i < precision; i++) {
+ value *= 10;
+ }
+ }
+ if (*e != 0 && *e == units) {
+ e++;
+ }
+ if (!ok || *e != 0) {
+ return (DNS_R_SYNTAX);
+ }
+ *valuep = value;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getprecision(const char *str, unsigned char *valuep) {
+ unsigned long poweroften[8] = { 1, 10, 100, 1000,
+ 10000, 100000, 1000000, 10000000 };
+ unsigned long m, cm;
+ bool ok;
+ char *e;
+ size_t i;
+ long tmp;
+ int man;
+ int exp;
+
+ m = strtoul(str, &e, 10);
+ if (*e != 0 && *e != '.' && *e != 'm') {
+ return (DNS_R_SYNTAX);
+ }
+ if (m > 90000000) {
+ return (ISC_R_RANGE);
+ }
+ cm = 0;
+ ok = e != str;
+ if (*e == '.') {
+ e++;
+ for (i = 0; i < 2; i++) {
+ if (*e == 0 || *e == 'm') {
+ break;
+ }
+ if ((tmp = decvalue(*e++)) < 0) {
+ return (DNS_R_SYNTAX);
+ }
+ ok = true;
+ cm *= 10;
+ cm += tmp;
+ }
+ for (; i < 2; i++) {
+ cm *= 10;
+ }
+ }
+ if (*e == 'm') {
+ e++;
+ }
+ if (!ok || *e != 0) {
+ return (DNS_R_SYNTAX);
+ }
+
+ /*
+ * We don't just multiply out as we will overflow.
+ */
+ if (m > 0) {
+ for (exp = 0; exp < 7; exp++) {
+ if (m < poweroften[exp + 1]) {
+ break;
+ }
+ }
+ man = m / poweroften[exp];
+ exp += 2;
+ } else if (cm >= 10) {
+ man = cm / 10;
+ exp = 1;
+ } else {
+ man = cm;
+ exp = 0;
+ }
+ *valuep = (man << 4) + exp;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+get_degrees(isc_lex_t *lexer, isc_token_t *token, unsigned long *d) {
+ RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_number,
+ false));
+ *d = token->value.as_ulong;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+check_coordinate(unsigned long d, unsigned long m, unsigned long s,
+ unsigned long maxd) {
+ if (d > maxd || m > 59U) {
+ return (ISC_R_RANGE);
+ }
+ if (d == maxd && (m != 0 || s != 0)) {
+ return (ISC_R_RANGE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+get_minutes(isc_lex_t *lexer, isc_token_t *token, unsigned long *m) {
+ RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_number,
+ false));
+
+ *m = token->value.as_ulong;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+get_seconds(isc_lex_t *lexer, isc_token_t *token, unsigned long *s) {
+ RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_string,
+ false));
+ RETERR(loc_getdecimal(DNS_AS_STR(*token), 59, 3, '\0', s));
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+get_direction(isc_lex_t *lexer, isc_token_t *token, const char *directions,
+ int *direction) {
+ RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_string,
+ false));
+ if (DNS_AS_STR(*token)[0] == directions[1] &&
+ DNS_AS_STR(*token)[1] == 0)
+ {
+ *direction = DNS_AS_STR(*token)[0];
+ return (ISC_R_SUCCESS);
+ }
+
+ if (DNS_AS_STR(*token)[0] == directions[0] &&
+ DNS_AS_STR(*token)[1] == 0)
+ {
+ *direction = DNS_AS_STR(*token)[0];
+ return (ISC_R_SUCCESS);
+ }
+
+ *direction = 0;
+ isc_lex_ungettoken(lexer, token);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getcoordinate(isc_lex_t *lexer, unsigned long *dp, unsigned long *mp,
+ unsigned long *sp, const char *directions, int *directionp,
+ unsigned long maxd) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_token_t token;
+ unsigned long d, m, s;
+ int direction = 0;
+
+ m = 0;
+ s = 0;
+
+ /*
+ * Degrees.
+ */
+ RETERR(get_degrees(lexer, &token, &d));
+ RETTOK(check_coordinate(d, m, s, maxd));
+
+ /*
+ * Minutes.
+ */
+ RETERR(get_direction(lexer, &token, directions, &direction));
+ if (direction > 0) {
+ goto done;
+ }
+
+ RETERR(get_minutes(lexer, &token, &m));
+ RETTOK(check_coordinate(d, m, s, maxd));
+
+ /*
+ * Seconds.
+ */
+ RETERR(get_direction(lexer, &token, directions, &direction));
+ if (direction > 0) {
+ goto done;
+ }
+
+ result = get_seconds(lexer, &token, &s);
+ if (result == ISC_R_RANGE || result == DNS_R_SYNTAX) {
+ RETTOK(result);
+ }
+ RETERR(result);
+ RETTOK(check_coordinate(d, m, s, maxd));
+
+ /*
+ * Direction.
+ */
+ RETERR(get_direction(lexer, &token, directions, &direction));
+ if (direction == 0) {
+ RETERR(DNS_R_SYNTAX);
+ }
+done:
+
+ *directionp = direction;
+ *dp = d;
+ *mp = m;
+ *sp = s;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getlatitude(isc_lex_t *lexer, unsigned long *latitude) {
+ unsigned long d1 = 0, m1 = 0, s1 = 0;
+ int direction = 0;
+
+ RETERR(loc_getcoordinate(lexer, &d1, &m1, &s1, "SN", &direction, 90U));
+
+ switch (direction) {
+ case 'N':
+ *latitude = 0x80000000 + (d1 * 3600 + m1 * 60) * 1000 + s1;
+ break;
+ case 'S':
+ *latitude = 0x80000000 - (d1 * 3600 + m1 * 60) * 1000 - s1;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getlongitude(isc_lex_t *lexer, unsigned long *longitude) {
+ unsigned long d2 = 0, m2 = 0, s2 = 0;
+ int direction = 0;
+
+ RETERR(loc_getcoordinate(lexer, &d2, &m2, &s2, "WE", &direction, 180U));
+
+ switch (direction) {
+ case 'E':
+ *longitude = 0x80000000 + (d2 * 3600 + m2 * 60) * 1000 + s2;
+ break;
+ case 'W':
+ *longitude = 0x80000000 - (d2 * 3600 + m2 * 60) * 1000 - s2;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getaltitude(isc_lex_t *lexer, unsigned long *altitude) {
+ isc_token_t token;
+ unsigned long cm;
+ const char *str;
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ str = DNS_AS_STR(token);
+ if (DNS_AS_STR(token)[0] == '-') {
+ RETTOK(loc_getdecimal(str + 1, 100000, 2, 'm', &cm));
+ if (cm > 10000000UL) {
+ RETTOK(ISC_R_RANGE);
+ }
+ *altitude = 10000000 - cm;
+ } else {
+ RETTOK(loc_getdecimal(str, 42849672, 2, 'm', &cm));
+ if (cm > 4284967295UL) {
+ RETTOK(ISC_R_RANGE);
+ }
+ *altitude = 10000000 + cm;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getoptionalprecision(isc_lex_t *lexer, unsigned char *valuep) {
+ isc_token_t token;
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ true));
+ if (token.type == isc_tokentype_eol || token.type == isc_tokentype_eof)
+ {
+ isc_lex_ungettoken(lexer, &token);
+ return (ISC_R_NOMORE);
+ }
+ RETTOK(loc_getprecision(DNS_AS_STR(token), valuep));
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+loc_getsize(isc_lex_t *lexer, unsigned char *sizep) {
+ return (loc_getoptionalprecision(lexer, sizep));
+}
+
+static isc_result_t
+loc_gethorizontalprecision(isc_lex_t *lexer, unsigned char *hpp) {
+ return (loc_getoptionalprecision(lexer, hpp));
+}
+
+static isc_result_t
+loc_getverticalprecision(isc_lex_t *lexer, unsigned char *vpp) {
+ return (loc_getoptionalprecision(lexer, vpp));
+}
+
+/* The LOC record is expressed in a master file in the following format:
+ *
+ * <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_loc(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_loc);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_loc(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_loc);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_loc(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_loc);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_loc(ARGS_COMPARE) {
+ return (compare_loc(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_LOC_29_C */
diff --git a/lib/dns/rdata/generic/loc_29.h b/lib/dns/rdata/generic/loc_29.h
new file mode 100644
index 0000000..c523785
--- /dev/null
+++ b/lib/dns/rdata/generic/loc_29.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_LOC_29_H
+#define GENERIC_LOC_29_H 1
+
+/*!
+ * \brief Per RFC1876 */
+
+typedef struct dns_rdata_loc_0 {
+ uint8_t version; /* must be first and zero */
+ uint8_t size;
+ uint8_t horizontal;
+ uint8_t vertical;
+ uint32_t latitude;
+ uint32_t longitude;
+ uint32_t altitude;
+} dns_rdata_loc_0_t;
+
+typedef struct dns_rdata_loc {
+ dns_rdatacommon_t common;
+ union {
+ dns_rdata_loc_0_t v0;
+ } v;
+} dns_rdata_loc_t;
+
+#endif /* GENERIC_LOC_29_H */
diff --git a/lib/dns/rdata/generic/lp_107.c b/lib/dns/rdata/generic/lp_107.c
new file mode 100644
index 0000000..50b2e29
--- /dev/null
+++ b/lib/dns/rdata/generic/lp_107.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_LP_107_C
+#define RDATA_GENERIC_LP_107_C
+
+#include <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);
+ RETERR(name_duporclone(&name, mctx, &lp->lp));
+ lp->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_lp(ARGS_FREESTRUCT) {
+ dns_rdata_lp_t *lp = source;
+
+ REQUIRE(lp != NULL);
+ REQUIRE(lp->common.rdtype == dns_rdatatype_lp);
+
+ if (lp->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&lp->lp, lp->mctx);
+ lp->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_lp(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_lp);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+
+ result = (add)(arg, &name, dns_rdatatype_l32);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return ((add)(arg, &name, dns_rdatatype_l64));
+}
+
+static isc_result_t
+digest_lp(ARGS_DIGEST) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_lp);
+
+ dns_rdata_toregion(rdata, &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..71cd196
--- /dev/null
+++ b/lib/dns/rdata/generic/lp_107.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_LP_107_H
+#define GENERIC_LP_107_H 1
+
+typedef struct dns_rdata_lp {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t pref;
+ dns_name_t lp;
+} dns_rdata_lp_t;
+
+#endif /* GENERIC_LP_107_H */
diff --git a/lib/dns/rdata/generic/mb_7.c b/lib/dns/rdata/generic/mb_7.c
new file mode 100644
index 0000000..a3923eb
--- /dev/null
+++ b/lib/dns/rdata/generic/mb_7.c
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MB_7_C
+#define RDATA_GENERIC_MB_7_C
+
+#define RRTYPE_MB_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_mb(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_mb);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_mb(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_mb);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &mb->mb));
+ mb->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_mb(ARGS_FREESTRUCT) {
+ dns_rdata_mb_t *mb = source;
+
+ REQUIRE(mb != NULL);
+
+ if (mb->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&mb->mb, mb->mctx);
+ mb->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_mb(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mb);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a));
+}
+
+static isc_result_t
+digest_mb(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mb);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_mb(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_mb);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (dns_name_ismailbox(name));
+}
+
+static bool
+checknames_mb(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_mb);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_mb(ARGS_COMPARE) {
+ return (compare_mb(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MB_7_C */
diff --git a/lib/dns/rdata/generic/mb_7.h b/lib/dns/rdata/generic/mb_7.h
new file mode 100644
index 0000000..ae7e2e2
--- /dev/null
+++ b/lib/dns/rdata/generic/mb_7.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_MB_7_H
+#define GENERIC_MB_7_H 1
+
+typedef struct dns_rdata_mb {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t mb;
+} dns_rdata_mb_t;
+
+#endif /* GENERIC_MB_7_H */
diff --git a/lib/dns/rdata/generic/md_3.c b/lib/dns/rdata/generic/md_3.c
new file mode 100644
index 0000000..9e2b778
--- /dev/null
+++ b/lib/dns/rdata/generic/md_3.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MD_3_C
+#define RDATA_GENERIC_MD_3_C
+
+#define RRTYPE_MD_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_md(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_md);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_md(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_md);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &md->md));
+ md->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_md(ARGS_FREESTRUCT) {
+ dns_rdata_md_t *md = source;
+
+ REQUIRE(md != NULL);
+ REQUIRE(md->common.rdtype == dns_rdatatype_md);
+
+ if (md->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&md->md, md->mctx);
+ md->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_md(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_md);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a));
+}
+
+static isc_result_t
+digest_md(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_md);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_md(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_md);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_md(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_md);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_md(ARGS_COMPARE) {
+ return (compare_md(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MD_3_C */
diff --git a/lib/dns/rdata/generic/md_3.h b/lib/dns/rdata/generic/md_3.h
new file mode 100644
index 0000000..643bd03
--- /dev/null
+++ b/lib/dns/rdata/generic/md_3.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_MD_3_H
+#define GENERIC_MD_3_H 1
+
+typedef struct dns_rdata_md {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t md;
+} dns_rdata_md_t;
+
+#endif /* GENERIC_MD_3_H */
diff --git a/lib/dns/rdata/generic/mf_4.c b/lib/dns/rdata/generic/mf_4.c
new file mode 100644
index 0000000..30ea010
--- /dev/null
+++ b/lib/dns/rdata/generic/mf_4.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MF_4_C
+#define RDATA_GENERIC_MF_4_C
+
+#define RRTYPE_MF_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_mf(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_mf);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_mf(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_mf);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &mf->mf));
+ mf->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_mf(ARGS_FREESTRUCT) {
+ dns_rdata_mf_t *mf = source;
+
+ REQUIRE(mf != NULL);
+ REQUIRE(mf->common.rdtype == dns_rdatatype_mf);
+
+ if (mf->mctx == NULL) {
+ return;
+ }
+ dns_name_free(&mf->mf, mf->mctx);
+ mf->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_mf(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mf);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a));
+}
+
+static isc_result_t
+digest_mf(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mf);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_mf(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_mf);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_mf(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_mf);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_mf(ARGS_COMPARE) {
+ return (compare_mf(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MF_4_C */
diff --git a/lib/dns/rdata/generic/mf_4.h b/lib/dns/rdata/generic/mf_4.h
new file mode 100644
index 0000000..6a4b514
--- /dev/null
+++ b/lib/dns/rdata/generic/mf_4.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_MF_4_H
+#define GENERIC_MF_4_H 1
+
+typedef struct dns_rdata_mf {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t mf;
+} dns_rdata_mf_t;
+
+#endif /* GENERIC_MF_4_H */
diff --git a/lib/dns/rdata/generic/mg_8.c b/lib/dns/rdata/generic/mg_8.c
new file mode 100644
index 0000000..183e428
--- /dev/null
+++ b/lib/dns/rdata/generic/mg_8.c
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MG_8_C
+#define RDATA_GENERIC_MG_8_C
+
+#define RRTYPE_MG_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_mg(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_mg);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_mg(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_mg);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &mg->mg));
+ mg->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_mg(ARGS_FREESTRUCT) {
+ dns_rdata_mg_t *mg = source;
+
+ REQUIRE(mg != NULL);
+ REQUIRE(mg->common.rdtype == dns_rdatatype_mg);
+
+ if (mg->mctx == NULL) {
+ return;
+ }
+ dns_name_free(&mg->mg, mg->mctx);
+ mg->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_mg(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_mg);
+
+ UNUSED(add);
+ UNUSED(arg);
+ UNUSED(rdata);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_mg(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mg);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_mg(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_mg);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (dns_name_ismailbox(name));
+}
+
+static bool
+checknames_mg(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_mg);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_mg(ARGS_COMPARE) {
+ return (compare_mg(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MG_8_C */
diff --git a/lib/dns/rdata/generic/mg_8.h b/lib/dns/rdata/generic/mg_8.h
new file mode 100644
index 0000000..815b46a
--- /dev/null
+++ b/lib/dns/rdata/generic/mg_8.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_MG_8_H
+#define GENERIC_MG_8_H 1
+
+typedef struct dns_rdata_mg {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t mg;
+} dns_rdata_mg_t;
+
+#endif /* GENERIC_MG_8_H */
diff --git a/lib/dns/rdata/generic/minfo_14.c b/lib/dns/rdata/generic/minfo_14.c
new file mode 100644
index 0000000..e396cff
--- /dev/null
+++ b/lib/dns/rdata/generic/minfo_14.c
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MINFO_14_C
+#define RDATA_GENERIC_MINFO_14_C
+
+#define RRTYPE_MINFO_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_minfo(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ int i;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_minfo);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ for (i = 0; i < 2; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ismailbox(&name);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_minfo(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t rmail;
+ dns_name_t email;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&rmail, NULL);
+ dns_name_init(&email, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+ REQUIRE(minfo != NULL);
+ REQUIRE(rdata->length != 0);
+
+ minfo->common.rdclass = rdata->rdclass;
+ minfo->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&minfo->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&minfo->rmailbox, NULL);
+ RETERR(name_duporclone(&name, mctx, &minfo->rmailbox));
+ isc_region_consume(&region, name_length(&name));
+
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&minfo->emailbox, NULL);
+ result = name_duporclone(&name, mctx, &minfo->emailbox);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ minfo->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&minfo->rmailbox, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_minfo(ARGS_FREESTRUCT) {
+ dns_rdata_minfo_t *minfo = source;
+
+ REQUIRE(minfo != NULL);
+ REQUIRE(minfo->common.rdtype == dns_rdatatype_minfo);
+
+ if (minfo->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&minfo->rmailbox, minfo->mctx);
+ dns_name_free(&minfo->emailbox, minfo->mctx);
+ minfo->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_minfo(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_minfo(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ result = dns_name_digest(&name, digest, arg);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_region_consume(&r, name_length(&name));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_minfo(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_minfo);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_minfo(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_minfo);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &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..f2017e6
--- /dev/null
+++ b/lib/dns/rdata/generic/minfo_14.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_MINFO_14_H
+#define GENERIC_MINFO_14_H 1
+
+typedef struct dns_rdata_minfo {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t rmailbox;
+ dns_name_t emailbox;
+} dns_rdata_minfo_t;
+
+#endif /* GENERIC_MINFO_14_H */
diff --git a/lib/dns/rdata/generic/mr_9.c b/lib/dns/rdata/generic/mr_9.c
new file mode 100644
index 0000000..aff5da3
--- /dev/null
+++ b/lib/dns/rdata/generic/mr_9.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MR_9_C
+#define RDATA_GENERIC_MR_9_C
+
+#define RRTYPE_MR_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_mr(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_mr);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_mr(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_mr);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &mr->mr));
+ mr->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_mr(ARGS_FREESTRUCT) {
+ dns_rdata_mr_t *mr = source;
+
+ REQUIRE(mr != NULL);
+ REQUIRE(mr->common.rdtype == dns_rdatatype_mr);
+
+ if (mr->mctx == NULL) {
+ return;
+ }
+ dns_name_free(&mr->mr, mr->mctx);
+ mr->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_mr(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_mr);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_mr(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mr);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_mr(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_mr);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_mr(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_mr);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_mr(ARGS_COMPARE) {
+ return (compare_mr(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_MR_9_C */
diff --git a/lib/dns/rdata/generic/mr_9.h b/lib/dns/rdata/generic/mr_9.h
new file mode 100644
index 0000000..6855933
--- /dev/null
+++ b/lib/dns/rdata/generic/mr_9.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_MR_9_H
+#define GENERIC_MR_9_H 1
+
+typedef struct dns_rdata_mr {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t mr;
+} dns_rdata_mr_t;
+
+#endif /* GENERIC_MR_9_H */
diff --git a/lib/dns/rdata/generic/mx_15.c b/lib/dns/rdata/generic/mx_15.c
new file mode 100644
index 0000000..ff6637c
--- /dev/null
+++ b/lib/dns/rdata/generic/mx_15.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_MX_15_C
+#define RDATA_GENERIC_MX_15_C
+
+#include <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);
+ RETERR(name_duporclone(&name, mctx, &mx->mx));
+ mx->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_mx(ARGS_FREESTRUCT) {
+ dns_rdata_mx_t *mx = source;
+
+ REQUIRE(mx != NULL);
+ REQUIRE(mx->common.rdtype == dns_rdatatype_mx);
+
+ if (mx->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&mx->mx, mx->mctx);
+ mx->mctx = NULL;
+}
+
+static unsigned char port25_offset[] = { 0, 3 };
+static unsigned char port25_ndata[] = "\003_25\004_tcp";
+static dns_name_t port25 = DNS_NAME_INITNONABSOLUTE(port25_ndata,
+ port25_offset);
+
+static isc_result_t
+additionaldata_mx(ARGS_ADDLDATA) {
+ isc_result_t result;
+ dns_fixedname_t fixed;
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_mx);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &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);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_fixedname_init(&fixed);
+ result = dns_name_concatenate(&port25, &name,
+ dns_fixedname_name(&fixed), NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ return ((add)(arg, dns_fixedname_name(&fixed), dns_rdatatype_tlsa));
+}
+
+static isc_result_t
+digest_mx(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mx);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 2);
+ r1.length = 2;
+ RETERR((digest)(arg, &r1));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_mx(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_mx);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (dns_name_ishostname(name, wildcard));
+}
+
+static bool
+checknames_mx(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_mx);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &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..14f9189
--- /dev/null
+++ b/lib/dns/rdata/generic/mx_15.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_MX_15_H
+#define GENERIC_MX_15_H 1
+
+typedef struct dns_rdata_mx {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t pref;
+ dns_name_t mx;
+} dns_rdata_mx_t;
+
+#endif /* GENERIC_MX_15_H */
diff --git a/lib/dns/rdata/generic/naptr_35.c b/lib/dns/rdata/generic/naptr_35.c
new file mode 100644
index 0000000..3f43e0e
--- /dev/null
+++ b/lib/dns/rdata/generic/naptr_35.c
@@ -0,0 +1,740 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2915 */
+
+#ifndef RDATA_GENERIC_NAPTR_35_C
+#define RDATA_GENERIC_NAPTR_35_C
+
+#define RRTYPE_NAPTR_ATTRIBUTES (0)
+
+#include <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;
+ isc_result_t result;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_naptr);
+ REQUIRE(naptr != NULL);
+ REQUIRE(rdata->length != 0);
+
+ naptr->common.rdclass = rdata->rdclass;
+ naptr->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&naptr->common, link);
+
+ naptr->flags = NULL;
+ naptr->service = NULL;
+ naptr->regexp = NULL;
+
+ dns_rdata_toregion(rdata, &r);
+
+ naptr->order = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+
+ naptr->preference = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+
+ naptr->flags_len = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ INSIST(naptr->flags_len <= r.length);
+ naptr->flags = mem_maybedup(mctx, r.base, naptr->flags_len);
+ if (naptr->flags == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&r, naptr->flags_len);
+
+ naptr->service_len = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ INSIST(naptr->service_len <= r.length);
+ naptr->service = mem_maybedup(mctx, r.base, naptr->service_len);
+ if (naptr->service == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&r, naptr->service_len);
+
+ naptr->regexp_len = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ INSIST(naptr->regexp_len <= r.length);
+ naptr->regexp = mem_maybedup(mctx, r.base, naptr->regexp_len);
+ if (naptr->regexp == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&r, naptr->regexp_len);
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ dns_name_init(&naptr->replacement, NULL);
+ result = name_duporclone(&name, mctx, &naptr->replacement);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ naptr->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL && naptr->flags != NULL) {
+ isc_mem_free(mctx, naptr->flags);
+ }
+ if (mctx != NULL && naptr->service != NULL) {
+ isc_mem_free(mctx, naptr->service);
+ }
+ if (mctx != NULL && naptr->regexp != NULL) {
+ isc_mem_free(mctx, naptr->regexp);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_naptr(ARGS_FREESTRUCT) {
+ dns_rdata_naptr_t *naptr = source;
+
+ REQUIRE(naptr != NULL);
+ REQUIRE(naptr->common.rdtype == dns_rdatatype_naptr);
+
+ if (naptr->mctx == NULL) {
+ return;
+ }
+
+ if (naptr->flags != NULL) {
+ isc_mem_free(naptr->mctx, naptr->flags);
+ }
+ if (naptr->service != NULL) {
+ isc_mem_free(naptr->mctx, naptr->service);
+ }
+ if (naptr->regexp != NULL) {
+ isc_mem_free(naptr->mctx, naptr->regexp);
+ }
+ dns_name_free(&naptr->replacement, naptr->mctx);
+ naptr->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_naptr(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t sr;
+ dns_rdatatype_t atype;
+ unsigned int i, flagslen;
+ char *cp;
+
+ REQUIRE(rdata->type == dns_rdatatype_naptr);
+
+ /*
+ * Order, preference.
+ */
+ dns_rdata_toregion(rdata, &sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Flags.
+ */
+ atype = 0;
+ flagslen = sr.base[0];
+ cp = (char *)&sr.base[1];
+ for (i = 0; i < flagslen; i++, cp++) {
+ if (*cp == 'S' || *cp == 's') {
+ atype = dns_rdatatype_srv;
+ break;
+ }
+ if (*cp == 'A' || *cp == 'a') {
+ atype = dns_rdatatype_a;
+ break;
+ }
+ }
+ isc_region_consume(&sr, flagslen + 1);
+
+ /*
+ * Service.
+ */
+ isc_region_consume(&sr, sr.base[0] + 1);
+
+ /*
+ * Regexp.
+ */
+ isc_region_consume(&sr, sr.base[0] + 1);
+
+ /*
+ * Replacement.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+
+ if (atype != 0) {
+ return ((add)(arg, &name, atype));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_naptr(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ unsigned int length, n;
+ isc_result_t result;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_naptr);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ length = 0;
+
+ /*
+ * Order, preference.
+ */
+ length += 4;
+ isc_region_consume(&r2, 4);
+
+ /*
+ * Flags.
+ */
+ n = r2.base[0] + 1;
+ length += n;
+ isc_region_consume(&r2, n);
+
+ /*
+ * Service.
+ */
+ n = r2.base[0] + 1;
+ length += n;
+ isc_region_consume(&r2, n);
+
+ /*
+ * Regexp.
+ */
+ n = r2.base[0] + 1;
+ length += n;
+ isc_region_consume(&r2, n);
+
+ /*
+ * Digest the RR up to the replacement name.
+ */
+ r1.length = length;
+ result = (digest)(arg, &r1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Replacement.
+ */
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_naptr(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_naptr);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_naptr(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_naptr);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_naptr(ARGS_COMPARE) {
+ return (compare_naptr(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NAPTR_35_C */
diff --git a/lib/dns/rdata/generic/naptr_35.h b/lib/dns/rdata/generic/naptr_35.h
new file mode 100644
index 0000000..d221823
--- /dev/null
+++ b/lib/dns/rdata/generic/naptr_35.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_NAPTR_35_H
+#define GENERIC_NAPTR_35_H 1
+
+/*!
+ * \brief Per RFC2915 */
+
+typedef struct dns_rdata_naptr {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t order;
+ uint16_t preference;
+ char *flags;
+ uint8_t flags_len;
+ char *service;
+ uint8_t service_len;
+ char *regexp;
+ uint8_t regexp_len;
+ dns_name_t replacement;
+} dns_rdata_naptr_t;
+
+#endif /* GENERIC_NAPTR_35_H */
diff --git a/lib/dns/rdata/generic/nid_104.c b/lib/dns/rdata/generic/nid_104.c
new file mode 100644
index 0000000..0297da7
--- /dev/null
+++ b/lib/dns/rdata/generic/nid_104.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_NID_104_C
+#define RDATA_GENERIC_NID_104_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_nid(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nid);
+ REQUIRE(rdata->length == 10);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_nid(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nid);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_nid(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nid);
+ REQUIRE(rdata->length == 10);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_nid(ARGS_COMPARE) {
+ return (compare_nid(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NID_104_C */
diff --git a/lib/dns/rdata/generic/nid_104.h b/lib/dns/rdata/generic/nid_104.h
new file mode 100644
index 0000000..cab4330
--- /dev/null
+++ b/lib/dns/rdata/generic/nid_104.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_NID_104_H
+#define GENERIC_NID_104_H 1
+
+typedef struct dns_rdata_nid {
+ dns_rdatacommon_t common;
+ uint16_t pref;
+ unsigned char nid[8];
+} dns_rdata_nid_t;
+
+#endif /* GENERIC_NID_104_H */
diff --git a/lib/dns/rdata/generic/ninfo_56.c b/lib/dns/rdata/generic/ninfo_56.c
new file mode 100644
index 0000000..3fcc582
--- /dev/null
+++ b/lib/dns/rdata/generic/ninfo_56.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_NINFO_56_C
+#define RDATA_GENERIC_NINFO_56_C
+
+#define RRTYPE_NINFO_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_ninfo(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_ninfo);
+
+ return (generic_fromtext_txt(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_ninfo(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+
+ return (generic_totext_txt(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_ninfo(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_ninfo);
+
+ return (generic_fromwire_txt(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_ninfo(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_ninfo(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_ninfo);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_ninfo(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_ninfo);
+
+ return (generic_fromstruct_txt(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_ninfo(ARGS_TOSTRUCT) {
+ dns_rdata_ninfo_t *ninfo = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+ REQUIRE(ninfo != NULL);
+
+ ninfo->common.rdclass = rdata->rdclass;
+ ninfo->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&ninfo->common, link);
+
+ return (generic_tostruct_txt(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_ninfo(ARGS_FREESTRUCT) {
+ dns_rdata_ninfo_t *ninfo = source;
+
+ REQUIRE(ninfo != NULL);
+ REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo);
+
+ generic_freestruct_txt(source);
+}
+
+static isc_result_t
+additionaldata_ninfo(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_ninfo(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_ninfo(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ninfo);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_ninfo(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_ninfo);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_ninfo(ARGS_COMPARE) {
+ return (compare_ninfo(rdata1, rdata2));
+}
+
+isc_result_t
+dns_rdata_ninfo_first(dns_rdata_ninfo_t *ninfo) {
+ REQUIRE(ninfo != NULL);
+ REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo);
+
+ return (generic_txt_first(ninfo));
+}
+
+isc_result_t
+dns_rdata_ninfo_next(dns_rdata_ninfo_t *ninfo) {
+ REQUIRE(ninfo != NULL);
+ REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo);
+
+ return (generic_txt_next(ninfo));
+}
+
+isc_result_t
+dns_rdata_ninfo_current(dns_rdata_ninfo_t *ninfo,
+ dns_rdata_ninfo_string_t *string) {
+ REQUIRE(ninfo != NULL);
+ REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo);
+
+ return (generic_txt_current(ninfo, string));
+}
+#endif /* RDATA_GENERIC_NINFO_56_C */
diff --git a/lib/dns/rdata/generic/ninfo_56.h b/lib/dns/rdata/generic/ninfo_56.h
new file mode 100644
index 0000000..0630ff2
--- /dev/null
+++ b/lib/dns/rdata/generic/ninfo_56.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_NINFO_56_H
+#define GENERIC_NINFO_56_H 1
+
+typedef struct dns_rdata_txt_string dns_rdata_ninfo_string_t;
+
+typedef struct dns_rdata_txt dns_rdata_ninfo_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
+
+isc_result_t
+dns_rdata_ninfo_first(dns_rdata_ninfo_t *);
+
+isc_result_t
+dns_rdata_ninfo_next(dns_rdata_ninfo_t *);
+
+isc_result_t
+dns_rdata_ninfo_current(dns_rdata_ninfo_t *, dns_rdata_ninfo_string_t *);
+
+#endif /* GENERIC_NINFO_16_H */
diff --git a/lib/dns/rdata/generic/ns_2.c b/lib/dns/rdata/generic/ns_2.c
new file mode 100644
index 0000000..265f3bb
--- /dev/null
+++ b/lib/dns/rdata/generic/ns_2.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_NS_2_C
+#define RDATA_GENERIC_NS_2_C
+
+#define RRTYPE_NS_ATTRIBUTES (DNS_RDATATYPEATTR_ZONECUTAUTH)
+
+static isc_result_t
+fromtext_ns(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_ns);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_ns(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_ns);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &ns->name));
+ ns->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_ns(ARGS_FREESTRUCT) {
+ dns_rdata_ns_t *ns = source;
+
+ REQUIRE(ns != NULL);
+
+ if (ns->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&ns->name, ns->mctx);
+ ns->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_ns(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_ns);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a));
+}
+
+static isc_result_t
+digest_ns(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_ns);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_ns(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ns);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_ns(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_ns);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &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..3cb9e65
--- /dev/null
+++ b/lib/dns/rdata/generic/ns_2.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_NS_2_H
+#define GENERIC_NS_2_H 1
+
+typedef struct dns_rdata_ns {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t name;
+} dns_rdata_ns_t;
+
+#endif /* GENERIC_NS_2_H */
diff --git a/lib/dns/rdata/generic/nsec3_50.c b/lib/dns/rdata/generic/nsec3_50.c
new file mode 100644
index 0000000..c0d81a6
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec3_50.c
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2004 Nominet, Ltd.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND NOMINET DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* RFC 5155 */
+
+#ifndef RDATA_GENERIC_NSEC3_50_C
+#define RDATA_GENERIC_NSEC3_50_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_nsec3(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec3);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_nsec3(ARGS_CHECKOWNER) {
+ unsigned char owner[NSEC3_MAX_HASH_LENGTH];
+ isc_buffer_t buffer;
+ dns_label_t label;
+
+ REQUIRE(type == dns_rdatatype_nsec3);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ /*
+ * First label is a base32hex string without padding.
+ */
+ dns_name_getlabel(name, 0, &label);
+ isc_region_consume(&label, 1);
+ isc_buffer_init(&buffer, owner, sizeof(owner));
+ if (isc_base32hexnp_decoderegion(&label, &buffer) == ISC_R_SUCCESS) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static bool
+checknames_nsec3(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nsec3);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_nsec3(ARGS_COMPARE) {
+ return (compare_nsec3(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NSEC3_50_C */
diff --git a/lib/dns/rdata/generic/nsec3_50.h b/lib/dns/rdata/generic/nsec3_50.h
new file mode 100644
index 0000000..e773437
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec3_50.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_NSEC3_50_H
+#define GENERIC_NSEC3_50_H 1
+
+/*!
+ * \brief Per RFC 5155 */
+
+#include <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
+
+#endif /* GENERIC_NSEC3_50_H */
diff --git a/lib/dns/rdata/generic/nsec3param_51.c b/lib/dns/rdata/generic/nsec3param_51.c
new file mode 100644
index 0000000..1d08d72
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec3param_51.c
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2004 Nominet, Ltd.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND NOMINET DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* RFC 5155 */
+
+#ifndef RDATA_GENERIC_NSEC3PARAM_51_C
+#define RDATA_GENERIC_NSEC3PARAM_51_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_nsec3param(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec3param);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_nsec3param(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nsec3param);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_nsec3param(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nsec3param);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_nsec3param(ARGS_COMPARE) {
+ return (compare_nsec3param(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NSEC3PARAM_51_C */
diff --git a/lib/dns/rdata/generic/nsec3param_51.h b/lib/dns/rdata/generic/nsec3param_51.h
new file mode 100644
index 0000000..2ff3029
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec3param_51.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_NSEC3PARAM_51_H
+#define GENERIC_NSEC3PARAM_51_H 1
+
+/*!
+ * \brief Per RFC 5155 */
+
+#include <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;
+
+#endif /* GENERIC_NSEC3PARAM_51_H */
diff --git a/lib/dns/rdata/generic/nsec_47.c b/lib/dns/rdata/generic/nsec_47.c
new file mode 100644
index 0000000..03aab8c
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec_47.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 3845 */
+
+#ifndef RDATA_GENERIC_NSEC_47_C
+#define RDATA_GENERIC_NSEC_47_C
+
+/*
+ * The attributes do not include DNS_RDATATYPEATTR_SINGLETON
+ * because we must be able to handle a parent/child NSEC pair.
+ */
+#define RRTYPE_NSEC_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_DNSSEC | DNS_RDATATYPEATTR_ZONECUTAUTH | \
+ DNS_RDATATYPEATTR_ATCNAME)
+
+static isc_result_t
+fromtext_nsec(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_nsec);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Next domain.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ return (typemap_fromtext(lexer, target, false));
+}
+
+static isc_result_t
+totext_nsec(ARGS_TOTEXT) {
+ isc_region_t sr;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_totext(&name, false, target));
+ /*
+ * Don't leave a trailing space when there's no typemap present.
+ */
+ if (sr.length > 0) {
+ RETERR(str_totext(" ", target));
+ }
+ return (typemap_totext(&sr, NULL, target));
+}
+
+static isc_result_t
+fromwire_nsec(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_nsec);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ isc_buffer_activeregion(source, &sr);
+ RETERR(typemap_test(&sr, false));
+ RETERR(mem_tobuffer(target, sr.base, sr.length));
+ isc_buffer_forward(source, sr.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_nsec(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_towire(&name, cctx, target));
+
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_nsec(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nsec);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_nsec(ARGS_FROMSTRUCT) {
+ dns_rdata_nsec_t *nsec = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_nsec);
+ REQUIRE(nsec != NULL);
+ REQUIRE(nsec->common.rdtype == type);
+ REQUIRE(nsec->common.rdclass == rdclass);
+ REQUIRE(nsec->typebits != NULL || nsec->len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&nsec->next, &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);
+ RETERR(name_duporclone(&name, mctx, &nsec->next));
+
+ nsec->len = region.length;
+ nsec->typebits = mem_maybedup(mctx, region.base, region.length);
+ if (nsec->typebits == NULL) {
+ goto cleanup;
+ }
+
+ nsec->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&nsec->next, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_nsec(ARGS_FREESTRUCT) {
+ dns_rdata_nsec_t *nsec = source;
+
+ REQUIRE(nsec != NULL);
+ REQUIRE(nsec->common.rdtype == dns_rdatatype_nsec);
+
+ if (nsec->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&nsec->next, nsec->mctx);
+ if (nsec->typebits != NULL) {
+ isc_mem_free(nsec->mctx, nsec->typebits);
+ }
+ nsec->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_nsec(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_nsec(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_nsec(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nsec);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_nsec(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nsec);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_nsec(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nsec);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &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..98cb788
--- /dev/null
+++ b/lib/dns/rdata/generic/nsec_47.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_NSEC_47_H
+#define GENERIC_NSEC_47_H 1
+
+/*!
+ * \brief Per RFC 3845 */
+
+typedef struct dns_rdata_nsec {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t next;
+ unsigned char *typebits;
+ uint16_t len;
+} dns_rdata_nsec_t;
+
+#endif /* GENERIC_NSEC_47_H */
diff --git a/lib/dns/rdata/generic/null_10.c b/lib/dns/rdata/generic/null_10.c
new file mode 100644
index 0000000..c5884bf
--- /dev/null
+++ b/lib/dns/rdata/generic/null_10.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_NULL_10_C
+#define RDATA_GENERIC_NULL_10_C
+
+#define RRTYPE_NULL_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_null(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_null);
+
+ UNUSED(rdclass);
+ UNUSED(type);
+ UNUSED(lexer);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(target);
+ UNUSED(callbacks);
+
+ return (DNS_R_SYNTAX);
+}
+
+static isc_result_t
+totext_null(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_null);
+
+ return (unknown_totext(rdata, tctx, target));
+}
+
+static isc_result_t
+fromwire_null(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_null);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_null(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_null);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_null(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_null);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_null(ARGS_FROMSTRUCT) {
+ dns_rdata_null_t *null = source;
+
+ REQUIRE(type == dns_rdatatype_null);
+ REQUIRE(null != NULL);
+ REQUIRE(null->common.rdtype == type);
+ REQUIRE(null->common.rdclass == rdclass);
+ REQUIRE(null->data != NULL || null->length == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (mem_tobuffer(target, null->data, null->length));
+}
+
+static isc_result_t
+tostruct_null(ARGS_TOSTRUCT) {
+ dns_rdata_null_t *null = target;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_null);
+ REQUIRE(null != NULL);
+
+ null->common.rdclass = rdata->rdclass;
+ null->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&null->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ null->length = r.length;
+ null->data = mem_maybedup(mctx, r.base, r.length);
+ if (null->data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ null->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_null(ARGS_FREESTRUCT) {
+ dns_rdata_null_t *null = source;
+
+ REQUIRE(null != NULL);
+ REQUIRE(null->common.rdtype == dns_rdatatype_null);
+
+ if (null->mctx == NULL) {
+ return;
+ }
+
+ if (null->data != NULL) {
+ isc_mem_free(null->mctx, null->data);
+ }
+ null->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_null(ARGS_ADDLDATA) {
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_null);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_null(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_null);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_null(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_null);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_null(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_null);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_null(ARGS_COMPARE) {
+ return (compare_null(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_NULL_10_C */
diff --git a/lib/dns/rdata/generic/null_10.h b/lib/dns/rdata/generic/null_10.h
new file mode 100644
index 0000000..cc17e23
--- /dev/null
+++ b/lib/dns/rdata/generic/null_10.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_NULL_10_H
+#define GENERIC_NULL_10_H 1
+
+typedef struct dns_rdata_null {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t length;
+ unsigned char *data;
+} dns_rdata_null_t;
+
+#endif /* GENERIC_NULL_10_H */
diff --git a/lib/dns/rdata/generic/nxt_30.c b/lib/dns/rdata/generic/nxt_30.c
new file mode 100644
index 0000000..5a3dcb5
--- /dev/null
+++ b/lib/dns/rdata/generic/nxt_30.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2535 */
+
+#ifndef RDATA_GENERIC_NXT_30_C
+#define RDATA_GENERIC_NXT_30_C
+
+/*
+ * The attributes do not include DNS_RDATATYPEATTR_SINGLETON
+ * because we must be able to handle a parent/child NXT pair.
+ */
+#define RRTYPE_NXT_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_nxt(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ char *e;
+ unsigned char bm[8 * 1024]; /* 64k bits */
+ dns_rdatatype_t covered;
+ dns_rdatatype_t maxcovered = 0;
+ bool first = true;
+ long n;
+
+ REQUIRE(type == dns_rdatatype_nxt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Next domain.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ memset(bm, 0, sizeof(bm));
+ do {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, true));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+ n = strtol(DNS_AS_STR(token), &e, 10);
+ if (e != DNS_AS_STR(token) && *e == '\0') {
+ covered = (dns_rdatatype_t)n;
+ } else if (dns_rdatatype_fromtext(&covered,
+ &token.value.as_textregion) ==
+ DNS_R_UNKNOWN)
+ {
+ RETTOK(DNS_R_UNKNOWN);
+ }
+ /*
+ * NXT is only specified for types 1..127.
+ */
+ if (covered < 1 || covered > 127) {
+ return (ISC_R_RANGE);
+ }
+ if (first || covered > maxcovered) {
+ maxcovered = covered;
+ }
+ first = false;
+ bm[covered / 8] |= (0x80 >> (covered % 8));
+ } while (1);
+ isc_lex_ungettoken(lexer, &token);
+ if (first) {
+ return (ISC_R_SUCCESS);
+ }
+ n = (maxcovered + 8) / 8;
+ return (mem_tobuffer(target, bm, n));
+}
+
+static isc_result_t
+totext_nxt(ARGS_TOTEXT) {
+ isc_region_t sr;
+ unsigned int i, j;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ for (i = 0; i < sr.length; i++) {
+ if (sr.base[i] != 0) {
+ for (j = 0; j < 8; j++) {
+ if ((sr.base[i] & (0x80 >> j)) != 0) {
+ {
+ dns_rdatatype_t t = i * 8 + j;
+ RETERR(str_totext(" ", target));
+ if (dns_rdatatype_isknown(t)) {
+ RETERR(dns_rdatatype_totext(
+ t, target));
+ } else {
+ char buf[sizeof("6553"
+ "5")];
+ snprintf(buf,
+ sizeof(buf),
+ "%u", t);
+ RETERR(str_totext(
+ buf, target));
+ }
+ }
+ }
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_nxt(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_nxt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length > 0 && ((sr.base[0] & 0x80) != 0 || sr.length > 16 ||
+ sr.base[sr.length - 1] == 0))
+ {
+ return (DNS_R_BADBITMAP);
+ }
+ RETERR(mem_tobuffer(target, sr.base, sr.length));
+ isc_buffer_forward(source, sr.length);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_nxt(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_towire(&name, cctx, target));
+
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_nxt(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_nxt);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_nxt(ARGS_FROMSTRUCT) {
+ dns_rdata_nxt_t *nxt = source;
+ isc_region_t region;
+
+ REQUIRE(type == dns_rdatatype_nxt);
+ REQUIRE(nxt != NULL);
+ REQUIRE(nxt->common.rdtype == type);
+ REQUIRE(nxt->common.rdclass == rdclass);
+ REQUIRE(nxt->typebits != NULL || nxt->len == 0);
+ if (nxt->typebits != NULL && (nxt->typebits[0] & 0x80) == 0) {
+ REQUIRE(nxt->len <= 16);
+ REQUIRE(nxt->typebits[nxt->len - 1] != 0);
+ }
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_name_toregion(&nxt->next, &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);
+ RETERR(name_duporclone(&name, mctx, &nxt->next));
+
+ nxt->len = region.length;
+ nxt->typebits = mem_maybedup(mctx, region.base, region.length);
+ if (nxt->typebits == NULL) {
+ goto cleanup;
+ }
+
+ nxt->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&nxt->next, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_nxt(ARGS_FREESTRUCT) {
+ dns_rdata_nxt_t *nxt = source;
+
+ REQUIRE(nxt != NULL);
+ REQUIRE(nxt->common.rdtype == dns_rdatatype_nxt);
+
+ if (nxt->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&nxt->next, nxt->mctx);
+ if (nxt->typebits != NULL) {
+ isc_mem_free(nxt->mctx, nxt->typebits);
+ }
+ nxt->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_nxt(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_nxt(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ result = dns_name_digest(&name, digest, arg);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_region_consume(&r, name_length(&name));
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_nxt(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nxt);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_nxt(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nxt);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_nxt(ARGS_COMPARE) {
+ return (compare_nxt(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_NXT_30_C */
diff --git a/lib/dns/rdata/generic/nxt_30.h b/lib/dns/rdata/generic/nxt_30.h
new file mode 100644
index 0000000..a7cae69
--- /dev/null
+++ b/lib/dns/rdata/generic/nxt_30.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_NXT_30_H
+#define GENERIC_NXT_30_H 1
+
+/*!
+ * \brief RFC2535 */
+
+typedef struct dns_rdata_nxt {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t next;
+ unsigned char *typebits;
+ uint16_t len;
+} dns_rdata_nxt_t;
+
+#endif /* GENERIC_NXT_30_H */
diff --git a/lib/dns/rdata/generic/openpgpkey_61.c b/lib/dns/rdata/generic/openpgpkey_61.c
new file mode 100644
index 0000000..719c081
--- /dev/null
+++ b/lib/dns/rdata/generic/openpgpkey_61.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_OPENPGPKEY_61_C
+#define RDATA_GENERIC_OPENPGPKEY_61_C
+
+#define RRTYPE_OPENPGPKEY_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_openpgpkey(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_openpgpkey);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+ UNUSED(options);
+ UNUSED(origin);
+
+ /*
+ * Keyring.
+ */
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_openpgpkey(ARGS_TOTEXT) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Keyring
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext("( ", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) {
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ } else {
+ RETERR(str_totext("[omitted]", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_openpgpkey(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_openpgpkey);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ /*
+ * Keyring.
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 1) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_openpgpkey(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_openpgpkey(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_openpgpkey);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_openpgpkey(ARGS_FROMSTRUCT) {
+ dns_rdata_openpgpkey_t *sig = source;
+
+ REQUIRE(type == dns_rdatatype_openpgpkey);
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == type);
+ REQUIRE(sig->common.rdclass == rdclass);
+ REQUIRE(sig->keyring != NULL && sig->length != 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Keyring.
+ */
+ return (mem_tobuffer(target, sig->keyring, sig->length));
+}
+
+static isc_result_t
+tostruct_openpgpkey(ARGS_TOSTRUCT) {
+ isc_region_t sr;
+ dns_rdata_openpgpkey_t *sig = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+ REQUIRE(sig != NULL);
+ REQUIRE(rdata->length != 0);
+
+ sig->common.rdclass = rdata->rdclass;
+ sig->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&sig->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Keyring.
+ */
+ sig->length = sr.length;
+ sig->keyring = mem_maybedup(mctx, sr.base, sig->length);
+ if (sig->keyring == NULL) {
+ goto cleanup;
+ }
+
+ sig->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_openpgpkey(ARGS_FREESTRUCT) {
+ dns_rdata_openpgpkey_t *sig = (dns_rdata_openpgpkey_t *)source;
+
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == dns_rdatatype_openpgpkey);
+
+ if (sig->mctx == NULL) {
+ return;
+ }
+
+ if (sig->keyring != NULL) {
+ isc_mem_free(sig->mctx, sig->keyring);
+ }
+ sig->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_openpgpkey(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_openpgpkey(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_openpgpkey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_openpgpkey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_openpgpkey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_openpgpkey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_openpgpkey(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_openpgpkey);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+
+ return (isc_region_compare(&r1, &r2));
+}
+
+#endif /* RDATA_GENERIC_OPENPGPKEY_61_C */
diff --git a/lib/dns/rdata/generic/openpgpkey_61.h b/lib/dns/rdata/generic/openpgpkey_61.h
new file mode 100644
index 0000000..b78923b
--- /dev/null
+++ b/lib/dns/rdata/generic/openpgpkey_61.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_OPENPGPKEY_61_H
+#define GENERIC_OPENPGPKEY_61_H 1
+
+typedef struct dns_rdata_openpgpkey {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t length;
+ unsigned char *keyring;
+} dns_rdata_openpgpkey_t;
+
+#endif /* GENERIC_OPENPGPKEY_61_H */
diff --git a/lib/dns/rdata/generic/opt_41.c b/lib/dns/rdata/generic/opt_41.c
new file mode 100644
index 0000000..8aa7b52
--- /dev/null
+++ b/lib/dns/rdata/generic/opt_41.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2671 */
+
+#ifndef RDATA_GENERIC_OPT_41_C
+#define RDATA_GENERIC_OPT_41_C
+
+#define RRTYPE_OPT_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_SINGLETON | DNS_RDATATYPEATTR_META | \
+ DNS_RDATATYPEATTR_NOTQUESTION)
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_opt(ARGS_DIGEST) {
+ /*
+ * OPT records are not digested.
+ */
+
+ REQUIRE(rdata->type == dns_rdatatype_opt);
+
+ UNUSED(rdata);
+ UNUSED(digest);
+ UNUSED(arg);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static bool
+checkowner_opt(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_opt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (dns_name_equal(name, dns_rootname));
+}
+
+static bool
+checknames_opt(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_opt);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_opt(ARGS_COMPARE) {
+ return (compare_opt(rdata1, rdata2));
+}
+
+isc_result_t
+dns_rdata_opt_first(dns_rdata_opt_t *opt) {
+ REQUIRE(opt != NULL);
+ REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
+ REQUIRE(opt->options != NULL || opt->length == 0);
+
+ if (opt->length == 0) {
+ return (ISC_R_NOMORE);
+ }
+
+ opt->offset = 0;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdata_opt_next(dns_rdata_opt_t *opt) {
+ isc_region_t r;
+ uint16_t length;
+
+ REQUIRE(opt != NULL);
+ REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
+ REQUIRE(opt->options != NULL && opt->length != 0);
+ REQUIRE(opt->offset < opt->length);
+
+ INSIST(opt->offset + 4 <= opt->length);
+ r.base = opt->options + opt->offset + 2;
+ r.length = opt->length - opt->offset - 2;
+ length = uint16_fromregion(&r);
+ INSIST(opt->offset + 4 + length <= opt->length);
+ opt->offset = opt->offset + 4 + length;
+ if (opt->offset == opt->length) {
+ return (ISC_R_NOMORE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdata_opt_current(dns_rdata_opt_t *opt, dns_rdata_opt_opcode_t *opcode) {
+ isc_region_t r;
+
+ REQUIRE(opt != NULL);
+ REQUIRE(opcode != NULL);
+ REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
+ REQUIRE(opt->options != NULL);
+ REQUIRE(opt->offset < opt->length);
+
+ INSIST(opt->offset + 4 <= opt->length);
+ r.base = opt->options + opt->offset;
+ r.length = opt->length - opt->offset;
+
+ opcode->opcode = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+ opcode->length = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+ opcode->data = r.base;
+ INSIST(opt->offset + 4 + opcode->length <= opt->length);
+
+ return (ISC_R_SUCCESS);
+}
+
+#endif /* RDATA_GENERIC_OPT_41_C */
diff --git a/lib/dns/rdata/generic/opt_41.h b/lib/dns/rdata/generic/opt_41.h
new file mode 100644
index 0000000..a5e4c0d
--- /dev/null
+++ b/lib/dns/rdata/generic/opt_41.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_OPT_41_H
+#define GENERIC_OPT_41_H 1
+
+/*!
+ * \brief Per RFC2671 */
+
+typedef struct dns_rdata_opt_opcode {
+ uint16_t opcode;
+ uint16_t length;
+ unsigned char *data;
+} dns_rdata_opt_opcode_t;
+
+typedef struct dns_rdata_opt {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *options;
+ uint16_t length;
+ /* private */
+ uint16_t offset;
+} dns_rdata_opt_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
+
+isc_result_t
+dns_rdata_opt_first(dns_rdata_opt_t *);
+
+isc_result_t
+dns_rdata_opt_next(dns_rdata_opt_t *);
+
+isc_result_t
+dns_rdata_opt_current(dns_rdata_opt_t *, dns_rdata_opt_opcode_t *);
+
+#endif /* GENERIC_OPT_41_H */
diff --git a/lib/dns/rdata/generic/proforma.c b/lib/dns/rdata/generic/proforma.c
new file mode 100644
index 0000000..9d97699
--- /dev/null
+++ b/lib/dns/rdata/generic/proforma.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_ #_ #_C
+#define RDATA_GENERIC_ #_ #_C
+
+#define RRTYPE_ #_ATTRIBUTES(0)
+
+static isc_result_t fromtext_ #(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdclass == #);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t totext_ #(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+ REQUIRE(rdata->length != 0); /* XXX */
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t fromwire_ #(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdclass == #);
+
+ /* NONE or GLOBAL14 */
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t towire_ #(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+ REQUIRE(rdata->length != 0); /* XXX */
+
+ /* NONE or GLOBAL14 */
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static int compare_ #(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == dns_rdatatype_proforma.crdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata1->rdclass == #);
+ REQUIRE(rdata1->length != 0); /* XXX */
+ REQUIRE(rdata2->length != 0); /* XXX */
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t fromstruct_ #(ARGS_FROMSTRUCT) {
+ dns_rdata_ #_t *# = source;
+
+ REQUIRE(type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdclass == #);
+ REQUIRE(# != NULL);
+ REQUIRE(#->common.rdtype == dns_rdatatype_proforma.ctype);
+ REQUIRE(#->common.rdclass == rdclass);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t tostruct_ #(ARGS_TOSTRUCT) {
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+ REQUIRE(rdata->length != 0); /* XXX */
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static void freestruct_ #(ARGS_FREESTRUCT) {
+ dns_rdata_ #_t *# = source;
+
+ REQUIRE(# != NULL);
+ REQUIRE(#->common.rdtype == dns_rdatatype_proforma.c #);
+ REQUIRE(#->common.rdclass == #);
+}
+
+static isc_result_t additionaldata_ #(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+
+ (void)add;
+ (void)arg;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t digest_ #(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool checkowner_ #(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdclass == #);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool checknames_ #(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata->rdclass == #);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int casecompare_ #(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == dns_rdatatype_proforma.crdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_proforma.c #);
+ REQUIRE(rdata1->rdclass == #);
+ REQUIRE(rdata1->length != 0); /* XXX */
+ REQUIRE(rdata2->length != 0); /* XXX */
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+#endif /* RDATA_GENERIC_#_#_C */
diff --git a/lib/dns/rdata/generic/proforma.h b/lib/dns/rdata/generic/proforma.h
new file mode 100644
index 0000000..5610c06
--- /dev/null
+++ b/lib/dns/rdata/generic/proforma.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_PROFORMA_H
+#define GENERIC_PROFORMA_H 1
+
+typedef struct dns_rdata_ #{
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx; /* if required */
+ /* type & class specific elements */
+}
+dns_rdata_ #_t;
+
+#endif /* GENERIC_PROFORMA_H */
diff --git a/lib/dns/rdata/generic/ptr_12.c b/lib/dns/rdata/generic/ptr_12.c
new file mode 100644
index 0000000..5d39300
--- /dev/null
+++ b/lib/dns/rdata/generic/ptr_12.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_PTR_12_C
+#define RDATA_GENERIC_PTR_12_C
+
+#define RRTYPE_PTR_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_ptr(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_ptr);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ if (rdclass == dns_rdataclass_in &&
+ (options & DNS_RDATA_CHECKNAMES) != 0 &&
+ (options & DNS_RDATA_CHECKREVERSE) != 0)
+ {
+ bool ok;
+ ok = dns_name_ishostname(&name, false);
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_ptr(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_ptr);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &ptr->ptr));
+ ptr->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_ptr(ARGS_FREESTRUCT) {
+ dns_rdata_ptr_t *ptr = source;
+
+ REQUIRE(ptr != NULL);
+ REQUIRE(ptr->common.rdtype == dns_rdatatype_ptr);
+
+ if (ptr->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&ptr->ptr, ptr->mctx);
+ ptr->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_ptr(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_ptr);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_ptr(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_ptr);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_ptr(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ptr);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA";
+static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 };
+static const dns_name_t ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data,
+ ip6_arpa_offsets);
+
+static unsigned char ip6_int_data[] = "\003IP6\003INT";
+static unsigned char ip6_int_offsets[] = { 0, 4, 8 };
+static const dns_name_t ip6_int = DNS_NAME_INITABSOLUTE(ip6_int_data,
+ ip6_int_offsets);
+
+static unsigned char in_addr_arpa_data[] = "\007IN-ADDR\004ARPA";
+static unsigned char in_addr_arpa_offsets[] = { 0, 8, 13 };
+static const dns_name_t in_addr_arpa =
+ DNS_NAME_INITABSOLUTE(in_addr_arpa_data, in_addr_arpa_offsets);
+
+static bool
+checknames_ptr(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_ptr);
+
+ if (rdata->rdclass != dns_rdataclass_in) {
+ return (true);
+ }
+
+ if (dns_name_isdnssd(owner)) {
+ return (true);
+ }
+
+ if (dns_name_issubdomain(owner, &in_addr_arpa) ||
+ dns_name_issubdomain(owner, &ip6_arpa) ||
+ dns_name_issubdomain(owner, &ip6_int))
+ {
+ dns_rdata_toregion(rdata, &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..310a4ea
--- /dev/null
+++ b/lib/dns/rdata/generic/ptr_12.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_PTR_12_H
+#define GENERIC_PTR_12_H 1
+
+typedef struct dns_rdata_ptr {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t ptr;
+} dns_rdata_ptr_t;
+
+#endif /* GENERIC_PTR_12_H */
diff --git a/lib/dns/rdata/generic/rkey_57.c b/lib/dns/rdata/generic/rkey_57.c
new file mode 100644
index 0000000..06d8495
--- /dev/null
+++ b/lib/dns/rdata/generic/rkey_57.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_RKEY_57_C
+#define RDATA_GENERIC_RKEY_57_C
+
+#define RRTYPE_RKEY_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_rkey(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_rkey);
+
+ return (generic_fromtext_key(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_rkey(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+
+ return (generic_totext_key(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_rkey(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_rkey);
+
+ return (generic_fromwire_key(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_rkey(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_rkey(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1 != NULL);
+ REQUIRE(rdata2 != NULL);
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_rkey);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_rkey(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_rkey);
+
+ return (generic_fromstruct_key(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_rkey(ARGS_TOSTRUCT) {
+ dns_rdata_rkey_t *rkey = target;
+
+ REQUIRE(rkey != NULL);
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+
+ rkey->common.rdclass = rdata->rdclass;
+ rkey->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&rkey->common, link);
+
+ return (generic_tostruct_key(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_rkey(ARGS_FREESTRUCT) {
+ dns_rdata_rkey_t *rkey = (dns_rdata_rkey_t *)source;
+
+ REQUIRE(rkey != NULL);
+ REQUIRE(rkey->common.rdtype == dns_rdatatype_rkey);
+
+ generic_freestruct_key(source);
+}
+
+static isc_result_t
+additionaldata_rkey(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_rkey(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_rkey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_rkey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_rkey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_rkey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_rkey(ARGS_COMPARE) {
+ /*
+ * Treat ALG 253 (private DNS) subtype name case sensitively.
+ */
+ return (compare_rkey(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_RKEY_57_C */
diff --git a/lib/dns/rdata/generic/rkey_57.h b/lib/dns/rdata/generic/rkey_57.h
new file mode 100644
index 0000000..f5665f6
--- /dev/null
+++ b/lib/dns/rdata/generic/rkey_57.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_RKEY_57_H
+#define GENERIC_RKEY_57_H 1
+
+typedef struct dns_rdata_key dns_rdata_rkey_t;
+
+#endif /* GENERIC_RKEY_57_H */
diff --git a/lib/dns/rdata/generic/rp_17.c b/lib/dns/rdata/generic/rp_17.c
new file mode 100644
index 0000000..3abe55d
--- /dev/null
+++ b/lib/dns/rdata/generic/rp_17.c
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1183 */
+
+#ifndef RDATA_GENERIC_RP_17_C
+#define RDATA_GENERIC_RP_17_C
+
+#define RRTYPE_RP_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_rp(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ int i;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_rp);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ for (i = 0; i < 2; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0 && i == 0) {
+ ok = dns_name_ismailbox(&name);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_rp(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t rmail;
+ dns_name_t email;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&rmail, NULL);
+ dns_name_init(&email, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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_result_t result;
+ isc_region_t region;
+ dns_rdata_rp_t *rp = target;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+ REQUIRE(rp != NULL);
+ REQUIRE(rdata->length != 0);
+
+ rp->common.rdclass = rdata->rdclass;
+ rp->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&rp->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&rp->mail, NULL);
+ RETERR(name_duporclone(&name, mctx, &rp->mail));
+ isc_region_consume(&region, name_length(&name));
+ dns_name_fromregion(&name, &region);
+ dns_name_init(&rp->text, NULL);
+ result = name_duporclone(&name, mctx, &rp->text);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ rp->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&rp->mail, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_rp(ARGS_FREESTRUCT) {
+ dns_rdata_rp_t *rp = source;
+
+ REQUIRE(rp != NULL);
+ REQUIRE(rp->common.rdtype == dns_rdatatype_rp);
+
+ if (rp->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&rp->mail, rp->mctx);
+ dns_name_free(&rp->text, rp->mctx);
+ rp->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_rp(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_rp(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+
+ dns_name_fromregion(&name, &r);
+ RETERR(dns_name_digest(&name, digest, arg));
+ isc_region_consume(&r, name_length(&name));
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_rp(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_rp);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_rp(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rp);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &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..824b576
--- /dev/null
+++ b/lib/dns/rdata/generic/rp_17.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_RP_17_H
+#define GENERIC_RP_17_H 1
+
+/*!
+ * \brief Per RFC1183 */
+
+typedef struct dns_rdata_rp {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t mail;
+ dns_name_t text;
+} dns_rdata_rp_t;
+
+#endif /* GENERIC_RP_17_H */
diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c
new file mode 100644
index 0000000..8e22030
--- /dev/null
+++ b/lib/dns/rdata/generic/rrsig_46.c
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2535 */
+
+#ifndef RDATA_GENERIC_RRSIG_46_C
+#define RDATA_GENERIC_RRSIG_46_C
+
+#define RRTYPE_RRSIG_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_DNSSEC | DNS_RDATATYPEATTR_ZONECUTAUTH | \
+ DNS_RDATATYPEATTR_ATCNAME)
+
+static isc_result_t
+fromtext_rrsig(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char c;
+ long i;
+ dns_rdatatype_t covered;
+ char *e;
+ isc_result_t result;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ uint32_t time_signed, time_expire;
+
+ REQUIRE(type == dns_rdatatype_rrsig);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Type covered.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ result = dns_rdatatype_fromtext(&covered, &token.value.as_textregion);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) {
+ i = strtol(DNS_AS_STR(token), &e, 10);
+ if (i < 0 || i > 65535) {
+ RETTOK(ISC_R_RANGE);
+ }
+ if (*e != 0) {
+ RETTOK(result);
+ }
+ covered = (dns_rdatatype_t)i;
+ }
+ RETERR(uint16_tobuffer(covered, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &c, 1));
+
+ /*
+ * Labels.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ c = (unsigned char)token.value.as_ulong;
+ RETERR(mem_tobuffer(target, &c, 1));
+
+ /*
+ * Original ttl.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signature expiration.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (strlen(DNS_AS_STR(token)) <= 10U && *DNS_AS_STR(token) != '-' &&
+ *DNS_AS_STR(token) != '+')
+ {
+ char *end;
+ unsigned long u;
+ uint64_t u64;
+
+ u64 = u = strtoul(DNS_AS_STR(token), &end, 10);
+ if (u == ULONG_MAX || *end != 0) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ if (u64 > 0xffffffffUL) {
+ RETTOK(ISC_R_RANGE);
+ }
+ time_expire = u;
+ } else {
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_expire));
+ }
+ RETERR(uint32_tobuffer(time_expire, target));
+
+ /*
+ * Time signed.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (strlen(DNS_AS_STR(token)) <= 10U && *DNS_AS_STR(token) != '-' &&
+ *DNS_AS_STR(token) != '+')
+ {
+ char *end;
+ unsigned long u;
+ uint64_t u64;
+
+ u64 = u = strtoul(DNS_AS_STR(token), &end, 10);
+ if (u == ULONG_MAX || *end != 0) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ if (u64 > 0xffffffffUL) {
+ RETTOK(ISC_R_RANGE);
+ }
+ time_signed = u;
+ } else {
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_signed));
+ }
+ RETERR(uint32_tobuffer(time_signed, target));
+
+ /*
+ * Key footprint.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signer.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ /*
+ * Sig.
+ */
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_rrsig(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("4294967295")]; /* Also TYPE65000. */
+ dns_rdatatype_t covered;
+ unsigned long ttl;
+ unsigned long when;
+ unsigned long exp;
+ unsigned long foot;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Type covered.
+ */
+ covered = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ /*
+ * XXXAG We should have something like dns_rdatatype_isknown()
+ * that does the right thing with type 0.
+ */
+ if (dns_rdatatype_isknown(covered) && covered != 0) {
+ RETERR(dns_rdatatype_totext(covered, target));
+ } else {
+ snprintf(buf, sizeof(buf), "TYPE%u", covered);
+ RETERR(str_totext(buf, target));
+ }
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Algorithm.
+ */
+ snprintf(buf, sizeof(buf), "%u", sr.base[0]);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Labels.
+ */
+ snprintf(buf, sizeof(buf), "%u", sr.base[0]);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Ttl.
+ */
+ ttl = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu", ttl);
+ RETERR(str_totext(buf, target));
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+
+ /*
+ * Sig exp.
+ */
+ exp = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(exp, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Time signed.
+ */
+ when = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(when, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Footprint.
+ */
+ foot = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu", foot);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_totext(&name, false, target));
+
+ /*
+ * Sig.
+ */
+ RETERR(str_totext(tctx->linebreak, target));
+ if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) {
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ } else {
+ RETERR(str_totext("[omitted]", target));
+ }
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_rrsig(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_rrsig);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ isc_buffer_activeregion(source, &sr);
+ /*
+ * type covered: 2
+ * algorithm: 1
+ * labels: 1
+ * original ttl: 4
+ * signature expiration: 4
+ * time signed: 4
+ * key footprint: 2
+ */
+ if (sr.length < 18) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, 18);
+ RETERR(mem_tobuffer(target, sr.base, 18));
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ /*
+ * Sig.
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 1) {
+ return (DNS_R_FORMERR);
+ }
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_rrsig(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_rdata_toregion(rdata, &sr);
+ /*
+ * type covered: 2
+ * algorithm: 1
+ * labels: 1
+ * original ttl: 4
+ * signature expiration: 4
+ * time signed: 4
+ * key footprint: 2
+ */
+ RETERR(mem_tobuffer(target, sr.base, 18));
+ isc_region_consume(&sr, 18);
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_towire(&name, cctx, target));
+
+ /*
+ * Signature.
+ */
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_rrsig(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_rrsig);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_rrsig(ARGS_FROMSTRUCT) {
+ dns_rdata_rrsig_t *sig = source;
+
+ REQUIRE(type == dns_rdatatype_rrsig);
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == type);
+ REQUIRE(sig->common.rdclass == rdclass);
+ REQUIRE(sig->signature != NULL || sig->siglen == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Type covered.
+ */
+ RETERR(uint16_tobuffer(sig->covered, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(uint8_tobuffer(sig->algorithm, target));
+
+ /*
+ * Labels.
+ */
+ RETERR(uint8_tobuffer(sig->labels, target));
+
+ /*
+ * Original TTL.
+ */
+ RETERR(uint32_tobuffer(sig->originalttl, target));
+
+ /*
+ * Expire time.
+ */
+ RETERR(uint32_tobuffer(sig->timeexpire, target));
+
+ /*
+ * Time signed.
+ */
+ RETERR(uint32_tobuffer(sig->timesigned, target));
+
+ /*
+ * Key ID.
+ */
+ RETERR(uint16_tobuffer(sig->keyid, target));
+
+ /*
+ * Signer name.
+ */
+ RETERR(name_tobuffer(&sig->signer, target));
+
+ /*
+ * Signature.
+ */
+ return (mem_tobuffer(target, sig->signature, sig->siglen));
+}
+
+static isc_result_t
+tostruct_rrsig(ARGS_TOSTRUCT) {
+ isc_region_t sr;
+ dns_rdata_rrsig_t *sig = target;
+ dns_name_t signer;
+
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+ REQUIRE(sig != NULL);
+ REQUIRE(rdata->length != 0);
+
+ sig->common.rdclass = rdata->rdclass;
+ sig->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&sig->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Type covered.
+ */
+ sig->covered = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Algorithm.
+ */
+ sig->algorithm = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Labels.
+ */
+ sig->labels = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Original TTL.
+ */
+ sig->originalttl = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Expire time.
+ */
+ sig->timeexpire = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Time signed.
+ */
+ sig->timesigned = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Key ID.
+ */
+ sig->keyid = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ dns_name_init(&signer, NULL);
+ dns_name_fromregion(&signer, &sr);
+ dns_name_init(&sig->signer, NULL);
+ RETERR(name_duporclone(&signer, mctx, &sig->signer));
+ isc_region_consume(&sr, name_length(&sig->signer));
+
+ /*
+ * Signature.
+ */
+ sig->siglen = sr.length;
+ sig->signature = mem_maybedup(mctx, sr.base, sig->siglen);
+ if (sig->signature == NULL) {
+ goto cleanup;
+ }
+
+ sig->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&sig->signer, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_rrsig(ARGS_FREESTRUCT) {
+ dns_rdata_rrsig_t *sig = (dns_rdata_rrsig_t *)source;
+
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == dns_rdatatype_rrsig);
+
+ if (sig->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&sig->signer, sig->mctx);
+ if (sig->signature != NULL) {
+ isc_mem_free(sig->mctx, sig->signature);
+ }
+ sig->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_rrsig(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_rrsig(ARGS_DIGEST) {
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+
+ UNUSED(rdata);
+ UNUSED(digest);
+ UNUSED(arg);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static dns_rdatatype_t
+covers_rrsig(dns_rdata_t *rdata) {
+ dns_rdatatype_t type;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+
+ dns_rdata_toregion(rdata, &r);
+ type = uint16_fromregion(&r);
+
+ return (type);
+}
+
+static bool
+checkowner_rrsig(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_rrsig);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_rrsig(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_rrsig);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_rrsig(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_rrsig);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+
+ INSIST(r1.length > 18);
+ INSIST(r2.length > 18);
+ r1.length = 18;
+ r2.length = 18;
+ order = isc_region_compare(&r1, &r2);
+ if (order != 0) {
+ return (order);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ isc_region_consume(&r1, 18);
+ isc_region_consume(&r2, 18);
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+
+ return (isc_region_compare(&r1, &r2));
+}
+
+#endif /* RDATA_GENERIC_RRSIG_46_C */
diff --git a/lib/dns/rdata/generic/rrsig_46.h b/lib/dns/rdata/generic/rrsig_46.h
new file mode 100644
index 0000000..3d4d7a5
--- /dev/null
+++ b/lib/dns/rdata/generic/rrsig_46.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_DNSSIG_46_H
+#define GENERIC_DNSSIG_46_H 1
+
+/*!
+ * \brief Per RFC2535 */
+typedef struct dns_rdata_rrsig {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_rdatatype_t covered;
+ dns_secalg_t algorithm;
+ uint8_t labels;
+ uint32_t originalttl;
+ uint32_t timeexpire;
+ uint32_t timesigned;
+ uint16_t keyid;
+ dns_name_t signer;
+ uint16_t siglen;
+ unsigned char *signature;
+} dns_rdata_rrsig_t;
+
+#endif /* GENERIC_DNSSIG_46_H */
diff --git a/lib/dns/rdata/generic/rt_21.c b/lib/dns/rdata/generic/rt_21.c
new file mode 100644
index 0000000..1396b4d
--- /dev/null
+++ b/lib/dns/rdata/generic/rt_21.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1183 */
+
+#ifndef RDATA_GENERIC_RT_21_C
+#define RDATA_GENERIC_RT_21_C
+
+#define RRTYPE_RT_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_rt(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_rt);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_rt(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_rt);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &rt->host));
+
+ rt->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_rt(ARGS_FREESTRUCT) {
+ dns_rdata_rt_t *rt = source;
+
+ REQUIRE(rt != NULL);
+ REQUIRE(rt->common.rdtype == dns_rdatatype_rt);
+
+ if (rt->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&rt->host, rt->mctx);
+ rt->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_rt(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_rt);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+
+ result = (add)(arg, &name, dns_rdatatype_x25);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = (add)(arg, &name, dns_rdatatype_isdn);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return ((add)(arg, &name, dns_rdatatype_a));
+}
+
+static isc_result_t
+digest_rt(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ isc_result_t result;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rt);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 2);
+ r1.length = 2;
+ result = (digest)(arg, &r1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_rt(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_rt);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_rt(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_rt);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &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..24a6529
--- /dev/null
+++ b/lib/dns/rdata/generic/rt_21.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_RT_21_H
+#define GENERIC_RT_21_H 1
+
+/*!
+ * \brief Per RFC1183 */
+
+typedef struct dns_rdata_rt {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t preference;
+ dns_name_t host;
+} dns_rdata_rt_t;
+
+#endif /* GENERIC_RT_21_H */
diff --git a/lib/dns/rdata/generic/sig_24.c b/lib/dns/rdata/generic/sig_24.c
new file mode 100644
index 0000000..8424992
--- /dev/null
+++ b/lib/dns/rdata/generic/sig_24.c
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2535 */
+
+#ifndef RDATA_GENERIC_SIG_24_C
+#define RDATA_GENERIC_SIG_24_C
+
+#define RRTYPE_SIG_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_sig(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char c;
+ long i;
+ dns_rdatatype_t covered;
+ char *e;
+ isc_result_t result;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ uint32_t time_signed, time_expire;
+
+ REQUIRE(type == dns_rdatatype_sig);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Type covered.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ result = dns_rdatatype_fromtext(&covered, &token.value.as_textregion);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) {
+ i = strtol(DNS_AS_STR(token), &e, 10);
+ if (i < 0 || i > 65535) {
+ RETTOK(ISC_R_RANGE);
+ }
+ if (*e != 0) {
+ RETTOK(result);
+ }
+ covered = (dns_rdatatype_t)i;
+ }
+ RETERR(uint16_tobuffer(covered, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &c, 1));
+
+ /*
+ * Labels.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ c = (unsigned char)token.value.as_ulong;
+ RETERR(mem_tobuffer(target, &c, 1));
+
+ /*
+ * Original ttl.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signature expiration.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_expire));
+ RETERR(uint32_tobuffer(time_expire, target));
+
+ /*
+ * Time signed.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_signed));
+ RETERR(uint32_tobuffer(time_signed, target));
+
+ /*
+ * Key footprint.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Signer.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ /*
+ * Sig.
+ */
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_sig(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("4294967295")];
+ dns_rdatatype_t covered;
+ unsigned long ttl;
+ unsigned long when;
+ unsigned long exp;
+ unsigned long foot;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Type covered.
+ */
+ covered = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ /*
+ * XXXAG We should have something like dns_rdatatype_isknown()
+ * that does the right thing with type 0.
+ */
+ if (dns_rdatatype_isknown(covered) && covered != 0) {
+ RETERR(dns_rdatatype_totext(covered, target));
+ } else {
+ snprintf(buf, sizeof(buf), "%u", covered);
+ RETERR(str_totext(buf, target));
+ }
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Algorithm.
+ */
+ snprintf(buf, sizeof(buf), "%u", sr.base[0]);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Labels.
+ */
+ snprintf(buf, sizeof(buf), "%u", sr.base[0]);
+ isc_region_consume(&sr, 1);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Ttl.
+ */
+ ttl = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu", ttl);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Sig exp.
+ */
+ exp = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(exp, target));
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+
+ /*
+ * Time signed.
+ */
+ when = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ RETERR(dns_time32_totext(when, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Footprint.
+ */
+ foot = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu", foot);
+ RETERR(str_totext(buf, target));
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ /*
+ * Sig.
+ */
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_sig(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_sig);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ isc_buffer_activeregion(source, &sr);
+ /*
+ * type covered: 2
+ * algorithm: 1
+ * labels: 1
+ * original ttl: 4
+ * signature expiration: 4
+ * time signed: 4
+ * key footprint: 2
+ */
+ if (sr.length < 18) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, 18);
+ RETERR(mem_tobuffer(target, sr.base, 18));
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ /*
+ * Sig.
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length == 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_sig(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ dns_rdata_toregion(rdata, &sr);
+ /*
+ * type covered: 2
+ * algorithm: 1
+ * labels: 1
+ * original ttl: 4
+ * signature expiration: 4
+ * time signed: 4
+ * key footprint: 2
+ */
+ RETERR(mem_tobuffer(target, sr.base, 18));
+ isc_region_consume(&sr, 18);
+
+ /*
+ * Signer.
+ */
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ isc_region_consume(&sr, name_length(&name));
+ RETERR(dns_name_towire(&name, cctx, target));
+
+ /*
+ * Signature.
+ */
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_sig(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_sig);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+
+ INSIST(r1.length > 18);
+ INSIST(r2.length > 18);
+ r1.length = 18;
+ r2.length = 18;
+ order = isc_region_compare(&r1, &r2);
+ if (order != 0) {
+ return (order);
+ }
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ isc_region_consume(&r1, 18);
+ isc_region_consume(&r2, 18);
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ order = dns_name_rdatacompare(&name1, &name2);
+ if (order != 0) {
+ return (order);
+ }
+
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_sig(ARGS_FROMSTRUCT) {
+ dns_rdata_sig_t *sig = source;
+
+ REQUIRE(type == dns_rdatatype_sig);
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == type);
+ REQUIRE(sig->common.rdclass == rdclass);
+ REQUIRE(sig->signature != NULL || sig->siglen == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Type covered.
+ */
+ RETERR(uint16_tobuffer(sig->covered, target));
+
+ /*
+ * Algorithm.
+ */
+ RETERR(uint8_tobuffer(sig->algorithm, target));
+
+ /*
+ * Labels.
+ */
+ RETERR(uint8_tobuffer(sig->labels, target));
+
+ /*
+ * Original TTL.
+ */
+ RETERR(uint32_tobuffer(sig->originalttl, target));
+
+ /*
+ * Expire time.
+ */
+ RETERR(uint32_tobuffer(sig->timeexpire, target));
+
+ /*
+ * Time signed.
+ */
+ RETERR(uint32_tobuffer(sig->timesigned, target));
+
+ /*
+ * Key ID.
+ */
+ RETERR(uint16_tobuffer(sig->keyid, target));
+
+ /*
+ * Signer name.
+ */
+ RETERR(name_tobuffer(&sig->signer, target));
+
+ /*
+ * Signature.
+ */
+ return (mem_tobuffer(target, sig->signature, sig->siglen));
+}
+
+static isc_result_t
+tostruct_sig(ARGS_TOSTRUCT) {
+ isc_region_t sr;
+ dns_rdata_sig_t *sig = target;
+ dns_name_t signer;
+
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+ REQUIRE(sig != NULL);
+ REQUIRE(rdata->length != 0);
+
+ sig->common.rdclass = rdata->rdclass;
+ sig->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&sig->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Type covered.
+ */
+ sig->covered = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Algorithm.
+ */
+ sig->algorithm = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Labels.
+ */
+ sig->labels = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+
+ /*
+ * Original TTL.
+ */
+ sig->originalttl = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Expire time.
+ */
+ sig->timeexpire = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Time signed.
+ */
+ sig->timesigned = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Key ID.
+ */
+ sig->keyid = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ dns_name_init(&signer, NULL);
+ dns_name_fromregion(&signer, &sr);
+ dns_name_init(&sig->signer, NULL);
+ RETERR(name_duporclone(&signer, mctx, &sig->signer));
+ isc_region_consume(&sr, name_length(&sig->signer));
+
+ /*
+ * Signature.
+ */
+ sig->siglen = sr.length;
+ sig->signature = mem_maybedup(mctx, sr.base, sig->siglen);
+ if (sig->signature == NULL) {
+ goto cleanup;
+ }
+
+ sig->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&sig->signer, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_sig(ARGS_FREESTRUCT) {
+ dns_rdata_sig_t *sig = (dns_rdata_sig_t *)source;
+
+ REQUIRE(sig != NULL);
+ REQUIRE(sig->common.rdtype == dns_rdatatype_sig);
+
+ if (sig->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&sig->signer, sig->mctx);
+ if (sig->signature != NULL) {
+ isc_mem_free(sig->mctx, sig->signature);
+ }
+ sig->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_sig(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_sig(ARGS_DIGEST) {
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+
+ UNUSED(rdata);
+ UNUSED(digest);
+ UNUSED(arg);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static dns_rdatatype_t
+covers_sig(dns_rdata_t *rdata) {
+ dns_rdatatype_t type;
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+
+ dns_rdata_toregion(rdata, &r);
+ type = uint16_fromregion(&r);
+
+ return (type);
+}
+
+static bool
+checkowner_sig(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_sig);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_sig(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_sig);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_sig(ARGS_COMPARE) {
+ return (compare_sig(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_SIG_24_C */
diff --git a/lib/dns/rdata/generic/sig_24.h b/lib/dns/rdata/generic/sig_24.h
new file mode 100644
index 0000000..5e5db8c
--- /dev/null
+++ b/lib/dns/rdata/generic/sig_24.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_SIG_24_H
+#define GENERIC_SIG_24_H 1
+
+/*!
+ * \brief Per RFC2535 */
+
+typedef struct dns_rdata_sig_t {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_rdatatype_t covered;
+ dns_secalg_t algorithm;
+ uint8_t labels;
+ uint32_t originalttl;
+ uint32_t timeexpire;
+ uint32_t timesigned;
+ uint16_t keyid;
+ dns_name_t signer;
+ uint16_t siglen;
+ unsigned char *signature;
+} dns_rdata_sig_t;
+
+#endif /* GENERIC_SIG_24_H */
diff --git a/lib/dns/rdata/generic/sink_40.c b/lib/dns/rdata/generic/sink_40.c
new file mode 100644
index 0000000..b4ad0b9
--- /dev/null
+++ b/lib/dns/rdata/generic/sink_40.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_SINK_40_C
+#define RDATA_GENERIC_SINK_40_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_sink(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_sink);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_sink(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_sink);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_sink(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_sink);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_sink(ARGS_COMPARE) {
+ return (compare_sink(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_SINK_40_C */
diff --git a/lib/dns/rdata/generic/sink_40.h b/lib/dns/rdata/generic/sink_40.h
new file mode 100644
index 0000000..b2956b7
--- /dev/null
+++ b/lib/dns/rdata/generic/sink_40.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_SINK_40_H
+#define GENERIC_SINK_40_H 1
+
+typedef struct dns_rdata_sink_t {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t meaning;
+ uint8_t coding;
+ uint8_t subcoding;
+ uint16_t datalen;
+ unsigned char *data;
+} dns_rdata_sink_t;
+
+#endif /* GENERIC_SINK_40_H */
diff --git a/lib/dns/rdata/generic/smimea_53.c b/lib/dns/rdata/generic/smimea_53.c
new file mode 100644
index 0000000..ecdfea3
--- /dev/null
+++ b/lib/dns/rdata/generic/smimea_53.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_SMIMEA_53_C
+#define RDATA_GENERIC_SMIMEA_53_C
+
+#define RRTYPE_SMIMEA_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_smimea(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_smimea);
+
+ return (generic_fromtext_tlsa(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_smimea(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+
+ return (generic_totext_tlsa(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_smimea(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_smimea);
+
+ return (generic_fromwire_tlsa(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_smimea(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_smimea(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_smimea);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_smimea(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_smimea);
+
+ return (generic_fromstruct_tlsa(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_smimea(ARGS_TOSTRUCT) {
+ dns_rdata_smimea_t *smimea = target;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+ REQUIRE(smimea != NULL);
+
+ smimea->common.rdclass = rdata->rdclass;
+ smimea->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&smimea->common, link);
+
+ return (generic_tostruct_tlsa(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_smimea(ARGS_FREESTRUCT) {
+ dns_rdata_smimea_t *smimea = source;
+
+ REQUIRE(smimea != NULL);
+ REQUIRE(smimea->common.rdtype == dns_rdatatype_smimea);
+
+ generic_freestruct_tlsa(source);
+}
+
+static isc_result_t
+additionaldata_smimea(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_smimea(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_smimea(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_smimea);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_smimea(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_smimea);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_smimea(ARGS_COMPARE) {
+ return (compare_smimea(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_SMIMEA_53_C */
diff --git a/lib/dns/rdata/generic/smimea_53.h b/lib/dns/rdata/generic/smimea_53.h
new file mode 100644
index 0000000..527f596
--- /dev/null
+++ b/lib/dns/rdata/generic/smimea_53.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_SMIMEA_53_H
+#define GENERIC_SMIMEA_53_H 1
+
+typedef struct dns_rdata_tlsa dns_rdata_smimea_t;
+
+#endif /* GENERIC_SMIMEA_53_H */
diff --git a/lib/dns/rdata/generic/soa_6.c b/lib/dns/rdata/generic/soa_6.c
new file mode 100644
index 0000000..70c9cd3
--- /dev/null
+++ b/lib/dns/rdata/generic/soa_6.c
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_SOA_6_C
+#define RDATA_GENERIC_SOA_6_C
+
+#define RRTYPE_SOA_ATTRIBUTES (DNS_RDATATYPEATTR_SINGLETON)
+
+static isc_result_t
+fromtext_soa(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ int i;
+ uint32_t n;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_soa);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ for (i = 0; i < 2; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ switch (i) {
+ case 0:
+ ok = dns_name_ishostname(&name, false);
+ break;
+ case 1:
+ ok = dns_name_ismailbox(&name);
+ break;
+ }
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ }
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ for (i = 0; i < 4; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+ RETTOK(dns_counter_fromtext(&token.value.as_textregion, &n));
+ RETERR(uint32_tobuffer(n, target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static const char *soa_fieldnames[5] = { "serial", "refresh", "retry", "expire",
+ "minimum" };
+
+static isc_result_t
+totext_soa(ARGS_TOTEXT) {
+ isc_region_t dregion;
+ dns_name_t mname;
+ dns_name_t rname;
+ dns_name_t prefix;
+ bool sub;
+ int i;
+ bool multiline;
+ bool comm;
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+ REQUIRE(rdata->length != 0);
+
+ multiline = ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0);
+ if (multiline) {
+ comm = ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0);
+ } else {
+ comm = false;
+ }
+
+ dns_name_init(&mname, NULL);
+ dns_name_init(&rname, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &dregion);
+
+ dns_name_fromregion(&mname, &dregion);
+ isc_region_consume(&dregion, name_length(&mname));
+
+ dns_name_fromregion(&rname, &dregion);
+ isc_region_consume(&dregion, name_length(&rname));
+
+ sub = name_prefix(&mname, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ RETERR(str_totext(" ", target));
+
+ sub = name_prefix(&rname, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ if (multiline) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+
+ for (i = 0; i < 5; i++) {
+ char buf[sizeof("0123456789 ; ")];
+ unsigned long num;
+ num = uint32_fromregion(&dregion);
+ isc_region_consume(&dregion, 4);
+ snprintf(buf, sizeof(buf), comm ? "%-10lu ; " : "%lu", num);
+ RETERR(str_totext(buf, target));
+ if (comm) {
+ RETERR(str_totext(soa_fieldnames[i], target));
+ /* Print times in week/day/hour/minute/second form */
+ if (i >= 1) {
+ RETERR(str_totext(" (", target));
+ RETERR(dns_ttl_totext(num, true, true, target));
+ RETERR(str_totext(")", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ } else if (i < 4) {
+ RETERR(str_totext(tctx->linebreak, target));
+ }
+ }
+
+ if (multiline) {
+ RETERR(str_totext(")", target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_soa(ARGS_FROMWIRE) {
+ dns_name_t mname;
+ dns_name_t rname;
+ isc_region_t sregion;
+ isc_region_t tregion;
+
+ REQUIRE(type == dns_rdatatype_soa);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&mname, NULL);
+ dns_name_init(&rname, NULL);
+
+ RETERR(dns_name_fromwire(&mname, source, dctx, options, target));
+ RETERR(dns_name_fromwire(&rname, source, dctx, options, target));
+
+ isc_buffer_activeregion(source, &sregion);
+ isc_buffer_availableregion(target, &tregion);
+
+ if (sregion.length < 20) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (tregion.length < 20) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tregion.base, sregion.base, 20);
+ isc_buffer_forward(source, 20);
+ isc_buffer_add(target, 20);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_soa(ARGS_TOWIRE) {
+ isc_region_t sregion;
+ isc_region_t tregion;
+ dns_name_t mname;
+ dns_name_t rname;
+ dns_offsets_t moffsets;
+ dns_offsets_t roffsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+
+ dns_name_init(&mname, moffsets);
+ dns_name_init(&rname, roffsets);
+
+ dns_rdata_toregion(rdata, &sregion);
+
+ dns_name_fromregion(&mname, &sregion);
+ isc_region_consume(&sregion, name_length(&mname));
+ RETERR(dns_name_towire(&mname, cctx, target));
+
+ dns_name_fromregion(&rname, &sregion);
+ isc_region_consume(&sregion, name_length(&rname));
+ RETERR(dns_name_towire(&rname, cctx, target));
+
+ isc_buffer_availableregion(target, &tregion);
+ if (tregion.length < 20) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tregion.base, sregion.base, 20);
+ isc_buffer_add(target, 20);
+ return (ISC_R_SUCCESS);
+}
+
+static int
+compare_soa(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_soa);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+
+ dns_rdata_toregion(rdata1, &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;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+ REQUIRE(soa != NULL);
+ REQUIRE(rdata->length != 0);
+
+ soa->common.rdclass = rdata->rdclass;
+ soa->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&soa->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_init(&soa->origin, NULL);
+ RETERR(name_duporclone(&name, mctx, &soa->origin));
+
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_init(&soa->contact, NULL);
+ result = name_duporclone(&name, mctx, &soa->contact);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ soa->serial = uint32_fromregion(&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);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&soa->origin, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_soa(ARGS_FREESTRUCT) {
+ dns_rdata_soa_t *soa = source;
+
+ REQUIRE(soa != NULL);
+ REQUIRE(soa->common.rdtype == dns_rdatatype_soa);
+
+ if (soa->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&soa->origin, soa->mctx);
+ dns_name_free(&soa->contact, soa->mctx);
+ soa->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_soa(ARGS_ADDLDATA) {
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_soa(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+
+ dns_rdata_toregion(rdata, &r);
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ RETERR(dns_name_digest(&name, digest, arg));
+ isc_region_consume(&r, name_length(&name));
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+ RETERR(dns_name_digest(&name, digest, arg));
+ isc_region_consume(&r, name_length(&name));
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_soa(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_soa);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_soa(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_soa);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &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..ba5f000
--- /dev/null
+++ b/lib/dns/rdata/generic/soa_6.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_SOA_6_H
+#define GENERIC_SOA_6_H 1
+
+typedef struct dns_rdata_soa {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t origin;
+ dns_name_t contact;
+ uint32_t serial; /*%< host order */
+ uint32_t refresh; /*%< host order */
+ uint32_t retry; /*%< host order */
+ uint32_t expire; /*%< host order */
+ uint32_t minimum; /*%< host order */
+} dns_rdata_soa_t;
+
+#endif /* GENERIC_SOA_6_H */
diff --git a/lib/dns/rdata/generic/spf_99.c b/lib/dns/rdata/generic/spf_99.c
new file mode 100644
index 0000000..1df3e75
--- /dev/null
+++ b/lib/dns/rdata/generic/spf_99.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_SPF_99_C
+#define RDATA_GENERIC_SPF_99_C
+
+#define RRTYPE_SPF_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_spf(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_spf);
+
+ return (generic_fromtext_txt(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_spf(ARGS_TOTEXT) {
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ return (generic_totext_txt(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_spf(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_spf);
+
+ return (generic_fromwire_txt(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_spf(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ UNUSED(cctx);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_spf(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_spf);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_spf(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_spf);
+
+ return (generic_fromstruct_txt(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_spf(ARGS_TOSTRUCT) {
+ dns_rdata_spf_t *spf = target;
+
+ REQUIRE(spf != NULL);
+ REQUIRE(rdata != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ spf->common.rdclass = rdata->rdclass;
+ spf->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&spf->common, link);
+
+ return (generic_tostruct_txt(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_spf(ARGS_FREESTRUCT) {
+ dns_rdata_spf_t *spf = source;
+
+ REQUIRE(spf != NULL);
+ REQUIRE(spf->common.rdtype == dns_rdatatype_spf);
+
+ generic_freestruct_txt(source);
+}
+
+static isc_result_t
+additionaldata_spf(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_spf(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_spf(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_spf);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_spf(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_spf);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_spf(ARGS_COMPARE) {
+ return (compare_spf(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_SPF_99_C */
diff --git a/lib/dns/rdata/generic/spf_99.h b/lib/dns/rdata/generic/spf_99.h
new file mode 100644
index 0000000..1c98259
--- /dev/null
+++ b/lib/dns/rdata/generic/spf_99.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_SPF_99_H
+#define GENERIC_SPF_99_H 1
+
+typedef struct dns_rdata_spf_string {
+ uint8_t length;
+ unsigned char *data;
+} dns_rdata_spf_string_t;
+
+typedef struct dns_rdata_spf {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *txt;
+ uint16_t txt_len;
+ /* private */
+ uint16_t offset;
+} dns_rdata_spf_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
+#endif /* GENERIC_SPF_99_H */
diff --git a/lib/dns/rdata/generic/sshfp_44.c b/lib/dns/rdata/generic/sshfp_44.c
new file mode 100644
index 0000000..9ca97b4
--- /dev/null
+++ b/lib/dns/rdata/generic/sshfp_44.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 4255 */
+
+#ifndef RDATA_GENERIC_SSHFP_44_C
+#define RDATA_GENERIC_SSHFP_44_C
+
+#define RRTYPE_SSHFP_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_sshfp(ARGS_FROMTEXT) {
+ isc_token_t token;
+ int len = -1;
+
+ REQUIRE(type == dns_rdatatype_sshfp);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Digest type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Enforce known digest lengths.
+ */
+ switch (token.value.as_ulong) {
+ case 1:
+ len = ISC_SHA1_DIGESTLENGTH;
+ break;
+ case 2:
+ len = ISC_SHA256_DIGESTLENGTH;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Digest.
+ */
+ return (isc_hex_tobuffer(lexer, target, len));
+}
+
+static isc_result_t
+totext_sshfp(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("64000 ")];
+ unsigned int n;
+
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Algorithm.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Digest type.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u", n);
+ RETERR(str_totext(buf, target));
+
+ if (sr.length == 0U) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Digest.
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_hex_totext(&sr, 0, "", target));
+ } else {
+ RETERR(isc_hex_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_sshfp(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_sshfp);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ if ((sr.base[1] == 1 && sr.length != ISC_SHA1_DIGESTLENGTH + 2) ||
+ (sr.base[1] == 2 && sr.length != ISC_SHA256_DIGESTLENGTH + 2))
+ {
+ return (DNS_R_FORMERR);
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_sshfp(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_sshfp(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_sshfp);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_sshfp(ARGS_FROMSTRUCT) {
+ dns_rdata_sshfp_t *sshfp = source;
+
+ REQUIRE(type == dns_rdatatype_sshfp);
+ REQUIRE(sshfp != NULL);
+ REQUIRE(sshfp->common.rdtype == type);
+ REQUIRE(sshfp->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(sshfp->algorithm, target));
+ RETERR(uint8_tobuffer(sshfp->digest_type, target));
+
+ return (mem_tobuffer(target, sshfp->digest, sshfp->length));
+}
+
+static isc_result_t
+tostruct_sshfp(ARGS_TOSTRUCT) {
+ dns_rdata_sshfp_t *sshfp = target;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+ REQUIRE(sshfp != NULL);
+ REQUIRE(rdata->length != 0);
+
+ sshfp->common.rdclass = rdata->rdclass;
+ sshfp->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&sshfp->common, link);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_sshfp(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_sshfp(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_sshfp);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_sshfp(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_sshfp);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_sshfp(ARGS_COMPARE) {
+ return (compare_sshfp(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_SSHFP_44_C */
diff --git a/lib/dns/rdata/generic/sshfp_44.h b/lib/dns/rdata/generic/sshfp_44.h
new file mode 100644
index 0000000..61d5a00
--- /dev/null
+++ b/lib/dns/rdata/generic/sshfp_44.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*!
+ * \brief Per RFC 4255 */
+
+#ifndef GENERIC_SSHFP_44_H
+#define GENERIC_SSHFP_44_H 1
+
+typedef struct dns_rdata_sshfp {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t algorithm;
+ uint8_t digest_type;
+ uint16_t length;
+ unsigned char *digest;
+} dns_rdata_sshfp_t;
+
+#endif /* GENERIC_SSHFP_44_H */
diff --git a/lib/dns/rdata/generic/ta_32768.c b/lib/dns/rdata/generic/ta_32768.c
new file mode 100644
index 0000000..eeab50b
--- /dev/null
+++ b/lib/dns/rdata/generic/ta_32768.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* http://www.watson.org/~weiler/INI1999-19.pdf */
+
+#ifndef RDATA_GENERIC_TA_32768_C
+#define RDATA_GENERIC_TA_32768_C
+
+#define RRTYPE_TA_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_ta(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_ta);
+
+ return (generic_fromtext_ds(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_ta(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+
+ return (generic_totext_ds(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_ta(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_ta);
+
+ return (generic_fromwire_ds(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_ta(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_ta(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_ta);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_ta(ARGS_FROMSTRUCT) {
+ REQUIRE(type == dns_rdatatype_ta);
+
+ return (generic_fromstruct_ds(CALL_FROMSTRUCT));
+}
+
+static isc_result_t
+tostruct_ta(ARGS_TOSTRUCT) {
+ dns_rdata_ds_t *ds = target;
+
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+ REQUIRE(ds != NULL);
+
+ /*
+ * Checked by generic_tostruct_ds().
+ */
+ ds->common.rdclass = rdata->rdclass;
+ ds->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&ds->common, link);
+
+ return (generic_tostruct_ds(CALL_TOSTRUCT));
+}
+
+static void
+freestruct_ta(ARGS_FREESTRUCT) {
+ dns_rdata_ta_t *ds = source;
+
+ REQUIRE(ds != NULL);
+ REQUIRE(ds->common.rdtype == dns_rdatatype_ta);
+
+ if (ds->mctx == NULL) {
+ return;
+ }
+
+ if (ds->digest != NULL) {
+ isc_mem_free(ds->mctx, ds->digest);
+ }
+ ds->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_ta(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_ta(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_ta(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_ta);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_ta(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_ta);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_ta(ARGS_COMPARE) {
+ return (compare_ta(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_TA_32768_C */
diff --git a/lib/dns/rdata/generic/ta_32768.h b/lib/dns/rdata/generic/ta_32768.h
new file mode 100644
index 0000000..7ea6e40
--- /dev/null
+++ b/lib/dns/rdata/generic/ta_32768.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_TA_32768_H
+#define GENERIC_TA_32768_H 1
+
+/*
+ * TA records are identical to DS records.
+ */
+typedef struct dns_rdata_ds dns_rdata_ta_t;
+
+#endif /* GENERIC_TA_32768_H */
diff --git a/lib/dns/rdata/generic/talink_58.c b/lib/dns/rdata/generic/talink_58.c
new file mode 100644
index 0000000..4460ffb
--- /dev/null
+++ b/lib/dns/rdata/generic/talink_58.c
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_TALINK_58_C
+#define RDATA_GENERIC_TALINK_58_C
+
+#define RRTYPE_TALINK_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_talink(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ int i;
+
+ REQUIRE(type == dns_rdatatype_talink);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ for (i = 0; i < 2; i++) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options,
+ target));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_talink(ARGS_TOTEXT) {
+ isc_region_t dregion;
+ dns_name_t prev;
+ dns_name_t next;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&prev, NULL);
+ dns_name_init(&next, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &dregion);
+
+ dns_name_fromregion(&prev, &dregion);
+ isc_region_consume(&dregion, name_length(&prev));
+
+ dns_name_fromregion(&next, &dregion);
+ isc_region_consume(&dregion, name_length(&next));
+
+ sub = name_prefix(&prev, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+
+ RETERR(str_totext(" ", target));
+
+ sub = name_prefix(&next, tctx->origin, &prefix);
+ return (dns_name_totext(&prefix, sub, target));
+}
+
+static isc_result_t
+fromwire_talink(ARGS_FROMWIRE) {
+ dns_name_t prev;
+ dns_name_t next;
+
+ REQUIRE(type == dns_rdatatype_talink);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&prev, NULL);
+ dns_name_init(&next, NULL);
+
+ RETERR(dns_name_fromwire(&prev, source, dctx, options, target));
+ return (dns_name_fromwire(&next, source, dctx, options, target));
+}
+
+static isc_result_t
+towire_talink(ARGS_TOWIRE) {
+ isc_region_t sregion;
+ dns_name_t prev;
+ dns_name_t next;
+ dns_offsets_t moffsets;
+ dns_offsets_t roffsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+
+ dns_name_init(&prev, moffsets);
+ dns_name_init(&next, roffsets);
+
+ dns_rdata_toregion(rdata, &sregion);
+
+ dns_name_fromregion(&prev, &sregion);
+ isc_region_consume(&sregion, name_length(&prev));
+ RETERR(dns_name_towire(&prev, cctx, target));
+
+ dns_name_fromregion(&next, &sregion);
+ isc_region_consume(&sregion, name_length(&next));
+ return (dns_name_towire(&next, cctx, target));
+}
+
+static int
+compare_talink(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_talink);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &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;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+ REQUIRE(talink != NULL);
+ REQUIRE(rdata->length != 0);
+
+ talink->common.rdclass = rdata->rdclass;
+ talink->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&talink->common, link);
+
+ dns_rdata_toregion(rdata, &region);
+
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_init(&talink->prev, NULL);
+ RETERR(name_duporclone(&name, mctx, &talink->prev));
+
+ dns_name_fromregion(&name, &region);
+ isc_region_consume(&region, name_length(&name));
+ dns_name_init(&talink->next, NULL);
+ result = name_duporclone(&name, mctx, &talink->next);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ talink->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&talink->prev, mctx);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_talink(ARGS_FREESTRUCT) {
+ dns_rdata_talink_t *talink = source;
+
+ REQUIRE(talink != NULL);
+ REQUIRE(talink->common.rdtype == dns_rdatatype_talink);
+
+ if (talink->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&talink->prev, talink->mctx);
+ dns_name_free(&talink->next, talink->mctx);
+ talink->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_talink(ARGS_ADDLDATA) {
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_talink(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+
+ dns_rdata_toregion(rdata, &r);
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_talink(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_talink);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_talink(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_talink);
+
+ UNUSED(bad);
+ UNUSED(owner);
+
+ return (true);
+}
+
+static int
+casecompare_talink(ARGS_COMPARE) {
+ return (compare_talink(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_TALINK_58_C */
diff --git a/lib/dns/rdata/generic/talink_58.h b/lib/dns/rdata/generic/talink_58.h
new file mode 100644
index 0000000..08ea600
--- /dev/null
+++ b/lib/dns/rdata/generic/talink_58.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * http://www.iana.org/assignments/dns-parameters/TALINK/talink-completed-template
+ */
+
+#ifndef GENERIC_TALINK_58_H
+#define GENERIC_TALINK_58_H 1
+
+typedef struct dns_rdata_talink {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t prev;
+ dns_name_t next;
+} dns_rdata_talink_t;
+
+#endif /* GENERIC_TALINK_58_H */
diff --git a/lib/dns/rdata/generic/tkey_249.c b/lib/dns/rdata/generic/tkey_249.c
new file mode 100644
index 0000000..7d3ea0e
--- /dev/null
+++ b/lib/dns/rdata/generic/tkey_249.c
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsext-tkey-01.txt */
+
+#ifndef RDATA_GENERIC_TKEY_249_C
+#define RDATA_GENERIC_TKEY_249_C
+
+#define RRTYPE_TKEY_ATTRIBUTES (DNS_RDATATYPEATTR_META)
+
+static isc_result_t
+fromtext_tkey(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_rcode_t rcode;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ long i;
+ char *e;
+
+ REQUIRE(type == dns_rdatatype_tkey);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Algorithm.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ /*
+ * Inception.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Expiration.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Mode.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Error.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ if (dns_tsigrcode_fromtext(&rcode, &token.value.as_textregion) !=
+ ISC_R_SUCCESS)
+ {
+ i = strtol(DNS_AS_STR(token), &e, 10);
+ if (*e != 0) {
+ RETTOK(DNS_R_UNKNOWN);
+ }
+ if (i < 0 || i > 0xffff) {
+ RETTOK(ISC_R_RANGE);
+ }
+ rcode = (dns_rcode_t)i;
+ }
+ RETERR(uint16_tobuffer(rcode, target));
+
+ /*
+ * Key Size.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Key Data.
+ */
+ RETERR(isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong));
+
+ /*
+ * Other Size.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Other Data.
+ */
+ return (isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong));
+}
+
+static isc_result_t
+totext_tkey(ARGS_TOTEXT) {
+ isc_region_t sr, dr;
+ char buf[sizeof("4294967295 ")];
+ unsigned long n;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Algorithm.
+ */
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+ dns_name_fromregion(&name, &sr);
+ sub = name_prefix(&name, tctx->origin, &prefix);
+ RETERR(dns_name_totext(&prefix, sub, target));
+ RETERR(str_totext(" ", target));
+ isc_region_consume(&sr, name_length(&name));
+
+ /*
+ * Inception.
+ */
+ n = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Expiration.
+ */
+ n = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Mode.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Error.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ if (dns_tsigrcode_totext((dns_rcode_t)n, target) == ISC_R_SUCCESS) {
+ RETERR(str_totext(" ", target));
+ } else {
+ snprintf(buf, sizeof(buf), "%lu ", n);
+ RETERR(str_totext(buf, target));
+ }
+
+ /*
+ * Key Size.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Key Data.
+ */
+ REQUIRE(n <= sr.length);
+ dr = sr;
+ dr.length = n;
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&dr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&dr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" ) ", target));
+ } else {
+ RETERR(str_totext(" ", target));
+ }
+ isc_region_consume(&sr, n);
+
+ /*
+ * Other Size.
+ */
+ n = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ snprintf(buf, sizeof(buf), "%lu", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Other Data.
+ */
+ REQUIRE(n <= sr.length);
+ if (n != 0U) {
+ dr = sr;
+ dr.length = n;
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&dr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&dr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_tkey(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ unsigned long n;
+ dns_name_t name;
+
+ REQUIRE(type == dns_rdatatype_tkey);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE);
+
+ /*
+ * Algorithm.
+ */
+ dns_name_init(&name, NULL);
+ RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+
+ /*
+ * Inception: 4
+ * Expiration: 4
+ * Mode: 2
+ * Error: 2
+ */
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length < 12) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, 12));
+ isc_region_consume(&sr, 12);
+ isc_buffer_forward(source, 12);
+
+ /*
+ * Key Length + Key Data.
+ */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ n = uint16_fromregion(&sr);
+ if (sr.length < n + 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ RETERR(mem_tobuffer(target, sr.base, n + 2));
+ isc_region_consume(&sr, n + 2);
+ isc_buffer_forward(source, n + 2);
+
+ /*
+ * Other Length + Other Data.
+ */
+ if (sr.length < 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ n = uint16_fromregion(&sr);
+ if (sr.length < n + 2) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ isc_buffer_forward(source, n + 2);
+ return (mem_tobuffer(target, sr.base, n + 2));
+}
+
+static isc_result_t
+towire_tkey(ARGS_TOWIRE) {
+ isc_region_t sr;
+ dns_name_t name;
+ dns_offsets_t offsets;
+
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+ REQUIRE(rdata->length != 0);
+
+ dns_compress_setmethods(cctx, DNS_COMPRESS_NONE);
+ /*
+ * Algorithm.
+ */
+ dns_rdata_toregion(rdata, &sr);
+ dns_name_init(&name, offsets);
+ dns_name_fromregion(&name, &sr);
+ RETERR(dns_name_towire(&name, cctx, target));
+ isc_region_consume(&sr, name_length(&name));
+
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_tkey(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+ dns_name_t name1;
+ dns_name_t name2;
+ int order;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_tkey);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ /*
+ * Algorithm.
+ */
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ dns_name_init(&name1, NULL);
+ dns_name_init(&name2, NULL);
+ dns_name_fromregion(&name1, &r1);
+ dns_name_fromregion(&name2, &r2);
+ if ((order = dns_name_rdatacompare(&name1, &name2)) != 0) {
+ return (order);
+ }
+ isc_region_consume(&r1, name_length(&name1));
+ isc_region_consume(&r2, name_length(&name2));
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_tkey(ARGS_FROMSTRUCT) {
+ dns_rdata_tkey_t *tkey = source;
+
+ REQUIRE(type == dns_rdatatype_tkey);
+ REQUIRE(tkey != NULL);
+ REQUIRE(tkey->common.rdtype == type);
+ REQUIRE(tkey->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * Algorithm Name.
+ */
+ RETERR(name_tobuffer(&tkey->algorithm, target));
+
+ /*
+ * Inception: 32 bits.
+ */
+ RETERR(uint32_tobuffer(tkey->inception, target));
+
+ /*
+ * Expire: 32 bits.
+ */
+ RETERR(uint32_tobuffer(tkey->expire, target));
+
+ /*
+ * Mode: 16 bits.
+ */
+ RETERR(uint16_tobuffer(tkey->mode, target));
+
+ /*
+ * Error: 16 bits.
+ */
+ RETERR(uint16_tobuffer(tkey->error, target));
+
+ /*
+ * Key size: 16 bits.
+ */
+ RETERR(uint16_tobuffer(tkey->keylen, target));
+
+ /*
+ * Key.
+ */
+ RETERR(mem_tobuffer(target, tkey->key, tkey->keylen));
+
+ /*
+ * Other size: 16 bits.
+ */
+ RETERR(uint16_tobuffer(tkey->otherlen, target));
+
+ /*
+ * Other data.
+ */
+ return (mem_tobuffer(target, tkey->other, tkey->otherlen));
+}
+
+static isc_result_t
+tostruct_tkey(ARGS_TOSTRUCT) {
+ dns_rdata_tkey_t *tkey = target;
+ dns_name_t alg;
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+ REQUIRE(tkey != NULL);
+ REQUIRE(rdata->length != 0);
+
+ tkey->common.rdclass = rdata->rdclass;
+ tkey->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&tkey->common, link);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Algorithm Name.
+ */
+ dns_name_init(&alg, NULL);
+ dns_name_fromregion(&alg, &sr);
+ dns_name_init(&tkey->algorithm, NULL);
+ RETERR(name_duporclone(&alg, mctx, &tkey->algorithm));
+ isc_region_consume(&sr, name_length(&tkey->algorithm));
+
+ /*
+ * Inception.
+ */
+ tkey->inception = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Expire.
+ */
+ tkey->expire = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+
+ /*
+ * Mode.
+ */
+ tkey->mode = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Error.
+ */
+ tkey->error = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Key size.
+ */
+ tkey->keylen = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Key.
+ */
+ INSIST(tkey->keylen + 2U <= sr.length);
+ tkey->key = mem_maybedup(mctx, sr.base, tkey->keylen);
+ if (tkey->key == NULL) {
+ goto cleanup;
+ }
+ isc_region_consume(&sr, tkey->keylen);
+
+ /*
+ * Other size.
+ */
+ tkey->otherlen = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+
+ /*
+ * Other.
+ */
+ INSIST(tkey->otherlen <= sr.length);
+ tkey->other = mem_maybedup(mctx, sr.base, tkey->otherlen);
+ if (tkey->other == NULL) {
+ goto cleanup;
+ }
+
+ tkey->mctx = mctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (mctx != NULL) {
+ dns_name_free(&tkey->algorithm, mctx);
+ }
+ if (mctx != NULL && tkey->key != NULL) {
+ isc_mem_free(mctx, tkey->key);
+ }
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_tkey(ARGS_FREESTRUCT) {
+ dns_rdata_tkey_t *tkey = (dns_rdata_tkey_t *)source;
+
+ REQUIRE(tkey != NULL);
+
+ if (tkey->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&tkey->algorithm, tkey->mctx);
+ if (tkey->key != NULL) {
+ isc_mem_free(tkey->mctx, tkey->key);
+ }
+ if (tkey->other != NULL) {
+ isc_mem_free(tkey->mctx, tkey->other);
+ }
+ tkey->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_tkey(ARGS_ADDLDATA) {
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_tkey(ARGS_DIGEST) {
+ UNUSED(rdata);
+ UNUSED(digest);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static bool
+checkowner_tkey(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_tkey);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_tkey(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_tkey);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_tkey(ARGS_COMPARE) {
+ return (compare_tkey(rdata1, rdata2));
+}
+#endif /* RDATA_GENERIC_TKEY_249_C */
diff --git a/lib/dns/rdata/generic/tkey_249.h b/lib/dns/rdata/generic/tkey_249.h
new file mode 100644
index 0000000..eed3c68
--- /dev/null
+++ b/lib/dns/rdata/generic/tkey_249.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_TKEY_249_H
+#define GENERIC_TKEY_249_H 1
+
+/*!
+ * \brief Per draft-ietf-dnsind-tkey-00.txt */
+
+typedef struct dns_rdata_tkey {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t algorithm;
+ uint32_t inception;
+ uint32_t expire;
+ uint16_t mode;
+ uint16_t error;
+ uint16_t keylen;
+ unsigned char *key;
+ uint16_t otherlen;
+ unsigned char *other;
+} dns_rdata_tkey_t;
+
+#endif /* GENERIC_TKEY_249_H */
diff --git a/lib/dns/rdata/generic/tlsa_52.c b/lib/dns/rdata/generic/tlsa_52.c
new file mode 100644
index 0000000..ddd437b
--- /dev/null
+++ b/lib/dns/rdata/generic/tlsa_52.c
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* rfc6698.txt */
+
+#ifndef RDATA_GENERIC_TLSA_52_C
+#define RDATA_GENERIC_TLSA_52_C
+
+#define RRTYPE_TLSA_ATTRIBUTES 0
+
+static isc_result_t
+generic_fromtext_tlsa(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Certificate Usage.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Selector.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Matching type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Certificate Association Data.
+ */
+ return (isc_hex_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+generic_totext_tlsa(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("64000 ")];
+ unsigned int n;
+
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Certificate Usage.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Selector.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u ", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Matching type.
+ */
+ n = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%u", n);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Certificate Association Data.
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_hex_totext(&sr, 0, "", target));
+ } else {
+ RETERR(isc_hex_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_fromwire_tlsa(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+
+ /* Usage(1), Selector(1), Type(1), Data(1+) */
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+fromtext_tlsa(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_tlsa);
+
+ return (generic_fromtext_tlsa(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_tlsa(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_tlsa);
+
+ return (generic_totext_tlsa(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_tlsa(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_tlsa);
+
+ return (generic_fromwire_tlsa(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_tlsa(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_tlsa);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_tlsa(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_tlsa);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+generic_fromstruct_tlsa(ARGS_FROMSTRUCT) {
+ dns_rdata_tlsa_t *tlsa = source;
+
+ REQUIRE(tlsa != NULL);
+ REQUIRE(tlsa->common.rdtype == type);
+ REQUIRE(tlsa->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ RETERR(uint8_tobuffer(tlsa->usage, target));
+ RETERR(uint8_tobuffer(tlsa->selector, target));
+ RETERR(uint8_tobuffer(tlsa->match, target));
+
+ return (mem_tobuffer(target, tlsa->data, tlsa->length));
+}
+
+static isc_result_t
+generic_tostruct_tlsa(ARGS_TOSTRUCT) {
+ dns_rdata_tlsa_t *tlsa = target;
+ isc_region_t region;
+
+ REQUIRE(tlsa != NULL);
+ REQUIRE(rdata->length != 0);
+
+ REQUIRE(tlsa != NULL);
+ REQUIRE(tlsa->common.rdclass == rdata->rdclass);
+ REQUIRE(tlsa->common.rdtype == rdata->type);
+ REQUIRE(!ISC_LINK_LINKED(&tlsa->common, link));
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_tlsa(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_tlsa);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_tlsa(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_tlsa);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_tlsa(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_tlsa);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_tlsa(ARGS_COMPARE) {
+ return (compare_tlsa(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_TLSA_52_C */
diff --git a/lib/dns/rdata/generic/tlsa_52.h b/lib/dns/rdata/generic/tlsa_52.h
new file mode 100644
index 0000000..a95b882
--- /dev/null
+++ b/lib/dns/rdata/generic/tlsa_52.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_TLSA_52_H
+#define GENERIC_TLSA_52_H 1
+
+/*!
+ * \brief per rfc6698.txt
+ */
+typedef struct dns_rdata_tlsa {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint8_t usage;
+ uint8_t selector;
+ uint8_t match;
+ uint16_t length;
+ unsigned char *data;
+} dns_rdata_tlsa_t;
+
+#endif /* GENERIC_TLSA_52_H */
diff --git a/lib/dns/rdata/generic/txt_16.c b/lib/dns/rdata/generic/txt_16.c
new file mode 100644
index 0000000..c4d338b
--- /dev/null
+++ b/lib/dns/rdata/generic/txt_16.c
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_GENERIC_TXT_16_C
+#define RDATA_GENERIC_TXT_16_C
+
+#define RRTYPE_TXT_ATTRIBUTES (0)
+
+static isc_result_t
+generic_fromtext_txt(ARGS_FROMTEXT) {
+ isc_token_t token;
+ int strings;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ strings = 0;
+ if ((options & DNS_RDATA_UNKNOWNESCAPE) != 0) {
+ isc_textregion_t r;
+ DE_CONST("#", r.base);
+ r.length = 1;
+ RETERR(txt_fromtext(&r, target));
+ strings++;
+ }
+ for (;;) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_qstring, true));
+ if (token.type != isc_tokentype_qstring &&
+ token.type != isc_tokentype_string)
+ {
+ break;
+ }
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ strings++;
+ }
+ /* Let upper layer handle eol/eof. */
+ isc_lex_ungettoken(lexer, &token);
+ return (strings == 0 ? ISC_R_UNEXPECTEDEND : ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_totext_txt(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_txt(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_txt);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_txt(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_txt);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_txt(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_txt);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_txt(ARGS_COMPARE) {
+ return (compare_txt(rdata1, rdata2));
+}
+
+static isc_result_t
+generic_txt_first(dns_rdata_txt_t *txt) {
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->txt != NULL || txt->txt_len == 0);
+
+ if (txt->txt_len == 0) {
+ return (ISC_R_NOMORE);
+ }
+
+ txt->offset = 0;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_txt_next(dns_rdata_txt_t *txt) {
+ isc_region_t r;
+ uint8_t length;
+
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->txt != NULL && txt->txt_len != 0);
+
+ INSIST(txt->offset + 1 <= txt->txt_len);
+ r.base = txt->txt + txt->offset;
+ r.length = txt->txt_len - txt->offset;
+ length = uint8_fromregion(&r);
+ INSIST(txt->offset + 1 + length <= txt->txt_len);
+ txt->offset = txt->offset + 1 + length;
+ if (txt->offset == txt->txt_len) {
+ return (ISC_R_NOMORE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+generic_txt_current(dns_rdata_txt_t *txt, dns_rdata_txt_string_t *string) {
+ isc_region_t r;
+
+ REQUIRE(txt != NULL);
+ REQUIRE(string != NULL);
+ REQUIRE(txt->txt != NULL);
+ REQUIRE(txt->offset < txt->txt_len);
+
+ INSIST(txt->offset + 1 <= txt->txt_len);
+ r.base = txt->txt + txt->offset;
+ r.length = txt->txt_len - txt->offset;
+
+ string->length = uint8_fromregion(&r);
+ isc_region_consume(&r, 1);
+ string->data = r.base;
+ INSIST(txt->offset + 1 + string->length <= txt->txt_len);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdata_txt_first(dns_rdata_txt_t *txt) {
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->common.rdtype == dns_rdatatype_txt);
+
+ return (generic_txt_first(txt));
+}
+
+isc_result_t
+dns_rdata_txt_next(dns_rdata_txt_t *txt) {
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->common.rdtype == dns_rdatatype_txt);
+
+ return (generic_txt_next(txt));
+}
+
+isc_result_t
+dns_rdata_txt_current(dns_rdata_txt_t *txt, dns_rdata_txt_string_t *string) {
+ REQUIRE(txt != NULL);
+ REQUIRE(txt->common.rdtype == dns_rdatatype_txt);
+
+ return (generic_txt_current(txt, string));
+}
+#endif /* RDATA_GENERIC_TXT_16_C */
diff --git a/lib/dns/rdata/generic/txt_16.h b/lib/dns/rdata/generic/txt_16.h
new file mode 100644
index 0000000..d94eb70
--- /dev/null
+++ b/lib/dns/rdata/generic/txt_16.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef GENERIC_TXT_16_H
+#define GENERIC_TXT_16_H 1
+
+typedef struct dns_rdata_txt_string {
+ uint8_t length;
+ unsigned char *data;
+} dns_rdata_txt_string_t;
+
+typedef struct dns_rdata_txt {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *txt;
+ uint16_t txt_len;
+ /* private */
+ uint16_t offset;
+} dns_rdata_txt_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
+
+isc_result_t
+dns_rdata_txt_first(dns_rdata_txt_t *);
+
+isc_result_t
+dns_rdata_txt_next(dns_rdata_txt_t *);
+
+isc_result_t
+dns_rdata_txt_current(dns_rdata_txt_t *, dns_rdata_txt_string_t *);
+
+#endif /* GENERIC_TXT_16_H */
diff --git a/lib/dns/rdata/generic/uri_256.c b/lib/dns/rdata/generic/uri_256.c
new file mode 100644
index 0000000..a3c61d8
--- /dev/null
+++ b/lib/dns/rdata/generic/uri_256.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_URI_256_C
+#define GENERIC_URI_256_C 1
+
+#define RRTYPE_URI_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_uri(ARGS_FROMTEXT) {
+ isc_token_t token;
+
+ REQUIRE(type == dns_rdatatype_uri);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Priority
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Weight
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Target URI
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ if (token.type != isc_tokentype_qstring) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ RETTOK(multitxt_fromtext(&token.value.as_textregion, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_uri(ARGS_TOTEXT) {
+ isc_region_t region;
+ unsigned short priority, weight;
+ char buf[sizeof("65000 ")];
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_uri);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_uri(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_uri);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_uri(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_uri);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_uri(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_uri);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_uri(ARGS_COMPARE) {
+ return (compare_uri(rdata1, rdata2));
+}
+
+#endif /* GENERIC_URI_256_C */
diff --git a/lib/dns/rdata/generic/uri_256.h b/lib/dns/rdata/generic/uri_256.h
new file mode 100644
index 0000000..8786eff
--- /dev/null
+++ b/lib/dns/rdata/generic/uri_256.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_URI_256_H
+#define GENERIC_URI_256_H 1
+
+typedef struct dns_rdata_uri {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t priority;
+ uint16_t weight;
+ unsigned char *target;
+ uint16_t tgt_len;
+} dns_rdata_uri_t;
+
+#endif /* GENERIC_URI_256_H */
diff --git a/lib/dns/rdata/generic/x25_19.c b/lib/dns/rdata/generic/x25_19.c
new file mode 100644
index 0000000..e8ed4dd
--- /dev/null
+++ b/lib/dns/rdata/generic/x25_19.c
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1183 */
+
+#ifndef RDATA_GENERIC_X25_19_C
+#define RDATA_GENERIC_X25_19_C
+
+#define RRTYPE_X25_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_x25(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned int i;
+
+ REQUIRE(type == dns_rdatatype_x25);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ if (token.value.as_textregion.length < 4) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ for (i = 0; i < token.value.as_textregion.length; i++) {
+ if (!isdigit((unsigned char)token.value.as_textregion.base[i]))
+ {
+ RETTOK(ISC_R_RANGE);
+ }
+ }
+ RETTOK(txt_fromtext(&token.value.as_textregion, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_x25(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_x25);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_x25(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_x25);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_x25(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_x25);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_x25(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_x25);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_x25(ARGS_COMPARE) {
+ return (compare_x25(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_X25_19_C */
diff --git a/lib/dns/rdata/generic/x25_19.h b/lib/dns/rdata/generic/x25_19.h
new file mode 100644
index 0000000..5f8981e
--- /dev/null
+++ b/lib/dns/rdata/generic/x25_19.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_X25_19_H
+#define GENERIC_X25_19_H 1
+
+/*!
+ * \brief Per RFC1183 */
+
+typedef struct dns_rdata_x25 {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *x25;
+ uint8_t x25_len;
+} dns_rdata_x25_t;
+
+#endif /* GENERIC_X25_19_H */
diff --git a/lib/dns/rdata/generic/zonemd_63.c b/lib/dns/rdata/generic/zonemd_63.c
new file mode 100644
index 0000000..949507e
--- /dev/null
+++ b/lib/dns/rdata/generic/zonemd_63.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 8976 */
+
+#ifndef RDATA_GENERIC_ZONEMD_63_C
+#define RDATA_GENERIC_ZONEMD_63_C
+
+#define RRTYPE_ZONEMD_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_zonemd(ARGS_FROMTEXT) {
+ isc_token_t token;
+ int digest_type, length;
+ isc_buffer_t save;
+ isc_result_t result;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ /*
+ * Zone Serial.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint32_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Digest Scheme.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ RETERR(uint8_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Digest Type.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ digest_type = token.value.as_ulong;
+ RETERR(uint8_tobuffer(digest_type, target));
+
+ /*
+ * Digest.
+ */
+ switch (digest_type) {
+ case DNS_ZONEMD_DIGEST_SHA384:
+ length = ISC_SHA384_DIGESTLENGTH;
+ break;
+ case DNS_ZONEMD_DIGEST_SHA512:
+ length = ISC_SHA512_DIGESTLENGTH;
+ break;
+ default:
+ length = -2;
+ break;
+ }
+
+ save = *target;
+ result = isc_hex_tobuffer(lexer, target, length);
+ /* Minimum length of digest is 12 octets. */
+ if (isc_buffer_usedlength(target) - isc_buffer_usedlength(&save) < 12) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ return (result);
+}
+
+static isc_result_t
+totext_zonemd(ARGS_TOTEXT) {
+ isc_region_t sr;
+ char buf[sizeof("0123456789")];
+ unsigned long num;
+
+ REQUIRE(rdata->length > 6);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+
+ /*
+ * Zone Serial.
+ */
+ num = uint32_fromregion(&sr);
+ isc_region_consume(&sr, 4);
+ snprintf(buf, sizeof(buf), "%lu", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Digest scheme.
+ */
+ num = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%lu", num);
+ RETERR(str_totext(buf, target));
+
+ RETERR(str_totext(" ", target));
+
+ /*
+ * Digest type.
+ */
+ num = uint8_fromregion(&sr);
+ isc_region_consume(&sr, 1);
+ snprintf(buf, sizeof(buf), "%lu", num);
+ RETERR(str_totext(buf, target));
+
+ /*
+ * Digest.
+ */
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" (", target));
+ }
+ RETERR(str_totext(tctx->linebreak, target));
+ if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) {
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_hex_totext(&sr, 0, "", target));
+ } else {
+ RETERR(isc_hex_totext(&sr, tctx->width - 2,
+ tctx->linebreak, target));
+ }
+ } else {
+ RETERR(str_totext("[omitted]", target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(" )", target));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_zonemd(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ size_t digestlen = 0;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+
+ /*
+ * If we do not recognize the digest type, ensure that the digest
+ * meets minimum length (12).
+ *
+ * If we do recognize the digest type, ensure that the digest is of the
+ * correct length.
+ */
+ if (sr.length < 18) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ switch (sr.base[5]) {
+ case DNS_ZONEMD_DIGEST_SHA384:
+ digestlen = ISC_SHA384_DIGESTLENGTH;
+ break;
+ case DNS_ZONEMD_DIGEST_SHA512:
+ digestlen = ISC_SHA512_DIGESTLENGTH;
+ break;
+ default:
+ break;
+ }
+
+ if (digestlen != 0 && sr.length < 6 + digestlen) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ /*
+ * Only specify the number of octets to consume if we recognize the
+ * digest type.
+ *
+ * If there is extra data, dns_rdata_fromwire() will detect that.
+ */
+ if (digestlen != 0) {
+ sr.length = 6 + digestlen;
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_zonemd(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_zonemd);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_zonemd(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_zonemd);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_zonemd(ARGS_FROMSTRUCT) {
+ dns_rdata_zonemd_t *zonemd = source;
+
+ REQUIRE(zonemd != NULL);
+ REQUIRE(zonemd->common.rdtype == type);
+ REQUIRE(zonemd->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ switch (zonemd->digest_type) {
+ case DNS_ZONEMD_DIGEST_SHA384:
+ REQUIRE(zonemd->length == ISC_SHA384_DIGESTLENGTH);
+ break;
+ case DNS_ZONEMD_DIGEST_SHA512:
+ REQUIRE(zonemd->length == ISC_SHA512_DIGESTLENGTH);
+ break;
+ }
+
+ RETERR(uint32_tobuffer(zonemd->serial, target));
+ RETERR(uint8_tobuffer(zonemd->scheme, target));
+ RETERR(uint8_tobuffer(zonemd->digest_type, target));
+
+ return (mem_tobuffer(target, zonemd->digest, zonemd->length));
+}
+
+static isc_result_t
+tostruct_zonemd(ARGS_TOSTRUCT) {
+ dns_rdata_zonemd_t *zonemd = target;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_zonemd);
+ REQUIRE(zonemd != NULL);
+ REQUIRE(rdata->length != 0);
+
+ zonemd->common.rdclass = rdata->rdclass;
+ zonemd->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&zonemd->common, link);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_zonemd(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_zonemd);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_zonemd(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_zonemd);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_zonemd(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_zonemd);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_zonemd(ARGS_COMPARE) {
+ return (compare_zonemd(rdata1, rdata2));
+}
+
+#endif /* RDATA_GENERIC_ZONEMD_63_C */
diff --git a/lib/dns/rdata/generic/zonemd_63.h b/lib/dns/rdata/generic/zonemd_63.h
new file mode 100644
index 0000000..02adc14
--- /dev/null
+++ b/lib/dns/rdata/generic/zonemd_63.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef GENERIC_ZONEMD_63_H
+#define GENERIC_ZONEMD_63_H 1
+
+/* Known digest type(s). */
+#define DNS_ZONEMD_DIGEST_SHA384 (1)
+#define DNS_ZONEMD_DIGEST_SHA512 (2)
+
+/*
+ * \brief per RFC 8976
+ */
+typedef struct dns_rdata_zonemd {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint32_t serial;
+ uint8_t scheme;
+ uint8_t digest_type;
+ unsigned char *digest;
+ uint16_t length;
+} dns_rdata_zonemd_t;
+
+#endif /* GENERIC_ZONEMD_63_H */
diff --git a/lib/dns/rdata/hs_4/a_1.c b/lib/dns/rdata/hs_4/a_1.c
new file mode 100644
index 0000000..8943e7e
--- /dev/null
+++ b/lib/dns/rdata/hs_4/a_1.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_HS_4_A_1_C
+#define RDATA_HS_4_A_1_C
+
+#include <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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_hs_a(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_hs);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_hs_a(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_a);
+ REQUIRE(rdclass == dns_rdataclass_hs);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_hs_a(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_a);
+ REQUIRE(rdata->rdclass == dns_rdataclass_hs);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_hs_a(ARGS_COMPARE) {
+ return (compare_hs_a(rdata1, rdata2));
+}
+
+#endif /* RDATA_HS_4_A_1_C */
diff --git a/lib/dns/rdata/hs_4/a_1.h b/lib/dns/rdata/hs_4/a_1.h
new file mode 100644
index 0000000..1bd8c5d
--- /dev/null
+++ b/lib/dns/rdata/hs_4/a_1.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef HS_4_A_1_H
+#define HS_4_A_1_H 1
+
+typedef struct dns_rdata_hs_a {
+ dns_rdatacommon_t common;
+ struct in_addr in_addr;
+} dns_rdata_hs_a_t;
+
+#endif /* HS_4_A_1_H */
diff --git a/lib/dns/rdata/in_1/a6_38.c b/lib/dns/rdata/in_1/a6_38.c
new file mode 100644
index 0000000..65c0a69
--- /dev/null
+++ b/lib/dns/rdata/in_1/a6_38.c
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2874 */
+
+#ifndef RDATA_IN_1_A6_28_C
+#define RDATA_IN_1_A6_28_C
+
+#include <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);
+ RETERR(name_duporclone(&name, mctx, &a6->prefix));
+ }
+ a6->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_a6(ARGS_FREESTRUCT) {
+ dns_rdata_in_a6_t *a6 = source;
+
+ REQUIRE(a6 != NULL);
+ REQUIRE(a6->common.rdclass == dns_rdataclass_in);
+ REQUIRE(a6->common.rdtype == dns_rdatatype_a6);
+
+ if (a6->mctx == NULL) {
+ return;
+ }
+
+ if (dns_name_dynamic(&a6->prefix)) {
+ dns_name_free(&a6->prefix, a6->mctx);
+ }
+ a6->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_a6(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_a6);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_a6(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ unsigned char prefixlen, octets;
+ isc_result_t result;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_a6);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ prefixlen = r1.base[0];
+ octets = 1 + 16 - prefixlen / 8;
+
+ r1.length = octets;
+ result = (digest)(arg, &r1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (prefixlen == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ isc_region_consume(&r2, octets);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_in_a6(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_a6);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (dns_name_ishostname(name, wildcard));
+}
+
+static bool
+checknames_in_a6(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+ unsigned int prefixlen;
+
+ REQUIRE(rdata->type == dns_rdatatype_a6);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &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..bd83092
--- /dev/null
+++ b/lib/dns/rdata/in_1/a6_38.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_A6_38_H
+#define IN_1_A6_38_H 1
+
+/*!
+ * \brief Per RFC2874 */
+
+typedef struct dns_rdata_in_a6 {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t prefix;
+ uint8_t prefixlen;
+ struct in6_addr in6_addr;
+} dns_rdata_in_a6_t;
+
+#endif /* IN_1_A6_38_H */
diff --git a/lib/dns/rdata/in_1/a_1.c b/lib/dns/rdata/in_1/a_1.c
new file mode 100644
index 0000000..b474aa7
--- /dev/null
+++ b/lib/dns/rdata/in_1/a_1.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_IN_1_A_1_C
+#define RDATA_IN_1_A_1_C
+
+#include <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(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..aefc4f3
--- /dev/null
+++ b/lib/dns/rdata/in_1/a_1.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef IN_1_A_1_H
+#define IN_1_A_1_H 1
+
+typedef struct dns_rdata_in_a {
+ dns_rdatacommon_t common;
+ struct in_addr in_addr;
+} dns_rdata_in_a_t;
+
+#endif /* IN_1_A_1_H */
diff --git a/lib/dns/rdata/in_1/aaaa_28.c b/lib/dns/rdata/in_1/aaaa_28.c
new file mode 100644
index 0000000..d60175a
--- /dev/null
+++ b/lib/dns/rdata/in_1/aaaa_28.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1886 */
+
+#ifndef RDATA_IN_1_AAAA_28_C
+#define RDATA_IN_1_AAAA_28_C
+
+#include <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(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..b7310b2
--- /dev/null
+++ b/lib/dns/rdata/in_1/aaaa_28.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_AAAA_28_H
+#define IN_1_AAAA_28_H 1
+
+/*!
+ * \brief Per RFC1886 */
+
+typedef struct dns_rdata_in_aaaa {
+ dns_rdatacommon_t common;
+ struct in6_addr in6_addr;
+} dns_rdata_in_aaaa_t;
+
+#endif /* IN_1_AAAA_28_H */
diff --git a/lib/dns/rdata/in_1/apl_42.c b/lib/dns/rdata/in_1/apl_42.c
new file mode 100644
index 0000000..96372e0
--- /dev/null
+++ b/lib/dns/rdata/in_1/apl_42.c
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC3123 */
+
+#ifndef RDATA_IN_1_APL_42_C
+#define RDATA_IN_1_APL_42_C
+
+#define RRTYPE_APL_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_apl(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned char addr[16];
+ unsigned long afi;
+ uint8_t prefix;
+ uint8_t len;
+ bool neg;
+ char *cp, *ap, *slash;
+ int n;
+
+ REQUIRE(type == dns_rdatatype_apl);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ do {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, true));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+
+ cp = DNS_AS_STR(token);
+ neg = (*cp == '!');
+ if (neg) {
+ cp++;
+ }
+ afi = strtoul(cp, &ap, 10);
+ if (*ap++ != ':' || cp == ap) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ if (afi > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ slash = strchr(ap, '/');
+ if (slash == NULL || slash == ap) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ RETTOK(isc_parse_uint8(&prefix, slash + 1, 10));
+ switch (afi) {
+ case 1:
+ *slash = '\0';
+ n = inet_pton(AF_INET, ap, addr);
+ *slash = '/';
+ if (n != 1) {
+ RETTOK(DNS_R_BADDOTTEDQUAD);
+ }
+ if (prefix > 32) {
+ RETTOK(ISC_R_RANGE);
+ }
+ for (len = 4; len > 0; len--) {
+ if (addr[len - 1] != 0) {
+ break;
+ }
+ }
+ break;
+
+ case 2:
+ *slash = '\0';
+ n = inet_pton(AF_INET6, ap, addr);
+ *slash = '/';
+ if (n != 1) {
+ RETTOK(DNS_R_BADAAAA);
+ }
+ if (prefix > 128) {
+ RETTOK(ISC_R_RANGE);
+ }
+ for (len = 16; len > 0; len--) {
+ if (addr[len - 1] != 0) {
+ break;
+ }
+ }
+ break;
+
+ default:
+ RETTOK(ISC_R_NOTIMPLEMENTED);
+ }
+ RETERR(uint16_tobuffer(afi, target));
+ RETERR(uint8_tobuffer(prefix, target));
+ RETERR(uint8_tobuffer(len | ((neg) ? 0x80 : 0), target));
+ RETERR(mem_tobuffer(target, addr, len));
+ } while (1);
+
+ /*
+ * Let upper layer handle eol/eof.
+ */
+ isc_lex_ungettoken(lexer, &token);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_apl(ARGS_TOTEXT) {
+ isc_region_t sr;
+ isc_region_t ir;
+ uint16_t afi;
+ uint8_t prefix;
+ uint8_t len;
+ bool neg;
+ unsigned char buf[16];
+ char txt[sizeof(" !64000:")];
+ const char *sep = "";
+ int n;
+
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ ir.base = buf;
+ ir.length = sizeof(buf);
+
+ while (sr.length > 0) {
+ INSIST(sr.length >= 4);
+ afi = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ prefix = *sr.base;
+ isc_region_consume(&sr, 1);
+ len = (*sr.base & 0x7f);
+ neg = (*sr.base & 0x80);
+ isc_region_consume(&sr, 1);
+ INSIST(len <= sr.length);
+ n = snprintf(txt, sizeof(txt), "%s%s%u:", sep, neg ? "!" : "",
+ afi);
+ INSIST(n < (int)sizeof(txt));
+ RETERR(str_totext(txt, target));
+ switch (afi) {
+ case 1:
+ INSIST(len <= 4);
+ INSIST(prefix <= 32);
+ memset(buf, 0, sizeof(buf));
+ memmove(buf, sr.base, len);
+ RETERR(inet_totext(AF_INET, tctx->flags, &ir, target));
+ break;
+
+ case 2:
+ INSIST(len <= 16);
+ INSIST(prefix <= 128);
+ memset(buf, 0, sizeof(buf));
+ memmove(buf, sr.base, len);
+ RETERR(inet_totext(AF_INET6, tctx->flags, &ir, target));
+ break;
+
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ n = snprintf(txt, sizeof(txt), "/%u", prefix);
+ INSIST(n < (int)sizeof(txt));
+ RETERR(str_totext(txt, target));
+ isc_region_consume(&sr, len);
+ sep = " ";
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_apl(ARGS_FROMWIRE) {
+ isc_region_t sr, sr2;
+ isc_region_t tr;
+ uint16_t afi;
+ uint8_t prefix;
+ uint8_t len;
+
+ REQUIRE(type == dns_rdatatype_apl);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(rdclass);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ isc_buffer_availableregion(target, &tr);
+ if (sr.length > tr.length) {
+ return (ISC_R_NOSPACE);
+ }
+ sr2 = sr;
+
+ /* Zero or more items */
+ while (sr.length > 0) {
+ if (sr.length < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ afi = uint16_fromregion(&sr);
+ isc_region_consume(&sr, 2);
+ prefix = *sr.base;
+ isc_region_consume(&sr, 1);
+ len = (*sr.base & 0x7f);
+ isc_region_consume(&sr, 1);
+ if (len > sr.length) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ switch (afi) {
+ case 1:
+ if (prefix > 32 || len > 4) {
+ return (ISC_R_RANGE);
+ }
+ break;
+ case 2:
+ if (prefix > 128 || len > 16) {
+ return (ISC_R_RANGE);
+ }
+ }
+ if (len > 0 && sr.base[len - 1] == 0) {
+ return (DNS_R_FORMERR);
+ }
+ isc_region_consume(&sr, len);
+ }
+ isc_buffer_forward(source, sr2.length);
+ return (mem_tobuffer(target, sr2.base, sr2.length));
+}
+
+static isc_result_t
+towire_in_apl(ARGS_TOWIRE) {
+ UNUSED(cctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ return (mem_tobuffer(target, rdata->data, rdata->length));
+}
+
+static int
+compare_in_apl(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_apl);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_apl(ARGS_FROMSTRUCT) {
+ dns_rdata_in_apl_t *apl = source;
+ isc_buffer_t b;
+
+ REQUIRE(type == dns_rdatatype_apl);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(apl != NULL);
+ REQUIRE(apl->common.rdtype == type);
+ REQUIRE(apl->common.rdclass == rdclass);
+ REQUIRE(apl->apl != NULL || apl->apl_len == 0);
+
+ isc_buffer_init(&b, apl->apl, apl->apl_len);
+ isc_buffer_add(&b, apl->apl_len);
+ isc_buffer_setactive(&b, apl->apl_len);
+ return (fromwire_in_apl(rdclass, type, &b, NULL, false, target));
+}
+
+static isc_result_t
+tostruct_in_apl(ARGS_TOSTRUCT) {
+ dns_rdata_in_apl_t *apl = target;
+ isc_region_t r;
+
+ REQUIRE(apl != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ apl->common.rdclass = rdata->rdclass;
+ apl->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&apl->common, link);
+
+ dns_rdata_toregion(rdata, &r);
+ apl->apl_len = r.length;
+ apl->apl = mem_maybedup(mctx, r.base, r.length);
+ if (apl->apl == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ apl->offset = 0;
+ apl->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_apl(ARGS_FREESTRUCT) {
+ dns_rdata_in_apl_t *apl = source;
+
+ REQUIRE(apl != NULL);
+ REQUIRE(apl->common.rdtype == dns_rdatatype_apl);
+ REQUIRE(apl->common.rdclass == dns_rdataclass_in);
+
+ if (apl->mctx == NULL) {
+ return;
+ }
+ if (apl->apl != NULL) {
+ isc_mem_free(apl->mctx, apl->apl);
+ }
+ apl->mctx = NULL;
+}
+
+isc_result_t
+dns_rdata_apl_first(dns_rdata_in_apl_t *apl) {
+ uint32_t length;
+
+ REQUIRE(apl != NULL);
+ REQUIRE(apl->common.rdtype == dns_rdatatype_apl);
+ REQUIRE(apl->common.rdclass == dns_rdataclass_in);
+ REQUIRE(apl->apl != NULL || apl->apl_len == 0);
+
+ /*
+ * If no APL return ISC_R_NOMORE.
+ */
+ if (apl->apl == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * Sanity check data.
+ */
+ INSIST(apl->apl_len > 3U);
+ length = apl->apl[apl->offset + 3] & 0x7f;
+ INSIST(4 + length <= apl->apl_len);
+
+ apl->offset = 0;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdata_apl_next(dns_rdata_in_apl_t *apl) {
+ uint32_t length;
+
+ REQUIRE(apl != NULL);
+ REQUIRE(apl->common.rdtype == dns_rdatatype_apl);
+ REQUIRE(apl->common.rdclass == dns_rdataclass_in);
+ REQUIRE(apl->apl != NULL || apl->apl_len == 0);
+
+ /*
+ * No APL or have already reached the end return ISC_R_NOMORE.
+ */
+ if (apl->apl == NULL || apl->offset == apl->apl_len) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * Sanity check data.
+ */
+ INSIST(apl->offset < apl->apl_len);
+ INSIST(apl->apl_len > 3U);
+ INSIST(apl->offset <= apl->apl_len - 4U);
+ length = apl->apl[apl->offset + 3] & 0x7f;
+ /*
+ * 16 to 32 bits promotion as 'length' is 32 bits so there is
+ * no overflow problems.
+ */
+ INSIST(4 + length + apl->offset <= apl->apl_len);
+
+ apl->offset += 4 + length;
+ return ((apl->offset < apl->apl_len) ? ISC_R_SUCCESS : ISC_R_NOMORE);
+}
+
+isc_result_t
+dns_rdata_apl_current(dns_rdata_in_apl_t *apl, dns_rdata_apl_ent_t *ent) {
+ uint32_t length;
+
+ REQUIRE(apl != NULL);
+ REQUIRE(apl->common.rdtype == dns_rdatatype_apl);
+ REQUIRE(apl->common.rdclass == dns_rdataclass_in);
+ REQUIRE(ent != NULL);
+ REQUIRE(apl->apl != NULL || apl->apl_len == 0);
+ REQUIRE(apl->offset <= apl->apl_len);
+
+ if (apl->offset == apl->apl_len) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * Sanity check data.
+ */
+ INSIST(apl->apl_len > 3U);
+ INSIST(apl->offset <= apl->apl_len - 4U);
+ length = (apl->apl[apl->offset + 3] & 0x7f);
+ /*
+ * 16 to 32 bits promotion as 'length' is 32 bits so there is
+ * no overflow problems.
+ */
+ INSIST(4 + length + apl->offset <= apl->apl_len);
+
+ ent->family = (apl->apl[apl->offset] << 8) + apl->apl[apl->offset + 1];
+ ent->prefix = apl->apl[apl->offset + 2];
+ ent->length = length;
+ ent->negative = (apl->apl[apl->offset + 3] & 0x80);
+ if (ent->length != 0) {
+ ent->data = &apl->apl[apl->offset + 4];
+ } else {
+ ent->data = NULL;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+unsigned int
+dns_rdata_apl_count(const dns_rdata_in_apl_t *apl) {
+ return (apl->apl_len);
+}
+
+static isc_result_t
+additionaldata_in_apl(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ (void)add;
+ (void)arg;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_apl(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_apl(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_apl);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_apl(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_apl);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_apl(ARGS_COMPARE) {
+ return (compare_in_apl(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_APL_42_C */
diff --git a/lib/dns/rdata/in_1/apl_42.h b/lib/dns/rdata/in_1/apl_42.h
new file mode 100644
index 0000000..603fd3e
--- /dev/null
+++ b/lib/dns/rdata/in_1/apl_42.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef IN_1_APL_42_H
+#define IN_1_APL_42_H 1
+
+typedef struct dns_rdata_apl_ent {
+ bool negative;
+ uint16_t family;
+ uint8_t prefix;
+ uint8_t length;
+ unsigned char *data;
+} dns_rdata_apl_ent_t;
+
+typedef struct dns_rdata_in_apl {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ /* type & class specific elements */
+ unsigned char *apl;
+ uint16_t apl_len;
+ /* private */
+ uint16_t offset;
+} dns_rdata_in_apl_t;
+
+/*
+ * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done
+ * via rdatastructpre.h and rdatastructsuf.h.
+ */
+
+isc_result_t
+dns_rdata_apl_first(dns_rdata_in_apl_t *);
+
+isc_result_t
+dns_rdata_apl_next(dns_rdata_in_apl_t *);
+
+isc_result_t
+dns_rdata_apl_current(dns_rdata_in_apl_t *, dns_rdata_apl_ent_t *);
+
+unsigned int
+dns_rdata_apl_count(const dns_rdata_in_apl_t *apl);
+
+#endif /* IN_1_APL_42_H */
diff --git a/lib/dns/rdata/in_1/atma_34.c b/lib/dns/rdata/in_1/atma_34.c
new file mode 100644
index 0000000..c00defd
--- /dev/null
+++ b/lib/dns/rdata/in_1/atma_34.c
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* http://www.broadband-forum.org/ftp/pub/approved-specs/af-dans-0152.000.pdf */
+
+#ifndef RDATA_IN_1_ATMA_22_C
+#define RDATA_IN_1_ATMA_22_C
+
+#define RRTYPE_ATMA_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_atma(ARGS_FROMTEXT) {
+ isc_token_t token;
+ isc_textregion_t *sr;
+ int n;
+ bool valid = false;
+ bool lastwasperiod = true; /* leading periods not allowed */
+ int digits = 0;
+ unsigned char c = 0;
+
+ REQUIRE(type == dns_rdatatype_atma);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /* ATM End System Address (AESA) format or E.164 */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ sr = &token.value.as_textregion;
+ if (sr->length < 1) {
+ RETTOK(ISC_R_UNEXPECTEDEND);
+ }
+
+ if (sr->base[0] != '+') {
+ /*
+ * Format 0: ATM End System Address (AESA) format.
+ */
+ c = 0;
+ RETERR(mem_tobuffer(target, &c, 1));
+ while (sr->length > 0) {
+ if (sr->base[0] == '.') {
+ if (lastwasperiod) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ isc_textregion_consume(sr, 1);
+ lastwasperiod = true;
+ continue;
+ }
+ if ((n = hexvalue(sr->base[0])) == -1) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ c <<= 4;
+ c += n;
+ if (++digits == 2) {
+ RETERR(mem_tobuffer(target, &c, 1));
+ valid = true;
+ digits = 0;
+ c = 0;
+ }
+ isc_textregion_consume(sr, 1);
+ lastwasperiod = false;
+ }
+ if (digits != 0 || !valid || lastwasperiod) {
+ RETTOK(ISC_R_UNEXPECTEDEND);
+ }
+ } else {
+ /*
+ * Format 1: E.164.
+ */
+ c = 1;
+ RETERR(mem_tobuffer(target, &c, 1));
+ isc_textregion_consume(sr, 1);
+ while (sr->length > 0) {
+ if (sr->base[0] == '.') {
+ if (lastwasperiod) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ isc_textregion_consume(sr, 1);
+ lastwasperiod = true;
+ continue;
+ }
+ if (!isdigit((unsigned char)sr->base[0])) {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ RETERR(mem_tobuffer(target, sr->base, 1));
+ isc_textregion_consume(sr, 1);
+ lastwasperiod = false;
+ }
+ if (lastwasperiod) {
+ RETTOK(ISC_R_UNEXPECTEDEND);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_atma(ARGS_TOTEXT) {
+ isc_region_t region;
+ char buf[sizeof("xx")];
+
+ REQUIRE(rdata->type == dns_rdatatype_atma);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(tctx);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_atma(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_atma);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_atma(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_atma);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_atma(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_atma);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_atma(ARGS_COMPARE) {
+ return (compare_in_atma(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_atma_22_C */
diff --git a/lib/dns/rdata/in_1/atma_34.h b/lib/dns/rdata/in_1/atma_34.h
new file mode 100644
index 0000000..abb5457
--- /dev/null
+++ b/lib/dns/rdata/in_1/atma_34.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_ATMA_22_H
+#define IN_1_ATMA_22_H 1
+
+/*!
+ * \brief Per RFC1706 */
+
+typedef struct dns_rdata_in_atma {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char format;
+ unsigned char *atma;
+ uint16_t atma_len;
+} dns_rdata_in_atma_t;
+
+#endif /* IN_1_ATMA_22_H */
diff --git a/lib/dns/rdata/in_1/dhcid_49.c b/lib/dns/rdata/in_1/dhcid_49.c
new file mode 100644
index 0000000..7d36d50
--- /dev/null
+++ b/lib/dns/rdata/in_1/dhcid_49.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC 4701 */
+
+#ifndef RDATA_IN_1_DHCID_49_C
+#define RDATA_IN_1_DHCID_49_C 1
+
+#define RRTYPE_DHCID_ATTRIBUTES 0
+
+static isc_result_t
+fromtext_in_dhcid(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_dhcid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(callbacks);
+
+ return (isc_base64_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_in_dhcid(ARGS_TOTEXT) {
+ isc_region_t sr, sr2;
+ /* " ; 64000 255 64000" */
+ char buf[5 + 3 * 11 + 1];
+
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+ sr2 = sr;
+
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext("( " /*)*/, target));
+ }
+ if (tctx->width == 0) { /* No splitting */
+ RETERR(isc_base64_totext(&sr, 60, "", target));
+ } else {
+ RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak,
+ target));
+ }
+ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
+ RETERR(str_totext(/* ( */ " )", target));
+ if (rdata->length > 2) {
+ snprintf(buf, sizeof(buf), " ; %u %u %u",
+ sr2.base[0] * 256U + sr2.base[1], sr2.base[2],
+ rdata->length - 3U);
+ RETERR(str_totext(buf, target));
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_dhcid(ARGS_FROMWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(type == dns_rdatatype_dhcid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+ UNUSED(options);
+
+ isc_buffer_activeregion(source, &sr);
+ if (sr.length == 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+
+ isc_buffer_forward(source, sr.length);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static isc_result_t
+towire_in_dhcid(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ UNUSED(cctx);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_in_dhcid(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_dhcid(ARGS_FROMSTRUCT) {
+ dns_rdata_in_dhcid_t *dhcid = source;
+
+ REQUIRE(type == dns_rdatatype_dhcid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(dhcid != NULL);
+ REQUIRE(dhcid->common.rdtype == type);
+ REQUIRE(dhcid->common.rdclass == rdclass);
+ REQUIRE(dhcid->length != 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (mem_tobuffer(target, dhcid->dhcid, dhcid->length));
+}
+
+static isc_result_t
+tostruct_in_dhcid(ARGS_TOSTRUCT) {
+ dns_rdata_in_dhcid_t *dhcid = target;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(dhcid != NULL);
+ REQUIRE(rdata->length != 0);
+
+ dhcid->common.rdclass = rdata->rdclass;
+ dhcid->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&dhcid->common, link);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_dhcid(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_dhcid(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_dhcid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_dhcid(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_dhcid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_dhcid(ARGS_COMPARE) {
+ return (compare_in_dhcid(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_DHCID_49_C */
diff --git a/lib/dns/rdata/in_1/dhcid_49.h b/lib/dns/rdata/in_1/dhcid_49.h
new file mode 100644
index 0000000..071ea41
--- /dev/null
+++ b/lib/dns/rdata/in_1/dhcid_49.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* */
+#ifndef IN_1_DHCID_49_H
+#define IN_1_DHCID_49_H 1
+
+typedef struct dns_rdata_in_dhcid {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *dhcid;
+ unsigned int length;
+} dns_rdata_in_dhcid_t;
+
+#endif /* IN_1_DHCID_49_H */
diff --git a/lib/dns/rdata/in_1/eid_31.c b/lib/dns/rdata/in_1/eid_31.c
new file mode 100644
index 0000000..104837c
--- /dev/null
+++ b/lib/dns/rdata/in_1/eid_31.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt */
+
+#ifndef RDATA_IN_1_EID_31_C
+#define RDATA_IN_1_EID_31_C
+
+#define RRTYPE_EID_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_eid(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_eid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ return (isc_hex_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_in_eid(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_eid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_eid(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_eid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_eid(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_eid);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_eid(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_eid);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_eid(ARGS_COMPARE) {
+ return (compare_in_eid(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_EID_31_C */
diff --git a/lib/dns/rdata/in_1/eid_31.h b/lib/dns/rdata/in_1/eid_31.h
new file mode 100644
index 0000000..879bcf5
--- /dev/null
+++ b/lib/dns/rdata/in_1/eid_31.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_EID_31_H
+#define IN_1_EID_31_H 1
+
+/*!
+ * \brief http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt
+ */
+
+typedef struct dns_rdata_in_eid {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *eid;
+ uint16_t eid_len;
+} dns_rdata_in_eid_t;
+
+#endif /* IN_1_EID_31_H */
diff --git a/lib/dns/rdata/in_1/https_65.c b/lib/dns/rdata/in_1/https_65.c
new file mode 100644
index 0000000..0581645
--- /dev/null
+++ b/lib/dns/rdata/in_1/https_65.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsop-svcb-https-02 */
+
+#ifndef RDATA_IN_1_HTTPS_65_C
+#define RDATA_IN_1_HTTPS_65_C
+
+#define RRTYPE_HTTPS_ATTRIBUTES 0
+
+/*
+ * Most of these functions refer to equivalent functions for SVCB,
+ * since wire and presentation formats are identical.
+ */
+
+static isc_result_t
+fromtext_in_https(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_https);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return (generic_fromtext_in_svcb(CALL_FROMTEXT));
+}
+
+static isc_result_t
+totext_in_https(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_https);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ return (generic_totext_in_svcb(CALL_TOTEXT));
+}
+
+static isc_result_t
+fromwire_in_https(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_https);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return (generic_fromwire_in_svcb(CALL_FROMWIRE));
+}
+
+static isc_result_t
+towire_in_https(ARGS_TOWIRE) {
+ REQUIRE(rdata->type == dns_rdatatype_https);
+ REQUIRE(rdata->length != 0);
+
+ return (generic_towire_in_svcb(CALL_TOWIRE));
+}
+
+static int
+compare_in_https(ARGS_COMPARE) {
+ isc_region_t region1;
+ isc_region_t region2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_https);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &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);
+}
+
+#endif /* RDATA_IN_1_HTTPS_65_C */
diff --git a/lib/dns/rdata/in_1/https_65.h b/lib/dns/rdata/in_1/https_65.h
new file mode 100644
index 0000000..a5967a3
--- /dev/null
+++ b/lib/dns/rdata/in_1/https_65.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_HTTPS_65_H
+#define IN_1_HTTPS_65_H 1
+
+/*!
+ * \brief Per draft-ietf-dnsop-svcb-https-02
+ */
+
+/*
+ * Wire and presentation formats for HTTPS are identical to SVCB.
+ */
+typedef struct dns_rdata_in_svcb dns_rdata_in_https_t;
+
+isc_result_t
+dns_rdata_in_https_first(dns_rdata_in_https_t *);
+
+isc_result_t
+dns_rdata_in_https_next(dns_rdata_in_https_t *);
+
+void
+dns_rdata_in_https_current(dns_rdata_in_https_t *, isc_region_t *);
+
+#endif /* IN_1_HTTPS_65_H */
diff --git a/lib/dns/rdata/in_1/kx_36.c b/lib/dns/rdata/in_1/kx_36.c
new file mode 100644
index 0000000..508def3
--- /dev/null
+++ b/lib/dns/rdata/in_1/kx_36.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2230 */
+
+#ifndef RDATA_IN_1_KX_36_C
+#define RDATA_IN_1_KX_36_C
+
+#define RRTYPE_KX_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_kx(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_kx);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_kx(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_kx);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &kx->exchange));
+ kx->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_kx(ARGS_FREESTRUCT) {
+ dns_rdata_in_kx_t *kx = source;
+
+ REQUIRE(kx != NULL);
+ REQUIRE(kx->common.rdclass == dns_rdataclass_in);
+ REQUIRE(kx->common.rdtype == dns_rdatatype_kx);
+
+ if (kx->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&kx->exchange, kx->mctx);
+ kx->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_kx(ARGS_ADDLDATA) {
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_kx);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &region);
+ isc_region_consume(&region, 2);
+ dns_name_fromregion(&name, &region);
+
+ return ((add)(arg, &name, dns_rdatatype_a));
+}
+
+static isc_result_t
+digest_in_kx(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_kx);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 2);
+ r1.length = 2;
+ RETERR((digest)(arg, &r1));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_in_kx(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_kx);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_kx(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_kx);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_kx(ARGS_COMPARE) {
+ return (compare_in_kx(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_KX_36_C */
diff --git a/lib/dns/rdata/in_1/kx_36.h b/lib/dns/rdata/in_1/kx_36.h
new file mode 100644
index 0000000..b71aadc
--- /dev/null
+++ b/lib/dns/rdata/in_1/kx_36.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_KX_36_H
+#define IN_1_KX_36_H 1
+
+/*!
+ * \brief Per RFC2230 */
+
+typedef struct dns_rdata_in_kx {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t preference;
+ dns_name_t exchange;
+} dns_rdata_in_kx_t;
+
+#endif /* IN_1_KX_36_H */
diff --git a/lib/dns/rdata/in_1/nimloc_32.c b/lib/dns/rdata/in_1/nimloc_32.c
new file mode 100644
index 0000000..6f1e3d6
--- /dev/null
+++ b/lib/dns/rdata/in_1/nimloc_32.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt */
+
+#ifndef RDATA_IN_1_NIMLOC_32_C
+#define RDATA_IN_1_NIMLOC_32_C
+
+#define RRTYPE_NIMLOC_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_nimloc(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_nimloc);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ return (isc_hex_tobuffer(lexer, target, -2));
+}
+
+static isc_result_t
+totext_in_nimloc(ARGS_TOTEXT) {
+ isc_region_t region;
+
+ REQUIRE(rdata->type == dns_rdatatype_nimloc);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_nimloc(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nimloc);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_nimloc(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nimloc);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_nimloc(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nimloc);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_nimloc(ARGS_COMPARE) {
+ return (compare_in_nimloc(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_NIMLOC_32_C */
diff --git a/lib/dns/rdata/in_1/nimloc_32.h b/lib/dns/rdata/in_1/nimloc_32.h
new file mode 100644
index 0000000..02ca047
--- /dev/null
+++ b/lib/dns/rdata/in_1/nimloc_32.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_NIMLOC_32_H
+#define IN_1_NIMLOC_32_H 1
+
+/*!
+ * \brief http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt
+ */
+
+typedef struct dns_rdata_in_nimloc {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *nimloc;
+ uint16_t nimloc_len;
+} dns_rdata_in_nimloc_t;
+
+#endif /* IN_1_NIMLOC_32_H */
diff --git a/lib/dns/rdata/in_1/nsap-ptr_23.c b/lib/dns/rdata/in_1/nsap-ptr_23.c
new file mode 100644
index 0000000..326e878
--- /dev/null
+++ b/lib/dns/rdata/in_1/nsap-ptr_23.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1348. Obsoleted in RFC 1706 - use PTR instead. */
+
+#ifndef RDATA_IN_1_NSAP_PTR_23_C
+#define RDATA_IN_1_NSAP_PTR_23_C
+
+#define RRTYPE_NSAP_PTR_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_nsap_ptr(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_nsap_ptr(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &nsap_ptr->owner));
+ nsap_ptr->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_nsap_ptr(ARGS_FREESTRUCT) {
+ dns_rdata_in_nsap_ptr_t *nsap_ptr = source;
+
+ REQUIRE(nsap_ptr != NULL);
+ REQUIRE(nsap_ptr->common.rdclass == dns_rdataclass_in);
+ REQUIRE(nsap_ptr->common.rdtype == dns_rdatatype_nsap_ptr);
+
+ if (nsap_ptr->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&nsap_ptr->owner, nsap_ptr->mctx);
+ nsap_ptr->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_nsap_ptr(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_nsap_ptr(ARGS_DIGEST) {
+ isc_region_t r;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_in_nsap_ptr(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_nsap_ptr(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nsap_ptr);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_nsap_ptr(ARGS_COMPARE) {
+ return (compare_in_nsap_ptr(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_NSAP_PTR_23_C */
diff --git a/lib/dns/rdata/in_1/nsap-ptr_23.h b/lib/dns/rdata/in_1/nsap-ptr_23.h
new file mode 100644
index 0000000..ef1ee60
--- /dev/null
+++ b/lib/dns/rdata/in_1/nsap-ptr_23.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_NSAP_PTR_23_H
+#define IN_1_NSAP_PTR_23_H 1
+
+/*!
+ * \brief Per RFC1348. Obsoleted in RFC 1706 - use PTR instead. */
+
+typedef struct dns_rdata_in_nsap_ptr {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ dns_name_t owner;
+} dns_rdata_in_nsap_ptr_t;
+
+#endif /* IN_1_NSAP_PTR_23_H */
diff --git a/lib/dns/rdata/in_1/nsap_22.c b/lib/dns/rdata/in_1/nsap_22.c
new file mode 100644
index 0000000..5ccb793
--- /dev/null
+++ b/lib/dns/rdata/in_1/nsap_22.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC1706 */
+
+#ifndef RDATA_IN_1_NSAP_22_C
+#define RDATA_IN_1_NSAP_22_C
+
+#define RRTYPE_NSAP_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_nsap(ARGS_FROMTEXT) {
+ isc_token_t token;
+ isc_textregion_t *sr;
+ int n;
+ bool valid = false;
+ int digits = 0;
+ unsigned char c = 0;
+
+ REQUIRE(type == dns_rdatatype_nsap);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /* 0x<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(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_nsap(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_nsap);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_nsap(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_nsap);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_nsap(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_nsap);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_nsap(ARGS_COMPARE) {
+ return (compare_in_nsap(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_NSAP_22_C */
diff --git a/lib/dns/rdata/in_1/nsap_22.h b/lib/dns/rdata/in_1/nsap_22.h
new file mode 100644
index 0000000..f65c913
--- /dev/null
+++ b/lib/dns/rdata/in_1/nsap_22.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_NSAP_22_H
+#define IN_1_NSAP_22_H 1
+
+/*!
+ * \brief Per RFC1706 */
+
+typedef struct dns_rdata_in_nsap {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *nsap;
+ uint16_t nsap_len;
+} dns_rdata_in_nsap_t;
+
+#endif /* IN_1_NSAP_22_H */
diff --git a/lib/dns/rdata/in_1/px_26.c b/lib/dns/rdata/in_1/px_26.c
new file mode 100644
index 0000000..9f6a21a
--- /dev/null
+++ b/lib/dns/rdata/in_1/px_26.c
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2163 */
+
+#ifndef RDATA_IN_1_PX_26_C
+#define RDATA_IN_1_PX_26_C
+
+#define RRTYPE_PX_ATTRIBUTES (0)
+
+static isc_result_t
+fromtext_in_px(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+
+ REQUIRE(type == dns_rdatatype_px);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ /*
+ * Preference.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * MAP822.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+
+ /*
+ * MAPX400.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_px(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ /*
+ * Preference.
+ */
+ dns_rdata_toregion(rdata, &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;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(px != NULL);
+ REQUIRE(rdata->length != 0);
+
+ px->common.rdclass = rdata->rdclass;
+ px->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&px->common, link);
+
+ dns_name_init(&name, NULL);
+ dns_rdata_toregion(rdata, &region);
+
+ px->preference = uint16_fromregion(&region);
+ isc_region_consume(&region, 2);
+
+ dns_name_fromregion(&name, &region);
+
+ dns_name_init(&px->map822, NULL);
+ RETERR(name_duporclone(&name, mctx, &px->map822));
+ isc_region_consume(&region, name_length(&px->map822));
+
+ dns_name_init(&px->mapx400, NULL);
+ result = name_duporclone(&name, mctx, &px->mapx400);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ px->mctx = mctx;
+ return (result);
+
+cleanup:
+ dns_name_free(&px->map822, mctx);
+ return (ISC_R_NOMEMORY);
+}
+
+static void
+freestruct_in_px(ARGS_FREESTRUCT) {
+ dns_rdata_in_px_t *px = source;
+
+ REQUIRE(px != NULL);
+ REQUIRE(px->common.rdclass == dns_rdataclass_in);
+ REQUIRE(px->common.rdtype == dns_rdatatype_px);
+
+ if (px->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&px->map822, px->mctx);
+ dns_name_free(&px->mapx400, px->mctx);
+ px->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_px(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_px(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ dns_name_t name;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 2);
+ r1.length = 2;
+ result = (digest)(arg, &r1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ result = dns_name_digest(&name, digest, arg);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_region_consume(&r2, name_length(&name));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_in_px(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_px);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_px(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_px);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_px(ARGS_COMPARE) {
+ return (compare_in_px(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_PX_26_C */
diff --git a/lib/dns/rdata/in_1/px_26.h b/lib/dns/rdata/in_1/px_26.h
new file mode 100644
index 0000000..1fcbd36
--- /dev/null
+++ b/lib/dns/rdata/in_1/px_26.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_PX_26_H
+#define IN_1_PX_26_H 1
+
+/*!
+ * \brief Per RFC2163 */
+
+typedef struct dns_rdata_in_px {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t preference;
+ dns_name_t map822;
+ dns_name_t mapx400;
+} dns_rdata_in_px_t;
+
+#endif /* IN_1_PX_26_H */
diff --git a/lib/dns/rdata/in_1/srv_33.c b/lib/dns/rdata/in_1/srv_33.c
new file mode 100644
index 0000000..6e24867
--- /dev/null
+++ b/lib/dns/rdata/in_1/srv_33.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* RFC2782 */
+
+#ifndef RDATA_IN_1_SRV_33_C
+#define RDATA_IN_1_SRV_33_C
+
+#define RRTYPE_SRV_ATTRIBUTES (DNS_RDATATYPEATTR_FOLLOWADDITIONAL)
+
+static isc_result_t
+fromtext_in_srv(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ bool ok;
+
+ REQUIRE(type == dns_rdatatype_srv);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * Priority.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Weight.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Port.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ /*
+ * Target.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ ok = true;
+ if ((options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+totext_in_srv(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("64000")];
+ unsigned short num;
+
+ REQUIRE(rdata->type == dns_rdatatype_srv);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ /*
+ * Priority.
+ */
+ dns_rdata_toregion(rdata, &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);
+ RETERR(name_duporclone(&name, mctx, &srv->target));
+ srv->mctx = mctx;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+freestruct_in_srv(ARGS_FREESTRUCT) {
+ dns_rdata_in_srv_t *srv = source;
+
+ REQUIRE(srv != NULL);
+ REQUIRE(srv->common.rdclass == dns_rdataclass_in);
+ REQUIRE(srv->common.rdtype == dns_rdatatype_srv);
+
+ if (srv->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&srv->target, srv->mctx);
+ srv->mctx = NULL;
+}
+
+static isc_result_t
+additionaldata_in_srv(ARGS_ADDLDATA) {
+ char buf[sizeof("_65000._tcp")];
+ dns_fixedname_t fixed;
+ dns_name_t name;
+ dns_offsets_t offsets;
+ isc_region_t region;
+ uint16_t port;
+ isc_result_t result;
+
+ REQUIRE(rdata->type == dns_rdatatype_srv);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_name_init(&name, offsets);
+ dns_rdata_toregion(rdata, &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);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_fixedname_init(&fixed);
+ snprintf(buf, sizeof(buf), "_%u._tcp", port);
+ result = dns_name_fromstring2(dns_fixedname_name(&fixed), buf, NULL, 0,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_name_concatenate(dns_fixedname_name(&fixed), &name,
+ dns_fixedname_name(&fixed), NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ return ((add)(arg, dns_fixedname_name(&fixed), dns_rdatatype_tlsa));
+}
+
+static isc_result_t
+digest_in_srv(ARGS_DIGEST) {
+ isc_region_t r1, r2;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_srv);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r1);
+ r2 = r1;
+ isc_region_consume(&r2, 6);
+ r1.length = 6;
+ RETERR((digest)(arg, &r1));
+ dns_name_init(&name, NULL);
+ dns_name_fromregion(&name, &r2);
+ return (dns_name_digest(&name, digest, arg));
+}
+
+static bool
+checkowner_in_srv(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_srv);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(name);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(wildcard);
+
+ return (true);
+}
+
+static bool
+checknames_in_srv(ARGS_CHECKNAMES) {
+ isc_region_t region;
+ dns_name_t name;
+
+ REQUIRE(rdata->type == dns_rdatatype_srv);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(owner);
+
+ dns_rdata_toregion(rdata, &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..554180a
--- /dev/null
+++ b/lib/dns/rdata/in_1/srv_33.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_SRV_33_H
+#define IN_1_SRV_33_H 1
+
+/*!
+ * \brief Per RFC2782 */
+
+typedef struct dns_rdata_in_srv {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t priority;
+ uint16_t weight;
+ uint16_t port;
+ dns_name_t target;
+} dns_rdata_in_srv_t;
+
+#endif /* IN_1_SRV_33_H */
diff --git a/lib/dns/rdata/in_1/svcb_64.c b/lib/dns/rdata/in_1/svcb_64.c
new file mode 100644
index 0000000..a87473f
--- /dev/null
+++ b/lib/dns/rdata/in_1/svcb_64.c
@@ -0,0 +1,1268 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* draft-ietf-dnsop-svcb-https-02 */
+
+#ifndef RDATA_IN_1_SVCB_64_C
+#define RDATA_IN_1_SVCB_64_C
+
+#define RRTYPE_SVCB_ATTRIBUTES 0
+
+#define SVCB_MAN_KEY 0
+#define SVCB_ALPN_KEY 1
+#define SVCB_NO_DEFAULT_ALPN_KEY 2
+
+/*
+ * Service Binding Parameter Registry
+ */
+enum encoding {
+ sbpr_text,
+ sbpr_port,
+ sbpr_ipv4s,
+ sbpr_ipv6s,
+ sbpr_base64,
+ sbpr_empty,
+ sbpr_alpn,
+ sbpr_keylist,
+ sbpr_dohpath
+};
+static const struct {
+ const char *name; /* Restricted to lowercase LDH by registry. */
+ unsigned int value;
+ enum encoding encoding;
+ bool initial; /* Part of the first defined set of encodings. */
+} sbpr[] = {
+ { "mandatory", 0, sbpr_keylist, true },
+ { "alpn", 1, sbpr_alpn, true },
+ { "no-default-alpn", 2, sbpr_empty, true },
+ { "port", 3, sbpr_port, true },
+ { "ipv4hint", 4, sbpr_ipv4s, true },
+ { "ech", 5, sbpr_base64, true },
+ { "ipv6hint", 6, sbpr_ipv6s, true },
+ { "dohpath", 7, sbpr_dohpath, false },
+};
+
+static isc_result_t
+alpn_fromtxt(isc_textregion_t *source, isc_buffer_t *target) {
+ isc_textregion_t source0 = *source;
+ do {
+ RETERR(commatxt_fromtext(&source0, true, target));
+ } while (source0.length != 0);
+ return (ISC_R_SUCCESS);
+}
+
+static int
+svckeycmp(const void *a1, const void *a2) {
+ const unsigned char *u1 = a1, *u2 = a2;
+ if (*u1 != *u2) {
+ return (*u1 - *u2);
+ }
+ return (*(++u1) - *(++u2));
+}
+
+static isc_result_t
+svcsortkeylist(isc_buffer_t *target, unsigned int used) {
+ isc_region_t region;
+
+ isc_buffer_usedregion(target, &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;
+
+#ifndef ARRAYSIZE
+/* defined in winnt.h */
+#define ARRAYSIZE(x) (sizeof(x) / sizeof(*x))
+#endif
+
+ for (i = 0; i < ARRAYSIZE(sbpr); i++) {
+ if (sbpr[i].value == key) {
+ switch (sbpr[i].encoding) {
+ case sbpr_port:
+ if (region->length != 2) {
+ return (DNS_R_FORMERR);
+ }
+ break;
+ case sbpr_ipv4s:
+ if ((region->length % 4) != 0 ||
+ region->length == 0)
+ {
+ return (DNS_R_FORMERR);
+ }
+ break;
+ case sbpr_ipv6s:
+ if ((region->length % 16) != 0 ||
+ region->length == 0)
+ {
+ return (DNS_R_FORMERR);
+ }
+ break;
+ case sbpr_alpn: {
+ if (region->length == 0) {
+ return (DNS_R_FORMERR);
+ }
+ while (region->length != 0) {
+ size_t l = *region->base + 1;
+ if (l == 1U || l > region->length) {
+ return (DNS_R_FORMERR);
+ }
+ isc_region_consume(region, l);
+ }
+ break;
+ }
+ case sbpr_keylist: {
+ if ((region->length % 2) != 0 ||
+ region->length == 0)
+ {
+ return (DNS_R_FORMERR);
+ }
+ /* In order? */
+ while (region->length >= 4) {
+ if (region->base[0] > region->base[2] ||
+ (region->base[0] ==
+ region->base[2] &&
+ region->base[1] >=
+ region->base[3]))
+ {
+ return (DNS_R_FORMERR);
+ }
+ isc_region_consume(region, 2);
+ }
+ break;
+ }
+ case sbpr_text:
+ case sbpr_base64:
+ break;
+ case sbpr_dohpath:
+ /*
+ * Minimum valid dohpath is "/{?dns}" as
+ * it MUST be relative (leading "/") and
+ * MUST contain "{?dns}".
+ */
+ if (region->length < 7) {
+ return (DNS_R_FORMERR);
+ }
+ /* MUST be relative */
+ if (region->base[0] != '/') {
+ return (DNS_R_FORMERR);
+ }
+ /* MUST be UTF8 */
+ if (!isc_utf8_valid(region->base,
+ region->length))
+ {
+ return (DNS_R_FORMERR);
+ }
+ /* MUST contain "{?dns}" */
+ if (strnstr((char *)region->base, "{?dns}",
+ region->length) == NULL)
+ {
+ return (DNS_R_FORMERR);
+ }
+ break;
+ case sbpr_empty:
+ if (region->length != 0) {
+ return (DNS_R_FORMERR);
+ }
+ break;
+ }
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Parse keyname from region.
+ */
+static isc_result_t
+svc_keyfromregion(isc_textregion_t *region, char sep, uint16_t *value,
+ isc_buffer_t *target) {
+ char *e = NULL;
+ size_t i;
+ unsigned long ul;
+
+ /* Look for known key names. */
+ for (i = 0; i < ARRAYSIZE(sbpr); i++) {
+ size_t len = strlen(sbpr[i].name);
+ if (strncasecmp(region->base, sbpr[i].name, len) != 0 ||
+ (region->base[len] != 0 && region->base[len] != sep))
+ {
+ continue;
+ }
+ isc_textregion_consume(region, len);
+ ul = sbpr[i].value;
+ goto finish;
+ }
+ /* Handle keyXXXXX form. */
+ if (strncmp(region->base, "key", 3) != 0) {
+ return (DNS_R_SYNTAX);
+ }
+ isc_textregion_consume(region, 3);
+ /* Disallow [+-]XXXXX which is allowed by strtoul. */
+ if (region->length == 0 || *region->base == '-' || *region->base == '+')
+ {
+ return (DNS_R_SYNTAX);
+ }
+ /* No zero padding. */
+ if (region->length > 1 && *region->base == '0' &&
+ region->base[1] != sep)
+ {
+ return (DNS_R_SYNTAX);
+ }
+ ul = strtoul(region->base, &e, 10);
+ /* Valid number? */
+ if (e == region->base || (*e != sep && *e != 0)) {
+ return (DNS_R_SYNTAX);
+ }
+ if (ul > 0xffff) {
+ return (ISC_R_RANGE);
+ }
+ isc_textregion_consume(region, e - region->base);
+finish:
+ if (sep == ',' && region->length == 1) {
+ return (DNS_R_SYNTAX);
+ }
+ /* Consume separator. */
+ if (region->length != 0) {
+ isc_textregion_consume(region, 1);
+ }
+ RETERR(uint16_tobuffer(ul, target));
+ if (value != NULL) {
+ *value = ul;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+svc_fromtext(isc_textregion_t *region, isc_buffer_t *target) {
+ char *e = NULL;
+ char abuf[16];
+ char tbuf[sizeof("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:255.255.255.255,")];
+ isc_buffer_t sb;
+ isc_region_t keyregion;
+ size_t len;
+ uint16_t key;
+ unsigned int i;
+ unsigned int used;
+ unsigned long ul;
+
+ for (i = 0; i < ARRAYSIZE(sbpr); i++) {
+ len = strlen(sbpr[i].name);
+ if (strncmp(region->base, sbpr[i].name, len) != 0 ||
+ (region->base[len] != 0 && region->base[len] != '='))
+ {
+ continue;
+ }
+
+ if (region->base[len] == '=') {
+ len++;
+ }
+
+ RETERR(uint16_tobuffer(sbpr[i].value, target));
+ isc_textregion_consume(region, len);
+
+ sb = *target;
+ RETERR(uint16_tobuffer(0, target)); /* length */
+
+ switch (sbpr[i].encoding) {
+ case sbpr_text:
+ case sbpr_dohpath:
+ RETERR(multitxt_fromtext(region, target));
+ break;
+ case sbpr_alpn:
+ RETERR(alpn_fromtxt(region, target));
+ break;
+ case sbpr_port:
+ if (!isdigit((unsigned char)*region->base)) {
+ return (DNS_R_SYNTAX);
+ }
+ ul = strtoul(region->base, &e, 10);
+ if (*e != '\0') {
+ return (DNS_R_SYNTAX);
+ }
+ if (ul > 0xffff) {
+ return (ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(ul, target));
+ break;
+ case sbpr_ipv4s:
+ do {
+ snprintf(tbuf, sizeof(tbuf), "%*s",
+ (int)(region->length), region->base);
+ e = strchr(tbuf, ',');
+ if (e != NULL) {
+ *e++ = 0;
+ isc_textregion_consume(region,
+ e - tbuf);
+ }
+ if (inet_pton(AF_INET, tbuf, abuf) != 1) {
+ return (DNS_R_SYNTAX);
+ }
+ mem_tobuffer(target, abuf, 4);
+ } while (e != NULL);
+ break;
+ case sbpr_ipv6s:
+ do {
+ snprintf(tbuf, sizeof(tbuf), "%*s",
+ (int)(region->length), region->base);
+ e = strchr(tbuf, ',');
+ if (e != NULL) {
+ *e++ = 0;
+ isc_textregion_consume(region,
+ e - tbuf);
+ }
+ if (inet_pton(AF_INET6, tbuf, abuf) != 1) {
+ return (DNS_R_SYNTAX);
+ }
+ mem_tobuffer(target, abuf, 16);
+ } while (e != NULL);
+ break;
+ case sbpr_base64:
+ RETERR(isc_base64_decodestring(region->base, target));
+ break;
+ case sbpr_empty:
+ if (region->length != 0) {
+ return (DNS_R_SYNTAX);
+ }
+ break;
+ case sbpr_keylist:
+ if (region->length == 0) {
+ return (DNS_R_SYNTAX);
+ }
+ used = isc_buffer_usedlength(target);
+ while (region->length != 0) {
+ RETERR(svc_keyfromregion(region, ',', NULL,
+ target));
+ }
+ RETERR(svcsortkeylist(target, used));
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ len = isc_buffer_usedlength(target) -
+ isc_buffer_usedlength(&sb) - 2;
+ RETERR(uint16_tobuffer(len, &sb)); /* length */
+ switch (sbpr[i].encoding) {
+ case sbpr_dohpath:
+ /*
+ * Apply constraints not applied by multitxt_fromtext.
+ */
+ keyregion.base = isc_buffer_used(&sb);
+ keyregion.length = isc_buffer_usedlength(target) -
+ isc_buffer_usedlength(&sb);
+ RETERR(svcb_validate(sbpr[i].value, &keyregion));
+ break;
+ default:
+ break;
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ RETERR(svc_keyfromregion(region, '=', &key, target));
+ if (region->length == 0) {
+ RETERR(uint16_tobuffer(0, target)); /* length */
+ /* Sanity check keyXXXXX form. */
+ keyregion.base = isc_buffer_used(target);
+ keyregion.length = 0;
+ return (svcb_validate(key, &keyregion));
+ }
+ sb = *target;
+ RETERR(uint16_tobuffer(0, target)); /* dummy length */
+ RETERR(multitxt_fromtext(region, target));
+ len = isc_buffer_usedlength(target) - isc_buffer_usedlength(&sb) - 2;
+ RETERR(uint16_tobuffer(len, &sb)); /* length */
+ /* Sanity check keyXXXXX form. */
+ keyregion.base = isc_buffer_used(&sb);
+ keyregion.length = len;
+ return (svcb_validate(key, &keyregion));
+}
+
+static const char *
+svcparamkey(unsigned short value, enum encoding *encoding, char *buf,
+ size_t len) {
+ size_t i;
+ int n;
+
+ for (i = 0; i < ARRAYSIZE(sbpr); i++) {
+ if (sbpr[i].value == value && sbpr[i].initial) {
+ *encoding = sbpr[i].encoding;
+ return (sbpr[i].name);
+ }
+ }
+ n = snprintf(buf, len, "key%u", value);
+ INSIST(n > 0 && (unsigned)n < len);
+ *encoding = sbpr_text;
+ return (buf);
+}
+
+static isc_result_t
+svcsortkeys(isc_buffer_t *target, unsigned int used) {
+ isc_region_t r1, r2, man = { .base = NULL, .length = 0 };
+ unsigned char buf[1024];
+ uint16_t mankey = 0;
+ bool have_alpn = false;
+
+ if (isc_buffer_usedlength(target) == used) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Get the parameters into r1.
+ */
+ isc_buffer_usedregion(target, &r1);
+ isc_region_consume(&r1, used);
+
+ while (1) {
+ uint16_t key1, len1, key2, len2;
+ unsigned char *base1, *base2;
+
+ r2 = r1;
+
+ /*
+ * Get the first parameter.
+ */
+ base1 = r1.base;
+ key1 = uint16_fromregion(&r1);
+ isc_region_consume(&r1, 2);
+ len1 = uint16_fromregion(&r1);
+ isc_region_consume(&r1, 2);
+ isc_region_consume(&r1, len1);
+
+ /*
+ * Was there only one key left?
+ */
+ if (r1.length == 0) {
+ if (mankey != 0) {
+ /* Is this the last mandatory key? */
+ if (key1 != mankey || man.length != 0) {
+ return (DNS_R_INCONSISTENTRR);
+ }
+ } else if (key1 == SVCB_MAN_KEY) {
+ /* Lone mandatory field. */
+ return (DNS_R_DISALLOWED);
+ } else if (key1 == SVCB_NO_DEFAULT_ALPN_KEY &&
+ !have_alpn)
+ {
+ /* Missing required ALPN field. */
+ return (DNS_R_DISALLOWED);
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Find the smallest parameter.
+ */
+ while (r1.length != 0) {
+ base2 = r1.base;
+ key2 = uint16_fromregion(&r1);
+ isc_region_consume(&r1, 2);
+ len2 = uint16_fromregion(&r1);
+ isc_region_consume(&r1, 2);
+ isc_region_consume(&r1, len2);
+ if (key2 == key1) {
+ return (DNS_R_DUPLICATE);
+ }
+ if (key2 < key1) {
+ base1 = base2;
+ key1 = key2;
+ len1 = len2;
+ }
+ }
+
+ /*
+ * Do we need to move the smallest parameter to the start?
+ */
+ if (base1 != r2.base) {
+ size_t offset = 0;
+ size_t bytes = len1 + 4;
+ size_t length = base1 - r2.base;
+
+ /*
+ * Move the smallest parameter to the start.
+ */
+ while (bytes > 0) {
+ size_t count;
+
+ if (bytes > sizeof(buf)) {
+ count = sizeof(buf);
+ } else {
+ count = bytes;
+ }
+ memmove(buf, base1, count);
+ memmove(r2.base + offset + count,
+ r2.base + offset, length);
+ memmove(r2.base + offset, buf, count);
+ base1 += count;
+ bytes -= count;
+ offset += count;
+ }
+ }
+
+ /*
+ * Check ALPN is present when NO-DEFAULT-ALPN is set.
+ */
+ if (key1 == SVCB_ALPN_KEY) {
+ have_alpn = true;
+ } else if (key1 == SVCB_NO_DEFAULT_ALPN_KEY && !have_alpn) {
+ /* Missing required ALPN field. */
+ return (DNS_R_DISALLOWED);
+ }
+
+ /*
+ * Check key against mandatory key list.
+ */
+ if (mankey != 0) {
+ if (key1 > mankey) {
+ return (DNS_R_INCONSISTENTRR);
+ }
+ if (key1 == mankey) {
+ if (man.length >= 2) {
+ mankey = uint16_fromregion(&man);
+ isc_region_consume(&man, 2);
+ } else {
+ mankey = 0;
+ }
+ }
+ }
+
+ /*
+ * Is this the mandatory key?
+ */
+ if (key1 == SVCB_MAN_KEY) {
+ man = r2;
+ man.length = len1 + 4;
+ isc_region_consume(&man, 4);
+ if (man.length >= 2) {
+ mankey = uint16_fromregion(&man);
+ isc_region_consume(&man, 2);
+ if (mankey == SVCB_MAN_KEY) {
+ return (DNS_R_DISALLOWED);
+ }
+ } else {
+ return (DNS_R_SYNTAX);
+ }
+ }
+
+ /*
+ * Consume the smallest parameter.
+ */
+ isc_region_consume(&r2, len1 + 4);
+ r1 = r2;
+ }
+}
+
+static isc_result_t
+generic_fromtext_in_svcb(ARGS_FROMTEXT) {
+ isc_token_t token;
+ dns_name_t name;
+ isc_buffer_t buffer;
+ bool alias;
+ bool ok = true;
+ unsigned int used;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ /*
+ * SvcPriority.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
+ false));
+ if (token.value.as_ulong > 0xffffU) {
+ RETTOK(ISC_R_RANGE);
+ }
+ RETERR(uint16_tobuffer(token.value.as_ulong, target));
+
+ alias = token.value.as_ulong == 0;
+
+ /*
+ * TargetName.
+ */
+ RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
+ false));
+ dns_name_init(&name, NULL);
+ buffer_fromregion(&buffer, &token.value.as_region);
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+ RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ if (!alias && (options & DNS_RDATA_CHECKNAMES) != 0) {
+ ok = dns_name_ishostname(&name, false);
+ }
+ if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETTOK(DNS_R_BADNAME);
+ }
+ if (!ok && callbacks != NULL) {
+ warn_badname(&name, lexer, callbacks);
+ }
+
+ if (alias) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * SvcParams
+ */
+ used = isc_buffer_usedlength(target);
+ while (1) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_qvpair, true));
+ if (token.type == isc_tokentype_eol ||
+ token.type == isc_tokentype_eof)
+ {
+ isc_lex_ungettoken(lexer, &token);
+ return (svcsortkeys(target, used));
+ }
+
+ if (token.type != isc_tokentype_string && /* key only */
+ token.type != isc_tokentype_qvpair &&
+ token.type != isc_tokentype_vpair)
+ {
+ RETTOK(DNS_R_SYNTAX);
+ }
+ RETTOK(svc_fromtext(&token.value.as_textregion, target));
+ }
+}
+
+static isc_result_t
+fromtext_in_svcb(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_svcb);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ return (generic_fromtext_in_svcb(CALL_FROMTEXT));
+}
+
+static isc_result_t
+generic_totext_in_svcb(ARGS_TOTEXT) {
+ isc_region_t region;
+ dns_name_t name;
+ dns_name_t prefix;
+ bool sub;
+ char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")];
+ unsigned short num;
+ int n;
+
+ REQUIRE(rdata->length != 0);
+
+ dns_name_init(&name, NULL);
+ dns_name_init(&prefix, NULL);
+
+ dns_rdata_toregion(rdata, &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));
+
+ RETERR(name_duporclone(&name, mctx, &svcb->svcdomain));
+ svcb->svclen = region.length;
+ svcb->svc = mem_maybedup(mctx, region.base, region.length);
+
+ if (svcb->svc == NULL) {
+ if (mctx != NULL) {
+ dns_name_free(&svcb->svcdomain, svcb->mctx);
+ }
+ return (ISC_R_NOMEMORY);
+ }
+
+ svcb->offset = 0;
+ svcb->mctx = mctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+tostruct_in_svcb(ARGS_TOSTRUCT) {
+ dns_rdata_in_svcb_t *svcb = target;
+
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->type == dns_rdatatype_svcb);
+ REQUIRE(svcb != NULL);
+ REQUIRE(rdata->length != 0);
+
+ return (generic_tostruct_in_svcb(CALL_TOSTRUCT));
+}
+
+static void
+generic_freestruct_in_svcb(ARGS_FREESTRUCT) {
+ dns_rdata_in_svcb_t *svcb = source;
+
+ REQUIRE(svcb != NULL);
+
+ if (svcb->mctx == NULL) {
+ return;
+ }
+
+ dns_name_free(&svcb->svcdomain, svcb->mctx);
+ isc_mem_free(svcb->mctx, svcb->svc);
+ svcb->mctx = NULL;
+}
+
+static void
+freestruct_in_svcb(ARGS_FREESTRUCT) {
+ dns_rdata_in_svcb_t *svcb = source;
+
+ REQUIRE(svcb != NULL);
+ REQUIRE(svcb->common.rdclass == dns_rdataclass_in);
+ REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb);
+
+ generic_freestruct_in_svcb(CALL_FREESTRUCT);
+}
+
+static isc_result_t
+generic_additionaldata_in_svcb(ARGS_ADDLDATA) {
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+additionaldata_in_svcb(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_svcb);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ return (generic_additionaldata_in_svcb(CALL_ADDLDATA));
+}
+
+static isc_result_t
+digest_in_svcb(ARGS_DIGEST) {
+ isc_region_t region1;
+
+ REQUIRE(rdata->type == dns_rdatatype_svcb);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &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..e4ddc64
--- /dev/null
+++ b/lib/dns/rdata/in_1/svcb_64.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_SVCB_64_H
+#define IN_1_SVCB_64_H 1
+
+/*!
+ * \brief Per draft-ietf-dnsop-svcb-https-02
+ */
+
+typedef struct dns_rdata_in_svcb {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ uint16_t priority;
+ dns_name_t svcdomain;
+ unsigned char *svc;
+ uint16_t svclen;
+ uint16_t offset;
+} dns_rdata_in_svcb_t;
+
+isc_result_t
+dns_rdata_in_svcb_first(dns_rdata_in_svcb_t *);
+
+isc_result_t
+dns_rdata_in_svcb_next(dns_rdata_in_svcb_t *);
+
+void
+dns_rdata_in_svcb_current(dns_rdata_in_svcb_t *, isc_region_t *);
+
+#endif /* IN_1_SVCB_64_H */
diff --git a/lib/dns/rdata/in_1/wks_11.c b/lib/dns/rdata/in_1/wks_11.c
new file mode 100644
index 0000000..bf9f830
--- /dev/null
+++ b/lib/dns/rdata/in_1/wks_11.c
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef RDATA_IN_1_WKS_11_C
+#define RDATA_IN_1_WKS_11_C
+
+#include <limits.h>
+#include <stdlib.h>
+
+#include <isc/net.h>
+#include <isc/netdb.h>
+#include <isc/once.h>
+
+/*
+ * Redefine CHECK here so cppcheck "sees" the define.
+ */
+#ifndef CHECK
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+#endif /* ifndef CHECK */
+
+#define RRTYPE_WKS_ATTRIBUTES (0)
+
+static isc_mutex_t wks_lock;
+
+static void
+init_lock(void) {
+ isc_mutex_init(&wks_lock);
+}
+
+static bool
+mygetprotobyname(const char *name, long *proto) {
+ struct protoent *pe;
+
+ LOCK(&wks_lock);
+ pe = getprotobyname(name);
+ if (pe != NULL) {
+ *proto = pe->p_proto;
+ }
+ UNLOCK(&wks_lock);
+ return (pe != NULL);
+}
+
+static bool
+mygetservbyname(const char *name, const char *proto, long *port) {
+ struct servent *se;
+
+ LOCK(&wks_lock);
+ se = getservbyname(name, proto);
+ if (se != NULL) {
+ *port = ntohs(se->s_port);
+ }
+ UNLOCK(&wks_lock);
+ return (se != NULL);
+}
+
+#ifdef _WIN32
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif /* ifdef _WIN32 */
+
+static isc_result_t
+fromtext_in_wks(ARGS_FROMTEXT) {
+ static isc_once_t once = ISC_ONCE_INIT;
+ isc_token_t token;
+ isc_region_t region;
+ struct in_addr addr;
+ char *e = NULL;
+ long proto;
+ unsigned char bm[8 * 1024]; /* 64k bits */
+ long port;
+ long maxport = -1;
+ const char *ps = NULL;
+ unsigned int n;
+ char service[32];
+ int i;
+ isc_result_t result;
+
+ REQUIRE(type == dns_rdatatype_wks);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(origin);
+ UNUSED(options);
+ UNUSED(rdclass);
+ UNUSED(callbacks);
+
+ RUNTIME_CHECK(isc_once_do(&once, init_lock) == ISC_R_SUCCESS);
+
+#ifdef _WIN32
+ {
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ int err;
+
+ wVersionRequested = MAKEWORD(2, 0);
+
+ err = WSAStartup(wVersionRequested, &wsaData);
+ if (err != 0) {
+ return (ISC_R_FAILURE);
+ }
+ }
+#endif /* ifdef _WIN32 */
+
+ /*
+ * IPv4 dotted quad.
+ */
+ CHECK(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
+ false));
+
+ isc_buffer_availableregion(target, &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:
+#ifdef _WIN32
+ WSACleanup();
+#endif /* ifdef _WIN32 */
+
+ return (result);
+}
+
+static isc_result_t
+totext_in_wks(ARGS_TOTEXT) {
+ isc_region_t sr;
+ unsigned short proto;
+ char buf[sizeof("65535")];
+ unsigned int i, j;
+
+ UNUSED(tctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length >= 5);
+
+ dns_rdata_toregion(rdata, &sr);
+ RETERR(inet_totext(AF_INET, tctx->flags, &sr, target));
+ isc_region_consume(&sr, 4);
+
+ proto = uint8_fromregion(&sr);
+ snprintf(buf, sizeof(buf), "%u", proto);
+ RETERR(str_totext(" ", target));
+ RETERR(str_totext(buf, target));
+ isc_region_consume(&sr, 1);
+
+ INSIST(sr.length <= 8 * 1024);
+ for (i = 0; i < sr.length; i++) {
+ if (sr.base[i] != 0) {
+ for (j = 0; j < 8; j++) {
+ if ((sr.base[i] & (0x80 >> j)) != 0) {
+ {
+ snprintf(buf, sizeof(buf), "%u",
+ i * 8 + j);
+ RETERR(str_totext(" ", target));
+ RETERR(str_totext(buf, target));
+ }
+ }
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fromwire_in_wks(ARGS_FROMWIRE) {
+ isc_region_t sr;
+ isc_region_t tr;
+
+ REQUIRE(type == dns_rdatatype_wks);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(dctx);
+ UNUSED(options);
+ UNUSED(rdclass);
+
+ isc_buffer_activeregion(source, &sr);
+ isc_buffer_availableregion(target, &tr);
+
+ if (sr.length < 5) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (sr.length > 8 * 1024 + 5) {
+ return (DNS_R_EXTRADATA);
+ }
+ if (sr.length > 5 && sr.base[sr.length - 1] == 0) {
+ return (DNS_R_FORMERR);
+ }
+ if (tr.length < sr.length) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memmove(tr.base, sr.base, sr.length);
+ isc_buffer_add(target, sr.length);
+ isc_buffer_forward(source, sr.length);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+towire_in_wks(ARGS_TOWIRE) {
+ isc_region_t sr;
+
+ UNUSED(cctx);
+
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, &sr);
+ return (mem_tobuffer(target, sr.base, sr.length));
+}
+
+static int
+compare_in_wks(ARGS_COMPARE) {
+ isc_region_t r1;
+ isc_region_t r2;
+
+ REQUIRE(rdata1->type == rdata2->type);
+ REQUIRE(rdata1->rdclass == rdata2->rdclass);
+ REQUIRE(rdata1->type == dns_rdatatype_wks);
+ REQUIRE(rdata1->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata1->length != 0);
+ REQUIRE(rdata2->length != 0);
+
+ dns_rdata_toregion(rdata1, &r1);
+ dns_rdata_toregion(rdata2, &r2);
+ return (isc_region_compare(&r1, &r2));
+}
+
+static isc_result_t
+fromstruct_in_wks(ARGS_FROMSTRUCT) {
+ dns_rdata_in_wks_t *wks = source;
+ uint32_t a;
+
+ REQUIRE(type == dns_rdatatype_wks);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(wks != NULL);
+ REQUIRE(wks->common.rdtype == type);
+ REQUIRE(wks->common.rdclass == rdclass);
+ REQUIRE((wks->map != NULL && wks->map_len <= 8 * 1024) ||
+ wks->map_len == 0);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ a = ntohl(wks->in_addr.s_addr);
+ RETERR(uint32_tobuffer(a, target));
+ RETERR(uint8_tobuffer(wks->protocol, target));
+ return (mem_tobuffer(target, wks->map, wks->map_len));
+}
+
+static isc_result_t
+tostruct_in_wks(ARGS_TOSTRUCT) {
+ dns_rdata_in_wks_t *wks = target;
+ uint32_t n;
+ isc_region_t region;
+
+ REQUIRE(wks != NULL);
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ wks->common.rdclass = rdata->rdclass;
+ wks->common.rdtype = rdata->type;
+ ISC_LINK_INIT(&wks->common, link);
+
+ dns_rdata_toregion(rdata, &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) {
+ UNUSED(rdata);
+ UNUSED(add);
+ UNUSED(arg);
+
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+digest_in_wks(ARGS_DIGEST) {
+ isc_region_t r;
+
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, &r);
+
+ return ((digest)(arg, &r));
+}
+
+static bool
+checkowner_in_wks(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_wks);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return (dns_name_ishostname(name, wildcard));
+}
+
+static bool
+checknames_in_wks(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_wks);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(bad);
+
+ return (true);
+}
+
+static int
+casecompare_in_wks(ARGS_COMPARE) {
+ return (compare_in_wks(rdata1, rdata2));
+}
+
+#endif /* RDATA_IN_1_WKS_11_C */
diff --git a/lib/dns/rdata/in_1/wks_11.h b/lib/dns/rdata/in_1/wks_11.h
new file mode 100644
index 0000000..9a926b1
--- /dev/null
+++ b/lib/dns/rdata/in_1/wks_11.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IN_1_WKS_11_H
+#define IN_1_WKS_11_H 1
+
+typedef struct dns_rdata_in_wks {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ struct in_addr in_addr;
+ uint16_t protocol;
+ unsigned char *map;
+ uint16_t map_len;
+} dns_rdata_in_wks_t;
+
+#endif /* IN_1_WKS_11_H */
diff --git a/lib/dns/rdata/rdatastructpre.h b/lib/dns/rdata/rdatastructpre.h
new file mode 100644
index 0000000..eb96ba8
--- /dev/null
+++ b/lib/dns/rdata/rdatastructpre.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RDATASTRUCT_H
+#define DNS_RDATASTRUCT_H 1
+
+#include <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..f242908
--- /dev/null
+++ b/lib/dns/rdata/rdatastructsuf.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_RDATASTRUCT_H */
diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c
new file mode 100644
index 0000000..a2643c7
--- /dev/null
+++ b/lib/dns/rdatalist.c
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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..adc4496
--- /dev/null
+++ b/lib/dns/rdatalist_p.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_RDATALIST_P_H
+#define DNS_RDATALIST_P_H
+
+/*! \file */
+
+#include <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
+
+#endif /* DNS_RDATALIST_P_H */
diff --git a/lib/dns/rdataset.c b/lib/dns/rdataset.c
new file mode 100644
index 0000000..221d7f8
--- /dev/null
+++ b/lib/dns/rdataset.c
@@ -0,0 +1,750 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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]);
+}
+
+#define DNS_RDATASET_COUNT_UNDEFINED UINT32_MAX
+
+void
+dns_rdataset_init(dns_rdataset_t *rdataset) {
+ /*
+ * Make 'rdataset' a valid, disassociated rdataset.
+ */
+
+ REQUIRE(rdataset != NULL);
+
+ rdataset->magic = DNS_RDATASET_MAGIC;
+ rdataset->methods = NULL;
+ ISC_LINK_INIT(rdataset, link);
+ rdataset->rdclass = 0;
+ rdataset->type = 0;
+ rdataset->ttl = 0;
+ rdataset->trust = 0;
+ rdataset->covers = 0;
+ rdataset->attributes = 0;
+ rdataset->count = DNS_RDATASET_COUNT_UNDEFINED;
+ rdataset->private1 = NULL;
+ rdataset->private2 = NULL;
+ rdataset->private3 = NULL;
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+ rdataset->private6 = NULL;
+ rdataset->private7 = NULL;
+ rdataset->resign = 0;
+}
+
+void
+dns_rdataset_invalidate(dns_rdataset_t *rdataset) {
+ /*
+ * Invalidate 'rdataset'.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods == NULL);
+
+ rdataset->magic = 0;
+ ISC_LINK_INIT(rdataset, link);
+ rdataset->rdclass = 0;
+ rdataset->type = 0;
+ rdataset->ttl = 0;
+ rdataset->trust = 0;
+ rdataset->covers = 0;
+ rdataset->attributes = 0;
+ rdataset->count = DNS_RDATASET_COUNT_UNDEFINED;
+ rdataset->private1 = NULL;
+ rdataset->private2 = NULL;
+ rdataset->private3 = NULL;
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+}
+
+void
+dns_rdataset_disassociate(dns_rdataset_t *rdataset) {
+ /*
+ * Disassociate 'rdataset' from its rdata, allowing it to be reused.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ (rdataset->methods->disassociate)(rdataset);
+ rdataset->methods = NULL;
+ ISC_LINK_INIT(rdataset, link);
+ rdataset->rdclass = 0;
+ rdataset->type = 0;
+ rdataset->ttl = 0;
+ rdataset->trust = 0;
+ rdataset->covers = 0;
+ rdataset->attributes = 0;
+ rdataset->count = DNS_RDATASET_COUNT_UNDEFINED;
+ rdataset->private1 = NULL;
+ rdataset->private2 = NULL;
+ rdataset->private3 = NULL;
+ rdataset->privateuint4 = 0;
+ rdataset->private5 = NULL;
+ rdataset->private6 = NULL;
+}
+
+bool
+dns_rdataset_isassociated(dns_rdataset_t *rdataset) {
+ /*
+ * Is 'rdataset' associated?
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+
+ if (rdataset->methods != NULL) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static void
+question_disassociate(dns_rdataset_t *rdataset) {
+ UNUSED(rdataset);
+}
+
+static isc_result_t
+question_cursor(dns_rdataset_t *rdataset) {
+ UNUSED(rdataset);
+
+ return (ISC_R_NOMORE);
+}
+
+static void
+question_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ /*
+ * This routine should never be called.
+ */
+ UNUSED(rdataset);
+ UNUSED(rdata);
+
+ REQUIRE(0);
+}
+
+static void
+question_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ *target = *source;
+}
+
+static unsigned int
+question_count(dns_rdataset_t *rdataset) {
+ /*
+ * This routine should never be called.
+ */
+ UNUSED(rdataset);
+ REQUIRE(0);
+
+ return (0);
+}
+
+static dns_rdatasetmethods_t question_methods = {
+ question_disassociate,
+ question_cursor,
+ question_cursor,
+ question_current,
+ question_clone,
+ question_count,
+ NULL, /* addnoqname */
+ NULL, /* getnoqname */
+ NULL, /* addclosest */
+ NULL, /* getclosest */
+ NULL, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL, /* setownercase */
+ NULL, /* getownercase */
+ NULL /* addglue */
+};
+
+void
+dns_rdataset_makequestion(dns_rdataset_t *rdataset, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type) {
+ /*
+ * Make 'rdataset' a valid, associated, question rdataset, with a
+ * question class of 'rdclass' and type 'type'.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods == NULL);
+
+ rdataset->methods = &question_methods;
+ rdataset->rdclass = rdclass;
+ rdataset->type = type;
+ rdataset->attributes |= DNS_RDATASETATTR_QUESTION;
+}
+
+unsigned int
+dns_rdataset_count(dns_rdataset_t *rdataset) {
+ /*
+ * Return the number of records in 'rdataset'.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ return ((rdataset->methods->count)(rdataset));
+}
+
+void
+dns_rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ /*
+ * Make 'target' refer to the same rdataset as 'source'.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(source));
+ REQUIRE(source->methods != NULL);
+ REQUIRE(DNS_RDATASET_VALID(target));
+ REQUIRE(target->methods == NULL);
+
+ (source->methods->clone)(source, target);
+}
+
+isc_result_t
+dns_rdataset_first(dns_rdataset_t *rdataset) {
+ /*
+ * Move the rdata cursor to the first rdata in the rdataset (if any).
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ return ((rdataset->methods->first)(rdataset));
+}
+
+isc_result_t
+dns_rdataset_next(dns_rdataset_t *rdataset) {
+ /*
+ * Move the rdata cursor to the next rdata in the rdataset (if any).
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ return ((rdataset->methods->next)(rdataset));
+}
+
+void
+dns_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
+ /*
+ * Make 'rdata' refer to the current rdata.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ (rdataset->methods->current)(rdataset, rdata);
+}
+
+#define MAX_SHUFFLE 32
+#define WANT_FIXED(r) (((r)->attributes & DNS_RDATASETATTR_FIXEDORDER) != 0)
+#define WANT_RANDOM(r) (((r)->attributes & DNS_RDATASETATTR_RANDOMIZE) != 0)
+#define WANT_CYCLIC(r) (((r)->attributes & DNS_RDATASETATTR_CYCLIC) != 0)
+
+struct towire_sort {
+ int key;
+ dns_rdata_t *rdata;
+};
+
+static int
+towire_compare(const void *av, const void *bv) {
+ const struct towire_sort *a = (const struct towire_sort *)av;
+ const struct towire_sort *b = (const struct towire_sort *)bv;
+ return (a->key - b->key);
+}
+
+static void
+swap_rdata(dns_rdata_t *in, unsigned int a, unsigned int b) {
+ dns_rdata_t rdata = in[a];
+ in[a] = in[b];
+ in[b] = rdata;
+}
+
+static isc_result_t
+towiresorted(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_compress_t *cctx, isc_buffer_t *target,
+ dns_rdatasetorderfunc_t order, const void *order_arg, bool partial,
+ unsigned int options, unsigned int *countp, void **state) {
+ isc_region_t r;
+ isc_result_t result;
+ unsigned int i, count = 0, added;
+ isc_buffer_t savedbuffer, rdlen, rrbuffer;
+ unsigned int headlen;
+ bool question = false;
+ bool shuffle = false, sort = false;
+ bool want_random, want_cyclic;
+ dns_rdata_t in_fixed[MAX_SHUFFLE];
+ dns_rdata_t *in = in_fixed;
+ struct towire_sort out_fixed[MAX_SHUFFLE];
+ struct towire_sort *out = out_fixed;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ uint16_t offset;
+
+ UNUSED(state);
+
+ /*
+ * Convert 'rdataset' to wire format, compressing names as specified
+ * in cctx, and storing the result in 'target'.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+ REQUIRE(countp != NULL);
+ REQUIRE(cctx != NULL && cctx->mctx != NULL);
+
+ want_random = WANT_RANDOM(rdataset);
+ want_cyclic = WANT_CYCLIC(rdataset);
+
+ if ((rdataset->attributes & DNS_RDATASETATTR_QUESTION) != 0) {
+ question = true;
+ count = 1;
+ result = dns_rdataset_first(rdataset);
+ INSIST(result == ISC_R_NOMORE);
+ } else if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ /*
+ * This is a negative caching rdataset.
+ */
+ unsigned int ncache_opts = 0;
+ if ((options & DNS_RDATASETTOWIRE_OMITDNSSEC) != 0) {
+ ncache_opts |= DNS_NCACHETOWIRE_OMITDNSSEC;
+ }
+ return (dns_ncache_towire(rdataset, cctx, target, ncache_opts,
+ countp));
+ } else {
+ count = (rdataset->methods->count)(rdataset);
+ result = dns_rdataset_first(rdataset);
+ if (result == ISC_R_NOMORE) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ /*
+ * Do we want to sort and/or shuffle this answer?
+ */
+ if (!question && count > 1 && rdataset->type != dns_rdatatype_rrsig) {
+ if (order != NULL) {
+ sort = true;
+ }
+ if (want_random || want_cyclic) {
+ shuffle = true;
+ }
+ }
+
+ if ((shuffle || sort)) {
+ if (count > MAX_SHUFFLE) {
+ in = isc_mem_get(cctx->mctx, count * sizeof(*in));
+ out = isc_mem_get(cctx->mctx, count * sizeof(*out));
+ if (in == NULL || out == NULL) {
+ shuffle = sort = false;
+ }
+ }
+ }
+
+ if ((shuffle || sort)) {
+ uint32_t seed = 0;
+ unsigned int j = 0;
+
+ /*
+ * First we get handles to all of the rdata.
+ */
+ i = 0;
+ do {
+ INSIST(i < count);
+ dns_rdata_init(&in[i]);
+ dns_rdataset_current(rdataset, &in[i]);
+ i++;
+ result = dns_rdataset_next(rdataset);
+ } while (result == ISC_R_SUCCESS);
+ if (result != ISC_R_NOMORE) {
+ goto cleanup;
+ }
+ INSIST(i == count);
+
+ if (ISC_LIKELY(want_random)) {
+ seed = isc_random32();
+ }
+
+ if (ISC_UNLIKELY(want_cyclic) &&
+ (rdataset->count != DNS_RDATASET_COUNT_UNDEFINED))
+ {
+ j = rdataset->count % count;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (ISC_LIKELY(want_random)) {
+ swap_rdata(in, j, j + seed % (count - j));
+ }
+
+ out[i].key = (sort) ? (*order)(&in[j], order_arg) : 0;
+ out[i].rdata = &in[j];
+ if (++j == count) {
+ j = 0;
+ }
+ }
+ /*
+ * Sortlist order.
+ */
+ if (sort) {
+ qsort(out, count, sizeof(out[0]), towire_compare);
+ }
+ }
+
+ savedbuffer = *target;
+ i = 0;
+ added = 0;
+
+ name = dns_fixedname_initname(&fixed);
+ dns_name_copynf(owner_name, name);
+ dns_rdataset_getownercase(rdataset, name);
+ offset = 0xffff;
+
+ name->attributes |= owner_name->attributes & DNS_NAMEATTR_NOCOMPRESS;
+
+ do {
+ /*
+ * Copy out the name, type, class, ttl.
+ */
+
+ rrbuffer = *target;
+ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
+ result = dns_name_towire2(name, cctx, target, &offset);
+ if (result != ISC_R_SUCCESS) {
+ goto rollback;
+ }
+ headlen = sizeof(dns_rdataclass_t) + sizeof(dns_rdatatype_t);
+ if (!question) {
+ headlen += sizeof(dns_ttl_t) + 2;
+ } /* XXX 2 for rdata len
+ */
+ isc_buffer_availableregion(target, &r);
+ if (r.length < headlen) {
+ result = ISC_R_NOSPACE;
+ goto rollback;
+ }
+ isc_buffer_putuint16(target, rdataset->type);
+ isc_buffer_putuint16(target, rdataset->rdclass);
+ if (!question) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ isc_buffer_putuint32(target, rdataset->ttl);
+
+ /*
+ * Save space for rdlen.
+ */
+ rdlen = *target;
+ isc_buffer_add(target, 2);
+
+ /*
+ * Copy out the rdata
+ */
+ if (shuffle || sort) {
+ rdata = *(out[i].rdata);
+ } else {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(rdataset, &rdata);
+ }
+ result = dns_rdata_towire(&rdata, cctx, target);
+ if (result != ISC_R_SUCCESS) {
+ goto rollback;
+ }
+ INSIST((target->used >= rdlen.used + 2) &&
+ (target->used - rdlen.used - 2 < 65536));
+ isc_buffer_putuint16(
+ &rdlen,
+ (uint16_t)(target->used - rdlen.used - 2));
+ added++;
+ }
+
+ if (shuffle || sort) {
+ i++;
+ if (i == count) {
+ result = ISC_R_NOMORE;
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ } else {
+ result = dns_rdataset_next(rdataset);
+ }
+ } while (result == ISC_R_SUCCESS);
+
+ if (result != ISC_R_NOMORE) {
+ goto rollback;
+ }
+
+ *countp += count;
+
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+
+rollback:
+ if (partial && result == ISC_R_NOSPACE) {
+ INSIST(rrbuffer.used < 65536);
+ dns_compress_rollback(cctx, (uint16_t)rrbuffer.used);
+ *countp += added;
+ *target = rrbuffer;
+ goto cleanup;
+ }
+ INSIST(savedbuffer.used < 65536);
+ dns_compress_rollback(cctx, (uint16_t)savedbuffer.used);
+ *countp = 0;
+ *target = savedbuffer;
+
+cleanup:
+ if (out != NULL && out != out_fixed) {
+ isc_mem_put(cctx->mctx, out, count * sizeof(*out));
+ }
+ if (in != NULL && in != in_fixed) {
+ isc_mem_put(cctx->mctx, in, count * sizeof(*in));
+ }
+ return (result);
+}
+
+isc_result_t
+dns_rdataset_towiresorted(dns_rdataset_t *rdataset,
+ const dns_name_t *owner_name, dns_compress_t *cctx,
+ isc_buffer_t *target, dns_rdatasetorderfunc_t order,
+ const void *order_arg, unsigned int options,
+ unsigned int *countp) {
+ return (towiresorted(rdataset, owner_name, cctx, target, order,
+ order_arg, false, options, countp, NULL));
+}
+
+isc_result_t
+dns_rdataset_towirepartial(dns_rdataset_t *rdataset,
+ const dns_name_t *owner_name, dns_compress_t *cctx,
+ isc_buffer_t *target, dns_rdatasetorderfunc_t order,
+ const void *order_arg, unsigned int options,
+ unsigned int *countp, void **state) {
+ REQUIRE(state == NULL); /* XXX remove when implemented */
+ return (towiresorted(rdataset, owner_name, cctx, target, order,
+ order_arg, true, options, countp, state));
+}
+
+isc_result_t
+dns_rdataset_towire(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
+ dns_compress_t *cctx, isc_buffer_t *target,
+ unsigned int options, unsigned int *countp) {
+ return (towiresorted(rdataset, owner_name, cctx, target, NULL, NULL,
+ false, options, countp, NULL));
+}
+
+isc_result_t
+dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
+ dns_additionaldatafunc_t add, void *arg) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+
+ /*
+ * For each rdata in rdataset, call 'add' for each name and type in the
+ * rdata which is subject to additional section processing.
+ */
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE((rdataset->attributes & DNS_RDATASETATTR_QUESTION) == 0);
+
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ do {
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_additionaldata(&rdata, add, arg);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_rdataset_next(rdataset);
+ }
+ dns_rdata_reset(&rdata);
+ } while (result == ISC_R_SUCCESS);
+
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+ if (rdataset->methods->addnoqname == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((rdataset->methods->addnoqname)(rdataset, name));
+}
+
+isc_result_t
+dns_rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->getnoqname == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((rdataset->methods->getnoqname)(rdataset, name, neg, negsig));
+}
+
+isc_result_t
+dns_rdataset_addclosest(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+ if (rdataset->methods->addclosest == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((rdataset->methods->addclosest)(rdataset, name));
+}
+
+isc_result_t
+dns_rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
+ dns_rdataset_t *neg, dns_rdataset_t *negsig) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->getclosest == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ return ((rdataset->methods->getclosest)(rdataset, name, neg, negsig));
+}
+
+void
+dns_rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->settrust != NULL) {
+ (rdataset->methods->settrust)(rdataset, trust);
+ } else {
+ rdataset->trust = trust;
+ }
+}
+
+void
+dns_rdataset_expire(dns_rdataset_t *rdataset) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->expire != NULL) {
+ (rdataset->methods->expire)(rdataset);
+ }
+}
+
+void
+dns_rdataset_clearprefetch(dns_rdataset_t *rdataset) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->clearprefetch != NULL) {
+ (rdataset->methods->clearprefetch)(rdataset);
+ }
+}
+
+void
+dns_rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->setownercase != NULL) {
+ (rdataset->methods->setownercase)(rdataset, name);
+ }
+}
+
+void
+dns_rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+
+ if (rdataset->methods->getownercase != NULL) {
+ (rdataset->methods->getownercase)(rdataset, name);
+ }
+}
+
+void
+dns_rdataset_trimttl(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_rdata_rrsig_t *rrsig, isc_stdtime_t now,
+ bool acceptexpired) {
+ uint32_t ttl = 0;
+
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(DNS_RDATASET_VALID(sigrdataset));
+ REQUIRE(rrsig != NULL);
+
+ /*
+ * If we accept expired RRsets keep them for no more than 120 seconds.
+ */
+ if (acceptexpired &&
+ (isc_serial_le(rrsig->timeexpire, ((now + 120) & 0xffffffff)) ||
+ isc_serial_le(rrsig->timeexpire, now)))
+ {
+ ttl = 120;
+ } else if (isc_serial_ge(rrsig->timeexpire, now)) {
+ ttl = rrsig->timeexpire - now;
+ }
+
+ ttl = ISC_MIN(ISC_MIN(rdataset->ttl, sigrdataset->ttl),
+ ISC_MIN(rrsig->originalttl, ttl));
+ rdataset->ttl = ttl;
+ sigrdataset->ttl = ttl;
+}
+
+isc_result_t
+dns_rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version,
+ dns_message_t *msg) {
+ REQUIRE(DNS_RDATASET_VALID(rdataset));
+ REQUIRE(rdataset->methods != NULL);
+ REQUIRE(rdataset->type == dns_rdatatype_ns);
+
+ if (rdataset->methods->addglue == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ return ((rdataset->methods->addglue)(rdataset, version, msg));
+}
diff --git a/lib/dns/rdatasetiter.c b/lib/dns/rdatasetiter.c
new file mode 100644
index 0000000..8e8159f
--- /dev/null
+++ b/lib/dns/rdatasetiter.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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..2783c1c
--- /dev/null
+++ b/lib/dns/rdataslab.c
@@ -0,0 +1,1005 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/mem.h>
+#include <isc/region.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>
+#include <dns/result.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..2e21ca6
--- /dev/null
+++ b/lib/dns/request.c
@@ -0,0 +1,1545 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/task.h>
+#include <isc/timer.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/result.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_mutex_t lock;
+ isc_mem_t *mctx;
+
+ /* locked */
+ int32_t eref;
+ int32_t iref;
+ isc_timermgr_t *timermgr;
+ isc_socketmgr_t *socketmgr;
+ isc_taskmgr_t *taskmgr;
+ dns_dispatchmgr_t *dispatchmgr;
+ dns_dispatch_t *dispatchv4;
+ dns_dispatch_t *dispatchv6;
+ bool exiting;
+ isc_eventlist_t whenshutdown;
+ unsigned int hash;
+ isc_mutex_t locks[DNS_REQUEST_NLOCKS];
+ dns_requestlist_t requests;
+};
+
+struct dns_request {
+ unsigned int magic;
+ unsigned int hash;
+ isc_mem_t *mctx;
+ int32_t flags;
+ ISC_LINK(dns_request_t) link;
+ isc_buffer_t *query;
+ isc_buffer_t *answer;
+ dns_requestevent_t *event;
+ dns_dispatch_t *dispatch;
+ dns_dispentry_t *dispentry;
+ isc_timer_t *timer;
+ dns_requestmgr_t *requestmgr;
+ isc_buffer_t *tsig;
+ dns_tsigkey_t *tsigkey;
+ isc_event_t ctlevent;
+ bool canceling; /* ctlevent outstanding */
+ isc_sockaddr_t destaddr;
+ unsigned int udpcount;
+ isc_dscp_t dscp;
+};
+
+#define DNS_REQUEST_F_CONNECTING 0x0001
+#define DNS_REQUEST_F_SENDING 0x0002
+#define DNS_REQUEST_F_CANCELED \
+ 0x0004 /*%< ctlevent received, or otherwise \
+ * synchronously canceled */
+#define DNS_REQUEST_F_TIMEDOUT 0x0008 /*%< canceled due to a timeout */
+#define DNS_REQUEST_F_TCP 0x0010 /*%< This request used TCP */
+#define DNS_REQUEST_CANCELED(r) (((r)->flags & DNS_REQUEST_F_CANCELED) != 0)
+#define DNS_REQUEST_CONNECTING(r) (((r)->flags & DNS_REQUEST_F_CONNECTING) != 0)
+#define DNS_REQUEST_SENDING(r) (((r)->flags & DNS_REQUEST_F_SENDING) != 0)
+#define DNS_REQUEST_TIMEDOUT(r) (((r)->flags & DNS_REQUEST_F_TIMEDOUT) != 0)
+
+/***
+ *** Forward
+ ***/
+
+static void
+mgr_destroy(dns_requestmgr_t *requestmgr);
+static void
+mgr_shutdown(dns_requestmgr_t *requestmgr);
+static unsigned int
+mgr_gethash(dns_requestmgr_t *requestmgr);
+static void
+send_shutdown_events(dns_requestmgr_t *requestmgr);
+
+static isc_result_t
+req_render(dns_message_t *message, isc_buffer_t **buffer, unsigned int options,
+ isc_mem_t *mctx);
+static void
+req_senddone(isc_task_t *task, isc_event_t *event);
+static void
+req_response(isc_task_t *task, isc_event_t *event);
+static void
+req_timeout(isc_task_t *task, isc_event_t *event);
+static isc_socket_t *
+req_getsocket(dns_request_t *request);
+static void
+req_connected(isc_task_t *task, isc_event_t *event);
+static void
+req_sendevent(dns_request_t *request, isc_result_t result);
+static void
+req_cancel(dns_request_t *request);
+static void
+req_destroy(dns_request_t *request);
+static void
+req_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3);
+static void
+do_cancel(isc_task_t *task, isc_event_t *event);
+
+/***
+ *** Public
+ ***/
+
+isc_result_t
+dns_requestmgr_create(isc_mem_t *mctx, isc_timermgr_t *timermgr,
+ isc_socketmgr_t *socketmgr, isc_taskmgr_t *taskmgr,
+ dns_dispatchmgr_t *dispatchmgr,
+ dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6,
+ dns_requestmgr_t **requestmgrp) {
+ dns_requestmgr_t *requestmgr;
+ int i;
+ unsigned int dispattr;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_create");
+
+ REQUIRE(requestmgrp != NULL && *requestmgrp == NULL);
+ REQUIRE(timermgr != NULL);
+ REQUIRE(socketmgr != NULL);
+ REQUIRE(taskmgr != NULL);
+ REQUIRE(dispatchmgr != NULL);
+
+ if (dispatchv4 != NULL) {
+ dispattr = dns_dispatch_getattributes(dispatchv4);
+ REQUIRE((dispattr & DNS_DISPATCHATTR_UDP) != 0);
+ }
+ if (dispatchv6 != NULL) {
+ dispattr = dns_dispatch_getattributes(dispatchv6);
+ REQUIRE((dispattr & DNS_DISPATCHATTR_UDP) != 0);
+ }
+
+ requestmgr = isc_mem_get(mctx, sizeof(*requestmgr));
+
+ isc_mutex_init(&requestmgr->lock);
+
+ for (i = 0; i < DNS_REQUEST_NLOCKS; i++) {
+ isc_mutex_init(&requestmgr->locks[i]);
+ }
+ requestmgr->timermgr = timermgr;
+ requestmgr->socketmgr = socketmgr;
+ requestmgr->taskmgr = taskmgr;
+ requestmgr->dispatchmgr = dispatchmgr;
+ requestmgr->dispatchv4 = NULL;
+ if (dispatchv4 != NULL) {
+ dns_dispatch_attach(dispatchv4, &requestmgr->dispatchv4);
+ }
+ requestmgr->dispatchv6 = NULL;
+ if (dispatchv6 != NULL) {
+ dns_dispatch_attach(dispatchv6, &requestmgr->dispatchv6);
+ }
+ requestmgr->mctx = NULL;
+ isc_mem_attach(mctx, &requestmgr->mctx);
+ requestmgr->eref = 1; /* implicit attach */
+ requestmgr->iref = 0;
+ ISC_LIST_INIT(requestmgr->whenshutdown);
+ ISC_LIST_INIT(requestmgr->requests);
+ requestmgr->exiting = false;
+ requestmgr->hash = 0;
+ requestmgr->magic = REQUESTMGR_MAGIC;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_create: %p", requestmgr);
+
+ *requestmgrp = requestmgr;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_requestmgr_whenshutdown(dns_requestmgr_t *requestmgr, isc_task_t *task,
+ isc_event_t **eventp) {
+ isc_task_t *tclone;
+ isc_event_t *event;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_whenshutdown");
+
+ REQUIRE(VALID_REQUESTMGR(requestmgr));
+ REQUIRE(eventp != NULL);
+
+ event = *eventp;
+ *eventp = NULL;
+
+ LOCK(&requestmgr->lock);
+
+ if (requestmgr->exiting) {
+ /*
+ * We're already shutdown. Send the event.
+ */
+ event->ev_sender = requestmgr;
+ isc_task_send(task, &event);
+ } else {
+ tclone = NULL;
+ isc_task_attach(task, &tclone);
+ event->ev_sender = tclone;
+ ISC_LIST_APPEND(requestmgr->whenshutdown, event, ev_link);
+ }
+ UNLOCK(&requestmgr->lock);
+}
+
+void
+dns_requestmgr_shutdown(dns_requestmgr_t *requestmgr) {
+ REQUIRE(VALID_REQUESTMGR(requestmgr));
+
+ req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_shutdown: %p", requestmgr);
+
+ LOCK(&requestmgr->lock);
+ mgr_shutdown(requestmgr);
+ UNLOCK(&requestmgr->lock);
+}
+
+static void
+mgr_shutdown(dns_requestmgr_t *requestmgr) {
+ dns_request_t *request;
+
+ /*
+ * Caller holds lock.
+ */
+ if (!requestmgr->exiting) {
+ requestmgr->exiting = true;
+ for (request = ISC_LIST_HEAD(requestmgr->requests);
+ request != NULL; request = ISC_LIST_NEXT(request, link))
+ {
+ dns_request_cancel(request);
+ }
+ if (requestmgr->iref == 0) {
+ INSIST(ISC_LIST_EMPTY(requestmgr->requests));
+ send_shutdown_events(requestmgr);
+ }
+ }
+}
+
+static void
+requestmgr_attach(dns_requestmgr_t *source, dns_requestmgr_t **targetp) {
+ /*
+ * Locked by caller.
+ */
+
+ REQUIRE(VALID_REQUESTMGR(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ REQUIRE(!source->exiting);
+
+ source->iref++;
+ *targetp = source;
+
+ req_log(ISC_LOG_DEBUG(3), "requestmgr_attach: %p: eref %d iref %d",
+ source, source->eref, source->iref);
+}
+
+static void
+requestmgr_detach(dns_requestmgr_t **requestmgrp) {
+ dns_requestmgr_t *requestmgr;
+ bool need_destroy = false;
+
+ REQUIRE(requestmgrp != NULL);
+ requestmgr = *requestmgrp;
+ *requestmgrp = NULL;
+ REQUIRE(VALID_REQUESTMGR(requestmgr));
+
+ LOCK(&requestmgr->lock);
+ INSIST(requestmgr->iref > 0);
+ requestmgr->iref--;
+
+ req_log(ISC_LOG_DEBUG(3), "requestmgr_detach: %p: eref %d iref %d",
+ requestmgr, requestmgr->eref, requestmgr->iref);
+
+ if (requestmgr->iref == 0 && requestmgr->exiting) {
+ INSIST(ISC_LIST_HEAD(requestmgr->requests) == NULL);
+ send_shutdown_events(requestmgr);
+ if (requestmgr->eref == 0) {
+ need_destroy = true;
+ }
+ }
+ UNLOCK(&requestmgr->lock);
+
+ if (need_destroy) {
+ mgr_destroy(requestmgr);
+ }
+}
+
+void
+dns_requestmgr_attach(dns_requestmgr_t *source, dns_requestmgr_t **targetp) {
+ REQUIRE(VALID_REQUESTMGR(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+ REQUIRE(!source->exiting);
+
+ LOCK(&source->lock);
+ source->eref++;
+ *targetp = source;
+ UNLOCK(&source->lock);
+
+ req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_attach: %p: eref %d iref %d",
+ source, source->eref, source->iref);
+}
+
+void
+dns_requestmgr_detach(dns_requestmgr_t **requestmgrp) {
+ dns_requestmgr_t *requestmgr;
+ bool need_destroy = false;
+
+ REQUIRE(requestmgrp != NULL);
+ requestmgr = *requestmgrp;
+ *requestmgrp = NULL;
+ REQUIRE(VALID_REQUESTMGR(requestmgr));
+
+ LOCK(&requestmgr->lock);
+ INSIST(requestmgr->eref > 0);
+ requestmgr->eref--;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_detach: %p: eref %d iref %d",
+ requestmgr, requestmgr->eref, requestmgr->iref);
+
+ if (requestmgr->eref == 0 && requestmgr->iref == 0) {
+ INSIST(requestmgr->exiting &&
+ ISC_LIST_HEAD(requestmgr->requests) == NULL);
+ need_destroy = true;
+ }
+ UNLOCK(&requestmgr->lock);
+
+ if (need_destroy) {
+ mgr_destroy(requestmgr);
+ }
+}
+
+static void
+send_shutdown_events(dns_requestmgr_t *requestmgr) {
+ isc_event_t *event, *next_event;
+ isc_task_t *etask;
+
+ req_log(ISC_LOG_DEBUG(3), "send_shutdown_events: %p", requestmgr);
+
+ /*
+ * Caller must be holding the manager lock.
+ */
+ for (event = ISC_LIST_HEAD(requestmgr->whenshutdown); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ ISC_LIST_UNLINK(requestmgr->whenshutdown, event, ev_link);
+ etask = event->ev_sender;
+ event->ev_sender = requestmgr;
+ isc_task_sendanddetach(&etask, &event);
+ }
+}
+
+static void
+mgr_destroy(dns_requestmgr_t *requestmgr) {
+ int i;
+
+ req_log(ISC_LOG_DEBUG(3), "mgr_destroy");
+
+ REQUIRE(requestmgr->eref == 0);
+ REQUIRE(requestmgr->iref == 0);
+
+ isc_mutex_destroy(&requestmgr->lock);
+ for (i = 0; i < DNS_REQUEST_NLOCKS; i++) {
+ isc_mutex_destroy(&requestmgr->locks[i]);
+ }
+ if (requestmgr->dispatchv4 != NULL) {
+ dns_dispatch_detach(&requestmgr->dispatchv4);
+ }
+ if (requestmgr->dispatchv6 != NULL) {
+ dns_dispatch_detach(&requestmgr->dispatchv6);
+ }
+ requestmgr->magic = 0;
+ isc_mem_putanddetach(&requestmgr->mctx, requestmgr,
+ sizeof(*requestmgr));
+}
+
+static unsigned int
+mgr_gethash(dns_requestmgr_t *requestmgr) {
+ req_log(ISC_LOG_DEBUG(3), "mgr_gethash");
+ /*
+ * Locked by caller.
+ */
+ requestmgr->hash++;
+ return (requestmgr->hash % DNS_REQUEST_NLOCKS);
+}
+
+static isc_result_t
+req_send(dns_request_t *request, isc_task_t *task,
+ const isc_sockaddr_t *address) {
+ isc_region_t r;
+ isc_socket_t *sock;
+ isc_socketevent_t *sendevent;
+ isc_result_t result;
+
+ req_log(ISC_LOG_DEBUG(3), "req_send: request %p", request);
+
+ REQUIRE(VALID_REQUEST(request));
+ sock = req_getsocket(request);
+ isc_buffer_usedregion(request->query, &r);
+ /*
+ * We could connect the socket when we are using an exclusive dispatch
+ * as we do in resolver.c, but we prefer implementation simplicity
+ * at this moment.
+ */
+ sendevent = isc_socket_socketevent(request->mctx, sock,
+ ISC_SOCKEVENT_SENDDONE, req_senddone,
+ request);
+ if (sendevent == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ if (request->dscp == -1) {
+ sendevent->attributes &= ~ISC_SOCKEVENTATTR_DSCP;
+ sendevent->dscp = 0;
+ } else {
+ sendevent->attributes |= ISC_SOCKEVENTATTR_DSCP;
+ sendevent->dscp = request->dscp;
+ }
+
+ request->flags |= DNS_REQUEST_F_SENDING;
+ result = isc_socket_sendto2(sock, &r, task, address, NULL, sendevent,
+ 0);
+ INSIST(result == ISC_R_SUCCESS);
+ return (result);
+}
+
+static isc_result_t
+new_request(isc_mem_t *mctx, dns_request_t **requestp) {
+ dns_request_t *request;
+
+ request = isc_mem_get(mctx, sizeof(*request));
+
+ /*
+ * Zero structure.
+ */
+ request->magic = 0;
+ request->mctx = NULL;
+ request->flags = 0;
+ ISC_LINK_INIT(request, link);
+ request->query = NULL;
+ request->answer = NULL;
+ request->event = NULL;
+ request->dispatch = NULL;
+ request->dispentry = NULL;
+ request->timer = NULL;
+ request->requestmgr = NULL;
+ request->tsig = NULL;
+ request->tsigkey = NULL;
+ request->dscp = -1;
+ ISC_EVENT_INIT(&request->ctlevent, sizeof(request->ctlevent), 0, NULL,
+ DNS_EVENT_REQUESTCONTROL, do_cancel, request, NULL, NULL,
+ NULL);
+ request->canceling = false;
+ request->udpcount = 0;
+
+ isc_mem_attach(mctx, &request->mctx);
+
+ request->magic = REQUEST_MAGIC;
+ *requestp = request;
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+isblackholed(dns_dispatchmgr_t *dispatchmgr, const isc_sockaddr_t *destaddr) {
+ dns_acl_t *blackhole;
+ isc_netaddr_t netaddr;
+ int match;
+ bool drop = false;
+ char netaddrstr[ISC_NETADDR_FORMATSIZE];
+
+ blackhole = dns_dispatchmgr_getblackhole(dispatchmgr);
+ if (blackhole != NULL) {
+ isc_netaddr_fromsockaddr(&netaddr, destaddr);
+ if (dns_acl_match(&netaddr, NULL, blackhole, NULL, &match,
+ NULL) == ISC_R_SUCCESS &&
+ match > 0)
+ {
+ drop = true;
+ }
+ }
+ if (drop) {
+ isc_netaddr_format(&netaddr, netaddrstr, sizeof(netaddrstr));
+ req_log(ISC_LOG_DEBUG(10), "blackholed address %s", netaddrstr);
+ }
+ return (drop);
+}
+
+static isc_result_t
+create_tcp_dispatch(bool newtcp, bool share, dns_requestmgr_t *requestmgr,
+ const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, isc_dscp_t dscp,
+ bool *connected, dns_dispatch_t **dispatchp) {
+ isc_result_t result;
+ isc_socket_t *sock = NULL;
+ isc_sockaddr_t src;
+ unsigned int attrs;
+ isc_sockaddr_t bind_any;
+
+ if (!newtcp && share) {
+ result = dns_dispatch_gettcp(requestmgr->dispatchmgr, destaddr,
+ srcaddr, connected, dispatchp);
+ if (result == ISC_R_SUCCESS) {
+ char peer[ISC_SOCKADDR_FORMATSIZE];
+
+ isc_sockaddr_format(destaddr, peer, sizeof(peer));
+ req_log(ISC_LOG_DEBUG(1),
+ "attached to %s TCP "
+ "connection to %s",
+ *connected ? "existing" : "pending", peer);
+ return (result);
+ }
+ } else if (!newtcp) {
+ result = dns_dispatch_gettcp(requestmgr->dispatchmgr, destaddr,
+ srcaddr, NULL, dispatchp);
+ if (result == ISC_R_SUCCESS) {
+ char peer[ISC_SOCKADDR_FORMATSIZE];
+
+ *connected = true;
+ isc_sockaddr_format(destaddr, peer, sizeof(peer));
+ req_log(ISC_LOG_DEBUG(1),
+ "attached to existing TCP "
+ "connection to %s",
+ peer);
+ return (result);
+ }
+ }
+
+ result = isc_socket_create(requestmgr->socketmgr,
+ isc_sockaddr_pf(destaddr),
+ isc_sockettype_tcp, &sock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+#ifndef BROKEN_TCP_BIND_BEFORE_CONNECT
+ if (srcaddr == NULL) {
+ isc_sockaddr_anyofpf(&bind_any, isc_sockaddr_pf(destaddr));
+ result = isc_socket_bind(sock, &bind_any, 0);
+ } else {
+ src = *srcaddr;
+ isc_sockaddr_setport(&src, 0);
+ result = isc_socket_bind(sock, &src, 0);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+#endif /* ifndef BROKEN_TCP_BIND_BEFORE_CONNECT */
+
+ attrs = 0;
+ attrs |= DNS_DISPATCHATTR_TCP;
+ if (isc_sockaddr_pf(destaddr) == AF_INET) {
+ attrs |= DNS_DISPATCHATTR_IPV4;
+ } else {
+ attrs |= DNS_DISPATCHATTR_IPV6;
+ }
+ attrs |= DNS_DISPATCHATTR_MAKEQUERY;
+
+ isc_socket_dscp(sock, dscp);
+ result = dns_dispatch_createtcp(
+ requestmgr->dispatchmgr, sock, requestmgr->taskmgr, srcaddr,
+ destaddr, 4096, 32768, 32768, 16411, 16433, attrs, dispatchp);
+cleanup:
+ isc_socket_detach(&sock);
+ return (result);
+}
+
+static isc_result_t
+find_udp_dispatch(dns_requestmgr_t *requestmgr, const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, dns_dispatch_t **dispatchp) {
+ dns_dispatch_t *disp = NULL;
+ unsigned int attrs, attrmask;
+
+ if (srcaddr == NULL) {
+ switch (isc_sockaddr_pf(destaddr)) {
+ case PF_INET:
+ disp = requestmgr->dispatchv4;
+ break;
+
+ case PF_INET6:
+ disp = requestmgr->dispatchv6;
+ break;
+
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ if (disp == NULL) {
+ return (ISC_R_FAMILYNOSUPPORT);
+ }
+ dns_dispatch_attach(disp, dispatchp);
+ return (ISC_R_SUCCESS);
+ }
+ attrs = 0;
+ attrs |= DNS_DISPATCHATTR_UDP;
+ switch (isc_sockaddr_pf(srcaddr)) {
+ case PF_INET:
+ attrs |= DNS_DISPATCHATTR_IPV4;
+ break;
+
+ case PF_INET6:
+ attrs |= DNS_DISPATCHATTR_IPV6;
+ break;
+
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ attrmask = 0;
+ attrmask |= DNS_DISPATCHATTR_UDP;
+ attrmask |= DNS_DISPATCHATTR_TCP;
+ attrmask |= DNS_DISPATCHATTR_IPV4;
+ attrmask |= DNS_DISPATCHATTR_IPV6;
+ return (dns_dispatch_getudp(requestmgr->dispatchmgr,
+ requestmgr->socketmgr, requestmgr->taskmgr,
+ srcaddr, 4096, 32768, 32768, 16411, 16433,
+ attrs, attrmask, dispatchp));
+}
+
+static isc_result_t
+get_dispatch(bool tcp, bool newtcp, bool share, dns_requestmgr_t *requestmgr,
+ const isc_sockaddr_t *srcaddr, const isc_sockaddr_t *destaddr,
+ isc_dscp_t dscp, bool *connected, dns_dispatch_t **dispatchp) {
+ isc_result_t result;
+
+ if (tcp) {
+ result = create_tcp_dispatch(newtcp, share, requestmgr, srcaddr,
+ destaddr, dscp, connected,
+ dispatchp);
+ } else {
+ result = find_udp_dispatch(requestmgr, srcaddr, destaddr,
+ dispatchp);
+ }
+ return (result);
+}
+
+static isc_result_t
+set_timer(isc_timer_t *timer, unsigned int timeout, unsigned int udpresend) {
+ isc_time_t expires;
+ isc_interval_t interval;
+ isc_result_t result;
+ isc_timertype_t timertype;
+
+ isc_interval_set(&interval, timeout, 0);
+ result = isc_time_nowplusinterval(&expires, &interval);
+ isc_interval_set(&interval, udpresend, 0);
+
+ timertype = udpresend != 0 ? isc_timertype_limited : isc_timertype_once;
+ if (result == ISC_R_SUCCESS) {
+ result = isc_timer_reset(timer, timertype, &expires, &interval,
+ false);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_request_createraw(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf,
+ const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, isc_dscp_t dscp,
+ unsigned int options, unsigned int timeout,
+ unsigned int udptimeout, unsigned int udpretries,
+ isc_task_t *task, isc_taskaction_t action, void *arg,
+ dns_request_t **requestp) {
+ dns_request_t *request = NULL;
+ isc_task_t *tclone = NULL;
+ isc_socket_t *sock = NULL;
+ isc_result_t result;
+ isc_mem_t *mctx;
+ dns_messageid_t id;
+ bool tcp = false;
+ bool newtcp = false;
+ bool share = false;
+ isc_region_t r;
+ bool connected = false;
+ unsigned int dispopt = 0;
+
+ REQUIRE(VALID_REQUESTMGR(requestmgr));
+ REQUIRE(msgbuf != NULL);
+ REQUIRE(destaddr != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(action != NULL);
+ REQUIRE(requestp != NULL && *requestp == NULL);
+ REQUIRE(timeout > 0);
+ if (srcaddr != NULL) {
+ REQUIRE(isc_sockaddr_pf(srcaddr) == isc_sockaddr_pf(destaddr));
+ }
+
+ mctx = requestmgr->mctx;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_createraw");
+
+ if (isblackholed(requestmgr->dispatchmgr, destaddr)) {
+ return (DNS_R_BLACKHOLED);
+ }
+
+ request = NULL;
+ result = new_request(mctx, &request);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (udptimeout == 0 && udpretries != 0) {
+ udptimeout = timeout / (udpretries + 1);
+ if (udptimeout == 0) {
+ udptimeout = 1;
+ }
+ }
+ request->udpcount = udpretries;
+ request->dscp = dscp;
+
+ /*
+ * Create timer now. We will set it below once.
+ */
+ result = isc_timer_create(requestmgr->timermgr, isc_timertype_inactive,
+ NULL, NULL, task, req_timeout, request,
+ &request->timer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ request->event = (dns_requestevent_t *)isc_event_allocate(
+ mctx, task, DNS_EVENT_REQUESTDONE, action, arg,
+ sizeof(dns_requestevent_t));
+ isc_task_attach(task, &tclone);
+ request->event->ev_sender = task;
+ request->event->request = request;
+ request->event->result = ISC_R_FAILURE;
+
+ isc_buffer_usedregion(msgbuf, &r);
+ if (r.length < DNS_MESSAGE_HEADERLEN || r.length > 65535) {
+ result = DNS_R_FORMERR;
+ goto cleanup;
+ }
+
+ if ((options & DNS_REQUESTOPT_TCP) != 0 || r.length > 512) {
+ tcp = true;
+ }
+ share = (options & DNS_REQUESTOPT_SHARE);
+
+again:
+ result = get_dispatch(tcp, newtcp, share, requestmgr, srcaddr, destaddr,
+ dscp, &connected, &request->dispatch);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if ((options & DNS_REQUESTOPT_FIXEDID) != 0) {
+ id = (r.base[0] << 8) | r.base[1];
+ dispopt |= DNS_DISPATCHOPT_FIXEDID;
+ }
+
+ result = dns_dispatch_addresponse(
+ request->dispatch, dispopt, destaddr, task, req_response,
+ request, &id, &request->dispentry, requestmgr->socketmgr);
+ if (result != ISC_R_SUCCESS) {
+ if ((options & DNS_REQUESTOPT_FIXEDID) != 0 && !newtcp) {
+ newtcp = true;
+ connected = false;
+ dns_dispatch_detach(&request->dispatch);
+ goto again;
+ }
+ goto cleanup;
+ }
+
+ sock = req_getsocket(request);
+ INSIST(sock != NULL);
+
+ isc_buffer_allocate(mctx, &request->query, r.length + (tcp ? 2 : 0));
+ if (tcp) {
+ isc_buffer_putuint16(request->query, (uint16_t)r.length);
+ }
+ result = isc_buffer_copyregion(request->query, &r);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* Add message ID. */
+ isc_buffer_usedregion(request->query, &r);
+ if (tcp) {
+ isc_region_consume(&r, 2);
+ }
+ r.base[0] = (id >> 8) & 0xff;
+ r.base[1] = id & 0xff;
+
+ LOCK(&requestmgr->lock);
+ if (requestmgr->exiting) {
+ UNLOCK(&requestmgr->lock);
+ result = ISC_R_SHUTTINGDOWN;
+ goto cleanup;
+ }
+ requestmgr_attach(requestmgr, &request->requestmgr);
+ request->hash = mgr_gethash(requestmgr);
+ ISC_LIST_APPEND(requestmgr->requests, request, link);
+ UNLOCK(&requestmgr->lock);
+
+ result = set_timer(request->timer, timeout, tcp ? 0 : udptimeout);
+ if (result != ISC_R_SUCCESS) {
+ goto unlink;
+ }
+
+ request->destaddr = *destaddr;
+ if (tcp && !connected) {
+ result = isc_socket_connect(sock, destaddr, task, req_connected,
+ request);
+ if (result != ISC_R_SUCCESS) {
+ goto unlink;
+ }
+ request->flags |= DNS_REQUEST_F_CONNECTING | DNS_REQUEST_F_TCP;
+ } else {
+ result = req_send(request, task, connected ? NULL : destaddr);
+ if (result != ISC_R_SUCCESS) {
+ goto unlink;
+ }
+ }
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_createraw: request %p", request);
+ *requestp = request;
+ return (ISC_R_SUCCESS);
+
+unlink:
+ LOCK(&requestmgr->lock);
+ ISC_LIST_UNLINK(requestmgr->requests, request, link);
+ UNLOCK(&requestmgr->lock);
+
+cleanup:
+ if (tclone != NULL) {
+ isc_task_detach(&tclone);
+ }
+ req_destroy(request);
+ req_log(ISC_LOG_DEBUG(3), "dns_request_createraw: failed %s",
+ dns_result_totext(result));
+ return (result);
+}
+
+isc_result_t
+dns_request_create(dns_requestmgr_t *requestmgr, dns_message_t *message,
+ const isc_sockaddr_t *address, unsigned int options,
+ dns_tsigkey_t *key, unsigned int timeout, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_request_t **requestp) {
+ return (dns_request_createvia(requestmgr, message, NULL, address, -1,
+ options, key, timeout, 0, 0, task, action,
+ arg, requestp));
+}
+
+isc_result_t
+dns_request_createvia(dns_requestmgr_t *requestmgr, dns_message_t *message,
+ const isc_sockaddr_t *srcaddr,
+ const isc_sockaddr_t *destaddr, isc_dscp_t dscp,
+ unsigned int options, dns_tsigkey_t *key,
+ unsigned int timeout, unsigned int udptimeout,
+ unsigned int udpretries, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_request_t **requestp) {
+ dns_request_t *request = NULL;
+ isc_task_t *tclone = NULL;
+ isc_socket_t *sock = NULL;
+ isc_result_t result;
+ isc_mem_t *mctx;
+ dns_messageid_t id;
+ bool tcp;
+ bool share;
+ bool settsigkey = true;
+ bool connected = false;
+
+ REQUIRE(VALID_REQUESTMGR(requestmgr));
+ REQUIRE(message != NULL);
+ REQUIRE(destaddr != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(action != NULL);
+ REQUIRE(requestp != NULL && *requestp == NULL);
+ REQUIRE(timeout > 0);
+
+ mctx = requestmgr->mctx;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_createvia");
+
+ if (srcaddr != NULL &&
+ isc_sockaddr_pf(srcaddr) != isc_sockaddr_pf(destaddr))
+ {
+ return (ISC_R_FAMILYMISMATCH);
+ }
+
+ if (isblackholed(requestmgr->dispatchmgr, destaddr)) {
+ return (DNS_R_BLACKHOLED);
+ }
+
+ request = NULL;
+ result = new_request(mctx, &request);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (udptimeout == 0 && udpretries != 0) {
+ udptimeout = timeout / (udpretries + 1);
+ if (udptimeout == 0) {
+ udptimeout = 1;
+ }
+ }
+ request->udpcount = udpretries;
+ request->dscp = dscp;
+
+ /*
+ * Create timer now. We will set it below once.
+ */
+ result = isc_timer_create(requestmgr->timermgr, isc_timertype_inactive,
+ NULL, NULL, task, req_timeout, request,
+ &request->timer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ request->event = (dns_requestevent_t *)isc_event_allocate(
+ mctx, task, DNS_EVENT_REQUESTDONE, action, arg,
+ sizeof(dns_requestevent_t));
+ isc_task_attach(task, &tclone);
+ request->event->ev_sender = task;
+ request->event->request = request;
+ request->event->result = ISC_R_FAILURE;
+ if (key != NULL) {
+ dns_tsigkey_attach(key, &request->tsigkey);
+ }
+
+use_tcp:
+ tcp = ((options & DNS_REQUESTOPT_TCP) != 0);
+ share = ((options & DNS_REQUESTOPT_SHARE) != 0);
+ result = get_dispatch(tcp, false, share, requestmgr, srcaddr, destaddr,
+ dscp, &connected, &request->dispatch);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_dispatch_addresponse(
+ request->dispatch, 0, destaddr, task, req_response, request,
+ &id, &request->dispentry, requestmgr->socketmgr);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ sock = req_getsocket(request);
+ INSIST(sock != NULL);
+
+ message->id = id;
+ if (settsigkey) {
+ result = dns_message_settsigkey(message, request->tsigkey);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ result = req_render(message, &request->query, options, mctx);
+ if (result == DNS_R_USETCP && (options & DNS_REQUESTOPT_TCP) == 0) {
+ /*
+ * Try again using TCP.
+ */
+ dns_message_renderreset(message);
+ dns_dispatch_removeresponse(&request->dispentry, NULL);
+ dns_dispatch_detach(&request->dispatch);
+ sock = NULL;
+ options |= DNS_REQUESTOPT_TCP;
+ settsigkey = false;
+ goto use_tcp;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_message_getquerytsig(message, mctx, &request->tsig);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ LOCK(&requestmgr->lock);
+ if (requestmgr->exiting) {
+ UNLOCK(&requestmgr->lock);
+ result = ISC_R_SHUTTINGDOWN;
+ goto cleanup;
+ }
+ requestmgr_attach(requestmgr, &request->requestmgr);
+ request->hash = mgr_gethash(requestmgr);
+ ISC_LIST_APPEND(requestmgr->requests, request, link);
+ UNLOCK(&requestmgr->lock);
+
+ result = set_timer(request->timer, timeout, tcp ? 0 : udptimeout);
+ if (result != ISC_R_SUCCESS) {
+ goto unlink;
+ }
+
+ request->destaddr = *destaddr;
+ if (tcp && !connected) {
+ result = isc_socket_connect(sock, destaddr, task, req_connected,
+ request);
+ if (result != ISC_R_SUCCESS) {
+ goto unlink;
+ }
+ request->flags |= DNS_REQUEST_F_CONNECTING | DNS_REQUEST_F_TCP;
+ } else {
+ result = req_send(request, task, connected ? NULL : destaddr);
+ if (result != ISC_R_SUCCESS) {
+ goto unlink;
+ }
+ }
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_createvia: request %p", request);
+ *requestp = request;
+ return (ISC_R_SUCCESS);
+
+unlink:
+ LOCK(&requestmgr->lock);
+ ISC_LIST_UNLINK(requestmgr->requests, request, link);
+ UNLOCK(&requestmgr->lock);
+
+cleanup:
+ if (tclone != NULL) {
+ isc_task_detach(&tclone);
+ }
+ req_destroy(request);
+ req_log(ISC_LOG_DEBUG(3), "dns_request_createvia: failed %s",
+ dns_result_totext(result));
+ return (result);
+}
+
+static isc_result_t
+req_render(dns_message_t *message, isc_buffer_t **bufferp, unsigned int options,
+ isc_mem_t *mctx) {
+ isc_buffer_t *buf1 = NULL;
+ isc_buffer_t *buf2 = NULL;
+ isc_result_t result;
+ isc_region_t r;
+ bool tcp = false;
+ dns_compress_t cctx;
+ bool cleanup_cctx = false;
+
+ REQUIRE(bufferp != NULL && *bufferp == NULL);
+
+ req_log(ISC_LOG_DEBUG(3), "request_render");
+
+ /*
+ * Create buffer able to hold largest possible message.
+ */
+ isc_buffer_allocate(mctx, &buf1, 65535);
+
+ result = dns_compress_init(&cctx, -1, mctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ cleanup_cctx = true;
+
+ if ((options & DNS_REQUESTOPT_CASE) != 0) {
+ dns_compress_setsensitive(&cctx, true);
+ }
+
+ /*
+ * Render message.
+ */
+ result = dns_message_renderbegin(message, &cctx, buf1);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_rendersection(message, DNS_SECTION_QUESTION, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_rendersection(message, DNS_SECTION_ANSWER, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_rendersection(message, DNS_SECTION_AUTHORITY, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_rendersection(message, DNS_SECTION_ADDITIONAL, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_renderend(message);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_compress_invalidate(&cctx);
+ cleanup_cctx = false;
+
+ /*
+ * Copy rendered message to exact sized buffer.
+ */
+ isc_buffer_usedregion(buf1, &r);
+ if ((options & DNS_REQUESTOPT_TCP) != 0) {
+ tcp = true;
+ } else if (r.length > 512) {
+ result = DNS_R_USETCP;
+ goto cleanup;
+ }
+ isc_buffer_allocate(mctx, &buf2, r.length + (tcp ? 2 : 0));
+ if (tcp) {
+ isc_buffer_putuint16(buf2, (uint16_t)r.length);
+ }
+ result = isc_buffer_copyregion(buf2, &r);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Cleanup and return.
+ */
+ isc_buffer_free(&buf1);
+ *bufferp = buf2;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dns_message_renderreset(message);
+ if (buf1 != NULL) {
+ isc_buffer_free(&buf1);
+ }
+ if (buf2 != NULL) {
+ isc_buffer_free(&buf2);
+ }
+ if (cleanup_cctx) {
+ dns_compress_invalidate(&cctx);
+ }
+ return (result);
+}
+
+/*
+ * If this request is no longer waiting for events,
+ * send the completion event. This will ultimately
+ * cause the request to be destroyed.
+ *
+ * Requires:
+ * 'request' is locked by the caller.
+ */
+static void
+send_if_done(dns_request_t *request, isc_result_t result) {
+ if (request->event != NULL && !request->canceling) {
+ req_sendevent(request, result);
+ }
+}
+
+/*
+ * Handle the control event.
+ */
+static void
+do_cancel(isc_task_t *task, isc_event_t *event) {
+ dns_request_t *request = event->ev_arg;
+ UNUSED(task);
+ INSIST(event->ev_type == DNS_EVENT_REQUESTCONTROL);
+ LOCK(&request->requestmgr->locks[request->hash]);
+ request->canceling = false;
+ if (!DNS_REQUEST_CANCELED(request)) {
+ req_cancel(request);
+ }
+ send_if_done(request, ISC_R_CANCELED);
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+}
+
+void
+dns_request_cancel(dns_request_t *request) {
+ REQUIRE(VALID_REQUEST(request));
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_cancel: request %p", request);
+
+ REQUIRE(VALID_REQUEST(request));
+
+ LOCK(&request->requestmgr->locks[request->hash]);
+ if (!request->canceling && !DNS_REQUEST_CANCELED(request)) {
+ isc_event_t *ev = &request->ctlevent;
+ isc_task_send(request->event->ev_sender, &ev);
+ request->canceling = true;
+ }
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+}
+
+isc_result_t
+dns_request_getresponse(dns_request_t *request, dns_message_t *message,
+ unsigned int options) {
+ isc_result_t result;
+
+ REQUIRE(VALID_REQUEST(request));
+ REQUIRE(request->answer != NULL);
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_getresponse: request %p",
+ request);
+
+ result = dns_message_setquerytsig(message, request->tsig);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dns_message_settsigkey(message, request->tsigkey);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dns_message_parse(message, request->answer, options);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (request->tsigkey != NULL) {
+ result = dns_tsig_verify(request->answer, message, NULL, NULL);
+ }
+ return (result);
+}
+
+isc_buffer_t *
+dns_request_getanswer(dns_request_t *request) {
+ REQUIRE(VALID_REQUEST(request));
+
+ return (request->answer);
+}
+
+bool
+dns_request_usedtcp(dns_request_t *request) {
+ REQUIRE(VALID_REQUEST(request));
+
+ return ((request->flags & DNS_REQUEST_F_TCP) != 0);
+}
+
+void
+dns_request_destroy(dns_request_t **requestp) {
+ dns_request_t *request;
+
+ REQUIRE(requestp != NULL && VALID_REQUEST(*requestp));
+
+ request = *requestp;
+ *requestp = NULL;
+
+ req_log(ISC_LOG_DEBUG(3), "dns_request_destroy: request %p", request);
+
+ LOCK(&request->requestmgr->lock);
+ LOCK(&request->requestmgr->locks[request->hash]);
+ ISC_LIST_UNLINK(request->requestmgr->requests, request, link);
+ INSIST(!DNS_REQUEST_CONNECTING(request));
+ INSIST(!DNS_REQUEST_SENDING(request));
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+ UNLOCK(&request->requestmgr->lock);
+
+ /*
+ * These should have been cleaned up by req_cancel() before
+ * the completion event was sent.
+ */
+ INSIST(!ISC_LINK_LINKED(request, link));
+ INSIST(request->dispentry == NULL);
+ INSIST(request->dispatch == NULL);
+ INSIST(request->timer == NULL);
+
+ req_destroy(request);
+}
+
+/***
+ *** Private: request.
+ ***/
+
+static isc_socket_t *
+req_getsocket(dns_request_t *request) {
+ unsigned int dispattr;
+ isc_socket_t *sock;
+
+ dispattr = dns_dispatch_getattributes(request->dispatch);
+ if ((dispattr & DNS_DISPATCHATTR_EXCLUSIVE) != 0) {
+ INSIST(request->dispentry != NULL);
+ sock = dns_dispatch_getentrysocket(request->dispentry);
+ } else {
+ sock = dns_dispatch_getsocket(request->dispatch);
+ }
+
+ return (sock);
+}
+
+static void
+req_connected(isc_task_t *task, isc_event_t *event) {
+ isc_socketevent_t *sevent = (isc_socketevent_t *)event;
+ isc_result_t result;
+ dns_request_t *request = event->ev_arg;
+
+ REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT);
+ REQUIRE(VALID_REQUEST(request));
+ REQUIRE(DNS_REQUEST_CONNECTING(request));
+
+ req_log(ISC_LOG_DEBUG(3), "req_connected: request %p", request);
+
+ result = sevent->result;
+ isc_event_free(&event);
+
+ LOCK(&request->requestmgr->locks[request->hash]);
+ request->flags &= ~DNS_REQUEST_F_CONNECTING;
+
+ if (DNS_REQUEST_CANCELED(request)) {
+ /*
+ * Send delayed event.
+ */
+ if (DNS_REQUEST_TIMEDOUT(request)) {
+ send_if_done(request, ISC_R_TIMEDOUT);
+ } else {
+ send_if_done(request, ISC_R_CANCELED);
+ }
+ } else {
+ dns_dispatch_starttcp(request->dispatch);
+ if (result == ISC_R_SUCCESS) {
+ result = req_send(request, task, NULL);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ req_cancel(request);
+ send_if_done(request, ISC_R_CANCELED);
+ }
+ }
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+}
+
+static void
+req_senddone(isc_task_t *task, isc_event_t *event) {
+ isc_socketevent_t *sevent = (isc_socketevent_t *)event;
+ dns_request_t *request = event->ev_arg;
+ isc_result_t result = sevent->result;
+
+ REQUIRE(event->ev_type == ISC_SOCKEVENT_SENDDONE);
+ REQUIRE(VALID_REQUEST(request));
+ REQUIRE(DNS_REQUEST_SENDING(request));
+
+ req_log(ISC_LOG_DEBUG(3), "req_senddone: request %p", request);
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+
+ LOCK(&request->requestmgr->locks[request->hash]);
+ request->flags &= ~DNS_REQUEST_F_SENDING;
+
+ if (DNS_REQUEST_CANCELED(request)) {
+ /*
+ * Send delayed event.
+ */
+ if (DNS_REQUEST_TIMEDOUT(request)) {
+ send_if_done(request, ISC_R_TIMEDOUT);
+ } else {
+ send_if_done(request, ISC_R_CANCELED);
+ }
+ } else if (result != ISC_R_SUCCESS) {
+ req_cancel(request);
+ send_if_done(request, ISC_R_CANCELED);
+ }
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+}
+
+static void
+req_response(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ dns_request_t *request = event->ev_arg;
+ dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event;
+ isc_region_t r;
+
+ REQUIRE(VALID_REQUEST(request));
+ REQUIRE(event->ev_type == DNS_EVENT_DISPATCH);
+
+ UNUSED(task);
+
+ req_log(ISC_LOG_DEBUG(3), "req_response: request %p: %s", request,
+ dns_result_totext(devent->result));
+
+ LOCK(&request->requestmgr->locks[request->hash]);
+ result = devent->result;
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+
+ /*
+ * Copy buffer to request.
+ */
+ isc_buffer_usedregion(&devent->buffer, &r);
+ isc_buffer_allocate(request->mctx, &request->answer, r.length);
+ result = isc_buffer_copyregion(request->answer, &r);
+ if (result != ISC_R_SUCCESS) {
+ isc_buffer_free(&request->answer);
+ }
+done:
+ /*
+ * Cleanup.
+ */
+ dns_dispatch_removeresponse(&request->dispentry, &devent);
+ req_cancel(request);
+ /*
+ * Send completion event.
+ */
+ send_if_done(request, result);
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+}
+
+static void
+req_timeout(isc_task_t *task, isc_event_t *event) {
+ dns_request_t *request = event->ev_arg;
+ isc_result_t result;
+ isc_eventtype_t ev_type = event->ev_type;
+
+ REQUIRE(VALID_REQUEST(request));
+
+ req_log(ISC_LOG_DEBUG(3), "req_timeout: request %p", request);
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+
+ LOCK(&request->requestmgr->locks[request->hash]);
+ if (ev_type == ISC_TIMEREVENT_TICK && request->udpcount-- != 0) {
+ if (!DNS_REQUEST_SENDING(request)) {
+ result = req_send(request, task, &request->destaddr);
+ if (result != ISC_R_SUCCESS) {
+ req_cancel(request);
+ send_if_done(request, result);
+ }
+ }
+ } else {
+ request->flags |= DNS_REQUEST_F_TIMEDOUT;
+ req_cancel(request);
+ send_if_done(request, ISC_R_TIMEDOUT);
+ }
+ UNLOCK(&request->requestmgr->locks[request->hash]);
+}
+
+static void
+req_sendevent(dns_request_t *request, isc_result_t result) {
+ isc_task_t *task;
+
+ REQUIRE(VALID_REQUEST(request));
+
+ req_log(ISC_LOG_DEBUG(3), "req_sendevent: request %p", request);
+
+ /*
+ * Lock held by caller.
+ */
+ task = request->event->ev_sender;
+ request->event->ev_sender = request;
+ request->event->result = result;
+ isc_task_sendanddetach(&task, (isc_event_t **)&request->event);
+}
+
+static void
+req_destroy(dns_request_t *request) {
+ REQUIRE(VALID_REQUEST(request));
+
+ req_log(ISC_LOG_DEBUG(3), "req_destroy: request %p", request);
+
+ request->magic = 0;
+ if (request->query != NULL) {
+ isc_buffer_free(&request->query);
+ }
+ if (request->answer != NULL) {
+ isc_buffer_free(&request->answer);
+ }
+ if (request->event != NULL) {
+ isc_event_free((isc_event_t **)&request->event);
+ }
+ if (request->dispentry != NULL) {
+ dns_dispatch_removeresponse(&request->dispentry, NULL);
+ }
+ if (request->dispatch != NULL) {
+ dns_dispatch_detach(&request->dispatch);
+ }
+ if (request->timer != NULL) {
+ isc_timer_destroy(&request->timer);
+ }
+ if (request->tsig != NULL) {
+ isc_buffer_free(&request->tsig);
+ }
+ if (request->tsigkey != NULL) {
+ dns_tsigkey_detach(&request->tsigkey);
+ }
+ if (request->requestmgr != NULL) {
+ requestmgr_detach(&request->requestmgr);
+ }
+ isc_mem_putanddetach(&request->mctx, request, sizeof(*request));
+}
+
+/*
+ * Stop the current request. Must be called from the request's task.
+ */
+static void
+req_cancel(dns_request_t *request) {
+ isc_socket_t *sock;
+ unsigned int dispattr;
+
+ REQUIRE(VALID_REQUEST(request));
+
+ req_log(ISC_LOG_DEBUG(3), "req_cancel: request %p", request);
+
+ /*
+ * Lock held by caller.
+ */
+ request->flags |= DNS_REQUEST_F_CANCELED;
+
+ if (request->timer != NULL) {
+ isc_timer_destroy(&request->timer);
+ }
+ dispattr = dns_dispatch_getattributes(request->dispatch);
+ sock = NULL;
+ if (DNS_REQUEST_CONNECTING(request) || DNS_REQUEST_SENDING(request)) {
+ if ((dispattr & DNS_DISPATCHATTR_EXCLUSIVE) != 0) {
+ if (request->dispentry != NULL) {
+ sock = dns_dispatch_getentrysocket(
+ request->dispentry);
+ }
+ } else {
+ sock = dns_dispatch_getsocket(request->dispatch);
+ }
+ if (DNS_REQUEST_CONNECTING(request) && sock != NULL) {
+ isc_socket_cancel(sock, NULL, ISC_SOCKCANCEL_CONNECT);
+ }
+ if (DNS_REQUEST_SENDING(request) && sock != NULL) {
+ isc_socket_cancel(sock, NULL, ISC_SOCKCANCEL_SEND);
+ }
+ }
+ if (request->dispentry != NULL) {
+ dns_dispatch_removeresponse(&request->dispentry, NULL);
+ }
+ dns_dispatch_detach(&request->dispatch);
+}
+
+static void
+req_log(int level, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_REQUEST,
+ level, fmt, ap);
+ va_end(ap);
+}
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
new file mode 100644
index 0000000..a97aaa8
--- /dev/null
+++ b/lib/dns/resolver.c
@@ -0,0 +1,12095 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/atomic.h>
+#include <isc/counter.h>
+#include <isc/log.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/siphash.h>
+#include <isc/socket.h>
+#include <isc/stats.h>
+#include <isc/string.h>
+#include <isc/task.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/result.h>
+#include <dns/rootns.h>
+#include <dns/stats.h>
+#include <dns/tsig.h>
+#include <dns/validator.h>
+#include <dns/zone.h>
+
+#ifdef WANT_QUERYTRACE
+#define RTRACE(m) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "res %p: %s", \
+ res, (m))
+#define RRTRACE(r, m) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "res %p: %s", \
+ (r), (m))
+#define FCTXTRACE(m) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fctx %p(%s): %s", fctx, fctx->info, (m))
+#define FCTXTRACE2(m1, m2) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fctx %p(%s): %s %s", fctx, fctx->info, (m1), (m2))
+#define FCTXTRACE3(m, res) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fctx %p(%s): [result: %s] %s", fctx, fctx->info, \
+ isc_result_totext(res), (m))
+#define FCTXTRACE4(m1, m2, res) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fctx %p(%s): [result: %s] %s %s", fctx, fctx->info, \
+ isc_result_totext(res), (m1), (m2))
+#define FCTXTRACE5(m1, m2, v) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fctx %p(%s): %s %s%u", fctx, fctx->info, (m1), (m2), \
+ (v))
+#define FTRACE(m) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "fetch %p (fctx %p(%s)): %s", fetch, fetch->private, \
+ fetch->private->info, (m))
+#define QTRACE(m) \
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
+ "resquery %p (fctx %p(%s)): %s", query, query->fctx, \
+ query->fctx->info, (m))
+#else /* ifdef WANT_QUERYTRACE */
+#define RTRACE(m) \
+ do { \
+ UNUSED(m); \
+ } while (0)
+#define RRTRACE(r, m) \
+ do { \
+ UNUSED(r); \
+ UNUSED(m); \
+ } while (0)
+#define FCTXTRACE(m) \
+ do { \
+ UNUSED(m); \
+ } while (0)
+#define FCTXTRACE2(m1, m2) \
+ do { \
+ UNUSED(m1); \
+ UNUSED(m2); \
+ } while (0)
+#define FCTXTRACE3(m1, res) \
+ do { \
+ UNUSED(m1); \
+ UNUSED(res); \
+ } while (0)
+#define FCTXTRACE4(m1, m2, res) \
+ do { \
+ UNUSED(m1); \
+ UNUSED(m2); \
+ UNUSED(res); \
+ } while (0)
+#define FCTXTRACE5(m1, m2, v) \
+ do { \
+ UNUSED(m1); \
+ UNUSED(m2); \
+ UNUSED(v); \
+ } while (0)
+#define FTRACE(m) \
+ do { \
+ UNUSED(m); \
+ } while (0)
+#define QTRACE(m) \
+ do { \
+ UNUSED(m); \
+ } while (0)
+#endif /* WANT_QUERYTRACE */
+
+#define US_PER_SEC 1000000U
+#define US_PER_MSEC 1000U
+/*
+ * The maximum time we will wait for a single query.
+ */
+#define MAX_SINGLE_QUERY_TIMEOUT 9000U
+#define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT * US_PER_MSEC)
+
+/*
+ * We need to allow a individual query time to complete / timeout.
+ */
+#define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U)
+
+/* The default time in seconds for the whole query to live. */
+#ifndef DEFAULT_QUERY_TIMEOUT
+#define DEFAULT_QUERY_TIMEOUT MINIMUM_QUERY_TIMEOUT
+#endif /* ifndef DEFAULT_QUERY_TIMEOUT */
+
+/* The maximum time in seconds for the whole query to live. */
+#ifndef MAXIMUM_QUERY_TIMEOUT
+#define MAXIMUM_QUERY_TIMEOUT 30000
+#endif /* ifndef MAXIMUM_QUERY_TIMEOUT */
+
+/* The default maximum number of recursions to follow before giving up. */
+#ifndef DEFAULT_RECURSION_DEPTH
+#define DEFAULT_RECURSION_DEPTH 7
+#endif /* ifndef DEFAULT_RECURSION_DEPTH */
+
+/* The default maximum number of iterative queries to allow before giving up. */
+#ifndef DEFAULT_MAX_QUERIES
+#define DEFAULT_MAX_QUERIES 100
+#endif /* ifndef DEFAULT_MAX_QUERIES */
+
+/*
+ * After NS_FAIL_LIMIT attempts to fetch a name server address,
+ * if the number of addresses in the NS RRset exceeds NS_RR_LIMIT,
+ * stop trying to fetch, in order to avoid wasting resources.
+ */
+#define NS_FAIL_LIMIT 4
+#define NS_RR_LIMIT 5
+/*
+ * IP address lookups are performed for at most NS_PROCESSING_LIMIT NS RRs in
+ * any NS RRset encountered, to avoid excessive resource use while processing
+ * large delegations.
+ */
+#define NS_PROCESSING_LIMIT 20
+
+/* Number of hash buckets for zone counters */
+#ifndef RES_DOMAIN_BUCKETS
+#define RES_DOMAIN_BUCKETS 523
+#endif /* ifndef RES_DOMAIN_BUCKETS */
+#define RES_NOBUCKET 0xffffffff
+
+/*%
+ * Maximum EDNS0 input packet size.
+ */
+#define RECV_BUFFER_SIZE 4096 /* XXXRTH Constant. */
+
+/*%
+ * This defines the maximum number of timeouts we will permit before we
+ * disable EDNS0 on the query.
+ */
+#define MAX_EDNS0_TIMEOUTS 3
+
+#define DNS_RESOLVER_BADCACHESIZE 1021
+#define DNS_RESOLVER_BADCACHETTL(fctx) \
+ (((fctx)->res->lame_ttl > 30) ? (fctx)->res->lame_ttl : 30)
+
+typedef struct fetchctx fetchctx_t;
+
+typedef struct query {
+ /* Locked by task event serialization. */
+ unsigned int magic;
+ fetchctx_t *fctx;
+ dns_message_t *rmessage;
+ isc_mem_t *mctx;
+ dns_dispatchmgr_t *dispatchmgr;
+ dns_dispatch_t *dispatch;
+ bool exclusivesocket;
+ dns_adbaddrinfo_t *addrinfo;
+ isc_socket_t *tcpsocket;
+ isc_time_t start;
+ dns_messageid_t id;
+ dns_dispentry_t *dispentry;
+ ISC_LINK(struct query) link;
+ isc_buffer_t buffer;
+ isc_buffer_t *tsig;
+ dns_tsigkey_t *tsigkey;
+ isc_socketevent_t sendevent;
+ isc_dscp_t dscp;
+ int ednsversion;
+ unsigned int options;
+ isc_sockeventattr_t attributes;
+ unsigned int sends;
+ unsigned int connects;
+ unsigned int udpsize;
+ unsigned char data[512];
+} resquery_t;
+
+struct tried {
+ isc_sockaddr_t addr;
+ unsigned int count;
+ ISC_LINK(struct tried) link;
+};
+
+#define QUERY_MAGIC ISC_MAGIC('Q', '!', '!', '!')
+#define VALID_QUERY(query) ISC_MAGIC_VALID(query, QUERY_MAGIC)
+
+#define RESQUERY_ATTR_CANCELED 0x02
+
+#define RESQUERY_CONNECTING(q) ((q)->connects > 0)
+#define RESQUERY_CANCELED(q) (((q)->attributes & RESQUERY_ATTR_CANCELED) != 0)
+#define RESQUERY_SENDING(q) ((q)->sends > 0)
+
+typedef enum {
+ fetchstate_init = 0, /*%< Start event has not run yet. */
+ fetchstate_active,
+ fetchstate_done /*%< FETCHDONE events posted. */
+} fetchstate;
+
+typedef enum {
+ badns_unreachable = 0,
+ badns_response,
+ badns_validation,
+ badns_forwarder,
+} badnstype_t;
+
+struct fetchctx {
+ /*% Not locked. */
+ unsigned int magic;
+ dns_resolver_t *res;
+ dns_name_t name;
+ dns_rdatatype_t type;
+ unsigned int options;
+ unsigned int bucketnum;
+ unsigned int dbucketnum;
+ char *info;
+ isc_mem_t *mctx;
+ isc_stdtime_t now;
+
+ /* Atomic */
+ isc_refcount_t references;
+
+ /*% Locked by appropriate bucket lock. */
+ fetchstate state;
+ bool want_shutdown;
+ bool cloned;
+ bool spilled;
+ isc_event_t control_event;
+ ISC_LINK(struct fetchctx) link;
+ ISC_LIST(dns_fetchevent_t) events;
+
+ /*% Locked by task event serialization. */
+ dns_name_t domain;
+ dns_rdataset_t nameservers;
+ atomic_uint_fast32_t attributes;
+ isc_timer_t *timer;
+ isc_timer_t *timer_try_stale;
+ isc_time_t expires;
+ isc_time_t expires_try_stale;
+ isc_interval_t interval;
+ dns_message_t *qmessage;
+ ISC_LIST(resquery_t) queries;
+ dns_adbfindlist_t finds;
+ dns_adbfind_t *find;
+ /*
+ * altfinds are names and/or addresses of dual stack servers that
+ * should be used when iterative resolution to a server is not
+ * possible because the address family of that server is not usable.
+ */
+ dns_adbfindlist_t altfinds;
+ dns_adbfind_t *altfind;
+ dns_adbaddrinfolist_t forwaddrs;
+ dns_adbaddrinfolist_t altaddrs;
+ dns_forwarderlist_t forwarders;
+ dns_fwdpolicy_t fwdpolicy;
+ isc_sockaddrlist_t bad;
+ ISC_LIST(struct tried) edns;
+ ISC_LIST(struct tried) edns512;
+ isc_sockaddrlist_t bad_edns;
+ dns_validator_t *validator;
+ ISC_LIST(dns_validator_t) validators;
+ dns_db_t *cache;
+ dns_adb_t *adb;
+ bool ns_ttl_ok;
+ uint32_t ns_ttl;
+ isc_counter_t *qc;
+ bool minimized;
+ unsigned int qmin_labels;
+ isc_result_t qmin_warning;
+ bool ip6arpaskip;
+ bool forwarding;
+ dns_name_t qminname;
+ dns_rdatatype_t qmintype;
+ dns_fetch_t *qminfetch;
+ dns_rdataset_t qminrrset;
+ dns_name_t qmindcname;
+ dns_fixedname_t fwdfname;
+ dns_name_t *fwdname;
+
+ /*%
+ * The number of events we're waiting for.
+ */
+ unsigned int pending; /* Bucket lock. */
+
+ /*%
+ * The number of times we've "restarted" the current
+ * nameserver set. This acts as a failsafe to prevent
+ * us from pounding constantly on a particular set of
+ * servers that, for whatever reason, are not giving
+ * us useful responses, but are responding in such a
+ * way that they are not marked "bad".
+ */
+ unsigned int restarts;
+
+ /*%
+ * The number of timeouts that have occurred since we
+ * last successfully received a response packet. This
+ * is used for EDNS0 black hole detection.
+ */
+ unsigned int timeouts;
+
+ /*%
+ * Look aside state for DS lookups.
+ */
+ dns_name_t nsname;
+ dns_fetch_t *nsfetch;
+ dns_rdataset_t nsrrset;
+
+ /*%
+ * Number of queries that reference this context.
+ */
+ unsigned int nqueries; /* Bucket lock. */
+
+ /*%
+ * The reason to print when logging a successful
+ * response to a query.
+ */
+ const char *reason;
+
+ /*%
+ * Random numbers to use for mixing up server addresses.
+ */
+ uint32_t rand_buf;
+ uint32_t rand_bits;
+
+ /*%
+ * Fetch-local statistics for detailed logging.
+ */
+ isc_result_t result; /*%< fetch result */
+ isc_result_t vresult; /*%< validation result */
+ int exitline;
+ isc_time_t start;
+ uint64_t duration;
+ bool logged;
+ unsigned int querysent;
+ unsigned int referrals;
+ unsigned int lamecount;
+ unsigned int quotacount;
+ unsigned int neterr;
+ unsigned int badresp;
+ unsigned int adberr;
+ unsigned int findfail;
+ unsigned int valfail;
+ bool timeout;
+ dns_adbaddrinfo_t *addrinfo;
+ dns_messageid_t id;
+ unsigned int depth;
+ char clientstr[ISC_SOCKADDR_FORMATSIZE];
+};
+
+#define FCTX_MAGIC ISC_MAGIC('F', '!', '!', '!')
+#define VALID_FCTX(fctx) ISC_MAGIC_VALID(fctx, FCTX_MAGIC)
+
+#define FCTX_ATTR_HAVEANSWER 0x0001
+#define FCTX_ATTR_GLUING 0x0002
+#define FCTX_ATTR_ADDRWAIT 0x0004
+#define FCTX_ATTR_SHUTTINGDOWN 0x0008 /* Bucket lock */
+#define FCTX_ATTR_WANTCACHE 0x0010
+#define FCTX_ATTR_WANTNCACHE 0x0020
+#define FCTX_ATTR_NEEDEDNS0 0x0040
+#define FCTX_ATTR_TRIEDFIND 0x0080
+#define FCTX_ATTR_TRIEDALT 0x0100
+
+#define HAVE_ANSWER(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_HAVEANSWER) != 0)
+#define GLUING(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_GLUING) != 0)
+#define ADDRWAIT(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_ADDRWAIT) != 0)
+#define SHUTTINGDOWN(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_SHUTTINGDOWN) != 0)
+#define WANTCACHE(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_WANTCACHE) != 0)
+#define WANTNCACHE(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_WANTNCACHE) != 0)
+#define NEEDEDNS0(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_NEEDEDNS0) != 0)
+#define TRIEDFIND(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_TRIEDFIND) != 0)
+#define TRIEDALT(f) \
+ ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_TRIEDALT) != 0)
+
+#define FCTX_ATTR_SET(f, a) atomic_fetch_or_release(&(f)->attributes, (a))
+#define FCTX_ATTR_CLR(f, a) atomic_fetch_and_release(&(f)->attributes, ~(a))
+
+typedef struct {
+ dns_adbaddrinfo_t *addrinfo;
+ fetchctx_t *fctx;
+ dns_message_t *message;
+} dns_valarg_t;
+
+struct dns_fetch {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ fetchctx_t *private;
+};
+
+#define DNS_FETCH_MAGIC ISC_MAGIC('F', 't', 'c', 'h')
+#define DNS_FETCH_VALID(fetch) ISC_MAGIC_VALID(fetch, DNS_FETCH_MAGIC)
+
+typedef struct fctxbucket {
+ isc_task_t *task;
+ isc_mutex_t lock;
+ ISC_LIST(fetchctx_t) fctxs;
+ atomic_bool exiting;
+ isc_mem_t *mctx;
+} fctxbucket_t;
+
+typedef struct fctxcount fctxcount_t;
+struct fctxcount {
+ dns_fixedname_t fdname;
+ dns_name_t *domain;
+ uint32_t count;
+ uint32_t allowed;
+ uint32_t dropped;
+ isc_stdtime_t logged;
+ ISC_LINK(fctxcount_t) link;
+};
+
+typedef struct zonebucket {
+ isc_mutex_t lock;
+ isc_mem_t *mctx;
+ ISC_LIST(fctxcount_t) list;
+} zonebucket_t;
+
+typedef struct alternate {
+ bool isaddress;
+ union {
+ isc_sockaddr_t addr;
+ struct {
+ dns_name_t name;
+ in_port_t port;
+ } _n;
+ } _u;
+ ISC_LINK(struct alternate) link;
+} alternate_t;
+
+struct dns_resolver {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ isc_mutex_t primelock;
+ dns_rdataclass_t rdclass;
+ isc_socketmgr_t *socketmgr;
+ isc_timermgr_t *timermgr;
+ isc_taskmgr_t *taskmgr;
+ dns_view_t *view;
+ bool frozen;
+ unsigned int options;
+ dns_dispatchmgr_t *dispatchmgr;
+ dns_dispatchset_t *dispatches4;
+ bool exclusivev4;
+ dns_dispatchset_t *dispatches6;
+ isc_dscp_t querydscp4;
+ isc_dscp_t querydscp6;
+ bool exclusivev6;
+ unsigned int nbuckets;
+ fctxbucket_t *buckets;
+ zonebucket_t *dbuckets;
+ uint32_t lame_ttl;
+ ISC_LIST(alternate_t) alternates;
+ uint16_t udpsize;
+#if USE_ALGLOCK
+ isc_rwlock_t alglock;
+#endif /* if USE_ALGLOCK */
+ dns_rbt_t *algorithms;
+ dns_rbt_t *digests;
+#if USE_MBSLOCK
+ isc_rwlock_t mbslock;
+#endif /* if USE_MBSLOCK */
+ dns_rbt_t *mustbesecure;
+ unsigned int spillatmax;
+ unsigned int spillatmin;
+ isc_timer_t *spillattimer;
+ bool zero_no_soa_ttl;
+ unsigned int query_timeout;
+ unsigned int maxdepth;
+ unsigned int maxqueries;
+ isc_result_t quotaresp[2];
+
+ /* Additions for serve-stale feature. */
+ unsigned int retryinterval; /* in milliseconds */
+ unsigned int nonbackofftries;
+
+ /* Atomic */
+ isc_refcount_t references;
+ atomic_uint_fast32_t zspill; /* fetches-per-zone */
+ atomic_bool exiting;
+ atomic_bool priming;
+
+ /* Locked by lock. */
+ isc_eventlist_t whenshutdown;
+ unsigned int activebuckets;
+ unsigned int spillat; /* clients-per-query */
+
+ dns_badcache_t *badcache; /* Bad cache. */
+
+ /* Locked by primelock. */
+ dns_fetch_t *primefetch;
+
+ /* Atomic. */
+ atomic_uint_fast32_t nfctx;
+};
+
+#define RES_MAGIC ISC_MAGIC('R', 'e', 's', '!')
+#define VALID_RESOLVER(res) ISC_MAGIC_VALID(res, RES_MAGIC)
+
+/*%
+ * Private addrinfo flags. These must not conflict with DNS_FETCHOPT_NOEDNS0
+ * (0x008) which we also use as an addrinfo flag.
+ */
+#define FCTX_ADDRINFO_MARK 0x00001
+#define FCTX_ADDRINFO_FORWARDER 0x01000
+#define FCTX_ADDRINFO_EDNSOK 0x04000
+#define FCTX_ADDRINFO_NOCOOKIE 0x08000
+#define FCTX_ADDRINFO_BADCOOKIE 0x10000
+#define FCTX_ADDRINFO_DUALSTACK 0x20000
+
+#define UNMARKED(a) (((a)->flags & FCTX_ADDRINFO_MARK) == 0)
+#define ISFORWARDER(a) (((a)->flags & FCTX_ADDRINFO_FORWARDER) != 0)
+#define NOCOOKIE(a) (((a)->flags & FCTX_ADDRINFO_NOCOOKIE) != 0)
+#define EDNSOK(a) (((a)->flags & FCTX_ADDRINFO_EDNSOK) != 0)
+#define BADCOOKIE(a) (((a)->flags & FCTX_ADDRINFO_BADCOOKIE) != 0)
+#define ISDUALSTACK(a) (((a)->flags & FCTX_ADDRINFO_DUALSTACK) != 0)
+
+#define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
+#define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0)
+
+#define NXDOMAIN_RESULT(r) \
+ ((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NCACHENXDOMAIN)
+#define NXRRSET_RESULT(r) \
+ ((r) == DNS_R_NCACHENXRRSET || (r) == DNS_R_NXRRSET || \
+ (r) == DNS_R_HINTNXRRSET)
+
+#ifdef ENABLE_AFL
+bool dns_fuzzing_resolver = false;
+void
+dns_resolver_setfuzzing() {
+ dns_fuzzing_resolver = true;
+}
+#endif /* ifdef ENABLE_AFL */
+
+static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA";
+static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 };
+static const dns_name_t ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data,
+ ip6_arpa_offsets);
+
+static unsigned char underscore_data[] = "\001_";
+static unsigned char underscore_offsets[] = { 0 };
+static const dns_name_t underscore_name =
+ DNS_NAME_INITNONABSOLUTE(underscore_data, underscore_offsets);
+
+static void
+destroy(dns_resolver_t *res);
+static void
+empty_bucket(dns_resolver_t *res);
+static isc_result_t
+resquery_send(resquery_t *query);
+static void
+resquery_response(isc_task_t *task, isc_event_t *event);
+static void
+resquery_connected(isc_task_t *task, isc_event_t *event);
+static void
+fctx_try(fetchctx_t *fctx, bool retrying, bool badcache);
+static isc_result_t
+fctx_minimize_qname(fetchctx_t *fctx);
+static void
+fctx_destroy(fetchctx_t *fctx);
+static bool
+fctx_unlink(fetchctx_t *fctx);
+static isc_result_t
+ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
+ dns_ttl_t maxttl, bool optout, bool secure,
+ dns_rdataset_t *ardataset, isc_result_t *eresultp);
+static void
+validated(isc_task_t *task, isc_event_t *event);
+static void
+add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo,
+ isc_result_t reason, badnstype_t badtype);
+static isc_result_t
+findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
+ dns_rdatatype_t type, dns_name_t **noqname);
+static void
+fctx_increference(fetchctx_t *fctx);
+static bool
+fctx_decreference(fetchctx_t *fctx);
+static void
+resume_qmin(isc_task_t *task, isc_event_t *event);
+
+/*%
+ * The structure and functions defined below implement the resolver
+ * query (resquery) response handling logic.
+ *
+ * When a resolver query is sent and a response is received, the
+ * resquery_response() event handler is run, which calls the rctx_*()
+ * functions. The respctx_t structure maintains state from function
+ * to function.
+ *
+ * The call flow is described below:
+ *
+ * 1. resquery_response():
+ * - Initialize a respctx_t structure (rctx_respinit()).
+ * - Check for dispatcher failure (rctx_dispfail()).
+ * - Parse the response (rctx_parse()).
+ * - Log the response (rctx_logpacket()).
+ * - Check the parsed response for an OPT record and handle
+ * EDNS (rctx_opt(), rctx_edns()).
+ * - Check for a bad or lame server (rctx_badserver(), rctx_lameserver()).
+ * - Handle delegation-only zones (rctx_delonly_zone()).
+ * - If RCODE and ANCOUNT suggest this is a positive answer, and
+ * if so, call rctx_answer(): go to step 2.
+ * - If RCODE and NSCOUNT suggest this is a negative answer or a
+ * referral, call rctx_answer_none(): go to step 4.
+ * - Check the additional section for data that should be cached
+ * (rctx_additional()).
+ * - Clean up and finish by calling rctx_done(): go to step 5.
+ *
+ * 2. rctx_answer():
+ * - If the answer appears to be positive, call rctx_answer_positive():
+ * go to step 3.
+ * - If the response is a malformed delegation (with glue or NS records
+ * in the answer section), call rctx_answer_none(): go to step 4.
+ *
+ * 3. rctx_answer_positive():
+ * - Initialize the portions of respctx_t needed for processing an answer
+ * (rctx_answer_init()).
+ * - Scan the answer section to find records that are responsive to the
+ * query (rctx_answer_scan()).
+ * - For whichever type of response was found, call a separate routine
+ * to handle it: matching QNAME/QTYPE (rctx_answer_match()),
+ * CNAME (rctx_answer_cname()), covering DNAME (rctx_answer_dname()),
+ * or any records returned in response to a query of type ANY
+ * (rctx_answer_any()).
+ * - Scan the authority section for NS or other records that may be
+ * included with a positive answer (rctx_authority_scan()).
+ *
+ * 4. rctx_answer_none():
+ * - Determine whether this is an NXDOMAIN, NXRRSET, or referral.
+ * - If referral, set up the resolver to follow the delegation
+ * (rctx_referral()).
+ * - If NXDOMAIN/NXRRSET, scan the authority section for NS and SOA
+ * records included with a negative response (rctx_authority_negative()),
+ * then for DNSSEC proof of nonexistence (rctx_authority_dnssec()).
+ *
+ * 5. rctx_done():
+ * - Set up chasing of DS records if needed (rctx_chaseds()).
+ * - If the response wasn't intended for us, wait for another response
+ * from the dispatcher (rctx_next()).
+ * - If there is a problem with the responding server, set up another
+ * query to a different server (rctx_nextserver()).
+ * - If there is a problem that might be temporary or dependent on
+ * EDNS options, set up another query to the same server with changed
+ * options (rctx_resend()).
+ * - Shut down the fetch context.
+ */
+
+typedef struct respctx {
+ isc_task_t *task;
+ dns_dispatchevent_t *devent;
+ resquery_t *query;
+ fetchctx_t *fctx;
+ isc_result_t result;
+ unsigned int retryopts; /* updated options to pass to
+ * fctx_query() when resending */
+
+ dns_rdatatype_t type; /* type being sought (set to
+ * ANY if qtype was SIG or RRSIG) */
+ bool aa; /* authoritative answer? */
+ dns_trust_t trust; /* answer trust level */
+ bool chaining; /* CNAME/DNAME processing? */
+ bool next_server; /* give up, try the next server
+ * */
+
+ badnstype_t broken_type; /* type of name server problem
+ * */
+ isc_result_t broken_server;
+
+ bool get_nameservers; /* get a new NS rrset at
+ * zone cut? */
+ bool resend; /* resend this query? */
+ bool nextitem; /* invalid response; keep
+ * listening for the correct one */
+ bool truncated; /* response was truncated */
+ bool no_response; /* no response was received */
+ bool glue_in_answer; /* glue may be in the answer
+ * section */
+ bool ns_in_answer; /* NS may be in the answer
+ * section */
+ bool negative; /* is this a negative response? */
+
+ isc_stdtime_t now; /* time info */
+ isc_time_t tnow;
+ isc_time_t *finish;
+
+ unsigned int dname_labels;
+ unsigned int domain_labels; /* range of permissible number
+ * of
+ * labels in a DNAME */
+
+ dns_name_t *aname; /* answer name */
+ dns_rdataset_t *ardataset; /* answer rdataset */
+
+ dns_name_t *cname; /* CNAME name */
+ dns_rdataset_t *crdataset; /* CNAME rdataset */
+
+ dns_name_t *dname; /* DNAME name */
+ dns_rdataset_t *drdataset; /* DNAME rdataset */
+
+ dns_name_t *ns_name; /* NS name */
+ dns_rdataset_t *ns_rdataset; /* NS rdataset */
+
+ dns_name_t *soa_name; /* SOA name in a negative answer */
+ dns_name_t *ds_name; /* DS name in a negative answer */
+
+ dns_name_t *found_name; /* invalid name in negative
+ * response */
+ dns_rdatatype_t found_type; /* invalid type in negative
+ * response */
+
+ dns_rdataset_t *opt; /* OPT rdataset */
+} respctx_t;
+
+static void
+rctx_respinit(isc_task_t *task, dns_dispatchevent_t *devent, resquery_t *query,
+ fetchctx_t *fctx, respctx_t *rctx);
+
+static void
+rctx_answer_init(respctx_t *rctx);
+
+static void
+rctx_answer_scan(respctx_t *rctx);
+
+static void
+rctx_authority_positive(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_any(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_match(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_cname(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_dname(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_positive(respctx_t *rctx);
+
+static isc_result_t
+rctx_authority_negative(respctx_t *rctx);
+
+static isc_result_t
+rctx_authority_dnssec(respctx_t *rctx);
+
+static void
+rctx_additional(respctx_t *rctx);
+
+static isc_result_t
+rctx_referral(respctx_t *rctx);
+
+static isc_result_t
+rctx_answer_none(respctx_t *rctx);
+
+static void
+rctx_nextserver(respctx_t *rctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_result_t result);
+
+static void
+rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo);
+
+static void
+rctx_next(respctx_t *rctx);
+
+static void
+rctx_chaseds(respctx_t *rctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_result_t result);
+
+static void
+rctx_done(respctx_t *rctx, isc_result_t result);
+
+static void
+rctx_logpacket(respctx_t *rctx);
+
+static void
+rctx_opt(respctx_t *rctx);
+
+static void
+rctx_edns(respctx_t *rctx);
+
+static isc_result_t
+rctx_parse(respctx_t *rctx);
+
+static isc_result_t
+rctx_badserver(respctx_t *rctx, isc_result_t result);
+
+static isc_result_t
+rctx_answer(respctx_t *rctx);
+
+static isc_result_t
+rctx_lameserver(respctx_t *rctx);
+
+static isc_result_t
+rctx_dispfail(respctx_t *rctx);
+
+static void
+rctx_delonly_zone(respctx_t *rctx);
+
+static void
+rctx_ncache(respctx_t *rctx);
+
+/*%
+ * Increment resolver-related statistics counters.
+ */
+static void
+inc_stats(dns_resolver_t *res, isc_statscounter_t counter) {
+ if (res->view->resstats != NULL) {
+ isc_stats_increment(res->view->resstats, counter);
+ }
+}
+
+static void
+dec_stats(dns_resolver_t *res, isc_statscounter_t counter) {
+ if (res->view->resstats != NULL) {
+ isc_stats_decrement(res->view->resstats, counter);
+ }
+}
+
+static isc_result_t
+valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo,
+ dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset, unsigned int valoptions,
+ isc_task_t *task) {
+ dns_validator_t *validator = NULL;
+ dns_valarg_t *valarg = NULL;
+ isc_result_t result;
+
+ if (SHUTTINGDOWN(fctx)) {
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ valarg = isc_mem_get(fctx->mctx, sizeof(*valarg));
+ *valarg = (dns_valarg_t){ .fctx = fctx, .addrinfo = addrinfo };
+
+ dns_message_attach(message, &valarg->message);
+
+ if (!ISC_LIST_EMPTY(fctx->validators)) {
+ valoptions |= DNS_VALIDATOR_DEFER;
+ } else {
+ valoptions &= ~DNS_VALIDATOR_DEFER;
+ }
+
+ result = dns_validator_create(fctx->res->view, name, type, rdataset,
+ sigrdataset, message, valoptions, task,
+ validated, valarg, &validator);
+ if (result == ISC_R_SUCCESS) {
+ inc_stats(fctx->res, dns_resstatscounter_val);
+ if ((valoptions & DNS_VALIDATOR_DEFER) == 0) {
+ INSIST(fctx->validator == NULL);
+ fctx->validator = validator;
+ }
+ ISC_LIST_APPEND(fctx->validators, validator, link);
+ } else {
+ dns_message_detach(&valarg->message);
+ isc_mem_put(fctx->mctx, valarg, sizeof(*valarg));
+ }
+ return (result);
+}
+
+static bool
+rrsig_fromchildzone(fetchctx_t *fctx, dns_rdataset_t *rdataset) {
+ dns_namereln_t namereln;
+ dns_rdata_rrsig_t rrsig;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ int order;
+ isc_result_t result;
+ unsigned int labels;
+
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ namereln = dns_name_fullcompare(&rrsig.signer, &fctx->domain,
+ &order, &labels);
+ if (namereln == dns_namereln_subdomain) {
+ return (true);
+ }
+ dns_rdata_reset(&rdata);
+ }
+ return (false);
+}
+
+static bool
+fix_mustbedelegationornxdomain(dns_message_t *message, fetchctx_t *fctx) {
+ dns_name_t *name;
+ dns_name_t *domain = &fctx->domain;
+ dns_rdataset_t *rdataset;
+ dns_rdatatype_t type;
+ isc_result_t result;
+ bool keep_auth = false;
+
+ if (message->rcode == dns_rcode_nxdomain) {
+ return (false);
+ }
+
+ /*
+ * A DS RRset can appear anywhere in a zone, even for a delegation-only
+ * zone. So a response to an explicit query for this type should be
+ * excluded from delegation-only fixup.
+ *
+ * SOA, NS, and DNSKEY can only exist at a zone apex, so a positive
+ * response to a query for these types can never violate the
+ * delegation-only assumption: if the query name is below a
+ * zone cut, the response should normally be a referral, which should
+ * be accepted; if the query name is below a zone cut but the server
+ * happens to have authority for the zone of the query name, the
+ * response is a (non-referral) answer. But this does not violate
+ * delegation-only because the query name must be in a different zone
+ * due to the "apex-only" nature of these types. Note that if the
+ * remote server happens to have authority for a child zone of a
+ * delegation-only zone, we may still incorrectly "fix" the response
+ * with NXDOMAIN for queries for other types. Unfortunately it's
+ * generally impossible to differentiate this case from violation of
+ * the delegation-only assumption. Once the resolver learns the
+ * correct zone cut, possibly via a separate query for an "apex-only"
+ * type, queries for other types will be resolved correctly.
+ *
+ * A query for type ANY will be accepted if it hits an exceptional
+ * type above in the answer section as it should be from a child
+ * zone.
+ *
+ * Also accept answers with RRSIG records from the child zone.
+ * Direct queries for RRSIG records should not be answered from
+ * the parent zone.
+ */
+
+ if (message->counts[DNS_SECTION_ANSWER] != 0 &&
+ (fctx->type == dns_rdatatype_ns || fctx->type == dns_rdatatype_ds ||
+ fctx->type == dns_rdatatype_soa ||
+ fctx->type == dns_rdatatype_any ||
+ fctx->type == dns_rdatatype_rrsig ||
+ fctx->type == dns_rdatatype_dnskey))
+ {
+ result = dns_message_firstname(message, DNS_SECTION_ANSWER);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_ANSWER,
+ &name);
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (!dns_name_equal(name, &fctx->name)) {
+ continue;
+ }
+ type = rdataset->type;
+ /*
+ * RRsig from child?
+ */
+ if (type == dns_rdatatype_rrsig &&
+ rrsig_fromchildzone(fctx, rdataset))
+ {
+ return (false);
+ }
+ /*
+ * Direct query for apex records or DS.
+ */
+ if (fctx->type == type &&
+ (type == dns_rdatatype_ds ||
+ type == dns_rdatatype_ns ||
+ type == dns_rdatatype_soa ||
+ type == dns_rdatatype_dnskey))
+ {
+ return (false);
+ }
+ /*
+ * Indirect query for apex records or DS.
+ */
+ if (fctx->type == dns_rdatatype_any &&
+ (type == dns_rdatatype_ns ||
+ type == dns_rdatatype_ds ||
+ type == dns_rdatatype_soa ||
+ type == dns_rdatatype_dnskey))
+ {
+ return (false);
+ }
+ }
+ result = dns_message_nextname(message,
+ DNS_SECTION_ANSWER);
+ }
+ }
+
+ /*
+ * A NODATA response to a DS query?
+ */
+ if (fctx->type == dns_rdatatype_ds &&
+ message->counts[DNS_SECTION_ANSWER] == 0)
+ {
+ return (false);
+ }
+
+ /* Look for referral or indication of answer from child zone? */
+ if (message->counts[DNS_SECTION_AUTHORITY] == 0) {
+ goto munge;
+ }
+
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ type = rdataset->type;
+ if (type == dns_rdatatype_soa &&
+ dns_name_equal(name, domain))
+ {
+ keep_auth = true;
+ }
+
+ if (type != dns_rdatatype_ns &&
+ type != dns_rdatatype_soa &&
+ type != dns_rdatatype_rrsig)
+ {
+ continue;
+ }
+
+ if (type == dns_rdatatype_rrsig) {
+ if (rrsig_fromchildzone(fctx, rdataset)) {
+ return (false);
+ } else {
+ continue;
+ }
+ }
+
+ /* NS or SOA records. */
+ if (dns_name_equal(name, domain)) {
+ /*
+ * If a query for ANY causes a negative
+ * response, we can be sure that this is
+ * an empty node. For other type of queries
+ * we cannot differentiate an empty node
+ * from a node that just doesn't have that
+ * type of record. We only accept the former
+ * case.
+ */
+ if (message->counts[DNS_SECTION_ANSWER] == 0 &&
+ fctx->type == dns_rdatatype_any)
+ {
+ return (false);
+ }
+ } else if (dns_name_issubdomain(name, domain)) {
+ /* Referral or answer from child zone. */
+ return (false);
+ }
+ }
+ result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
+ }
+
+munge:
+ message->rcode = dns_rcode_nxdomain;
+ message->counts[DNS_SECTION_ANSWER] = 0;
+ if (!keep_auth) {
+ message->counts[DNS_SECTION_AUTHORITY] = 0;
+ }
+ message->counts[DNS_SECTION_ADDITIONAL] = 0;
+ return (true);
+}
+
+static isc_result_t
+fctx_starttimer(fetchctx_t *fctx) {
+ /*
+ * Start the lifetime timer for fctx.
+ *
+ * This is also used for stopping the idle timer; in that
+ * case we must purge events already posted to ensure that
+ * no further idle events are delivered.
+ */
+ return (isc_timer_reset(fctx->timer, isc_timertype_once, &fctx->expires,
+ NULL, true));
+}
+
+static isc_result_t
+fctx_starttimer_trystale(fetchctx_t *fctx) {
+ /*
+ * Start the stale-answer-client-timeout timer for fctx.
+ */
+
+ return (isc_timer_reset(fctx->timer_try_stale, isc_timertype_once,
+ &fctx->expires_try_stale, NULL, true));
+}
+
+static void
+fctx_stoptimer(fetchctx_t *fctx) {
+ isc_result_t result;
+
+ /*
+ * We don't return a result if resetting the timer to inactive fails
+ * since there's nothing to be done about it. Resetting to inactive
+ * should never fail anyway, since the code as currently written
+ * cannot fail in that case.
+ */
+ result = isc_timer_reset(fctx->timer, isc_timertype_inactive, NULL,
+ NULL, true);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_timer_reset(): %s",
+ isc_result_totext(result));
+ }
+}
+
+static void
+fctx_stoptimer_trystale(fetchctx_t *fctx) {
+ isc_result_t result;
+
+ if (fctx->timer_try_stale != NULL) {
+ result = isc_timer_reset(fctx->timer_try_stale,
+ isc_timertype_inactive, NULL, NULL,
+ true);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_timer_reset(): %s",
+ isc_result_totext(result));
+ }
+ }
+}
+
+static isc_result_t
+fctx_startidletimer(fetchctx_t *fctx, isc_interval_t *interval) {
+ /*
+ * Start the idle timer for fctx. The lifetime timer continues
+ * to be in effect.
+ */
+ return (isc_timer_reset(fctx->timer, isc_timertype_once, &fctx->expires,
+ interval, false));
+}
+
+/*
+ * Stopping the idle timer is equivalent to calling fctx_starttimer(), but
+ * we use fctx_stopidletimer for readability in the code below.
+ */
+#define fctx_stopidletimer fctx_starttimer
+
+static void
+resquery_destroy(resquery_t **queryp) {
+ dns_resolver_t *res;
+ bool empty;
+ resquery_t *query;
+ fetchctx_t *fctx;
+ unsigned int bucket;
+
+ REQUIRE(queryp != NULL);
+ query = *queryp;
+ *queryp = NULL;
+ REQUIRE(!ISC_LINK_LINKED(query, link));
+
+ INSIST(query->tcpsocket == NULL);
+
+ fctx = query->fctx;
+ res = fctx->res;
+ bucket = fctx->bucketnum;
+
+ LOCK(&res->buckets[bucket].lock);
+ fctx->nqueries--;
+ empty = fctx_decreference(query->fctx);
+ UNLOCK(&res->buckets[bucket].lock);
+
+ if (query->rmessage != NULL) {
+ dns_message_detach(&query->rmessage);
+ }
+
+ query->magic = 0;
+ isc_mem_put(query->mctx, query, sizeof(*query));
+
+ if (empty) {
+ empty_bucket(res);
+ }
+}
+
+/*%
+ * Update EDNS statistics for a server after not getting a response to a UDP
+ * query sent to it.
+ */
+static void
+update_edns_stats(resquery_t *query) {
+ fetchctx_t *fctx = query->fctx;
+
+ if ((query->options & DNS_FETCHOPT_TCP) != 0) {
+ return;
+ }
+
+ if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
+ dns_adb_ednsto(fctx->adb, query->addrinfo, query->udpsize);
+ } else {
+ dns_adb_timeout(fctx->adb, query->addrinfo);
+ }
+}
+
+static void
+fctx_cancelquery(resquery_t **queryp, dns_dispatchevent_t **deventp,
+ isc_time_t *finish, bool no_response, bool age_untried) {
+ fetchctx_t *fctx;
+ resquery_t *query;
+ unsigned int rtt, rttms;
+ unsigned int factor;
+ dns_adbfind_t *find;
+ dns_adbaddrinfo_t *addrinfo;
+ isc_socket_t *sock;
+ isc_stdtime_t now;
+
+ query = *queryp;
+ fctx = query->fctx;
+
+ FCTXTRACE("cancelquery");
+
+ REQUIRE(!RESQUERY_CANCELED(query));
+
+ query->attributes |= RESQUERY_ATTR_CANCELED;
+
+ /*
+ * Should we update the RTT?
+ */
+ if (finish != NULL || no_response) {
+ if (finish != NULL) {
+ /*
+ * We have both the start and finish times for this
+ * packet, so we can compute a real RTT.
+ */
+ rtt = (unsigned int)isc_time_microdiff(finish,
+ &query->start);
+ factor = DNS_ADB_RTTADJDEFAULT;
+
+ rttms = rtt / 1000;
+ if (rttms < DNS_RESOLVER_QRYRTTCLASS0) {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt0);
+ } else if (rttms < DNS_RESOLVER_QRYRTTCLASS1) {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt1);
+ } else if (rttms < DNS_RESOLVER_QRYRTTCLASS2) {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt2);
+ } else if (rttms < DNS_RESOLVER_QRYRTTCLASS3) {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt3);
+ } else if (rttms < DNS_RESOLVER_QRYRTTCLASS4) {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt4);
+ } else {
+ inc_stats(fctx->res,
+ dns_resstatscounter_queryrtt5);
+ }
+ } else {
+ uint32_t value;
+ uint32_t mask;
+
+ update_edns_stats(query);
+
+ /*
+ * If "forward first;" is used and a forwarder timed
+ * out, do not attempt to query it again in this fetch
+ * context.
+ */
+ if (fctx->fwdpolicy == dns_fwdpolicy_first &&
+ ISFORWARDER(query->addrinfo))
+ {
+ add_bad(fctx, query->rmessage, query->addrinfo,
+ ISC_R_TIMEDOUT, badns_forwarder);
+ }
+
+ /*
+ * We don't have an RTT for this query. Maybe the
+ * packet was lost, or maybe this server is very
+ * slow. We don't know. Increase the RTT.
+ */
+ INSIST(no_response);
+ value = isc_random32();
+ if (query->addrinfo->srtt > 800000) {
+ mask = 0x3fff;
+ } else if (query->addrinfo->srtt > 400000) {
+ mask = 0x7fff;
+ } else if (query->addrinfo->srtt > 200000) {
+ mask = 0xffff;
+ } else if (query->addrinfo->srtt > 100000) {
+ mask = 0x1ffff;
+ } else if (query->addrinfo->srtt > 50000) {
+ mask = 0x3ffff;
+ } else if (query->addrinfo->srtt > 25000) {
+ mask = 0x7ffff;
+ } else {
+ mask = 0xfffff;
+ }
+
+ /*
+ * Don't adjust timeout on EDNS queries unless we have
+ * seen a EDNS response.
+ */
+ if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0 &&
+ !EDNSOK(query->addrinfo))
+ {
+ mask >>= 2;
+ }
+
+ rtt = query->addrinfo->srtt + (value & mask);
+ if (rtt > MAX_SINGLE_QUERY_TIMEOUT_US) {
+ rtt = MAX_SINGLE_QUERY_TIMEOUT_US;
+ }
+
+ /*
+ * Replace the current RTT with our value.
+ */
+ factor = DNS_ADB_RTTADJREPLACE;
+ }
+
+ dns_adb_adjustsrtt(fctx->adb, query->addrinfo, rtt, factor);
+ }
+ if ((query->options & DNS_FETCHOPT_TCP) == 0) {
+ /* Inform the ADB that we're ending a UDP fetch */
+ dns_adb_endudpfetch(fctx->adb, query->addrinfo);
+ }
+
+ /*
+ * Age RTTs of servers not tried.
+ */
+ isc_stdtime_get(&now);
+ if (finish != NULL || age_untried) {
+ for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs);
+ addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (UNMARKED(addrinfo)) {
+ dns_adb_agesrtt(fctx->adb, addrinfo, now);
+ }
+ }
+ }
+
+ if ((finish != NULL || age_untried) && TRIEDFIND(fctx)) {
+ for (find = ISC_LIST_HEAD(fctx->finds); find != NULL;
+ find = ISC_LIST_NEXT(find, publink))
+ {
+ for (addrinfo = ISC_LIST_HEAD(find->list);
+ addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (UNMARKED(addrinfo)) {
+ dns_adb_agesrtt(fctx->adb, addrinfo,
+ now);
+ }
+ }
+ }
+ }
+
+ if ((finish != NULL || age_untried) && TRIEDALT(fctx)) {
+ for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (UNMARKED(addrinfo)) {
+ dns_adb_agesrtt(fctx->adb, addrinfo, now);
+ }
+ }
+ for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL;
+ find = ISC_LIST_NEXT(find, publink))
+ {
+ for (addrinfo = ISC_LIST_HEAD(find->list);
+ addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (UNMARKED(addrinfo)) {
+ dns_adb_agesrtt(fctx->adb, addrinfo,
+ now);
+ }
+ }
+ }
+ }
+
+ /*
+ * Check for any outstanding socket events. If they exist, cancel
+ * them and let the event handlers finish the cleanup. The resolver
+ * only needs to worry about managing the connect and send events;
+ * the dispatcher manages the recv events.
+ */
+ if (RESQUERY_CONNECTING(query)) {
+ /*
+ * Cancel the connect.
+ */
+ if (query->tcpsocket != NULL) {
+ isc_socket_cancel(query->tcpsocket, NULL,
+ ISC_SOCKCANCEL_CONNECT);
+ } else if (query->dispentry != NULL) {
+ INSIST(query->exclusivesocket);
+ sock = dns_dispatch_getentrysocket(query->dispentry);
+ if (sock != NULL) {
+ isc_socket_cancel(sock, NULL,
+ ISC_SOCKCANCEL_CONNECT);
+ }
+ }
+ }
+ if (RESQUERY_SENDING(query)) {
+ /*
+ * Cancel the pending send.
+ */
+ if (query->exclusivesocket && query->dispentry != NULL) {
+ sock = dns_dispatch_getentrysocket(query->dispentry);
+ } else {
+ sock = dns_dispatch_getsocket(query->dispatch);
+ }
+ if (sock != NULL) {
+ isc_socket_cancel(sock, NULL, ISC_SOCKCANCEL_SEND);
+ }
+ }
+
+ if (query->dispentry != NULL) {
+ dns_dispatch_removeresponse(&query->dispentry, deventp);
+ }
+
+ ISC_LIST_UNLINK(fctx->queries, query, link);
+
+ if (query->tsig != NULL) {
+ isc_buffer_free(&query->tsig);
+ }
+
+ if (query->tsigkey != NULL) {
+ dns_tsigkey_detach(&query->tsigkey);
+ }
+
+ if (query->dispatch != NULL) {
+ dns_dispatch_detach(&query->dispatch);
+ }
+
+ if (!(RESQUERY_CONNECTING(query) || RESQUERY_SENDING(query))) {
+ /*
+ * It's safe to destroy the query now.
+ */
+ resquery_destroy(&query);
+ }
+}
+
+static void
+fctx_cancelqueries(fetchctx_t *fctx, bool no_response, bool age_untried) {
+ resquery_t *query, *next_query;
+
+ FCTXTRACE("cancelqueries");
+
+ for (query = ISC_LIST_HEAD(fctx->queries); query != NULL;
+ query = next_query)
+ {
+ next_query = ISC_LIST_NEXT(query, link);
+ fctx_cancelquery(&query, NULL, NULL, no_response, age_untried);
+ }
+}
+
+static void
+fctx_cleanupfinds(fetchctx_t *fctx) {
+ dns_adbfind_t *find, *next_find;
+
+ REQUIRE(ISC_LIST_EMPTY(fctx->queries));
+
+ for (find = ISC_LIST_HEAD(fctx->finds); find != NULL; find = next_find)
+ {
+ next_find = ISC_LIST_NEXT(find, publink);
+ ISC_LIST_UNLINK(fctx->finds, find, publink);
+ dns_adb_destroyfind(&find);
+ }
+ fctx->find = NULL;
+}
+
+static void
+fctx_cleanupaltfinds(fetchctx_t *fctx) {
+ dns_adbfind_t *find, *next_find;
+
+ REQUIRE(ISC_LIST_EMPTY(fctx->queries));
+
+ for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL;
+ find = next_find)
+ {
+ next_find = ISC_LIST_NEXT(find, publink);
+ ISC_LIST_UNLINK(fctx->altfinds, find, publink);
+ dns_adb_destroyfind(&find);
+ }
+ fctx->altfind = NULL;
+}
+
+static void
+fctx_cleanupforwaddrs(fetchctx_t *fctx) {
+ dns_adbaddrinfo_t *addr, *next_addr;
+
+ REQUIRE(ISC_LIST_EMPTY(fctx->queries));
+
+ for (addr = ISC_LIST_HEAD(fctx->forwaddrs); addr != NULL;
+ addr = next_addr)
+ {
+ next_addr = ISC_LIST_NEXT(addr, publink);
+ ISC_LIST_UNLINK(fctx->forwaddrs, addr, publink);
+ dns_adb_freeaddrinfo(fctx->adb, &addr);
+ }
+}
+
+static void
+fctx_cleanupaltaddrs(fetchctx_t *fctx) {
+ dns_adbaddrinfo_t *addr, *next_addr;
+
+ REQUIRE(ISC_LIST_EMPTY(fctx->queries));
+
+ for (addr = ISC_LIST_HEAD(fctx->altaddrs); addr != NULL;
+ addr = next_addr)
+ {
+ next_addr = ISC_LIST_NEXT(addr, publink);
+ ISC_LIST_UNLINK(fctx->altaddrs, addr, publink);
+ dns_adb_freeaddrinfo(fctx->adb, &addr);
+ }
+}
+
+static void
+fctx_stopqueries(fetchctx_t *fctx, bool no_response, bool age_untried) {
+ FCTXTRACE("stopqueries");
+ fctx_cancelqueries(fctx, no_response, age_untried);
+ fctx_stoptimer(fctx);
+ fctx_stoptimer_trystale(fctx);
+}
+
+static void
+fctx_cleanupall(fetchctx_t *fctx) {
+ fctx_cleanupfinds(fctx);
+ fctx_cleanupaltfinds(fctx);
+ fctx_cleanupforwaddrs(fctx);
+ fctx_cleanupaltaddrs(fctx);
+}
+
+static void
+fcount_logspill(fetchctx_t *fctx, fctxcount_t *counter, bool final) {
+ char dbuf[DNS_NAME_FORMATSIZE];
+ isc_stdtime_t now;
+
+ if (!isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) {
+ return;
+ }
+
+ /* Do not log a message if there were no dropped fetches. */
+ if (counter->dropped == 0) {
+ return;
+ }
+
+ /* Do not log the cumulative message if the previous log is recent. */
+ isc_stdtime_get(&now);
+ if (!final && counter->logged > now - 60) {
+ return;
+ }
+
+ dns_name_format(&fctx->domain, dbuf, sizeof(dbuf));
+
+ if (!final) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_SPILL,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "too many simultaneous fetches for %s "
+ "(allowed %d spilled %d)",
+ dbuf, counter->allowed, counter->dropped);
+ } else {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_SPILL,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "fetch counters for %s now being discarded "
+ "(allowed %d spilled %d; cumulative since "
+ "initial trigger event)",
+ dbuf, counter->allowed, counter->dropped);
+ }
+
+ counter->logged = now;
+}
+
+static isc_result_t
+fcount_incr(fetchctx_t *fctx, bool force) {
+ isc_result_t result = ISC_R_SUCCESS;
+ zonebucket_t *dbucket;
+ fctxcount_t *counter;
+ unsigned int bucketnum;
+
+ REQUIRE(fctx != NULL);
+ REQUIRE(fctx->res != NULL);
+
+ INSIST(fctx->dbucketnum == RES_NOBUCKET);
+ bucketnum = dns_name_fullhash(&fctx->domain, false) %
+ RES_DOMAIN_BUCKETS;
+
+ dbucket = &fctx->res->dbuckets[bucketnum];
+
+ LOCK(&dbucket->lock);
+ for (counter = ISC_LIST_HEAD(dbucket->list); counter != NULL;
+ counter = ISC_LIST_NEXT(counter, link))
+ {
+ if (dns_name_equal(counter->domain, &fctx->domain)) {
+ break;
+ }
+ }
+
+ if (counter == NULL) {
+ counter = isc_mem_get(dbucket->mctx, sizeof(fctxcount_t));
+ {
+ ISC_LINK_INIT(counter, link);
+ counter->count = 1;
+ counter->logged = 0;
+ counter->allowed = 1;
+ counter->dropped = 0;
+ counter->domain =
+ dns_fixedname_initname(&counter->fdname);
+ dns_name_copynf(&fctx->domain, counter->domain);
+ ISC_LIST_APPEND(dbucket->list, counter, link);
+ }
+ } else {
+ uint_fast32_t spill = atomic_load_acquire(&fctx->res->zspill);
+ if (!force && spill != 0 && counter->count >= spill) {
+ counter->dropped++;
+ fcount_logspill(fctx, counter, false);
+ result = ISC_R_QUOTA;
+ } else {
+ counter->count++;
+ counter->allowed++;
+ }
+ }
+ UNLOCK(&dbucket->lock);
+
+ if (result == ISC_R_SUCCESS) {
+ fctx->dbucketnum = bucketnum;
+ }
+
+ return (result);
+}
+
+static void
+fcount_decr(fetchctx_t *fctx) {
+ zonebucket_t *dbucket;
+ fctxcount_t *counter;
+
+ REQUIRE(fctx != NULL);
+
+ if (fctx->dbucketnum == RES_NOBUCKET) {
+ return;
+ }
+
+ dbucket = &fctx->res->dbuckets[fctx->dbucketnum];
+
+ LOCK(&dbucket->lock);
+ for (counter = ISC_LIST_HEAD(dbucket->list); counter != NULL;
+ counter = ISC_LIST_NEXT(counter, link))
+ {
+ if (dns_name_equal(counter->domain, &fctx->domain)) {
+ break;
+ }
+ }
+
+ if (counter != NULL) {
+ INSIST(counter->count != 0);
+ counter->count--;
+ fctx->dbucketnum = RES_NOBUCKET;
+
+ if (counter->count == 0) {
+ fcount_logspill(fctx, counter, true);
+ ISC_LIST_UNLINK(dbucket->list, counter, link);
+ isc_mem_put(dbucket->mctx, counter, sizeof(*counter));
+ }
+ }
+
+ UNLOCK(&dbucket->lock);
+}
+
+static void
+fctx_sendevents(fetchctx_t *fctx, isc_result_t result, int line) {
+ dns_fetchevent_t *event, *next_event;
+ isc_task_t *task;
+ unsigned int count = 0;
+ isc_interval_t i;
+ bool logit = false;
+ isc_time_t now;
+ unsigned int old_spillat;
+ unsigned int new_spillat = 0; /* initialized to silence
+ * compiler warnings */
+
+ /*
+ * Caller must be holding the appropriate bucket lock.
+ */
+ REQUIRE(fctx->state == fetchstate_done);
+
+ FCTXTRACE("sendevents");
+
+ /*
+ * Keep some record of fetch result for logging later (if required).
+ */
+ fctx->result = result;
+ fctx->exitline = line;
+ TIME_NOW(&now);
+ fctx->duration = isc_time_microdiff(&now, &fctx->start);
+
+ for (event = ISC_LIST_HEAD(fctx->events); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ ISC_LIST_UNLINK(fctx->events, event, ev_link);
+ if (event->ev_type == DNS_EVENT_TRYSTALE) {
+ /*
+ * Not applicable to TRY STALE events, this function is
+ * called when the fetch has either completed or timed
+ * out due to resolver-query-timeout being reached.
+ */
+ isc_task_detach((isc_task_t **)&event->ev_sender);
+ isc_event_free((isc_event_t **)&event);
+ continue;
+ }
+ task = event->ev_sender;
+ event->ev_sender = fctx;
+ event->vresult = fctx->vresult;
+ if (!HAVE_ANSWER(fctx)) {
+ event->result = result;
+ }
+
+ INSIST(event->result != ISC_R_SUCCESS ||
+ dns_rdataset_isassociated(event->rdataset) ||
+ fctx->type == dns_rdatatype_any ||
+ fctx->type == dns_rdatatype_rrsig ||
+ fctx->type == dns_rdatatype_sig);
+
+ /*
+ * Negative results must be indicated in event->result.
+ */
+ if (dns_rdataset_isassociated(event->rdataset) &&
+ NEGATIVE(event->rdataset))
+ {
+ INSIST(event->result == DNS_R_NCACHENXDOMAIN ||
+ event->result == DNS_R_NCACHENXRRSET);
+ }
+
+ isc_task_sendanddetach(&task, ISC_EVENT_PTR(&event));
+ count++;
+ }
+
+ if (HAVE_ANSWER(fctx) && fctx->spilled &&
+ (count < fctx->res->spillatmax || fctx->res->spillatmax == 0))
+ {
+ LOCK(&fctx->res->lock);
+ if (count == fctx->res->spillat &&
+ !atomic_load_acquire(&fctx->res->exiting))
+ {
+ old_spillat = fctx->res->spillat;
+ fctx->res->spillat += 5;
+ if (fctx->res->spillat > fctx->res->spillatmax &&
+ fctx->res->spillatmax != 0)
+ {
+ fctx->res->spillat = fctx->res->spillatmax;
+ }
+ new_spillat = fctx->res->spillat;
+ if (new_spillat != old_spillat) {
+ logit = true;
+ }
+ isc_interval_set(&i, 20 * 60, 0);
+ result = isc_timer_reset(fctx->res->spillattimer,
+ isc_timertype_ticker, NULL, &i,
+ true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ UNLOCK(&fctx->res->lock);
+ if (logit) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "clients-per-query increased to %u",
+ new_spillat);
+ }
+ }
+}
+
+static void
+log_edns(fetchctx_t *fctx) {
+ char domainbuf[DNS_NAME_FORMATSIZE];
+
+ if (fctx->reason == NULL) {
+ return;
+ }
+
+ /*
+ * We do not know if fctx->domain is the actual domain the record
+ * lives in or a parent domain so we have a '?' after it.
+ */
+ dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_EDNS_DISABLED,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "success resolving '%s' (in '%s'?) after %s", fctx->info,
+ domainbuf, fctx->reason);
+}
+
+static void
+fctx_done(fetchctx_t *fctx, isc_result_t result, int line) {
+ dns_resolver_t *res;
+ bool no_response = false;
+ bool age_untried = false;
+
+ REQUIRE(line >= 0);
+
+ FCTXTRACE("done");
+
+ res = fctx->res;
+
+ if (result == ISC_R_SUCCESS) {
+ /*%
+ * Log any deferred EDNS timeout messages.
+ */
+ log_edns(fctx);
+ no_response = true;
+ if (fctx->qmin_warning != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "success resolving '%s' "
+ "after disabling qname minimization due "
+ "to '%s'",
+ fctx->info,
+ isc_result_totext(fctx->qmin_warning));
+ }
+ } else if (result == ISC_R_TIMEDOUT) {
+ age_untried = true;
+ }
+
+ fctx->qmin_warning = ISC_R_SUCCESS;
+ fctx->reason = NULL;
+
+ fctx_stopqueries(fctx, no_response, age_untried);
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+
+ fctx->state = fetchstate_done;
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ fctx_sendevents(fctx, result, line);
+
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+}
+
+static void
+process_sendevent(resquery_t *query, isc_event_t *event) {
+ isc_socketevent_t *sevent = (isc_socketevent_t *)event;
+ bool destroy_query = false;
+ bool retry = false;
+ isc_result_t result;
+ fetchctx_t *fctx;
+
+ fctx = query->fctx;
+
+ if (RESQUERY_CANCELED(query)) {
+ if (query->sends == 0 && query->connects == 0) {
+ /*
+ * This query was canceled while the
+ * isc_socket_sendto/connect() was in progress.
+ */
+ if (query->tcpsocket != NULL) {
+ isc_socket_detach(&query->tcpsocket);
+ }
+ destroy_query = true;
+ }
+ } else {
+ switch (sevent->result) {
+ case ISC_R_SUCCESS:
+ break;
+
+ case ISC_R_HOSTUNREACH:
+ case ISC_R_NETUNREACH:
+ case ISC_R_NOPERM:
+ case ISC_R_ADDRNOTAVAIL:
+ case ISC_R_CONNREFUSED:
+ FCTXTRACE3("query canceled in sendevent(): "
+ "no route to host; no response",
+ sevent->result);
+
+ /*
+ * No route to remote.
+ */
+ add_bad(fctx, query->rmessage, query->addrinfo,
+ sevent->result, badns_unreachable);
+ fctx_cancelquery(&query, NULL, NULL, true, false);
+ retry = true;
+ break;
+
+ default:
+ FCTXTRACE3("query canceled in sendevent() due to "
+ "unexpected event result; responding",
+ sevent->result);
+
+ fctx_cancelquery(&query, NULL, NULL, false, false);
+ break;
+ }
+ }
+
+ if (event->ev_type == ISC_SOCKEVENT_CONNECT) {
+ isc_event_free(&event);
+ }
+
+ if (retry) {
+ /*
+ * Behave as if the idle timer has expired. For TCP
+ * this may not actually reflect the latest timer.
+ */
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ result = fctx_stopidletimer(fctx);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, result, __LINE__);
+ } else {
+ fctx_try(fctx, true, false);
+ }
+ }
+
+ if (destroy_query) {
+ resquery_destroy(&query);
+ }
+}
+
+static void
+resquery_udpconnected(isc_task_t *task, isc_event_t *event) {
+ resquery_t *query = event->ev_arg;
+
+ REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT);
+
+ QTRACE("udpconnected");
+
+ UNUSED(task);
+
+ INSIST(RESQUERY_CONNECTING(query));
+
+ query->connects--;
+
+ process_sendevent(query, event);
+}
+
+static void
+resquery_senddone(isc_task_t *task, isc_event_t *event) {
+ resquery_t *query = event->ev_arg;
+
+ REQUIRE(event->ev_type == ISC_SOCKEVENT_SENDDONE);
+
+ QTRACE("senddone");
+
+ /*
+ * XXXRTH
+ *
+ * Currently we don't wait for the senddone event before retrying
+ * a query. This means that if we get really behind, we may end
+ * up doing extra work!
+ */
+
+ UNUSED(task);
+
+ INSIST(RESQUERY_SENDING(query));
+
+ query->sends--;
+
+ process_sendevent(query, event);
+}
+
+static isc_result_t
+fctx_addopt(dns_message_t *message, unsigned int version, uint16_t udpsize,
+ dns_ednsopt_t *ednsopts, size_t count) {
+ dns_rdataset_t *rdataset = NULL;
+ isc_result_t result;
+
+ result = dns_message_buildopt(message, &rdataset, version, udpsize,
+ DNS_MESSAGEEXTFLAG_DO, ednsopts, count);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ return (dns_message_setopt(message, rdataset));
+}
+
+static void
+fctx_setretryinterval(fetchctx_t *fctx, unsigned int rtt) {
+ unsigned int seconds;
+ unsigned int us;
+
+ us = fctx->res->retryinterval * 1000;
+ /*
+ * Exponential backoff after the first few tries.
+ */
+ if (fctx->restarts > fctx->res->nonbackofftries) {
+ int shift = fctx->restarts - fctx->res->nonbackofftries;
+ if (shift > 6) {
+ shift = 6;
+ }
+ us <<= shift;
+ }
+
+ /*
+ * Add a fudge factor to the expected rtt based on the current
+ * estimate.
+ */
+ if (rtt < 50000) {
+ rtt += 50000;
+ } else if (rtt < 100000) {
+ rtt += 100000;
+ } else {
+ rtt += 200000;
+ }
+
+ /*
+ * Always wait for at least the expected rtt.
+ */
+ if (us < rtt) {
+ us = rtt;
+ }
+
+ /*
+ * But don't ever wait for more than 10 seconds.
+ */
+ if (us > MAX_SINGLE_QUERY_TIMEOUT_US) {
+ us = MAX_SINGLE_QUERY_TIMEOUT_US;
+ }
+
+ seconds = us / US_PER_SEC;
+ us -= seconds * US_PER_SEC;
+ isc_interval_set(&fctx->interval, seconds, us * 1000);
+}
+
+static isc_result_t
+fctx_query(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo,
+ unsigned int options) {
+ dns_resolver_t *res;
+ isc_task_t *task;
+ isc_result_t result;
+ resquery_t *query;
+ isc_sockaddr_t addr;
+ bool have_addr = false;
+ unsigned int srtt;
+ isc_dscp_t dscp = -1;
+ unsigned int bucketnum;
+
+ FCTXTRACE("query");
+
+ res = fctx->res;
+ task = res->buckets[fctx->bucketnum].task;
+
+ srtt = addrinfo->srtt;
+
+ /*
+ * Allow an additional second for the kernel to resend the SYN (or
+ * SYN without ECN in the case of stupid firewalls blocking ECN
+ * negotiation) over the current RTT estimate.
+ */
+ if ((options & DNS_FETCHOPT_TCP) != 0) {
+ srtt += 1000000;
+ }
+
+ /*
+ * A forwarder needs to make multiple queries. Give it at least
+ * a second to do these in.
+ */
+ if (ISFORWARDER(addrinfo) && srtt < 1000000) {
+ srtt = 1000000;
+ }
+
+ fctx_setretryinterval(fctx, srtt);
+ result = fctx_startidletimer(fctx, &fctx->interval);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ INSIST(ISC_LIST_EMPTY(fctx->validators));
+
+ query = isc_mem_get(fctx->mctx, sizeof(*query));
+ query->rmessage = NULL;
+ dns_message_create(fctx->mctx, DNS_MESSAGE_INTENTPARSE,
+ &query->rmessage);
+ query->mctx = fctx->mctx;
+ query->options = options;
+ query->attributes = 0;
+ query->sends = 0;
+ query->connects = 0;
+ query->dscp = addrinfo->dscp;
+ query->udpsize = 0;
+ /*
+ * Note that the caller MUST guarantee that 'addrinfo' will remain
+ * valid until this query is canceled.
+ */
+ query->addrinfo = addrinfo;
+ TIME_NOW(&query->start);
+
+ /*
+ * If this is a TCP query, then we need to make a socket and
+ * a dispatch for it here. Otherwise we use the resolver's
+ * shared dispatch.
+ */
+ query->dispatchmgr = res->dispatchmgr;
+ query->dispatch = NULL;
+ query->exclusivesocket = false;
+ query->tcpsocket = NULL;
+ if (res->view->peers != NULL) {
+ dns_peer_t *peer = NULL;
+ isc_netaddr_t dstip;
+ bool usetcp = false;
+ isc_netaddr_fromsockaddr(&dstip, &addrinfo->sockaddr);
+ result = dns_peerlist_peerbyaddr(res->view->peers, &dstip,
+ &peer);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_peer_getquerysource(peer, &addr);
+ if (result == ISC_R_SUCCESS) {
+ have_addr = true;
+ }
+ result = dns_peer_getquerydscp(peer, &dscp);
+ if (result == ISC_R_SUCCESS) {
+ query->dscp = dscp;
+ }
+ result = dns_peer_getforcetcp(peer, &usetcp);
+ if (result == ISC_R_SUCCESS && usetcp) {
+ query->options |= DNS_FETCHOPT_TCP;
+ }
+ }
+ }
+
+ dscp = -1;
+ if ((query->options & DNS_FETCHOPT_TCP) != 0) {
+ int pf;
+
+ pf = isc_sockaddr_pf(&addrinfo->sockaddr);
+ if (!have_addr) {
+ switch (pf) {
+ case PF_INET:
+ result = dns_dispatch_getlocaladdress(
+ res->dispatches4->dispatches[0], &addr);
+ dscp = dns_resolver_getquerydscp4(fctx->res);
+ break;
+ case PF_INET6:
+ result = dns_dispatch_getlocaladdress(
+ res->dispatches6->dispatches[0], &addr);
+ dscp = dns_resolver_getquerydscp6(fctx->res);
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ break;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_query;
+ }
+ }
+ isc_sockaddr_setport(&addr, 0);
+ if (query->dscp == -1) {
+ query->dscp = dscp;
+ }
+
+ result = isc_socket_create(res->socketmgr, pf,
+ isc_sockettype_tcp,
+ &query->tcpsocket);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_query;
+ }
+
+#ifndef BROKEN_TCP_BIND_BEFORE_CONNECT
+ result = isc_socket_bind(query->tcpsocket, &addr, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_socket;
+ }
+#endif /* ifndef BROKEN_TCP_BIND_BEFORE_CONNECT */
+
+ /*
+ * A dispatch will be created once the connect succeeds.
+ */
+ } else {
+ if (have_addr) {
+ unsigned int attrs, attrmask;
+ attrs = DNS_DISPATCHATTR_UDP;
+ switch (isc_sockaddr_pf(&addr)) {
+ case AF_INET:
+ attrs |= DNS_DISPATCHATTR_IPV4;
+ dscp = dns_resolver_getquerydscp4(fctx->res);
+ break;
+ case AF_INET6:
+ attrs |= DNS_DISPATCHATTR_IPV6;
+ dscp = dns_resolver_getquerydscp6(fctx->res);
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup_query;
+ }
+ attrmask = DNS_DISPATCHATTR_UDP;
+ attrmask |= DNS_DISPATCHATTR_TCP;
+ attrmask |= DNS_DISPATCHATTR_IPV4;
+ attrmask |= DNS_DISPATCHATTR_IPV6;
+ result = dns_dispatch_getudp(
+ res->dispatchmgr, res->socketmgr, res->taskmgr,
+ &addr, 4096, 20000, 32768, 16411, 16433, attrs,
+ attrmask, &query->dispatch);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_query;
+ }
+ } else {
+ switch (isc_sockaddr_pf(&addrinfo->sockaddr)) {
+ case PF_INET:
+ dns_dispatch_attach(
+ dns_resolver_dispatchv4(res),
+ &query->dispatch);
+ query->exclusivesocket = res->exclusivev4;
+ dscp = dns_resolver_getquerydscp4(fctx->res);
+ break;
+ case PF_INET6:
+ dns_dispatch_attach(
+ dns_resolver_dispatchv6(res),
+ &query->dispatch);
+ query->exclusivesocket = res->exclusivev6;
+ dscp = dns_resolver_getquerydscp6(fctx->res);
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup_query;
+ }
+ }
+
+ if (query->dscp == -1) {
+ query->dscp = dscp;
+ }
+ /*
+ * We should always have a valid dispatcher here. If we
+ * don't support a protocol family, then its dispatcher
+ * will be NULL, but we shouldn't be finding addresses for
+ * protocol types we don't support, so the dispatcher
+ * we found should never be NULL.
+ */
+ INSIST(query->dispatch != NULL);
+ }
+
+ query->dispentry = NULL;
+ query->fctx = fctx; /* reference added by caller */
+ query->tsig = NULL;
+ query->tsigkey = NULL;
+ ISC_LINK_INIT(query, link);
+ query->magic = QUERY_MAGIC;
+
+ if ((query->options & DNS_FETCHOPT_TCP) != 0) {
+ /*
+ * Connect to the remote server.
+ *
+ * XXXRTH Should we attach to the socket?
+ */
+ if (query->dscp != -1) {
+ isc_socket_dscp(query->tcpsocket, query->dscp);
+ }
+ result = isc_socket_connect(query->tcpsocket,
+ &addrinfo->sockaddr, task,
+ resquery_connected, query);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_socket;
+ }
+ query->connects++;
+ QTRACE("connecting via TCP");
+ } else {
+ if (dns_adbentry_overquota(addrinfo->entry)) {
+ goto cleanup_dispatch;
+ }
+
+ /* Inform the ADB that we're starting a UDP fetch */
+ dns_adb_beginudpfetch(fctx->adb, addrinfo);
+
+ result = resquery_send(query);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_udpfetch;
+ }
+ }
+
+ fctx->querysent++;
+
+ ISC_LIST_APPEND(fctx->queries, query, link);
+ bucketnum = fctx->bucketnum;
+ LOCK(&res->buckets[bucketnum].lock);
+ fctx->nqueries++;
+ UNLOCK(&res->buckets[bucketnum].lock);
+ if (isc_sockaddr_pf(&addrinfo->sockaddr) == PF_INET) {
+ inc_stats(res, dns_resstatscounter_queryv4);
+ } else {
+ inc_stats(res, dns_resstatscounter_queryv6);
+ }
+ if (res->view->resquerystats != NULL) {
+ dns_rdatatypestats_increment(res->view->resquerystats,
+ fctx->type);
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup_socket:
+ isc_socket_detach(&query->tcpsocket);
+
+cleanup_udpfetch:
+ if (!RESQUERY_CANCELED(query)) {
+ if ((query->options & DNS_FETCHOPT_TCP) == 0) {
+ /* Inform the ADB that we're ending a UDP fetch */
+ dns_adb_endudpfetch(fctx->adb, addrinfo);
+ }
+ }
+
+cleanup_dispatch:
+ if (query->dispatch != NULL) {
+ dns_dispatch_detach(&query->dispatch);
+ }
+
+cleanup_query:
+ if (query->connects == 0) {
+ query->magic = 0;
+ dns_message_detach(&query->rmessage);
+ isc_mem_put(fctx->mctx, query, sizeof(*query));
+ }
+
+ RUNTIME_CHECK(fctx_stopidletimer(fctx) == ISC_R_SUCCESS);
+
+ return (result);
+}
+
+static bool
+bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ isc_sockaddr_t *sa;
+
+ for (sa = ISC_LIST_HEAD(fctx->bad_edns); sa != NULL;
+ sa = ISC_LIST_NEXT(sa, link))
+ {
+ if (isc_sockaddr_equal(sa, address)) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+static void
+add_bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ isc_sockaddr_t *sa;
+
+#ifdef ENABLE_AFL
+ if (dns_fuzzing_resolver) {
+ return;
+ }
+#endif /* ifdef ENABLE_AFL */
+ if (bad_edns(fctx, address)) {
+ return;
+ }
+
+ sa = isc_mem_get(fctx->mctx, sizeof(*sa));
+
+ *sa = *address;
+ ISC_LIST_INITANDAPPEND(fctx->bad_edns, sa, link);
+}
+
+static struct tried *
+triededns(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ struct tried *tried;
+
+ for (tried = ISC_LIST_HEAD(fctx->edns); tried != NULL;
+ tried = ISC_LIST_NEXT(tried, link))
+ {
+ if (isc_sockaddr_equal(&tried->addr, address)) {
+ return (tried);
+ }
+ }
+
+ return (NULL);
+}
+
+static void
+add_triededns(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ struct tried *tried;
+
+ tried = triededns(fctx, address);
+ if (tried != NULL) {
+ tried->count++;
+ return;
+ }
+
+ tried = isc_mem_get(fctx->mctx, sizeof(*tried));
+
+ tried->addr = *address;
+ tried->count = 1;
+ ISC_LIST_INITANDAPPEND(fctx->edns, tried, link);
+}
+
+static struct tried *
+triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ struct tried *tried;
+
+ for (tried = ISC_LIST_HEAD(fctx->edns512); tried != NULL;
+ tried = ISC_LIST_NEXT(tried, link))
+ {
+ if (isc_sockaddr_equal(&tried->addr, address)) {
+ return (tried);
+ }
+ }
+
+ return (NULL);
+}
+
+static void
+add_triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ struct tried *tried;
+
+ tried = triededns512(fctx, address);
+ if (tried != NULL) {
+ tried->count++;
+ return;
+ }
+
+ tried = isc_mem_get(fctx->mctx, sizeof(*tried));
+
+ tried->addr = *address;
+ tried->count = 1;
+ ISC_LIST_INITANDAPPEND(fctx->edns512, tried, link);
+}
+
+static size_t
+addr2buf(void *buf, const size_t bufsize, const isc_sockaddr_t *sockaddr) {
+ isc_netaddr_t netaddr;
+ isc_netaddr_fromsockaddr(&netaddr, sockaddr);
+ switch (netaddr.family) {
+ case AF_INET:
+ INSIST(bufsize >= 4);
+ memmove(buf, &netaddr.type.in, 4);
+ return (4);
+ case AF_INET6:
+ INSIST(bufsize >= 16);
+ memmove(buf, &netaddr.type.in6, 16);
+ return (16);
+ default:
+ UNREACHABLE();
+ }
+ return (0);
+}
+
+static isc_socket_t *
+query2sock(const resquery_t *query) {
+ if (query->exclusivesocket) {
+ return (dns_dispatch_getentrysocket(query->dispentry));
+ } else {
+ return (dns_dispatch_getsocket(query->dispatch));
+ }
+}
+
+static size_t
+add_serveraddr(uint8_t *buf, const size_t bufsize, const resquery_t *query) {
+ return (addr2buf(buf, bufsize, &query->addrinfo->sockaddr));
+}
+
+/*
+ * Client cookie is 8 octets.
+ * Server cookie is [8..32] octets.
+ */
+#define CLIENT_COOKIE_SIZE 8U
+#define COOKIE_BUFFER_SIZE (8U + 32U)
+
+static void
+compute_cc(const resquery_t *query, uint8_t *cookie, const size_t len) {
+ INSIST(len >= CLIENT_COOKIE_SIZE);
+ STATIC_ASSERT(sizeof(query->fctx->res->view->secret) >=
+ ISC_SIPHASH24_KEY_LENGTH,
+ "The view->secret size can't fit SipHash 2-4 key length");
+
+ uint8_t buf[16] ISC_NONSTRING = { 0 };
+ size_t buflen = add_serveraddr(buf, sizeof(buf), query);
+
+ uint8_t digest[ISC_SIPHASH24_TAG_LENGTH] ISC_NONSTRING = { 0 };
+ isc_siphash24(query->fctx->res->view->secret, buf, buflen, digest);
+ memmove(cookie, digest, CLIENT_COOKIE_SIZE);
+}
+
+static isc_result_t
+issecuredomain(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type,
+ isc_stdtime_t now, bool checknta, bool *ntap, bool *issecure) {
+ dns_name_t suffix;
+ unsigned int labels;
+
+ /*
+ * For DS variants we need to check fom the parent domain,
+ * since there may be a negative trust anchor for the name,
+ * while the enclosing domain where the DS record lives is
+ * under a secure entry point.
+ */
+ labels = dns_name_countlabels(name);
+ if (dns_rdatatype_atparent(type) && labels > 1) {
+ dns_name_init(&suffix, NULL);
+ dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
+ name = &suffix;
+ }
+
+ return (dns_view_issecuredomain(view, name, now, checknta, ntap,
+ issecure));
+}
+
+static isc_result_t
+resquery_send(resquery_t *query) {
+ fetchctx_t *fctx;
+ isc_result_t result;
+ dns_name_t *qname = NULL;
+ dns_rdataset_t *qrdataset = NULL;
+ isc_region_t r;
+ dns_resolver_t *res;
+ isc_task_t *task;
+ isc_socket_t *sock;
+ isc_buffer_t tcpbuffer;
+ isc_sockaddr_t *address;
+ isc_buffer_t *buffer;
+ isc_netaddr_t ipaddr;
+ dns_tsigkey_t *tsigkey = NULL;
+ dns_peer_t *peer = NULL;
+ bool useedns;
+ dns_compress_t cctx;
+ bool cleanup_cctx = false;
+ bool secure_domain;
+ bool tcp = ((query->options & DNS_FETCHOPT_TCP) != 0);
+ dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS];
+ unsigned ednsopt = 0;
+ uint16_t hint = 0, udpsize = 0; /* No EDNS */
+#ifdef HAVE_DNSTAP
+ isc_sockaddr_t localaddr, *la = NULL;
+ unsigned char zone[DNS_NAME_MAXWIRE];
+ dns_dtmsgtype_t dtmsgtype;
+ isc_region_t zr;
+ isc_buffer_t zb;
+#endif /* HAVE_DNSTAP */
+
+ fctx = query->fctx;
+ QTRACE("send");
+
+ res = fctx->res;
+ task = res->buckets[fctx->bucketnum].task;
+ address = NULL;
+
+ if (tcp) {
+ /*
+ * Reserve space for the TCP message length.
+ */
+ isc_buffer_init(&tcpbuffer, query->data, sizeof(query->data));
+ isc_buffer_init(&query->buffer, query->data + 2,
+ sizeof(query->data) - 2);
+ buffer = &tcpbuffer;
+ } else {
+ isc_buffer_init(&query->buffer, query->data,
+ sizeof(query->data));
+ buffer = &query->buffer;
+ }
+
+ result = dns_message_gettempname(fctx->qmessage, &qname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_temps;
+ }
+ result = dns_message_gettemprdataset(fctx->qmessage, &qrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_temps;
+ }
+
+ /*
+ * Get a query id from the dispatch.
+ */
+ result = dns_dispatch_addresponse(query->dispatch, 0,
+ &query->addrinfo->sockaddr, task,
+ resquery_response, query, &query->id,
+ &query->dispentry, res->socketmgr);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_temps;
+ }
+
+ fctx->qmessage->opcode = dns_opcode_query;
+
+ /*
+ * Set up question.
+ */
+ dns_name_clone(&fctx->name, qname);
+ dns_rdataset_makequestion(qrdataset, res->rdclass, fctx->type);
+ ISC_LIST_APPEND(qname->list, qrdataset, link);
+ dns_message_addname(fctx->qmessage, qname, DNS_SECTION_QUESTION);
+ qname = NULL;
+ qrdataset = NULL;
+
+ /*
+ * Set RD if the client has requested that we do a recursive query,
+ * or if we're sending to a forwarder.
+ */
+ if ((query->options & DNS_FETCHOPT_RECURSIVE) != 0 ||
+ ISFORWARDER(query->addrinfo))
+ {
+ fctx->qmessage->flags |= DNS_MESSAGEFLAG_RD;
+ }
+
+ /*
+ * Set CD if the client says not to validate, or if the
+ * question is under a secure entry point and this is a
+ * recursive/forward query -- unless the client said not to.
+ */
+ if ((query->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
+ /* Do nothing */
+ } else if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
+ fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
+ } else if (res->view->enablevalidation &&
+ ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0))
+ {
+ bool checknta = ((query->options & DNS_FETCHOPT_NONTA) == 0);
+ bool ntacovered = false;
+ result = issecuredomain(res->view, &fctx->name, fctx->type,
+ isc_time_seconds(&query->start),
+ checknta, &ntacovered, &secure_domain);
+ if (result != ISC_R_SUCCESS) {
+ secure_domain = false;
+ }
+ if (secure_domain ||
+ (ISFORWARDER(query->addrinfo) && ntacovered))
+ {
+ fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
+ }
+ }
+
+ /*
+ * We don't have to set opcode because it defaults to query.
+ */
+ fctx->qmessage->id = query->id;
+
+ /*
+ * Convert the question to wire format.
+ */
+ result = dns_compress_init(&cctx, -1, fctx->res->mctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+ cleanup_cctx = true;
+
+ result = dns_message_renderbegin(fctx->qmessage, &cctx, &query->buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+
+ result = dns_message_rendersection(fctx->qmessage, DNS_SECTION_QUESTION,
+ 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+
+ peer = NULL;
+ isc_netaddr_fromsockaddr(&ipaddr, &query->addrinfo->sockaddr);
+ (void)dns_peerlist_peerbyaddr(fctx->res->view->peers, &ipaddr, &peer);
+
+ /*
+ * The ADB does not know about servers with "edns no". Check this,
+ * and then inform the ADB for future use.
+ */
+ if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0 &&
+ peer != NULL &&
+ dns_peer_getsupportedns(peer, &useedns) == ISC_R_SUCCESS &&
+ !useedns)
+ {
+ query->options |= DNS_FETCHOPT_NOEDNS0;
+ dns_adb_changeflags(fctx->adb, query->addrinfo,
+ DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_NOEDNS0);
+ }
+
+ /* Sync NOEDNS0 flag in addrinfo->flags and options now. */
+ if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) != 0) {
+ query->options |= DNS_FETCHOPT_NOEDNS0;
+ }
+
+ if (fctx->timeout && (query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
+ isc_sockaddr_t *sockaddr = &query->addrinfo->sockaddr;
+ struct tried *tried;
+
+ if ((tried = triededns(fctx, sockaddr)) != NULL) {
+ if (tried->count == 1U) {
+ hint = dns_adb_getudpsize(fctx->adb,
+ query->addrinfo);
+ } else if (tried->count >= 2U) {
+ query->options |= DNS_FETCHOPT_EDNS512;
+ fctx->reason = "reducing the advertised EDNS "
+ "UDP packet size to 512 octets";
+ }
+ }
+ }
+ fctx->timeout = false;
+
+ /*
+ * Use EDNS0, unless the caller doesn't want it, or we know that the
+ * remote server doesn't like it.
+ */
+ if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
+ if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0) {
+ unsigned int version = DNS_EDNS_VERSION;
+ unsigned int flags = query->addrinfo->flags;
+ bool reqnsid = res->view->requestnsid;
+ bool sendcookie = res->view->sendcookie;
+ bool tcpkeepalive = false;
+ unsigned char cookie[COOKIE_BUFFER_SIZE];
+ uint16_t padding = 0;
+
+ if ((flags & FCTX_ADDRINFO_EDNSOK) != 0 &&
+ (query->options & DNS_FETCHOPT_EDNS512) == 0)
+ {
+ udpsize = dns_adb_probesize(fctx->adb,
+ query->addrinfo,
+ fctx->timeouts);
+ if (udpsize > res->udpsize) {
+ udpsize = res->udpsize;
+ }
+ }
+
+ if (peer != NULL) {
+ (void)dns_peer_getudpsize(peer, &udpsize);
+ }
+
+ if (udpsize == 0U && res->udpsize == 512U) {
+ udpsize = 512;
+ }
+
+ /*
+ * Was the size forced to 512 in the configuration?
+ */
+ if (udpsize == 512U) {
+ query->options |= DNS_FETCHOPT_EDNS512;
+ }
+
+ /*
+ * We have talked to this server before.
+ */
+ if (hint != 0U) {
+ udpsize = hint;
+ }
+
+ /*
+ * We know nothing about the peer's capabilities
+ * so start with minimal EDNS UDP size.
+ */
+ if (udpsize == 0U) {
+ udpsize = 512;
+ }
+
+ if ((flags & DNS_FETCHOPT_EDNSVERSIONSET) != 0) {
+ version = flags & DNS_FETCHOPT_EDNSVERSIONMASK;
+ version >>= DNS_FETCHOPT_EDNSVERSIONSHIFT;
+ }
+
+ /* Request NSID/COOKIE/VERSION for current peer? */
+ if (peer != NULL) {
+ uint8_t ednsversion;
+ (void)dns_peer_getrequestnsid(peer, &reqnsid);
+ (void)dns_peer_getsendcookie(peer, &sendcookie);
+ result = dns_peer_getednsversion(peer,
+ &ednsversion);
+ if (result == ISC_R_SUCCESS &&
+ ednsversion < version)
+ {
+ version = ednsversion;
+ }
+ }
+ if (NOCOOKIE(query->addrinfo)) {
+ sendcookie = false;
+ }
+ if (reqnsid) {
+ INSIST(ednsopt < DNS_EDNSOPTIONS);
+ ednsopts[ednsopt].code = DNS_OPT_NSID;
+ ednsopts[ednsopt].length = 0;
+ ednsopts[ednsopt].value = NULL;
+ ednsopt++;
+ }
+ if (sendcookie) {
+ INSIST(ednsopt < DNS_EDNSOPTIONS);
+ ednsopts[ednsopt].code = DNS_OPT_COOKIE;
+ ednsopts[ednsopt].length =
+ (uint16_t)dns_adb_getcookie(
+ fctx->adb, query->addrinfo,
+ cookie, sizeof(cookie));
+ if (ednsopts[ednsopt].length != 0) {
+ ednsopts[ednsopt].value = cookie;
+ inc_stats(
+ fctx->res,
+ dns_resstatscounter_cookieout);
+ } else {
+ compute_cc(query, cookie,
+ CLIENT_COOKIE_SIZE);
+ ednsopts[ednsopt].value = cookie;
+ ednsopts[ednsopt].length =
+ CLIENT_COOKIE_SIZE;
+ inc_stats(
+ fctx->res,
+ dns_resstatscounter_cookienew);
+ }
+ ednsopt++;
+ }
+
+ /* Add TCP keepalive option if appropriate */
+ if ((peer != NULL) && tcp) {
+ (void)dns_peer_gettcpkeepalive(peer,
+ &tcpkeepalive);
+ }
+ if (tcpkeepalive) {
+ INSIST(ednsopt < DNS_EDNSOPTIONS);
+ ednsopts[ednsopt].code = DNS_OPT_TCP_KEEPALIVE;
+ ednsopts[ednsopt].length = 0;
+ ednsopts[ednsopt].value = NULL;
+ ednsopt++;
+ }
+
+ /* Add PAD for current peer? Require TCP for now */
+ if ((peer != NULL) && tcp) {
+ (void)dns_peer_getpadding(peer, &padding);
+ }
+ if (padding != 0) {
+ INSIST(ednsopt < DNS_EDNSOPTIONS);
+ ednsopts[ednsopt].code = DNS_OPT_PAD;
+ ednsopts[ednsopt].length = 0;
+ ednsopt++;
+ dns_message_setpadding(fctx->qmessage, padding);
+ }
+
+ query->ednsversion = version;
+ result = fctx_addopt(fctx->qmessage, version, udpsize,
+ ednsopts, ednsopt);
+ if (reqnsid && result == ISC_R_SUCCESS) {
+ query->options |= DNS_FETCHOPT_WANTNSID;
+ } else if (result != ISC_R_SUCCESS) {
+ /*
+ * We couldn't add the OPT, but we'll press on.
+ * We're not using EDNS0, so set the NOEDNS0
+ * bit.
+ */
+ query->options |= DNS_FETCHOPT_NOEDNS0;
+ query->ednsversion = -1;
+ udpsize = 0;
+ }
+ } else {
+ /*
+ * We know this server doesn't like EDNS0, so we
+ * won't use it. Set the NOEDNS0 bit since we're
+ * not using EDNS0.
+ */
+ query->options |= DNS_FETCHOPT_NOEDNS0;
+ query->ednsversion = -1;
+ }
+ } else {
+ query->ednsversion = -1;
+ }
+
+ /*
+ * Record the UDP EDNS size chosen.
+ */
+ query->udpsize = udpsize;
+
+ /*
+ * If we need EDNS0 to do this query and aren't using it, we lose.
+ */
+ if (NEEDEDNS0(fctx) && (query->options & DNS_FETCHOPT_NOEDNS0) != 0) {
+ result = DNS_R_SERVFAIL;
+ goto cleanup_message;
+ }
+
+ if (udpsize > 512U) {
+ add_triededns(fctx, &query->addrinfo->sockaddr);
+ }
+
+ if (udpsize == 512U) {
+ add_triededns512(fctx, &query->addrinfo->sockaddr);
+ }
+
+ /*
+ * Clear CD if EDNS is not in use.
+ */
+ if ((query->options & DNS_FETCHOPT_NOEDNS0) != 0) {
+ fctx->qmessage->flags &= ~DNS_MESSAGEFLAG_CD;
+ }
+
+ /*
+ * Add TSIG record tailored to the current recipient.
+ */
+ result = dns_view_getpeertsig(fctx->res->view, &ipaddr, &tsigkey);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto cleanup_message;
+ }
+
+ if (tsigkey != NULL) {
+ result = dns_message_settsigkey(fctx->qmessage, tsigkey);
+ dns_tsigkey_detach(&tsigkey);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+ }
+
+ result = dns_message_rendersection(fctx->qmessage,
+ DNS_SECTION_ADDITIONAL, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+
+ result = dns_message_renderend(fctx->qmessage);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+
+#ifdef HAVE_DNSTAP
+ memset(&zr, 0, sizeof(zr));
+ isc_buffer_init(&zb, zone, sizeof(zone));
+ dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE);
+ result = dns_name_towire(&fctx->domain, &cctx, &zb);
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_usedregion(&zb, &zr);
+ }
+#endif /* HAVE_DNSTAP */
+
+ dns_compress_invalidate(&cctx);
+ cleanup_cctx = false;
+
+ if (dns_message_gettsigkey(fctx->qmessage) != NULL) {
+ dns_tsigkey_attach(dns_message_gettsigkey(fctx->qmessage),
+ &query->tsigkey);
+ result = dns_message_getquerytsig(
+ fctx->qmessage, fctx->res->mctx, &query->tsig);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+ }
+
+ /*
+ * If using TCP, write the length of the message at the beginning
+ * of the buffer.
+ */
+ if (tcp) {
+ isc_buffer_usedregion(&query->buffer, &r);
+ isc_buffer_putuint16(&tcpbuffer, (uint16_t)r.length);
+ isc_buffer_add(&tcpbuffer, r.length);
+ }
+
+ /*
+ * Log the outgoing packet.
+ */
+ dns_message_logfmtpacket(
+ fctx->qmessage, "sending packet to", &query->addrinfo->sockaddr,
+ DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_PACKETS,
+ &dns_master_style_comment, ISC_LOG_DEBUG(11), fctx->res->mctx);
+
+ /*
+ * We're now done with the query message.
+ */
+ dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER);
+
+ sock = query2sock(query);
+
+ /*
+ * Send the query!
+ */
+ if (!tcp) {
+ address = &query->addrinfo->sockaddr;
+ if (query->exclusivesocket) {
+ result = isc_socket_connect(sock, address, task,
+ resquery_udpconnected,
+ query);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_message;
+ }
+ query->connects++;
+ }
+ }
+ isc_buffer_usedregion(buffer, &r);
+
+ /*
+ * XXXRTH Make sure we don't send to ourselves! We should probably
+ * prune out these addresses when we get them from the ADB.
+ */
+ memset(&query->sendevent, 0, sizeof(query->sendevent));
+ ISC_EVENT_INIT(&query->sendevent, sizeof(query->sendevent), 0, NULL,
+ ISC_SOCKEVENT_SENDDONE, resquery_senddone, query, NULL,
+ NULL, NULL);
+
+ if (query->dscp == -1) {
+ query->sendevent.attributes &= ~ISC_SOCKEVENTATTR_DSCP;
+ query->sendevent.dscp = 0;
+ } else {
+ query->sendevent.attributes |= ISC_SOCKEVENTATTR_DSCP;
+ query->sendevent.dscp = query->dscp;
+ if (tcp) {
+ isc_socket_dscp(sock, query->dscp);
+ }
+ }
+
+ result = isc_socket_sendto2(sock, &r, task, address, NULL,
+ &query->sendevent, 0);
+ INSIST(result == ISC_R_SUCCESS);
+
+ query->sends++;
+
+ QTRACE("sent");
+
+#ifdef HAVE_DNSTAP
+ /*
+ * Log the outgoing query via dnstap.
+ */
+ if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ dtmsgtype = DNS_DTTYPE_FQ;
+ } else {
+ dtmsgtype = DNS_DTTYPE_RQ;
+ }
+
+ result = isc_socket_getsockname(sock, &localaddr);
+ if (result == ISC_R_SUCCESS) {
+ la = &localaddr;
+ }
+
+ dns_dt_send(fctx->res->view, dtmsgtype, la, &query->addrinfo->sockaddr,
+ tcp, &zr, &query->start, NULL, &query->buffer);
+#endif /* HAVE_DNSTAP */
+
+ return (ISC_R_SUCCESS);
+
+cleanup_message:
+ if (cleanup_cctx) {
+ dns_compress_invalidate(&cctx);
+ }
+
+ dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER);
+
+ /*
+ * Stop the dispatcher from listening.
+ */
+ dns_dispatch_removeresponse(&query->dispentry, NULL);
+
+cleanup_temps:
+ if (qname != NULL) {
+ dns_message_puttempname(fctx->qmessage, &qname);
+ }
+ if (qrdataset != NULL) {
+ dns_message_puttemprdataset(fctx->qmessage, &qrdataset);
+ }
+
+ return (result);
+}
+
+static void
+resquery_connected(isc_task_t *task, isc_event_t *event) {
+ isc_socketevent_t *sevent = (isc_socketevent_t *)event;
+ resquery_t *query = event->ev_arg;
+ bool retry = false;
+ isc_interval_t interval;
+ isc_result_t result;
+ unsigned int attrs;
+ fetchctx_t *fctx;
+
+ REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT);
+ REQUIRE(VALID_QUERY(query));
+
+ QTRACE("connected");
+
+ UNUSED(task);
+
+ /*
+ * XXXRTH
+ *
+ * Currently we don't wait for the connect event before retrying
+ * a query. This means that if we get really behind, we may end
+ * up doing extra work!
+ */
+
+ query->connects--;
+ fctx = query->fctx;
+
+ if (RESQUERY_CANCELED(query)) {
+ /*
+ * This query was canceled while the connect() was in
+ * progress.
+ */
+ isc_socket_detach(&query->tcpsocket);
+ resquery_destroy(&query);
+ } else {
+ switch (sevent->result) {
+ case ISC_R_SUCCESS:
+
+ /*
+ * Extend the idle timer for TCP. Half of
+ * "resolver-query-timeout" will hopefully be long
+ * enough for a TCP connection to be established, a
+ * single DNS request to be sent, and the response
+ * received.
+ */
+ isc_interval_set(&interval,
+ fctx->res->query_timeout / 1000 / 2,
+ 0);
+ result = fctx_startidletimer(query->fctx, &interval);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE("query canceled: idle timer failed; "
+ "responding");
+
+ fctx_cancelquery(&query, NULL, NULL, false,
+ false);
+ fctx_done(fctx, result, __LINE__);
+ break;
+ }
+ /*
+ * We are connected. Create a dispatcher and
+ * send the query.
+ */
+ attrs = 0;
+ attrs |= DNS_DISPATCHATTR_TCP;
+ attrs |= DNS_DISPATCHATTR_PRIVATE;
+ attrs |= DNS_DISPATCHATTR_CONNECTED;
+ if (isc_sockaddr_pf(&query->addrinfo->sockaddr) ==
+ AF_INET)
+ {
+ attrs |= DNS_DISPATCHATTR_IPV4;
+ } else {
+ attrs |= DNS_DISPATCHATTR_IPV6;
+ }
+ attrs |= DNS_DISPATCHATTR_MAKEQUERY;
+
+ result = dns_dispatch_createtcp(
+ query->dispatchmgr, query->tcpsocket,
+ query->fctx->res->taskmgr, NULL, NULL, 4096, 2,
+ 1, 1, 3, attrs, &query->dispatch);
+
+ /*
+ * Regardless of whether dns_dispatch_create()
+ * succeeded or not, we don't need our reference
+ * to the socket anymore.
+ */
+ isc_socket_detach(&query->tcpsocket);
+
+ if (result == ISC_R_SUCCESS) {
+ result = resquery_send(query);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE("query canceled: "
+ "resquery_send() failed; responding");
+
+ fctx_cancelquery(&query, NULL, NULL, false,
+ false);
+ fctx_done(fctx, result, __LINE__);
+ }
+ break;
+
+ case ISC_R_NETUNREACH:
+ case ISC_R_HOSTUNREACH:
+ case ISC_R_CONNREFUSED:
+ case ISC_R_NOPERM:
+ case ISC_R_ADDRNOTAVAIL:
+ case ISC_R_CONNECTIONRESET:
+ FCTXTRACE3("query canceled in connected(): "
+ "no route to host; no response",
+ sevent->result);
+
+ /*
+ * No route to remote.
+ */
+ isc_socket_detach(&query->tcpsocket);
+ /*
+ * Do not query this server again in this fetch context
+ * if we already tried reducing the advertised EDNS UDP
+ * payload size to 512 bytes and the server is
+ * unavailable over TCP. This prevents query loops
+ * lasting until the fetch context restart limit is
+ * reached when attempting to get answers whose size
+ * exceeds 512 bytes from broken servers.
+ */
+ if ((query->options & DNS_FETCHOPT_EDNS512) != 0) {
+ add_bad(fctx, query->rmessage, query->addrinfo,
+ sevent->result, badns_unreachable);
+ }
+ fctx_cancelquery(&query, NULL, NULL, true, false);
+ retry = true;
+ break;
+
+ default:
+ FCTXTRACE3("query canceled in connected() due to "
+ "unexpected event result; responding",
+ sevent->result);
+
+ isc_socket_detach(&query->tcpsocket);
+ fctx_cancelquery(&query, NULL, NULL, false, false);
+ break;
+ }
+ }
+
+ isc_event_free(&event);
+
+ if (retry) {
+ /*
+ * Behave as if the idle timer has expired. For TCP
+ * connections this may not actually reflect the latest timer.
+ */
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ result = fctx_stopidletimer(fctx);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, result, __LINE__);
+ } else {
+ fctx_try(fctx, true, false);
+ }
+ }
+}
+
+static void
+fctx_finddone(isc_task_t *task, isc_event_t *event) {
+ fetchctx_t *fctx;
+ dns_adbfind_t *find;
+ dns_resolver_t *res;
+ bool want_try = false;
+ bool want_done = false;
+ bool bucket_empty = false;
+ unsigned int bucketnum;
+ bool dodestroy = false;
+
+ find = event->ev_sender;
+ fctx = event->ev_arg;
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+
+ UNUSED(task);
+
+ FCTXTRACE("finddone");
+
+ bucketnum = fctx->bucketnum;
+ LOCK(&res->buckets[bucketnum].lock);
+
+ INSIST(fctx->pending > 0);
+ fctx->pending--;
+
+ if (ADDRWAIT(fctx)) {
+ /*
+ * The fetch is waiting for a name to be found.
+ */
+ INSIST(!SHUTTINGDOWN(fctx));
+ if (event->ev_type == DNS_EVENT_ADBMOREADDRESSES) {
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ want_try = true;
+ } else {
+ fctx->findfail++;
+ if (fctx->pending == 0) {
+ /*
+ * We've got nothing else to wait for and don't
+ * know the answer. There's nothing to do but
+ * fail the fctx.
+ */
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+ want_done = true;
+ }
+ }
+ } else if (SHUTTINGDOWN(fctx) && fctx->pending == 0 &&
+ fctx->nqueries == 0 && ISC_LIST_EMPTY(fctx->validators))
+ {
+ if (isc_refcount_current(&fctx->references) == 0) {
+ bucket_empty = fctx_unlink(fctx);
+ dodestroy = true;
+ }
+ }
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ isc_event_free(&event);
+ dns_adb_destroyfind(&find);
+
+ if (want_try) {
+ fctx_try(fctx, true, false);
+ } else if (want_done) {
+ FCTXTRACE("fetch failed in finddone(); return ISC_R_FAILURE");
+ fctx_done(fctx, ISC_R_FAILURE, __LINE__);
+ } else if (dodestroy) {
+ fctx_destroy(fctx);
+ if (bucket_empty) {
+ empty_bucket(res);
+ }
+ }
+}
+
+static bool
+bad_server(fetchctx_t *fctx, isc_sockaddr_t *address) {
+ isc_sockaddr_t *sa;
+
+ for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL;
+ sa = ISC_LIST_NEXT(sa, link))
+ {
+ if (isc_sockaddr_equal(sa, address)) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+static bool
+mark_bad(fetchctx_t *fctx) {
+ dns_adbfind_t *curr;
+ dns_adbaddrinfo_t *addrinfo;
+ bool all_bad = true;
+
+#ifdef ENABLE_AFL
+ if (dns_fuzzing_resolver) {
+ return (false);
+ }
+#endif /* ifdef ENABLE_AFL */
+
+ /*
+ * Mark all known bad servers, so we don't try to talk to them
+ * again.
+ */
+
+ /*
+ * Mark any bad nameservers.
+ */
+ for (curr = ISC_LIST_HEAD(fctx->finds); curr != NULL;
+ curr = ISC_LIST_NEXT(curr, publink))
+ {
+ for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (bad_server(fctx, &addrinfo->sockaddr)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ } else {
+ all_bad = false;
+ }
+ }
+ }
+
+ /*
+ * Mark any bad forwarders.
+ */
+ for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (bad_server(fctx, &addrinfo->sockaddr)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ } else {
+ all_bad = false;
+ }
+ }
+
+ /*
+ * Mark any bad alternates.
+ */
+ for (curr = ISC_LIST_HEAD(fctx->altfinds); curr != NULL;
+ curr = ISC_LIST_NEXT(curr, publink))
+ {
+ for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (bad_server(fctx, &addrinfo->sockaddr)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ } else {
+ all_bad = false;
+ }
+ }
+ }
+
+ for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (bad_server(fctx, &addrinfo->sockaddr)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ } else {
+ all_bad = false;
+ }
+ }
+
+ return (all_bad);
+}
+
+static void
+add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo,
+ isc_result_t reason, badnstype_t badtype) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ char classbuf[64];
+ char typebuf[64];
+ char code[64];
+ isc_buffer_t b;
+ isc_sockaddr_t *sa;
+ const char *spc = "";
+ isc_sockaddr_t *address = &addrinfo->sockaddr;
+
+#ifdef ENABLE_AFL
+ if (dns_fuzzing_resolver) {
+ return;
+ }
+#endif /* ifdef ENABLE_AFL */
+
+ if (reason == DNS_R_LAME) {
+ fctx->lamecount++;
+ } else {
+ switch (badtype) {
+ case badns_unreachable:
+ fctx->neterr++;
+ break;
+ case badns_response:
+ fctx->badresp++;
+ break;
+ case badns_validation:
+ break; /* counted as 'valfail' */
+ case badns_forwarder:
+ /*
+ * We were called to prevent the given forwarder from
+ * being used again for this fetch context.
+ */
+ break;
+ }
+ }
+
+ if (bad_server(fctx, address)) {
+ /*
+ * We already know this server is bad.
+ */
+ return;
+ }
+
+ FCTXTRACE("add_bad");
+
+ sa = isc_mem_get(fctx->mctx, sizeof(*sa));
+ *sa = *address;
+ ISC_LIST_INITANDAPPEND(fctx->bad, sa, link);
+
+ if (reason == DNS_R_LAME) { /* already logged */
+ return;
+ }
+
+ if (reason == DNS_R_UNEXPECTEDRCODE &&
+ rmessage->rcode == dns_rcode_servfail && ISFORWARDER(addrinfo))
+ {
+ return;
+ }
+
+ if (reason == DNS_R_UNEXPECTEDRCODE) {
+ isc_buffer_init(&b, code, sizeof(code) - 1);
+ dns_rcode_totext(rmessage->rcode, &b);
+ code[isc_buffer_usedlength(&b)] = '\0';
+ spc = " ";
+ } else if (reason == DNS_R_UNEXPECTEDOPCODE) {
+ isc_buffer_init(&b, code, sizeof(code) - 1);
+ dns_opcode_totext((dns_opcode_t)rmessage->opcode, &b);
+ code[isc_buffer_usedlength(&b)] = '\0';
+ spc = " ";
+ } else {
+ code[0] = '\0';
+ }
+ dns_name_format(&fctx->name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf));
+ dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf));
+ isc_sockaddr_format(address, addrbuf, sizeof(addrbuf));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, DNS_LOGMODULE_RESOLVER,
+ ISC_LOG_INFO, "%s%s%s resolving '%s/%s/%s': %s", code, spc,
+ dns_result_totext(reason), namebuf, typebuf, classbuf, addrbuf);
+}
+
+/*
+ * Sort addrinfo list by RTT.
+ */
+static void
+sort_adbfind(dns_adbfind_t *find, unsigned int bias) {
+ dns_adbaddrinfo_t *best, *curr;
+ dns_adbaddrinfolist_t sorted;
+ unsigned int best_srtt, curr_srtt;
+
+ /* Lame N^2 bubble sort. */
+ ISC_LIST_INIT(sorted);
+ while (!ISC_LIST_EMPTY(find->list)) {
+ best = ISC_LIST_HEAD(find->list);
+ best_srtt = best->srtt;
+ if (isc_sockaddr_pf(&best->sockaddr) != AF_INET6) {
+ best_srtt += bias;
+ }
+ curr = ISC_LIST_NEXT(best, publink);
+ while (curr != NULL) {
+ curr_srtt = curr->srtt;
+ if (isc_sockaddr_pf(&curr->sockaddr) != AF_INET6) {
+ curr_srtt += bias;
+ }
+ if (curr_srtt < best_srtt) {
+ best = curr;
+ best_srtt = curr_srtt;
+ }
+ curr = ISC_LIST_NEXT(curr, publink);
+ }
+ ISC_LIST_UNLINK(find->list, best, publink);
+ ISC_LIST_APPEND(sorted, best, publink);
+ }
+ find->list = sorted;
+}
+
+/*
+ * Sort a list of finds by server RTT.
+ */
+static void
+sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) {
+ dns_adbfind_t *best, *curr;
+ dns_adbfindlist_t sorted;
+ dns_adbaddrinfo_t *addrinfo, *bestaddrinfo;
+ unsigned int best_srtt, curr_srtt;
+
+ /* Sort each find's addrinfo list by SRTT. */
+ for (curr = ISC_LIST_HEAD(*findlist); curr != NULL;
+ curr = ISC_LIST_NEXT(curr, publink))
+ {
+ sort_adbfind(curr, bias);
+ }
+
+ /* Lame N^2 bubble sort. */
+ ISC_LIST_INIT(sorted);
+ while (!ISC_LIST_EMPTY(*findlist)) {
+ best = ISC_LIST_HEAD(*findlist);
+ bestaddrinfo = ISC_LIST_HEAD(best->list);
+ INSIST(bestaddrinfo != NULL);
+ best_srtt = bestaddrinfo->srtt;
+ if (isc_sockaddr_pf(&bestaddrinfo->sockaddr) != AF_INET6) {
+ best_srtt += bias;
+ }
+ curr = ISC_LIST_NEXT(best, publink);
+ while (curr != NULL) {
+ addrinfo = ISC_LIST_HEAD(curr->list);
+ INSIST(addrinfo != NULL);
+ curr_srtt = addrinfo->srtt;
+ if (isc_sockaddr_pf(&addrinfo->sockaddr) != AF_INET6) {
+ curr_srtt += bias;
+ }
+ if (curr_srtt < best_srtt) {
+ best = curr;
+ best_srtt = curr_srtt;
+ }
+ curr = ISC_LIST_NEXT(curr, publink);
+ }
+ ISC_LIST_UNLINK(*findlist, best, publink);
+ ISC_LIST_APPEND(sorted, best, publink);
+ }
+ *findlist = sorted;
+}
+
+static void
+findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port,
+ unsigned int options, unsigned int flags, isc_stdtime_t now,
+ bool *overquota, bool *need_alternate, unsigned int *no_addresses) {
+ dns_adbaddrinfo_t *ai;
+ dns_adbfind_t *find;
+ dns_resolver_t *res;
+ bool unshared;
+ isc_result_t result;
+
+ FCTXTRACE("FINDNAME");
+ res = fctx->res;
+ unshared = ((fctx->options & DNS_FETCHOPT_UNSHARED) != 0);
+ /*
+ * If this name is a subdomain of the query domain, tell
+ * the ADB to start looking using zone/hint data. This keeps us
+ * from getting stuck if the nameserver is beneath the zone cut
+ * and we don't know its address (e.g. because the A record has
+ * expired).
+ */
+ if (dns_name_issubdomain(name, &fctx->domain)) {
+ options |= DNS_ADBFIND_STARTATZONE;
+ }
+ options |= DNS_ADBFIND_GLUEOK;
+ options |= DNS_ADBFIND_HINTOK;
+
+ /*
+ * See what we know about this address.
+ */
+ find = NULL;
+ result = dns_adb_createfind(
+ fctx->adb, res->buckets[fctx->bucketnum].task, fctx_finddone,
+ fctx, name, &fctx->name, fctx->type, options, now, NULL,
+ res->view->dstport, fctx->depth + 1, fctx->qc, &find);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ "fctx %p(%s): createfind for %s/%d - %s", fctx,
+ fctx->info, fctx->clientstr, fctx->id,
+ isc_result_totext(result));
+
+ if (result != ISC_R_SUCCESS) {
+ if (result == DNS_R_ALIAS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ /*
+ * XXXRTH Follow the CNAME/DNAME chain?
+ */
+ dns_adb_destroyfind(&find);
+ fctx->adberr++;
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_CNAME,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "skipping nameserver '%s' because it "
+ "is a CNAME, while resolving '%s'",
+ namebuf, fctx->info);
+ }
+ } else if (!ISC_LIST_EMPTY(find->list)) {
+ /*
+ * We have at least some of the addresses for the
+ * name.
+ */
+ INSIST((find->options & DNS_ADBFIND_WANTEVENT) == 0);
+ if (flags != 0 || port != 0) {
+ for (ai = ISC_LIST_HEAD(find->list); ai != NULL;
+ ai = ISC_LIST_NEXT(ai, publink))
+ {
+ ai->flags |= flags;
+ if (port != 0) {
+ isc_sockaddr_setport(&ai->sockaddr,
+ port);
+ }
+ }
+ }
+ if ((flags & FCTX_ADDRINFO_DUALSTACK) != 0) {
+ ISC_LIST_APPEND(fctx->altfinds, find, publink);
+ } else {
+ ISC_LIST_APPEND(fctx->finds, find, publink);
+ }
+ } else {
+ /*
+ * We don't know any of the addresses for this
+ * name.
+ */
+ if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) {
+ /*
+ * We're looking for them and will get an
+ * event about it later.
+ */
+ fctx->pending++;
+ /*
+ * Bootstrap.
+ */
+ if (need_alternate != NULL && !*need_alternate &&
+ unshared &&
+ ((res->dispatches4 == NULL &&
+ find->result_v6 != DNS_R_NXDOMAIN) ||
+ (res->dispatches6 == NULL &&
+ find->result_v4 != DNS_R_NXDOMAIN)))
+ {
+ *need_alternate = true;
+ }
+ if (no_addresses != NULL) {
+ (*no_addresses)++;
+ }
+ } else {
+ if ((find->options & DNS_ADBFIND_OVERQUOTA) != 0) {
+ if (overquota != NULL) {
+ *overquota = true;
+ }
+ fctx->quotacount++; /* quota exceeded */
+ } else if ((find->options & DNS_ADBFIND_LAMEPRUNED) !=
+ 0)
+ {
+ fctx->lamecount++; /* cached lame server */
+ } else {
+ fctx->adberr++; /* unreachable server, etc. */
+ }
+
+ /*
+ * If we know there are no addresses for
+ * the family we are using then try to add
+ * an alternative server.
+ */
+ if (need_alternate != NULL && !*need_alternate &&
+ ((res->dispatches4 == NULL &&
+ find->result_v6 == DNS_R_NXRRSET) ||
+ (res->dispatches6 == NULL &&
+ find->result_v4 == DNS_R_NXRRSET)))
+ {
+ *need_alternate = true;
+ }
+ dns_adb_destroyfind(&find);
+ }
+ }
+}
+
+static bool
+isstrictsubdomain(const dns_name_t *name1, const dns_name_t *name2) {
+ int order;
+ unsigned int nlabels;
+ dns_namereln_t namereln;
+
+ namereln = dns_name_fullcompare(name1, name2, &order, &nlabels);
+ return (namereln == dns_namereln_subdomain);
+}
+
+static isc_result_t
+fctx_getaddresses(fetchctx_t *fctx, bool badcache) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ dns_resolver_t *res;
+ isc_stdtime_t now;
+ unsigned int stdoptions = 0;
+ dns_forwarder_t *fwd;
+ dns_adbaddrinfo_t *ai;
+ bool all_bad;
+ dns_rdata_ns_t ns;
+ bool need_alternate = false;
+ bool all_spilled = true;
+ unsigned int no_addresses = 0;
+ unsigned int ns_processed = 0;
+
+ FCTXTRACE5("getaddresses", "fctx->depth=", fctx->depth);
+
+ /*
+ * Don't pound on remote servers. (Failsafe!)
+ */
+ fctx->restarts++;
+ if (fctx->restarts > 100) {
+ FCTXTRACE("too many restarts");
+ return (DNS_R_SERVFAIL);
+ }
+
+ res = fctx->res;
+
+ if (fctx->depth > res->maxdepth) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ "too much NS indirection resolving '%s' "
+ "(depth=%u, maxdepth=%u)",
+ fctx->info, fctx->depth, res->maxdepth);
+ return (DNS_R_SERVFAIL);
+ }
+
+ /*
+ * Forwarders.
+ */
+
+ INSIST(ISC_LIST_EMPTY(fctx->forwaddrs));
+ INSIST(ISC_LIST_EMPTY(fctx->altaddrs));
+
+ /*
+ * If we have DNS_FETCHOPT_NOFORWARD set and forwarding policy
+ * allows us to not forward - skip forwarders and go straight
+ * to NSes. This is currently used to make sure that priming query
+ * gets root servers' IP addresses in ADDITIONAL section.
+ */
+ if ((fctx->options & DNS_FETCHOPT_NOFORWARD) != 0 &&
+ (fctx->fwdpolicy != dns_fwdpolicy_only))
+ {
+ goto normal_nses;
+ }
+
+ /*
+ * If this fctx has forwarders, use them; otherwise use any
+ * selective forwarders specified in the view; otherwise use the
+ * resolver's forwarders (if any).
+ */
+ fwd = ISC_LIST_HEAD(fctx->forwarders);
+ if (fwd == NULL) {
+ dns_forwarders_t *forwarders = NULL;
+ dns_name_t *name = &fctx->name;
+ dns_name_t suffix;
+ unsigned int labels;
+ dns_fixedname_t fixed;
+ dns_name_t *domain;
+
+ /*
+ * DS records are found in the parent server.
+ * Strip label to get the correct forwarder (if any).
+ */
+ if (dns_rdatatype_atparent(fctx->type) &&
+ dns_name_countlabels(name) > 1)
+ {
+ dns_name_init(&suffix, NULL);
+ labels = dns_name_countlabels(name);
+ dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
+ name = &suffix;
+ }
+
+ domain = dns_fixedname_initname(&fixed);
+ result = dns_fwdtable_find(res->view->fwdtable, name, domain,
+ &forwarders);
+ if (result == ISC_R_SUCCESS) {
+ fwd = ISC_LIST_HEAD(forwarders->fwdrs);
+ fctx->fwdpolicy = forwarders->fwdpolicy;
+ dns_name_copynf(domain, fctx->fwdname);
+ if (fctx->fwdpolicy == dns_fwdpolicy_only &&
+ isstrictsubdomain(domain, &fctx->domain))
+ {
+ fcount_decr(fctx);
+ dns_name_free(&fctx->domain, fctx->mctx);
+ dns_name_init(&fctx->domain, NULL);
+ dns_name_dup(domain, fctx->mctx, &fctx->domain);
+ result = fcount_incr(fctx, true);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ }
+ }
+
+ while (fwd != NULL) {
+ if ((isc_sockaddr_pf(&fwd->addr) == AF_INET &&
+ res->dispatches4 == NULL) ||
+ (isc_sockaddr_pf(&fwd->addr) == AF_INET6 &&
+ res->dispatches6 == NULL))
+ {
+ fwd = ISC_LIST_NEXT(fwd, link);
+ continue;
+ }
+ ai = NULL;
+ result = dns_adb_findaddrinfo(fctx->adb, &fwd->addr, &ai, 0);
+ if (result == ISC_R_SUCCESS) {
+ dns_adbaddrinfo_t *cur;
+ ai->flags |= FCTX_ADDRINFO_FORWARDER;
+ ai->dscp = fwd->dscp;
+ cur = ISC_LIST_HEAD(fctx->forwaddrs);
+ while (cur != NULL && cur->srtt < ai->srtt) {
+ cur = ISC_LIST_NEXT(cur, publink);
+ }
+ if (cur != NULL) {
+ ISC_LIST_INSERTBEFORE(fctx->forwaddrs, cur, ai,
+ publink);
+ } else {
+ ISC_LIST_APPEND(fctx->forwaddrs, ai, publink);
+ }
+ }
+ fwd = ISC_LIST_NEXT(fwd, link);
+ }
+
+ /*
+ * If the forwarding policy is "only", we don't need the addresses
+ * of the nameservers.
+ */
+ if (fctx->fwdpolicy == dns_fwdpolicy_only) {
+ goto out;
+ }
+
+ /*
+ * Normal nameservers.
+ */
+normal_nses:
+ stdoptions = DNS_ADBFIND_WANTEVENT | DNS_ADBFIND_EMPTYEVENT;
+ if (fctx->restarts == 1) {
+ /*
+ * To avoid sending out a flood of queries likely to
+ * result in NXRRSET, we suppress fetches for address
+ * families we don't have the first time through,
+ * provided that we have addresses in some family we
+ * can use.
+ *
+ * We don't want to set this option all the time, since
+ * if fctx->restarts > 1, we've clearly been having trouble
+ * with the addresses we had, so getting more could help.
+ */
+ stdoptions |= DNS_ADBFIND_AVOIDFETCHES;
+ }
+ if (res->dispatches4 != NULL) {
+ stdoptions |= DNS_ADBFIND_INET;
+ }
+ if (res->dispatches6 != NULL) {
+ stdoptions |= DNS_ADBFIND_INET6;
+ }
+
+ if ((stdoptions & DNS_ADBFIND_ADDRESSMASK) == 0) {
+ return (DNS_R_SERVFAIL);
+ }
+
+ isc_stdtime_get(&now);
+
+ INSIST(ISC_LIST_EMPTY(fctx->finds));
+ INSIST(ISC_LIST_EMPTY(fctx->altfinds));
+
+ for (result = dns_rdataset_first(&fctx->nameservers);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&fctx->nameservers))
+ {
+ bool overquota = false;
+
+ dns_rdataset_current(&fctx->nameservers, &rdata);
+ /*
+ * Extract the name from the NS record.
+ */
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ if (no_addresses > NS_FAIL_LIMIT &&
+ dns_rdataset_count(&fctx->nameservers) > NS_RR_LIMIT)
+ {
+ stdoptions |= DNS_ADBFIND_NOFETCH;
+ }
+ findname(fctx, &ns.name, 0, stdoptions, 0, now, &overquota,
+ &need_alternate, &no_addresses);
+
+ if (!overquota) {
+ all_spilled = false;
+ }
+
+ dns_rdata_reset(&rdata);
+ dns_rdata_freestruct(&ns);
+
+ if (++ns_processed >= NS_PROCESSING_LIMIT) {
+ result = ISC_R_NOMORE;
+ break;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+
+ /*
+ * Do we need to use 6 to 4?
+ */
+ if (need_alternate) {
+ int family;
+ alternate_t *a;
+ family = (res->dispatches6 != NULL) ? AF_INET6 : AF_INET;
+ for (a = ISC_LIST_HEAD(res->alternates); a != NULL;
+ a = ISC_LIST_NEXT(a, link))
+ {
+ if (!a->isaddress) {
+ findname(fctx, &a->_u._n.name, a->_u._n.port,
+ stdoptions, FCTX_ADDRINFO_DUALSTACK,
+ now, NULL, NULL, NULL);
+ continue;
+ }
+ if (isc_sockaddr_pf(&a->_u.addr) != family) {
+ continue;
+ }
+ ai = NULL;
+ result = dns_adb_findaddrinfo(fctx->adb, &a->_u.addr,
+ &ai, 0);
+ if (result == ISC_R_SUCCESS) {
+ dns_adbaddrinfo_t *cur;
+ ai->flags |= FCTX_ADDRINFO_FORWARDER;
+ ai->flags |= FCTX_ADDRINFO_DUALSTACK;
+ cur = ISC_LIST_HEAD(fctx->altaddrs);
+ while (cur != NULL && cur->srtt < ai->srtt) {
+ cur = ISC_LIST_NEXT(cur, publink);
+ }
+ if (cur != NULL) {
+ ISC_LIST_INSERTBEFORE(fctx->altaddrs,
+ cur, ai, publink);
+ } else {
+ ISC_LIST_APPEND(fctx->altaddrs, ai,
+ publink);
+ }
+ }
+ }
+ }
+
+out:
+ /*
+ * Mark all known bad servers.
+ */
+ all_bad = mark_bad(fctx);
+
+ /*
+ * How are we doing?
+ */
+ if (all_bad) {
+ /*
+ * We've got no addresses.
+ */
+ if (fctx->pending > 0) {
+ /*
+ * We're fetching the addresses, but don't have any
+ * yet. Tell the caller to wait for an answer.
+ */
+ result = DNS_R_WAIT;
+ } else {
+ isc_time_t expire;
+ isc_interval_t i;
+ /*
+ * We've lost completely. We don't know any
+ * addresses, and the ADB has told us it can't get
+ * them.
+ */
+ FCTXTRACE("no addresses");
+ isc_interval_set(&i, DNS_RESOLVER_BADCACHETTL(fctx), 0);
+ result = isc_time_nowplusinterval(&expire, &i);
+ if (badcache &&
+ (fctx->type == dns_rdatatype_dnskey ||
+ fctx->type == dns_rdatatype_ds) &&
+ result == ISC_R_SUCCESS)
+ {
+ dns_resolver_addbadcache(res, &fctx->name,
+ fctx->type, &expire);
+ }
+
+ result = ISC_R_FAILURE;
+
+ /*
+ * If all of the addresses found were over the
+ * fetches-per-server quota, return the configured
+ * response.
+ */
+ if (all_spilled) {
+ result = res->quotaresp[dns_quotatype_server];
+ inc_stats(res, dns_resstatscounter_serverquota);
+ }
+ }
+ } else {
+ /*
+ * We've found some addresses. We might still be looking
+ * for more addresses.
+ */
+ sort_finds(&fctx->finds, res->view->v6bias);
+ sort_finds(&fctx->altfinds, 0);
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static void
+possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr) {
+ isc_netaddr_t na;
+ char buf[ISC_NETADDR_FORMATSIZE];
+ isc_sockaddr_t *sa;
+ bool aborted = false;
+ bool bogus;
+ dns_acl_t *blackhole;
+ isc_netaddr_t ipaddr;
+ dns_peer_t *peer = NULL;
+ dns_resolver_t *res;
+ const char *msg = NULL;
+
+ sa = &addr->sockaddr;
+
+ res = fctx->res;
+ isc_netaddr_fromsockaddr(&ipaddr, sa);
+ blackhole = dns_dispatchmgr_getblackhole(res->dispatchmgr);
+ (void)dns_peerlist_peerbyaddr(res->view->peers, &ipaddr, &peer);
+
+ if (blackhole != NULL) {
+ int match;
+
+ if ((dns_acl_match(&ipaddr, NULL, blackhole, &res->view->aclenv,
+ &match, NULL) == ISC_R_SUCCESS) &&
+ match > 0)
+ {
+ aborted = true;
+ }
+ }
+
+ if (peer != NULL && dns_peer_getbogus(peer, &bogus) == ISC_R_SUCCESS &&
+ bogus)
+ {
+ aborted = true;
+ }
+
+ if (aborted) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring blackholed / bogus server: ";
+ } else if (isc_sockaddr_isnetzero(sa)) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring net zero address: ";
+ } else if (isc_sockaddr_ismulticast(sa)) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring multicast address: ";
+ } else if (isc_sockaddr_isexperimental(sa)) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring experimental address: ";
+ } else if (sa->type.sa.sa_family != AF_INET6) {
+ return;
+ } else if (IN6_IS_ADDR_V4MAPPED(&sa->type.sin6.sin6_addr)) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring IPv6 mapped IPV4 address: ";
+ } else if (IN6_IS_ADDR_V4COMPAT(&sa->type.sin6.sin6_addr)) {
+ addr->flags |= FCTX_ADDRINFO_MARK;
+ msg = "ignoring IPv6 compatibility IPV4 address: ";
+ } else {
+ return;
+ }
+
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
+ isc_netaddr_fromsockaddr(&na, sa);
+ isc_netaddr_format(&na, buf, sizeof(buf));
+ FCTXTRACE2(msg, buf);
+ }
+}
+
+static dns_adbaddrinfo_t *
+fctx_nextaddress(fetchctx_t *fctx) {
+ dns_adbfind_t *find, *start;
+ dns_adbaddrinfo_t *addrinfo;
+ dns_adbaddrinfo_t *faddrinfo;
+
+ /*
+ * Return the next untried address, if any.
+ */
+
+ /*
+ * Find the first unmarked forwarder (if any).
+ */
+ for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (!UNMARKED(addrinfo)) {
+ continue;
+ }
+ possibly_mark(fctx, addrinfo);
+ if (UNMARKED(addrinfo)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ fctx->find = NULL;
+ fctx->forwarding = true;
+
+ /*
+ * QNAME minimization is disabled when
+ * forwarding, and has to remain disabled if
+ * we switch back to normal recursion; otherwise
+ * forwarding could leave us in an inconsistent
+ * state.
+ */
+ fctx->minimized = false;
+ return (addrinfo);
+ }
+ }
+
+ /*
+ * No forwarders. Move to the next find.
+ */
+ fctx->forwarding = false;
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDFIND);
+
+ find = fctx->find;
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->finds);
+ } else {
+ find = ISC_LIST_NEXT(find, publink);
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->finds);
+ }
+ }
+
+ /*
+ * Find the first unmarked addrinfo.
+ */
+ addrinfo = NULL;
+ if (find != NULL) {
+ start = find;
+ do {
+ for (addrinfo = ISC_LIST_HEAD(find->list);
+ addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (!UNMARKED(addrinfo)) {
+ continue;
+ }
+ possibly_mark(fctx, addrinfo);
+ if (UNMARKED(addrinfo)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ break;
+ }
+ }
+ if (addrinfo != NULL) {
+ break;
+ }
+ find = ISC_LIST_NEXT(find, publink);
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->finds);
+ }
+ } while (find != start);
+ }
+
+ fctx->find = find;
+ if (addrinfo != NULL) {
+ return (addrinfo);
+ }
+
+ /*
+ * No nameservers left. Try alternates.
+ */
+
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDALT);
+
+ find = fctx->altfind;
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->altfinds);
+ } else {
+ find = ISC_LIST_NEXT(find, publink);
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->altfinds);
+ }
+ }
+
+ /*
+ * Find the first unmarked addrinfo.
+ */
+ addrinfo = NULL;
+ if (find != NULL) {
+ start = find;
+ do {
+ for (addrinfo = ISC_LIST_HEAD(find->list);
+ addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (!UNMARKED(addrinfo)) {
+ continue;
+ }
+ possibly_mark(fctx, addrinfo);
+ if (UNMARKED(addrinfo)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ break;
+ }
+ }
+ if (addrinfo != NULL) {
+ break;
+ }
+ find = ISC_LIST_NEXT(find, publink);
+ if (find == NULL) {
+ find = ISC_LIST_HEAD(fctx->altfinds);
+ }
+ } while (find != start);
+ }
+
+ faddrinfo = addrinfo;
+
+ /*
+ * See if we have a better alternate server by address.
+ */
+
+ for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL;
+ addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+ {
+ if (!UNMARKED(addrinfo)) {
+ continue;
+ }
+ possibly_mark(fctx, addrinfo);
+ if (UNMARKED(addrinfo) &&
+ (faddrinfo == NULL || addrinfo->srtt < faddrinfo->srtt))
+ {
+ if (faddrinfo != NULL) {
+ faddrinfo->flags &= ~FCTX_ADDRINFO_MARK;
+ }
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+ break;
+ }
+ }
+
+ if (addrinfo == NULL) {
+ addrinfo = faddrinfo;
+ fctx->altfind = find;
+ }
+
+ return (addrinfo);
+}
+
+static void
+fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
+ isc_result_t result;
+ dns_adbaddrinfo_t *addrinfo = NULL;
+ dns_resolver_t *res;
+ isc_task_t *task;
+ unsigned int bucketnum;
+ bool bucket_empty;
+
+ FCTXTRACE5("try", "fctx->qc=", isc_counter_used(fctx->qc));
+
+ REQUIRE(!ADDRWAIT(fctx));
+
+ res = fctx->res;
+ bucketnum = fctx->bucketnum;
+
+ /* We've already exceeded maximum query count */
+ if (isc_counter_used(fctx->qc) > res->maxqueries) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ "exceeded max queries resolving '%s' "
+ "(querycount=%u, maxqueries=%u)",
+ fctx->info, isc_counter_used(fctx->qc),
+ res->maxqueries);
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ return;
+ }
+
+ addrinfo = fctx_nextaddress(fctx);
+
+ /* Try to find an address that isn't over quota */
+ while (addrinfo != NULL && dns_adbentry_overquota(addrinfo->entry)) {
+ addrinfo = fctx_nextaddress(fctx);
+ }
+
+ if (addrinfo == NULL) {
+ /* We have no more addresses. Start over. */
+ fctx_cancelqueries(fctx, true, false);
+ fctx_cleanupall(fctx);
+ result = fctx_getaddresses(fctx, badcache);
+ if (result == DNS_R_WAIT) {
+ /*
+ * Sleep waiting for addresses.
+ */
+ FCTXTRACE("addrwait");
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_ADDRWAIT);
+ return;
+ } else if (result != ISC_R_SUCCESS) {
+ /*
+ * Something bad happened.
+ */
+ fctx_done(fctx, result, __LINE__);
+ return;
+ }
+
+ addrinfo = fctx_nextaddress(fctx);
+
+ while (addrinfo != NULL &&
+ dns_adbentry_overquota(addrinfo->entry))
+ {
+ addrinfo = fctx_nextaddress(fctx);
+ }
+
+ /*
+ * While we may have addresses from the ADB, they
+ * might be bad ones. In this case, return SERVFAIL.
+ */
+ if (addrinfo == NULL) {
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ return;
+ }
+ }
+ /*
+ * We're minimizing and we're not yet at the final NS -
+ * we need to launch a query for NS for 'upper' domain
+ */
+ if (fctx->minimized && !fctx->forwarding) {
+ unsigned int options = fctx->options;
+ /*
+ * Also clear DNS_FETCHOPT_TRYSTALE_ONTIMEOUT here, otherwise
+ * every query minimization step will activate the try-stale
+ * timer again.
+ */
+ options &= ~(DNS_FETCHOPT_QMINIMIZE |
+ DNS_FETCHOPT_TRYSTALE_ONTIMEOUT);
+
+ /*
+ * Is another QNAME minimization fetch still running?
+ */
+ if (fctx->qminfetch != NULL) {
+ bool validfctx = (DNS_FETCH_VALID(fctx->qminfetch) &&
+ VALID_FCTX(fctx->qminfetch->private));
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(&fctx->qminname, namebuf,
+ sizeof(namebuf));
+ dns_rdatatype_format(fctx->qmintype, typebuf,
+ sizeof(typebuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR,
+ "fctx %p(%s): attempting QNAME "
+ "minimization fetch for %s/%s but "
+ "fetch %p(%s) still running",
+ fctx, fctx->info, namebuf, typebuf,
+ fctx->qminfetch,
+ validfctx ? fctx->qminfetch->private->info
+ : "<invalid>");
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ return;
+ }
+
+ /*
+ * In "_ A" mode we're asking for _.domain -
+ * resolver by default will follow delegations
+ * then, we don't want that.
+ */
+ if ((options & DNS_FETCHOPT_QMIN_USE_A) != 0) {
+ options |= DNS_FETCHOPT_NOFOLLOW;
+ }
+ fctx_increference(fctx);
+ task = res->buckets[bucketnum].task;
+ fctx_stoptimer(fctx);
+ fctx_stoptimer_trystale(fctx);
+ result = dns_resolver_createfetch(
+ fctx->res, &fctx->qminname, fctx->qmintype,
+ &fctx->domain, &fctx->nameservers, NULL, NULL, 0,
+ options, 0, fctx->qc, task, resume_qmin, fctx,
+ &fctx->qminrrset, NULL, &fctx->qminfetch);
+ if (result != ISC_R_SUCCESS) {
+ LOCK(&res->buckets[bucketnum].lock);
+ RUNTIME_CHECK(!fctx_decreference(fctx));
+ UNLOCK(&res->buckets[bucketnum].lock);
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ }
+ return;
+ }
+
+ result = isc_counter_increment(fctx->qc);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ "exceeded max queries resolving '%s'",
+ fctx->info);
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ return;
+ }
+
+ fctx_increference(fctx);
+
+ result = fctx_query(fctx, addrinfo, fctx->options);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, result, __LINE__);
+ LOCK(&res->buckets[bucketnum].lock);
+ bucket_empty = fctx_decreference(fctx);
+ UNLOCK(&res->buckets[bucketnum].lock);
+ if (bucket_empty) {
+ empty_bucket(res);
+ }
+ } else if (retrying) {
+ inc_stats(res, dns_resstatscounter_retry);
+ }
+}
+
+static void
+resume_qmin(isc_task_t *task, isc_event_t *event) {
+ dns_fetchevent_t *fevent;
+ dns_resolver_t *res;
+ fetchctx_t *fctx;
+ isc_result_t result;
+ bool bucket_empty;
+ unsigned int bucketnum;
+ unsigned int findoptions = 0;
+ dns_name_t *fname, *dcname;
+ dns_fixedname_t ffixed, dcfixed;
+ fname = dns_fixedname_initname(&ffixed);
+ dcname = dns_fixedname_initname(&dcfixed);
+
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+ fevent = (dns_fetchevent_t *)event;
+ fctx = event->ev_arg;
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+
+ UNUSED(task);
+ FCTXTRACE("resume_qmin");
+
+ if (fevent->node != NULL) {
+ dns_db_detachnode(fevent->db, &fevent->node);
+ }
+ if (fevent->db != NULL) {
+ dns_db_detach(&fevent->db);
+ }
+
+ bucketnum = fctx->bucketnum;
+
+ if (dns_rdataset_isassociated(fevent->rdataset)) {
+ dns_rdataset_disassociate(fevent->rdataset);
+ }
+ result = fevent->result;
+ fevent = NULL;
+ isc_event_free(&event);
+
+ dns_resolver_destroyfetch(&fctx->qminfetch);
+
+ LOCK(&res->buckets[bucketnum].lock);
+ if (SHUTTINGDOWN(fctx)) {
+ UNLOCK(&res->buckets[bucketnum].lock);
+ goto cleanup;
+ }
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ /*
+ * Note: fevent->rdataset must be disassociated and
+ * isc_event_free(&event) be called before resuming
+ * processing of the 'fctx' to prevent use-after-free.
+ * 'fevent' is set to NULL so as to not have a dangling
+ * pointer.
+ */
+ if (result == ISC_R_CANCELED) {
+ fctx_done(fctx, result, __LINE__);
+ goto cleanup;
+ }
+
+ /*
+ * If we're doing "_ A"-style minimization we can get
+ * NX answer to minimized query - we need to continue then.
+ *
+ * Otherwise - either disable minimization if we're
+ * in relaxed mode or fail if we're in strict mode.
+ */
+
+ if ((NXDOMAIN_RESULT(result) &&
+ (fctx->options & DNS_FETCHOPT_QMIN_USE_A) == 0) ||
+ result == DNS_R_FORMERR || result == DNS_R_REMOTEFORMERR ||
+ result == ISC_R_FAILURE)
+ {
+ if ((fctx->options & DNS_FETCHOPT_QMIN_STRICT) == 0) {
+ fctx->qmin_labels = DNS_MAX_LABELS + 1;
+ /*
+ * We store the result. If we succeed in the end
+ * we'll issue a warning that the server is broken.
+ */
+ fctx->qmin_warning = result;
+ } else {
+ fctx_done(fctx, result, __LINE__);
+ goto cleanup;
+ }
+ }
+
+ if (dns_rdataset_isassociated(&fctx->nameservers)) {
+ dns_rdataset_disassociate(&fctx->nameservers);
+ }
+
+ if (dns_rdatatype_atparent(fctx->type)) {
+ findoptions |= DNS_DBFIND_NOEXACT;
+ }
+ result = dns_view_findzonecut(res->view, &fctx->name, fname, dcname,
+ fctx->now, findoptions, true, true,
+ &fctx->nameservers, NULL);
+
+ /*
+ * DNS_R_NXDOMAIN here means we have not loaded the root zone mirror
+ * yet - but DNS_R_NXDOMAIN is not a valid return value when doing
+ * recursion, we need to patch it.
+ */
+ if (result == DNS_R_NXDOMAIN) {
+ result = DNS_R_SERVFAIL;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, result, __LINE__);
+ goto cleanup;
+ }
+ fcount_decr(fctx);
+ dns_name_free(&fctx->domain, fctx->mctx);
+ dns_name_init(&fctx->domain, NULL);
+ dns_name_dup(fname, fctx->mctx, &fctx->domain);
+
+ result = fcount_incr(fctx, false);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, result, __LINE__);
+ goto cleanup;
+ }
+
+ dns_name_free(&fctx->qmindcname, fctx->mctx);
+ dns_name_init(&fctx->qmindcname, NULL);
+ dns_name_dup(dcname, fctx->mctx, &fctx->qmindcname);
+ fctx->ns_ttl = fctx->nameservers.ttl;
+ fctx->ns_ttl_ok = true;
+
+ result = fctx_minimize_qname(fctx);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, result, __LINE__);
+ goto cleanup;
+ }
+
+ if (!fctx->minimized) {
+ /*
+ * We have finished minimizing, but fctx->finds was filled at
+ * the beginning of the run - now we need to clear it before
+ * sending the final query to use proper nameservers.
+ */
+ fctx_cancelqueries(fctx, false, false);
+ fctx_cleanupall(fctx);
+ }
+
+ fctx_try(fctx, true, false);
+
+cleanup:
+ INSIST(event == NULL);
+ INSIST(fevent == NULL);
+ LOCK(&res->buckets[bucketnum].lock);
+ bucket_empty = fctx_decreference(fctx);
+ UNLOCK(&res->buckets[bucketnum].lock);
+ if (bucket_empty) {
+ empty_bucket(res);
+ }
+}
+
+static bool
+fctx_unlink(fetchctx_t *fctx) {
+ dns_resolver_t *res;
+ unsigned int bucketnum;
+
+ /*
+ * Caller must be holding the bucket lock.
+ */
+
+ REQUIRE(VALID_FCTX(fctx));
+ REQUIRE(fctx->state == fetchstate_done ||
+ fctx->state == fetchstate_init);
+ REQUIRE(ISC_LIST_EMPTY(fctx->events));
+ REQUIRE(ISC_LIST_EMPTY(fctx->queries));
+ REQUIRE(ISC_LIST_EMPTY(fctx->finds));
+ REQUIRE(ISC_LIST_EMPTY(fctx->altfinds));
+ REQUIRE(fctx->pending == 0);
+ REQUIRE(ISC_LIST_EMPTY(fctx->validators));
+
+ FCTXTRACE("unlink");
+
+ isc_refcount_destroy(&fctx->references);
+
+ res = fctx->res;
+ bucketnum = fctx->bucketnum;
+
+ ISC_LIST_UNLINK(res->buckets[bucketnum].fctxs, fctx, link);
+
+ INSIST(atomic_fetch_sub_release(&res->nfctx, 1) > 0);
+
+ dec_stats(res, dns_resstatscounter_nfetch);
+
+ if (atomic_load_acquire(&res->buckets[bucketnum].exiting) &&
+ ISC_LIST_EMPTY(res->buckets[bucketnum].fctxs))
+ {
+ return (true);
+ }
+
+ return (false);
+}
+
+static void
+fctx_destroy(fetchctx_t *fctx) {
+ isc_sockaddr_t *sa, *next_sa;
+ struct tried *tried;
+
+ REQUIRE(VALID_FCTX(fctx));
+ REQUIRE(fctx->state == fetchstate_done ||
+ fctx->state == fetchstate_init);
+ REQUIRE(ISC_LIST_EMPTY(fctx->events));
+ REQUIRE(ISC_LIST_EMPTY(fctx->queries));
+ REQUIRE(ISC_LIST_EMPTY(fctx->finds));
+ REQUIRE(ISC_LIST_EMPTY(fctx->altfinds));
+ REQUIRE(fctx->pending == 0);
+ REQUIRE(ISC_LIST_EMPTY(fctx->validators));
+ REQUIRE(!ISC_LINK_LINKED(fctx, link));
+
+ FCTXTRACE("destroy");
+
+ isc_refcount_destroy(&fctx->references);
+
+ /*
+ * Free bad.
+ */
+ for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; sa = next_sa) {
+ next_sa = ISC_LIST_NEXT(sa, link);
+ ISC_LIST_UNLINK(fctx->bad, sa, link);
+ isc_mem_put(fctx->mctx, sa, sizeof(*sa));
+ }
+
+ for (tried = ISC_LIST_HEAD(fctx->edns); tried != NULL;
+ tried = ISC_LIST_HEAD(fctx->edns))
+ {
+ ISC_LIST_UNLINK(fctx->edns, tried, link);
+ isc_mem_put(fctx->mctx, tried, sizeof(*tried));
+ }
+
+ for (tried = ISC_LIST_HEAD(fctx->edns512); tried != NULL;
+ tried = ISC_LIST_HEAD(fctx->edns512))
+ {
+ ISC_LIST_UNLINK(fctx->edns512, tried, link);
+ isc_mem_put(fctx->mctx, tried, sizeof(*tried));
+ }
+
+ for (sa = ISC_LIST_HEAD(fctx->bad_edns); sa != NULL; sa = next_sa) {
+ next_sa = ISC_LIST_NEXT(sa, link);
+ ISC_LIST_UNLINK(fctx->bad_edns, sa, link);
+ isc_mem_put(fctx->mctx, sa, sizeof(*sa));
+ }
+
+ isc_counter_detach(&fctx->qc);
+ fcount_decr(fctx);
+ isc_timer_destroy(&fctx->timer);
+ if (fctx->timer_try_stale != NULL) {
+ isc_timer_destroy(&fctx->timer_try_stale);
+ }
+ dns_message_detach(&fctx->qmessage);
+ if (dns_name_countlabels(&fctx->domain) > 0) {
+ dns_name_free(&fctx->domain, fctx->mctx);
+ }
+ if (dns_rdataset_isassociated(&fctx->nameservers)) {
+ dns_rdataset_disassociate(&fctx->nameservers);
+ }
+ dns_name_free(&fctx->name, fctx->mctx);
+ dns_name_free(&fctx->qminname, fctx->mctx);
+ dns_name_free(&fctx->qmindcname, fctx->mctx);
+ dns_db_detach(&fctx->cache);
+ dns_adb_detach(&fctx->adb);
+ isc_mem_free(fctx->mctx, fctx->info);
+ isc_mem_putanddetach(&fctx->mctx, fctx, sizeof(*fctx));
+}
+
+/*
+ * Fetch event handlers.
+ */
+
+static void
+fctx_timeout(isc_task_t *task, isc_event_t *event) {
+ fetchctx_t *fctx = event->ev_arg;
+ isc_timerevent_t *tevent = (isc_timerevent_t *)event;
+ resquery_t *query;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+ UNUSED(task);
+
+ FCTXTRACE("timeout");
+
+ inc_stats(fctx->res, dns_resstatscounter_querytimeout);
+
+ if (event->ev_type == ISC_TIMEREVENT_LIFE) {
+ fctx->reason = NULL;
+ fctx_done(fctx, ISC_R_TIMEDOUT, __LINE__);
+ } else {
+ isc_result_t result;
+
+ fctx->timeouts++;
+ fctx->timeout = true;
+
+ /*
+ * We could cancel the running queries here, or we could let
+ * them keep going. Since we normally use separate sockets for
+ * different queries, we adopt the former approach to reduce
+ * the number of open sockets: cancel the oldest query if it
+ * expired after the query had started (this is usually the
+ * case but is not always so, depending on the task schedule
+ * timing).
+ */
+ query = ISC_LIST_HEAD(fctx->queries);
+ if (query != NULL &&
+ isc_time_compare(&tevent->due, &query->start) >= 0)
+ {
+ FCTXTRACE("query timed out; no response");
+ fctx_cancelquery(&query, NULL, NULL, true, false);
+ }
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+
+ /*
+ * Our timer has triggered. Reestablish the fctx lifetime
+ * timer.
+ */
+ result = fctx_starttimer(fctx);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, result, __LINE__);
+ } else {
+ /* Keep trying */
+ fctx_try(fctx, true, false);
+ }
+ }
+
+ isc_event_free(&event);
+}
+
+/*
+ * Fetch event handlers called if stale answers are enabled
+ * (stale-answer-enabled) and the fetch took more than
+ * stale-answer-client-timeout to complete.
+ */
+static void
+fctx_timeout_try_stale(isc_task_t *task, isc_event_t *event) {
+ fetchctx_t *fctx = event->ev_arg;
+ dns_fetchevent_t *dns_event, *next_event;
+ isc_task_t *sender_task;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+ UNUSED(task);
+
+ FCTXTRACE("timeout_try_stale");
+
+ if (event->ev_type != ISC_TIMEREVENT_LIFE) {
+ return;
+ }
+
+ LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+ /*
+ * Trigger events of type DNS_EVENT_TRYSTALE.
+ */
+ for (dns_event = ISC_LIST_HEAD(fctx->events); dns_event != NULL;
+ dns_event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(dns_event, ev_link);
+
+ if (dns_event->ev_type != DNS_EVENT_TRYSTALE) {
+ continue;
+ }
+
+ ISC_LIST_UNLINK(fctx->events, dns_event, ev_link);
+ sender_task = dns_event->ev_sender;
+ dns_event->ev_sender = fctx;
+ dns_event->vresult = ISC_R_TIMEDOUT;
+ dns_event->result = ISC_R_TIMEDOUT;
+
+ isc_task_sendanddetach(&sender_task, ISC_EVENT_PTR(&dns_event));
+ }
+
+ UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+ isc_event_free(&event);
+}
+
+static void
+fctx_shutdown(fetchctx_t *fctx) {
+ isc_event_t *cevent;
+
+ /*
+ * Start the shutdown process for fctx, if it isn't already underway.
+ */
+
+ FCTXTRACE("shutdown");
+
+ /*
+ * The caller must be holding the appropriate bucket lock.
+ */
+
+ if (fctx->want_shutdown) {
+ return;
+ }
+
+ fctx->want_shutdown = true;
+
+ /*
+ * Unless we're still initializing (in which case the
+ * control event is still outstanding), we need to post
+ * the control event to tell the fetch we want it to
+ * exit.
+ */
+ if (fctx->state != fetchstate_init) {
+ cevent = &fctx->control_event;
+ isc_task_sendto(fctx->res->buckets[fctx->bucketnum].task,
+ &cevent, fctx->bucketnum);
+ }
+}
+
+static void
+fctx_doshutdown(isc_task_t *task, isc_event_t *event) {
+ fetchctx_t *fctx = event->ev_arg;
+ bool bucket_empty = false;
+ dns_resolver_t *res;
+ unsigned int bucketnum;
+ dns_validator_t *validator;
+ bool dodestroy = false;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+ UNUSED(task);
+
+ res = fctx->res;
+ bucketnum = fctx->bucketnum;
+
+ FCTXTRACE("doshutdown");
+
+ /*
+ * An fctx that is shutting down is no longer in ADDRWAIT mode.
+ */
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
+
+ /*
+ * Cancel all pending validators. Note that this must be done
+ * without the bucket lock held, since that could cause deadlock.
+ */
+ validator = ISC_LIST_HEAD(fctx->validators);
+ while (validator != NULL) {
+ dns_validator_cancel(validator);
+ validator = ISC_LIST_NEXT(validator, link);
+ }
+
+ if (fctx->nsfetch != NULL) {
+ dns_resolver_cancelfetch(fctx->nsfetch);
+ }
+
+ if (fctx->qminfetch != NULL) {
+ dns_resolver_cancelfetch(fctx->qminfetch);
+ }
+
+ /*
+ * Shut down anything still running on behalf of this
+ * fetch, and clean up finds and addresses. To avoid deadlock
+ * with the ADB, we must do this before we lock the bucket lock.
+ */
+ fctx_stopqueries(fctx, false, false);
+ fctx_cleanupall(fctx);
+
+ LOCK(&res->buckets[bucketnum].lock);
+
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN);
+
+ INSIST(fctx->state == fetchstate_active ||
+ fctx->state == fetchstate_done);
+ INSIST(fctx->want_shutdown);
+
+ if (fctx->state != fetchstate_done) {
+ fctx->state = fetchstate_done;
+ fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__);
+ }
+
+ if (isc_refcount_current(&fctx->references) == 0 &&
+ fctx->pending == 0 && fctx->nqueries == 0 &&
+ ISC_LIST_EMPTY(fctx->validators))
+ {
+ bucket_empty = fctx_unlink(fctx);
+ dodestroy = true;
+ }
+
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ if (dodestroy) {
+ fctx_destroy(fctx);
+ if (bucket_empty) {
+ empty_bucket(res);
+ }
+ }
+}
+
+static void
+fctx_start(isc_task_t *task, isc_event_t *event) {
+ fetchctx_t *fctx = event->ev_arg;
+ bool done = false, bucket_empty = false;
+ dns_resolver_t *res;
+ unsigned int bucketnum;
+ bool dodestroy = false;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+ UNUSED(task);
+
+ res = fctx->res;
+ bucketnum = fctx->bucketnum;
+
+ FCTXTRACE("start");
+
+ LOCK(&res->buckets[bucketnum].lock);
+
+ INSIST(fctx->state == fetchstate_init);
+ if (fctx->want_shutdown) {
+ /*
+ * We haven't started this fctx yet, and we've been requested
+ * to shut it down.
+ */
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN);
+ fctx->state = fetchstate_done;
+ fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__);
+ /*
+ * Since we haven't started, we INSIST that we have no
+ * pending ADB finds and no pending validations.
+ */
+ INSIST(fctx->pending == 0);
+ INSIST(fctx->nqueries == 0);
+ INSIST(ISC_LIST_EMPTY(fctx->validators));
+ if (isc_refcount_current(&fctx->references) == 0) {
+ /*
+ * It's now safe to destroy this fctx.
+ */
+ bucket_empty = fctx_unlink(fctx);
+ dodestroy = true;
+ }
+ done = true;
+ } else {
+ /*
+ * Normal fctx startup.
+ */
+ fctx->state = fetchstate_active;
+ /*
+ * Reset the control event for later use in shutting down
+ * the fctx.
+ */
+ ISC_EVENT_INIT(event, sizeof(*event), 0, NULL,
+ DNS_EVENT_FETCHCONTROL, fctx_doshutdown, fctx,
+ NULL, NULL, NULL);
+ }
+
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ if (!done) {
+ isc_result_t result;
+
+ INSIST(!dodestroy);
+
+ /*
+ * All is well. Start working on the fetch.
+ */
+ result = fctx_starttimer(fctx);
+ if (result == ISC_R_SUCCESS && fctx->timer_try_stale != NULL) {
+ result = fctx_starttimer_trystale(fctx);
+ }
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, result, __LINE__);
+ } else {
+ fctx_try(fctx, false, false);
+ }
+ } else if (dodestroy) {
+ fctx_destroy(fctx);
+ if (bucket_empty) {
+ empty_bucket(res);
+ }
+ }
+}
+
+/*
+ * Fetch Creation, Joining, and Cancellation.
+ */
+
+static isc_result_t
+fctx_join(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client,
+ dns_messageid_t id, isc_taskaction_t action, void *arg,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_fetch_t *fetch) {
+ isc_task_t *tclone;
+ dns_fetchevent_t *event;
+
+ FCTXTRACE("join");
+
+ /*
+ * We store the task we're going to send this event to in the
+ * sender field. We'll make the fetch the sender when we actually
+ * send the event.
+ */
+ tclone = NULL;
+ isc_task_attach(task, &tclone);
+ event = (dns_fetchevent_t *)isc_event_allocate(
+ fctx->res->mctx, tclone, DNS_EVENT_FETCHDONE, action, arg,
+ sizeof(*event));
+ event->result = DNS_R_SERVFAIL;
+ event->qtype = fctx->type;
+ event->db = NULL;
+ event->node = NULL;
+ event->rdataset = rdataset;
+ event->sigrdataset = sigrdataset;
+ event->fetch = fetch;
+ event->client = client;
+ event->id = id;
+ dns_fixedname_init(&event->foundname);
+
+ /*
+ * Make sure that we can store the sigrdataset in the
+ * first event if it is needed by any of the events.
+ */
+ if (event->sigrdataset != NULL) {
+ ISC_LIST_PREPEND(fctx->events, event, ev_link);
+ } else {
+ ISC_LIST_APPEND(fctx->events, event, ev_link);
+ }
+
+ fctx_increference(fctx);
+
+ fetch->magic = DNS_FETCH_MAGIC;
+ fetch->private = fctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+fctx_add_event(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client,
+ dns_messageid_t id, isc_taskaction_t action, void *arg,
+ dns_fetch_t *fetch, isc_eventtype_t event_type) {
+ isc_task_t *tclone;
+ dns_fetchevent_t *event;
+ /*
+ * We store the task we're going to send this event to in the
+ * sender field. We'll make the fetch the sender when we actually
+ * send the event.
+ */
+ tclone = NULL;
+ isc_task_attach(task, &tclone);
+ event = (dns_fetchevent_t *)isc_event_allocate(fctx->res->mctx, tclone,
+ event_type, action, arg,
+ sizeof(*event));
+ event->result = DNS_R_SERVFAIL;
+ event->qtype = fctx->type;
+ event->db = NULL;
+ event->node = NULL;
+ event->rdataset = NULL;
+ event->sigrdataset = NULL;
+ event->fetch = fetch;
+ event->client = client;
+ event->id = id;
+ ISC_LIST_APPEND(fctx->events, event, ev_link);
+}
+
+static void
+log_ns_ttl(fetchctx_t *fctx, const char *where) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char domainbuf[DNS_NAME_FORMATSIZE];
+
+ dns_name_format(&fctx->name, namebuf, sizeof(namebuf));
+ dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10),
+ "log_ns_ttl: fctx %p: %s: %s (in '%s'?): %u %u", fctx,
+ where, namebuf, domainbuf, fctx->ns_ttl_ok, fctx->ns_ttl);
+}
+
+static isc_result_t
+fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type,
+ const dns_name_t *domain, dns_rdataset_t *nameservers,
+ const isc_sockaddr_t *client, dns_messageid_t id,
+ unsigned int options, unsigned int bucketnum, unsigned int depth,
+ isc_counter_t *qc, fetchctx_t **fctxp) {
+ fetchctx_t *fctx;
+ isc_result_t result;
+ isc_result_t iresult;
+ isc_interval_t interval;
+ unsigned int findoptions = 0;
+ char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + 1];
+ isc_mem_t *mctx;
+ size_t p;
+ bool try_stale;
+
+ /*
+ * Caller must be holding the lock for bucket number 'bucketnum'.
+ */
+ REQUIRE(fctxp != NULL && *fctxp == NULL);
+
+ mctx = res->buckets[bucketnum].mctx;
+ fctx = isc_mem_get(mctx, sizeof(*fctx));
+
+ fctx->qc = NULL;
+ if (qc != NULL) {
+ isc_counter_attach(qc, &fctx->qc);
+ } else {
+ result = isc_counter_create(res->mctx, res->maxqueries,
+ &fctx->qc);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_fetch;
+ }
+ }
+
+ /*
+ * Make fctx->info point to a copy of a formatted string
+ * "name/type".
+ */
+ dns_name_format(name, buf, sizeof(buf));
+ p = strlcat(buf, "/", sizeof(buf));
+ INSIST(p + DNS_RDATATYPE_FORMATSIZE < sizeof(buf));
+ dns_rdatatype_format(type, buf + p, sizeof(buf) - p);
+ fctx->info = isc_mem_strdup(mctx, buf);
+
+ FCTXTRACE("create");
+ dns_name_init(&fctx->name, NULL);
+ dns_name_dup(name, mctx, &fctx->name);
+ dns_name_init(&fctx->qminname, NULL);
+ dns_name_dup(name, mctx, &fctx->qminname);
+ dns_name_init(&fctx->domain, NULL);
+ dns_rdataset_init(&fctx->nameservers);
+
+ fctx->type = type;
+ fctx->qmintype = type;
+ fctx->options = options;
+ /*
+ * Note! We do not attach to the task. We are relying on the
+ * resolver to ensure that this task doesn't go away while we are
+ * using it.
+ */
+ fctx->res = res;
+ isc_refcount_init(&fctx->references, 0);
+ fctx->bucketnum = bucketnum;
+ fctx->dbucketnum = RES_NOBUCKET;
+ fctx->state = fetchstate_init;
+ fctx->want_shutdown = false;
+ fctx->cloned = false;
+ fctx->depth = depth;
+ fctx->minimized = false;
+ fctx->ip6arpaskip = false;
+ fctx->forwarding = false;
+ fctx->qmin_labels = 1;
+ fctx->qmin_warning = ISC_R_SUCCESS;
+ fctx->qminfetch = NULL;
+ dns_rdataset_init(&fctx->qminrrset);
+ dns_name_init(&fctx->qmindcname, NULL);
+ isc_stdtime_get(&fctx->now);
+ ISC_LIST_INIT(fctx->queries);
+ ISC_LIST_INIT(fctx->finds);
+ ISC_LIST_INIT(fctx->altfinds);
+ ISC_LIST_INIT(fctx->forwaddrs);
+ ISC_LIST_INIT(fctx->altaddrs);
+ ISC_LIST_INIT(fctx->forwarders);
+ fctx->fwdpolicy = dns_fwdpolicy_none;
+ ISC_LIST_INIT(fctx->bad);
+ ISC_LIST_INIT(fctx->edns);
+ ISC_LIST_INIT(fctx->edns512);
+ ISC_LIST_INIT(fctx->bad_edns);
+ ISC_LIST_INIT(fctx->validators);
+ fctx->validator = NULL;
+ fctx->find = NULL;
+ fctx->altfind = NULL;
+ fctx->pending = 0;
+ fctx->restarts = 0;
+ fctx->querysent = 0;
+ fctx->referrals = 0;
+
+ fctx->fwdname = dns_fixedname_initname(&fctx->fwdfname);
+
+ TIME_NOW(&fctx->start);
+ fctx->timeouts = 0;
+ fctx->lamecount = 0;
+ fctx->quotacount = 0;
+ fctx->adberr = 0;
+ fctx->neterr = 0;
+ fctx->badresp = 0;
+ fctx->findfail = 0;
+ fctx->valfail = 0;
+ fctx->result = ISC_R_FAILURE;
+ fctx->vresult = ISC_R_SUCCESS;
+ fctx->exitline = -1; /* sentinel */
+ fctx->logged = false;
+ atomic_init(&fctx->attributes, 0);
+ fctx->spilled = false;
+ fctx->nqueries = 0;
+ fctx->reason = NULL;
+ fctx->rand_buf = 0;
+ fctx->rand_bits = 0;
+ fctx->timeout = false;
+ fctx->addrinfo = NULL;
+ if (client != NULL) {
+ isc_sockaddr_format(client, fctx->clientstr,
+ sizeof(fctx->clientstr));
+ } else {
+ strlcpy(fctx->clientstr, "<unknown>", sizeof(fctx->clientstr));
+ }
+ fctx->id = id;
+ fctx->ns_ttl = 0;
+ fctx->ns_ttl_ok = false;
+
+ dns_name_init(&fctx->nsname, NULL);
+ fctx->nsfetch = NULL;
+ dns_rdataset_init(&fctx->nsrrset);
+
+ if (domain == NULL) {
+ dns_forwarders_t *forwarders = NULL;
+ dns_fixedname_t fixed;
+ unsigned int labels;
+ const dns_name_t *fwdname = name;
+ dns_name_t suffix;
+ dns_name_t *fname;
+
+ /*
+ * DS records are found in the parent server. Strip one
+ * leading label from the name (to be used in finding
+ * the forwarder).
+ */
+ if (dns_rdatatype_atparent(fctx->type) &&
+ dns_name_countlabels(name) > 1)
+ {
+ dns_name_init(&suffix, NULL);
+ labels = dns_name_countlabels(name);
+ dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
+ fwdname = &suffix;
+ }
+
+ /* Find the forwarder for this name. */
+ fname = dns_fixedname_initname(&fixed);
+ result = dns_fwdtable_find(fctx->res->view->fwdtable, fwdname,
+ fname, &forwarders);
+ if (result == ISC_R_SUCCESS) {
+ fctx->fwdpolicy = forwarders->fwdpolicy;
+ dns_name_copynf(fname, fctx->fwdname);
+ }
+
+ if (fctx->fwdpolicy != dns_fwdpolicy_only) {
+ dns_fixedname_t dcfixed;
+ dns_name_t *dcname;
+ dcname = dns_fixedname_initname(&dcfixed);
+ /*
+ * The caller didn't supply a query domain and
+ * nameservers, and we're not in forward-only mode,
+ * so find the best nameservers to use.
+ */
+ if (dns_rdatatype_atparent(fctx->type)) {
+ findoptions |= DNS_DBFIND_NOEXACT;
+ }
+ result = dns_view_findzonecut(res->view, name, fname,
+ dcname, fctx->now,
+ findoptions, true, true,
+ &fctx->nameservers, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_nameservers;
+ }
+
+ dns_name_dup(fname, mctx, &fctx->domain);
+ dns_name_dup(dcname, mctx, &fctx->qmindcname);
+ fctx->ns_ttl = fctx->nameservers.ttl;
+ fctx->ns_ttl_ok = true;
+ } else {
+ /*
+ * We're in forward-only mode. Set the query domain.
+ */
+ dns_name_dup(fname, mctx, &fctx->domain);
+ dns_name_dup(fname, mctx, &fctx->qmindcname);
+ /*
+ * Disable query minimization
+ */
+ options &= ~DNS_FETCHOPT_QMINIMIZE;
+ }
+ } else {
+ dns_name_dup(domain, mctx, &fctx->domain);
+ dns_name_dup(domain, mctx, &fctx->qmindcname);
+ dns_rdataset_clone(nameservers, &fctx->nameservers);
+ fctx->ns_ttl = fctx->nameservers.ttl;
+ fctx->ns_ttl_ok = true;
+ }
+
+ /*
+ * Are there too many simultaneous queries for this domain?
+ */
+ result = fcount_incr(fctx, false);
+ if (result != ISC_R_SUCCESS) {
+ result = fctx->res->quotaresp[dns_quotatype_zone];
+ inc_stats(res, dns_resstatscounter_zonequota);
+ goto cleanup_domain;
+ }
+
+ log_ns_ttl(fctx, "fctx_create");
+
+ if (!dns_name_issubdomain(&fctx->name, &fctx->domain)) {
+ dns_name_format(&fctx->domain, buf, sizeof(buf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "'%s' is not subdomain of '%s'", fctx->info,
+ buf);
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_fcount;
+ }
+
+ fctx->qmessage = NULL;
+ dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &fctx->qmessage);
+
+ /*
+ * Compute an expiration time for the entire fetch.
+ */
+ isc_interval_set(&interval, res->query_timeout / 1000,
+ res->query_timeout % 1000 * 1000000);
+ iresult = isc_time_nowplusinterval(&fctx->expires, &interval);
+ if (iresult != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_time_nowplusinterval: %s",
+ isc_result_totext(iresult));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_qmessage;
+ }
+
+ try_stale = ((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0);
+ if (try_stale) {
+ INSIST(res->view->staleanswerclienttimeout <=
+ (res->query_timeout - 1000));
+ /*
+ * Compute an expiration time after which stale data will
+ * attempted to be served, if stale answers are enabled and
+ * target RRset is available in cache.
+ */
+ isc_interval_set(
+ &interval, res->view->staleanswerclienttimeout / 1000,
+ res->view->staleanswerclienttimeout % 1000 * 1000000);
+ iresult = isc_time_nowplusinterval(&fctx->expires_try_stale,
+ &interval);
+ if (iresult != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_time_nowplusinterval: %s",
+ isc_result_totext(iresult));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_qmessage;
+ }
+ }
+
+ /*
+ * Default retry interval initialization. We set the interval now
+ * mostly so it won't be uninitialized. It will be set to the
+ * correct value before a query is issued.
+ */
+ isc_interval_set(&fctx->interval, 2, 0);
+
+ /*
+ * Create an inactive timer for resolver-query-timeout. It
+ * will be made active when the fetch is actually started.
+ */
+ fctx->timer = NULL;
+
+ iresult = isc_timer_create(res->timermgr, isc_timertype_inactive, NULL,
+ NULL, res->buckets[bucketnum].task,
+ fctx_timeout, fctx, &fctx->timer);
+ if (iresult != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_timer_create: %s",
+ isc_result_totext(iresult));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_qmessage;
+ }
+
+ /*
+ * If stale answers are enabled, then create an inactive timer
+ * for stale-answer-client-timeout. It will be made active when
+ * the fetch is actually started.
+ */
+ fctx->timer_try_stale = NULL;
+ if (try_stale) {
+ iresult = isc_timer_create(
+ res->timermgr, isc_timertype_inactive, NULL, NULL,
+ res->buckets[bucketnum].task, fctx_timeout_try_stale,
+ fctx, &fctx->timer_try_stale);
+ if (iresult != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_timer_create: %s",
+ isc_result_totext(iresult));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup_qmessage;
+ }
+ }
+
+ /*
+ * Attach to the view's cache and adb.
+ */
+ fctx->cache = NULL;
+ dns_db_attach(res->view->cachedb, &fctx->cache);
+ fctx->adb = NULL;
+ dns_adb_attach(res->view->adb, &fctx->adb);
+ fctx->mctx = NULL;
+ isc_mem_attach(mctx, &fctx->mctx);
+
+ ISC_LIST_INIT(fctx->events);
+ ISC_LINK_INIT(fctx, link);
+ fctx->magic = FCTX_MAGIC;
+
+ /*
+ * If qname minimization is enabled we need to trim
+ * the name in fctx to proper length.
+ */
+ if ((options & DNS_FETCHOPT_QMINIMIZE) != 0) {
+ fctx->ip6arpaskip =
+ (options & DNS_FETCHOPT_QMIN_SKIP_IP6A) != 0 &&
+ dns_name_issubdomain(&fctx->name, &ip6_arpa);
+ result = fctx_minimize_qname(fctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_mctx;
+ }
+ }
+
+ ISC_LIST_APPEND(res->buckets[bucketnum].fctxs, fctx, link);
+
+ INSIST(atomic_fetch_add_relaxed(&res->nfctx, 1) < UINT32_MAX);
+
+ inc_stats(res, dns_resstatscounter_nfetch);
+
+ *fctxp = fctx;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_mctx:
+ fctx->magic = 0;
+ isc_mem_detach(&fctx->mctx);
+ dns_adb_detach(&fctx->adb);
+ dns_db_detach(&fctx->cache);
+ isc_timer_destroy(&fctx->timer);
+ isc_timer_destroy(&fctx->timer_try_stale);
+
+cleanup_qmessage:
+ dns_message_detach(&fctx->qmessage);
+
+cleanup_fcount:
+ fcount_decr(fctx);
+
+cleanup_domain:
+ if (dns_name_countlabels(&fctx->domain) > 0) {
+ dns_name_free(&fctx->domain, mctx);
+ }
+ if (dns_name_countlabels(&fctx->qmindcname) > 0) {
+ dns_name_free(&fctx->qmindcname, mctx);
+ }
+
+cleanup_nameservers:
+ if (dns_rdataset_isassociated(&fctx->nameservers)) {
+ dns_rdataset_disassociate(&fctx->nameservers);
+ }
+ dns_name_free(&fctx->name, mctx);
+ dns_name_free(&fctx->qminname, mctx);
+ isc_mem_free(mctx, fctx->info);
+ isc_counter_detach(&fctx->qc);
+
+cleanup_fetch:
+ isc_mem_put(mctx, fctx, sizeof(*fctx));
+
+ return (result);
+}
+
+/*
+ * Handle Responses
+ */
+static bool
+is_lame(fetchctx_t *fctx, dns_message_t *message) {
+ dns_name_t *name;
+ dns_rdataset_t *rdataset;
+ isc_result_t result;
+
+ if (message->rcode != dns_rcode_noerror &&
+ message->rcode != dns_rcode_yxdomain &&
+ message->rcode != dns_rcode_nxdomain)
+ {
+ return (false);
+ }
+
+ if (message->counts[DNS_SECTION_ANSWER] != 0) {
+ return (false);
+ }
+
+ if (message->counts[DNS_SECTION_AUTHORITY] == 0) {
+ return (false);
+ }
+
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ dns_namereln_t namereln;
+ int order;
+ unsigned int labels;
+ if (rdataset->type != dns_rdatatype_ns) {
+ continue;
+ }
+ namereln = dns_name_fullcompare(name, &fctx->domain,
+ &order, &labels);
+ if (namereln == dns_namereln_equal &&
+ (message->flags & DNS_MESSAGEFLAG_AA) != 0)
+ {
+ return (false);
+ }
+ if (namereln == dns_namereln_subdomain) {
+ return (false);
+ }
+ return (true);
+ }
+ result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
+ }
+
+ return (false);
+}
+
+static void
+log_lame(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char domainbuf[DNS_NAME_FORMATSIZE];
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+
+ dns_name_format(&fctx->name, namebuf, sizeof(namebuf));
+ dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf));
+ isc_sockaddr_format(&addrinfo->sockaddr, addrbuf, sizeof(addrbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "lame server resolving '%s' (in '%s'?): %s", namebuf,
+ domainbuf, addrbuf);
+}
+
+static void
+log_formerr(fetchctx_t *fctx, const char *format, ...) {
+ char nsbuf[ISC_SOCKADDR_FORMATSIZE];
+ char msgbuf[2048];
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(msgbuf, sizeof(msgbuf), format, args);
+ va_end(args);
+
+ isc_sockaddr_format(&fctx->addrinfo->sockaddr, nsbuf, sizeof(nsbuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "DNS format error from %s resolving %s for %s: %s", nsbuf,
+ fctx->info, fctx->clientstr, msgbuf);
+}
+
+static isc_result_t
+same_question(fetchctx_t *fctx, dns_message_t *message) {
+ isc_result_t result;
+ dns_name_t *name;
+ dns_rdataset_t *rdataset;
+
+ /*
+ * Caller must be holding the fctx lock.
+ */
+
+ /*
+ * XXXRTH Currently we support only one question.
+ */
+ if (ISC_UNLIKELY(message->counts[DNS_SECTION_QUESTION] == 0)) {
+ if ((message->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ /*
+ * If TC=1 and the question section is empty, we
+ * accept the reply message as a truncated
+ * answer, to be retried over TCP.
+ *
+ * It is really a FORMERR condition, but this is
+ * a workaround to accept replies from some
+ * implementations.
+ *
+ * Because the question section matching is not
+ * performed, the worst that could happen is
+ * that an attacker who gets past the ID and
+ * source port checks can force the use of
+ * TCP. This is considered an acceptable risk.
+ */
+ log_formerr(fctx, "empty question section, "
+ "accepting it anyway as TC=1");
+ return (ISC_R_SUCCESS);
+ } else {
+ log_formerr(fctx, "empty question section");
+ return (DNS_R_FORMERR);
+ }
+ } else if (ISC_UNLIKELY(message->counts[DNS_SECTION_QUESTION] > 1)) {
+ log_formerr(fctx, "too many questions");
+ return (DNS_R_FORMERR);
+ }
+
+ result = dns_message_firstname(message, DNS_SECTION_QUESTION);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_QUESTION, &name);
+ rdataset = ISC_LIST_HEAD(name->list);
+ INSIST(rdataset != NULL);
+ INSIST(ISC_LIST_NEXT(rdataset, link) == NULL);
+
+ if (fctx->type != rdataset->type ||
+ fctx->res->rdclass != rdataset->rdclass ||
+ !dns_name_equal(&fctx->name, name))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdataclass_format(rdataset->rdclass, classbuf,
+ sizeof(classbuf));
+ dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
+ log_formerr(fctx, "question section mismatch: got %s/%s/%s",
+ namebuf, classbuf, typebuf);
+ return (DNS_R_FORMERR);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+clone_results(fetchctx_t *fctx) {
+ dns_fetchevent_t *event, *hevent;
+ dns_name_t *name, *hname;
+
+ FCTXTRACE("clone_results");
+
+ /*
+ * Set up any other events to have the same data as the first
+ * event.
+ *
+ * Caller must be holding the appropriate lock.
+ */
+
+ fctx->cloned = true;
+ hevent = ISC_LIST_HEAD(fctx->events);
+ if (hevent == NULL) {
+ return;
+ }
+ hname = dns_fixedname_name(&hevent->foundname);
+ for (event = ISC_LIST_NEXT(hevent, ev_link); event != NULL;
+ event = ISC_LIST_NEXT(event, ev_link))
+ {
+ if (event->ev_type == DNS_EVENT_TRYSTALE) {
+ /*
+ * We don't need to clone resulting data to this
+ * type of event, as its associated callback is only
+ * called when stale-answer-client-timeout triggers,
+ * and the logic in there doesn't expect any result
+ * as input, as it will itself lookup for stale data
+ * in cache to use as result, if any is available.
+ *
+ * Also, if we reached this point, then the whole fetch
+ * context is done, it will cancel timers, process
+ * associated callbacks of type DNS_EVENT_FETCHDONE, and
+ * silently remove/free events of type
+ * DNS_EVENT_TRYSTALE.
+ */
+ continue;
+ }
+ name = dns_fixedname_name(&event->foundname);
+ dns_name_copynf(hname, name);
+ event->result = hevent->result;
+ dns_db_attach(hevent->db, &event->db);
+ dns_db_attachnode(hevent->db, hevent->node, &event->node);
+ INSIST(hevent->rdataset != NULL);
+ INSIST(event->rdataset != NULL);
+ if (dns_rdataset_isassociated(hevent->rdataset)) {
+ dns_rdataset_clone(hevent->rdataset, event->rdataset);
+ }
+ INSIST(!(hevent->sigrdataset == NULL &&
+ event->sigrdataset != NULL));
+ if (hevent->sigrdataset != NULL &&
+ dns_rdataset_isassociated(hevent->sigrdataset) &&
+ event->sigrdataset != NULL)
+ {
+ dns_rdataset_clone(hevent->sigrdataset,
+ event->sigrdataset);
+ }
+ }
+}
+
+#define CACHE(r) (((r)->attributes & DNS_RDATASETATTR_CACHE) != 0)
+#define ANSWER(r) (((r)->attributes & DNS_RDATASETATTR_ANSWER) != 0)
+#define ANSWERSIG(r) (((r)->attributes & DNS_RDATASETATTR_ANSWERSIG) != 0)
+#define EXTERNAL(r) (((r)->attributes & DNS_RDATASETATTR_EXTERNAL) != 0)
+#define CHAINING(r) (((r)->attributes & DNS_RDATASETATTR_CHAINING) != 0)
+#define CHASE(r) (((r)->attributes & DNS_RDATASETATTR_CHASE) != 0)
+#define CHECKNAMES(r) (((r)->attributes & DNS_RDATASETATTR_CHECKNAMES) != 0)
+
+/*
+ * The validator has finished.
+ */
+static void
+validated(isc_task_t *task, isc_event_t *event) {
+ dns_adbaddrinfo_t *addrinfo;
+ dns_dbnode_t *node = NULL;
+ dns_dbnode_t *nsnode = NULL;
+ dns_fetchevent_t *hevent;
+ dns_name_t *name;
+ dns_rdataset_t *ardataset = NULL;
+ dns_rdataset_t *asigrdataset = NULL;
+ dns_rdataset_t *rdataset;
+ dns_rdataset_t *sigrdataset;
+ dns_resolver_t *res;
+ dns_valarg_t *valarg = event->ev_arg;
+ dns_validatorevent_t *vevent;
+ fetchctx_t *fctx;
+ bool chaining;
+ bool negative;
+ bool sentresponse;
+ bool bucket_empty;
+ isc_result_t eresult = ISC_R_SUCCESS;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_stdtime_t now;
+ uint32_t ttl;
+ unsigned options;
+ uint32_t bucketnum;
+ dns_fixedname_t fwild;
+ dns_name_t *wild = NULL;
+ dns_message_t *message = NULL;
+
+ UNUSED(task); /* for now */
+
+ REQUIRE(event->ev_type == DNS_EVENT_VALIDATORDONE);
+ REQUIRE(VALID_FCTX(valarg->fctx));
+ REQUIRE(!ISC_LIST_EMPTY(valarg->fctx->validators));
+
+ fctx = valarg->fctx;
+ fctx_increference(fctx);
+ dns_message_attach(valarg->message, &message);
+
+ res = fctx->res;
+ addrinfo = valarg->addrinfo;
+
+ vevent = (dns_validatorevent_t *)event;
+ fctx->vresult = vevent->result;
+
+ FCTXTRACE("received validation completion event");
+
+ bucketnum = fctx->bucketnum;
+ LOCK(&res->buckets[bucketnum].lock);
+
+ ISC_LIST_UNLINK(fctx->validators, vevent->validator, link);
+ fctx->validator = NULL;
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ /*
+ * Destroy the validator early so that we can
+ * destroy the fctx if necessary. Save the wildcard name.
+ */
+ if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) {
+ wild = dns_fixedname_initname(&fwild);
+ dns_name_copynf(dns_fixedname_name(&vevent->validator->wild),
+ wild);
+ }
+ dns_validator_destroy(&vevent->validator);
+ dns_message_detach(&valarg->message);
+ isc_mem_put(fctx->mctx, valarg, sizeof(*valarg));
+
+ negative = (vevent->rdataset == NULL);
+
+ LOCK(&res->buckets[bucketnum].lock);
+ sentresponse = ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0);
+
+ /*
+ * If shutting down, ignore the results.
+ */
+ if (SHUTTINGDOWN(fctx) && !sentresponse) {
+ UNLOCK(&res->buckets[bucketnum].lock);
+ goto cleanup_event;
+ }
+
+ isc_stdtime_get(&now);
+
+ /*
+ * If chaining, we need to make sure that the right result code is
+ * returned, and that the rdatasets are bound.
+ */
+ if (vevent->result == ISC_R_SUCCESS && !negative &&
+ vevent->rdataset != NULL && CHAINING(vevent->rdataset))
+ {
+ if (vevent->rdataset->type == dns_rdatatype_cname) {
+ eresult = DNS_R_CNAME;
+ } else {
+ INSIST(vevent->rdataset->type == dns_rdatatype_dname);
+ eresult = DNS_R_DNAME;
+ }
+ chaining = true;
+ } else {
+ chaining = false;
+ }
+
+ /*
+ * Either we're not shutting down, or we are shutting down but want
+ * to cache the result anyway (if this was a validation started by
+ * a query with cd set)
+ */
+
+ hevent = ISC_LIST_HEAD(fctx->events);
+ if (hevent != NULL) {
+ if (!negative && !chaining &&
+ (fctx->type == dns_rdatatype_any ||
+ fctx->type == dns_rdatatype_rrsig ||
+ fctx->type == dns_rdatatype_sig))
+ {
+ /*
+ * Don't bind rdatasets; the caller
+ * will iterate the node.
+ */
+ } else {
+ ardataset = hevent->rdataset;
+ asigrdataset = hevent->sigrdataset;
+ }
+ }
+
+ if (vevent->result != ISC_R_SUCCESS) {
+ FCTXTRACE("validation failed");
+ inc_stats(res, dns_resstatscounter_valfail);
+ fctx->valfail++;
+ fctx->vresult = vevent->result;
+ if (fctx->vresult != DNS_R_BROKENCHAIN) {
+ result = ISC_R_NOTFOUND;
+ if (vevent->rdataset != NULL) {
+ result = dns_db_findnode(
+ fctx->cache, vevent->name, true, &node);
+ }
+ if (result == ISC_R_SUCCESS) {
+ (void)dns_db_deleterdataset(fctx->cache, node,
+ NULL, vevent->type,
+ 0);
+ if (vevent->sigrdataset != NULL) {
+ (void)dns_db_deleterdataset(
+ fctx->cache, node, NULL,
+ dns_rdatatype_rrsig,
+ vevent->type);
+ }
+ dns_db_detachnode(fctx->cache, &node);
+ }
+ } else if (!negative) {
+ /*
+ * Cache the data as pending for later validation.
+ */
+ result = ISC_R_NOTFOUND;
+ if (vevent->rdataset != NULL) {
+ result = dns_db_findnode(
+ fctx->cache, vevent->name, true, &node);
+ }
+ if (result == ISC_R_SUCCESS) {
+ (void)dns_db_addrdataset(
+ fctx->cache, node, NULL, now,
+ vevent->rdataset, 0, NULL);
+ if (vevent->sigrdataset != NULL) {
+ (void)dns_db_addrdataset(
+ fctx->cache, node, NULL, now,
+ vevent->sigrdataset, 0, NULL);
+ }
+ dns_db_detachnode(fctx->cache, &node);
+ }
+ }
+ result = fctx->vresult;
+ add_bad(fctx, message, addrinfo, result, badns_validation);
+ UNLOCK(&res->buckets[bucketnum].lock);
+ INSIST(fctx->validator == NULL);
+ fctx->validator = ISC_LIST_HEAD(fctx->validators);
+ if (fctx->validator != NULL) {
+ dns_validator_send(fctx->validator);
+ } else if (sentresponse) {
+ fctx_done(fctx, result, __LINE__); /* Locks bucket. */
+ } else if (result == DNS_R_BROKENCHAIN) {
+ isc_result_t tresult;
+ isc_time_t expire;
+ isc_interval_t i;
+
+ isc_interval_set(&i, DNS_RESOLVER_BADCACHETTL(fctx), 0);
+ tresult = isc_time_nowplusinterval(&expire, &i);
+ if (negative &&
+ (fctx->type == dns_rdatatype_dnskey ||
+ fctx->type == dns_rdatatype_ds) &&
+ tresult == ISC_R_SUCCESS)
+ {
+ dns_resolver_addbadcache(res, &fctx->name,
+ fctx->type, &expire);
+ }
+ fctx_done(fctx, result, __LINE__); /* Locks bucket. */
+ } else {
+ fctx_try(fctx, true, true); /* Locks bucket. */
+ }
+
+ goto cleanup_event;
+ }
+
+ if (negative) {
+ dns_rdatatype_t covers;
+ FCTXTRACE("nonexistence validation OK");
+
+ inc_stats(res, dns_resstatscounter_valnegsuccess);
+
+ /*
+ * Cache DS NXDOMAIN separately to other types.
+ */
+ if (message->rcode == dns_rcode_nxdomain &&
+ fctx->type != dns_rdatatype_ds)
+ {
+ covers = dns_rdatatype_any;
+ } else {
+ covers = fctx->type;
+ }
+
+ result = dns_db_findnode(fctx->cache, vevent->name, true,
+ &node);
+ if (result != ISC_R_SUCCESS) {
+ goto noanswer_response;
+ }
+
+ /*
+ * If we are asking for a SOA record set the cache time
+ * to zero to facilitate locating the containing zone of
+ * a arbitrary zone.
+ */
+ ttl = res->view->maxncachettl;
+ if (fctx->type == dns_rdatatype_soa &&
+ covers == dns_rdatatype_any && res->zero_no_soa_ttl)
+ {
+ ttl = 0;
+ }
+
+ result = ncache_adderesult(message, fctx->cache, node, covers,
+ now, fctx->res->view->minncachettl,
+ ttl, vevent->optout, vevent->secure,
+ ardataset, &eresult);
+ if (result != ISC_R_SUCCESS) {
+ goto noanswer_response;
+ }
+ goto answer_response;
+ } else {
+ inc_stats(res, dns_resstatscounter_valsuccess);
+ }
+
+ FCTXTRACE("validation OK");
+
+ if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) {
+ result = dns_rdataset_addnoqname(
+ vevent->rdataset,
+ vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF]);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ INSIST(vevent->sigrdataset != NULL);
+ vevent->sigrdataset->ttl = vevent->rdataset->ttl;
+ if (vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) {
+ result = dns_rdataset_addclosest(
+ vevent->rdataset,
+ vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER]);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ } else if (vevent->rdataset->trust == dns_trust_answer &&
+ vevent->rdataset->type != dns_rdatatype_rrsig)
+ {
+ isc_result_t tresult;
+ dns_name_t *noqname = NULL;
+ tresult = findnoqname(fctx, message, vevent->name,
+ vevent->rdataset->type, &noqname);
+ if (tresult == ISC_R_SUCCESS && noqname != NULL) {
+ tresult = dns_rdataset_addnoqname(vevent->rdataset,
+ noqname);
+ RUNTIME_CHECK(tresult == ISC_R_SUCCESS);
+ }
+ }
+
+ /*
+ * The data was already cached as pending data.
+ * Re-cache it as secure and bind the cached
+ * rdatasets to the first event on the fetch
+ * event list.
+ */
+ result = dns_db_findnode(fctx->cache, vevent->name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto noanswer_response;
+ }
+
+ options = 0;
+ if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0) {
+ options = DNS_DBADD_PREFETCH;
+ }
+ result = dns_db_addrdataset(fctx->cache, node, NULL, now,
+ vevent->rdataset, options, ardataset);
+ if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) {
+ goto noanswer_response;
+ }
+ if (ardataset != NULL && NEGATIVE(ardataset)) {
+ if (NXDOMAIN(ardataset)) {
+ eresult = DNS_R_NCACHENXDOMAIN;
+ } else {
+ eresult = DNS_R_NCACHENXRRSET;
+ }
+ } else if (vevent->sigrdataset != NULL) {
+ result = dns_db_addrdataset(fctx->cache, node, NULL, now,
+ vevent->sigrdataset, options,
+ asigrdataset);
+ if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) {
+ goto noanswer_response;
+ }
+ }
+
+ if (sentresponse) {
+ /*
+ * If we only deferred the destroy because we wanted to cache
+ * the data, destroy now.
+ */
+ dns_db_detachnode(fctx->cache, &node);
+ UNLOCK(&res->buckets[bucketnum].lock);
+ goto cleanup_event;
+ }
+
+ if (!ISC_LIST_EMPTY(fctx->validators)) {
+ INSIST(!negative);
+ INSIST(fctx->type == dns_rdatatype_any ||
+ fctx->type == dns_rdatatype_rrsig ||
+ fctx->type == dns_rdatatype_sig);
+ /*
+ * Don't send a response yet - we have
+ * more rdatasets that still need to
+ * be validated.
+ */
+ dns_db_detachnode(fctx->cache, &node);
+ UNLOCK(&res->buckets[bucketnum].lock);
+ dns_validator_send(ISC_LIST_HEAD(fctx->validators));
+ goto cleanup_event;
+ }
+
+answer_response:
+ /*
+ * Cache any SOA/NS/NSEC records that happened to be validated.
+ */
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if ((rdataset->type != dns_rdatatype_ns &&
+ rdataset->type != dns_rdatatype_soa &&
+ rdataset->type != dns_rdatatype_nsec) ||
+ rdataset->trust != dns_trust_secure)
+ {
+ continue;
+ }
+ for (sigrdataset = ISC_LIST_HEAD(name->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (sigrdataset->type != dns_rdatatype_rrsig ||
+ sigrdataset->covers != rdataset->type)
+ {
+ continue;
+ }
+ break;
+ }
+ if (sigrdataset == NULL ||
+ sigrdataset->trust != dns_trust_secure)
+ {
+ continue;
+ }
+ result = dns_db_findnode(fctx->cache, name, true,
+ &nsnode);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ result = dns_db_addrdataset(fctx->cache, nsnode, NULL,
+ now, rdataset, 0, NULL);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_db_addrdataset(
+ fctx->cache, nsnode, NULL, now,
+ sigrdataset, 0, NULL);
+ }
+ dns_db_detachnode(fctx->cache, &nsnode);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ }
+ result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
+ }
+
+ /*
+ * Add the wild card entry.
+ */
+ if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL &&
+ vevent->rdataset != NULL &&
+ dns_rdataset_isassociated(vevent->rdataset) &&
+ vevent->rdataset->trust == dns_trust_secure &&
+ vevent->sigrdataset != NULL &&
+ dns_rdataset_isassociated(vevent->sigrdataset) &&
+ vevent->sigrdataset->trust == dns_trust_secure && wild != NULL)
+ {
+ dns_dbnode_t *wnode = NULL;
+
+ result = dns_db_findnode(fctx->cache, wild, true, &wnode);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_db_addrdataset(fctx->cache, wnode, NULL,
+ now, vevent->rdataset, 0,
+ NULL);
+ }
+ if (result == ISC_R_SUCCESS) {
+ (void)dns_db_addrdataset(fctx->cache, wnode, NULL, now,
+ vevent->sigrdataset, 0, NULL);
+ }
+ if (wnode != NULL) {
+ dns_db_detachnode(fctx->cache, &wnode);
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+ /*
+ * Respond with an answer, positive or negative,
+ * as opposed to an error. 'node' must be non-NULL.
+ */
+
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
+
+ if (hevent != NULL) {
+ /*
+ * Negative results must be indicated in event->result.
+ */
+ INSIST(hevent->rdataset != NULL);
+ if (dns_rdataset_isassociated(hevent->rdataset) &&
+ NEGATIVE(hevent->rdataset))
+ {
+ INSIST(eresult == DNS_R_NCACHENXDOMAIN ||
+ eresult == DNS_R_NCACHENXRRSET);
+ }
+ hevent->result = eresult;
+ dns_name_copynf(vevent->name,
+ dns_fixedname_name(&hevent->foundname));
+ dns_db_attach(fctx->cache, &hevent->db);
+ dns_db_transfernode(fctx->cache, &node, &hevent->node);
+ clone_results(fctx);
+ }
+
+noanswer_response:
+ if (node != NULL) {
+ dns_db_detachnode(fctx->cache, &node);
+ }
+
+ UNLOCK(&res->buckets[bucketnum].lock);
+ fctx_done(fctx, result, __LINE__); /* Locks bucket. */
+
+cleanup_event:
+ INSIST(node == NULL);
+ dns_message_detach(&message);
+ isc_event_free(&event);
+
+ LOCK(&res->buckets[bucketnum].lock);
+ bucket_empty = fctx_decreference(fctx);
+ UNLOCK(&res->buckets[bucketnum].lock);
+ if (bucket_empty) {
+ empty_bucket(res);
+ }
+}
+
+static void
+fctx_log(void *arg, int level, const char *fmt, ...) {
+ char msgbuf[2048];
+ va_list args;
+ fetchctx_t *fctx = arg;
+
+ va_start(args, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
+ va_end(args);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, level, "fctx %p(%s): %s", fctx,
+ fctx->info, msgbuf);
+}
+
+static isc_result_t
+findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
+ dns_rdatatype_t type, dns_name_t **noqnamep) {
+ dns_rdataset_t *nrdataset, *next, *sigrdataset;
+ dns_rdata_rrsig_t rrsig;
+ isc_result_t result;
+ unsigned int labels;
+ dns_section_t section;
+ dns_name_t *zonename;
+ dns_fixedname_t fzonename;
+ dns_name_t *closest;
+ dns_fixedname_t fclosest;
+ dns_name_t *nearest;
+ dns_fixedname_t fnearest;
+ dns_rdatatype_t found = dns_rdatatype_none;
+ dns_name_t *noqname = NULL;
+
+ FCTXTRACE("findnoqname");
+
+ REQUIRE(noqnamep != NULL && *noqnamep == NULL);
+
+ /*
+ * Find the SIG for this rdataset, if we have it.
+ */
+ for (sigrdataset = ISC_LIST_HEAD(name->list); sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (sigrdataset->type == dns_rdatatype_rrsig &&
+ sigrdataset->covers == type)
+ {
+ break;
+ }
+ }
+
+ if (sigrdataset == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ labels = dns_name_countlabels(name);
+
+ for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(sigrdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(sigrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ /* Wildcard has rrsig.labels < labels - 1. */
+ if (rrsig.labels + 1U >= labels) {
+ continue;
+ }
+ break;
+ }
+
+ if (result == ISC_R_NOMORE) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ zonename = dns_fixedname_initname(&fzonename);
+ closest = dns_fixedname_initname(&fclosest);
+ nearest = dns_fixedname_initname(&fnearest);
+
+#define NXND(x) ((x) == ISC_R_SUCCESS)
+
+ section = DNS_SECTION_AUTHORITY;
+ for (result = dns_message_firstname(message, section);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(message, section))
+ {
+ dns_name_t *nsec = NULL;
+ dns_message_currentname(message, section, &nsec);
+ for (nrdataset = ISC_LIST_HEAD(nsec->list); nrdataset != NULL;
+ nrdataset = next)
+ {
+ bool data = false, exists = false;
+ bool optout = false, unknown = false;
+ bool setclosest = false;
+ bool setnearest = false;
+
+ next = ISC_LIST_NEXT(nrdataset, link);
+ if (nrdataset->type != dns_rdatatype_nsec &&
+ nrdataset->type != dns_rdatatype_nsec3)
+ {
+ continue;
+ }
+
+ if (nrdataset->type == dns_rdatatype_nsec &&
+ NXND(dns_nsec_noexistnodata(
+ type, name, nsec, nrdataset, &exists, &data,
+ NULL, fctx_log, fctx)))
+ {
+ if (!exists) {
+ noqname = nsec;
+ found = dns_rdatatype_nsec;
+ }
+ }
+
+ if (nrdataset->type == dns_rdatatype_nsec3 &&
+ NXND(dns_nsec3_noexistnodata(
+ type, name, nsec, nrdataset, zonename,
+ &exists, &data, &optout, &unknown,
+ &setclosest, &setnearest, closest, nearest,
+ fctx_log, fctx)))
+ {
+ if (!exists && setnearest) {
+ noqname = nsec;
+ found = dns_rdatatype_nsec3;
+ }
+ }
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+ if (noqname != NULL) {
+ for (sigrdataset = ISC_LIST_HEAD(noqname->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (sigrdataset->type == dns_rdatatype_rrsig &&
+ sigrdataset->covers == found)
+ {
+ break;
+ }
+ }
+ if (sigrdataset != NULL) {
+ *noqnamep = noqname;
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) {
+ dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
+ dns_rdataset_t *addedrdataset = NULL;
+ dns_rdataset_t *ardataset = NULL, *asigrdataset = NULL;
+ dns_rdataset_t *valrdataset = NULL, *valsigrdataset = NULL;
+ dns_dbnode_t *node = NULL, **anodep = NULL;
+ dns_db_t **adbp = NULL;
+ dns_name_t *aname = NULL;
+ dns_resolver_t *res = fctx->res;
+ bool need_validation = false;
+ bool secure_domain = false;
+ bool have_answer = false;
+ isc_result_t result, eresult = ISC_R_SUCCESS;
+ dns_fetchevent_t *event = NULL;
+ unsigned int options;
+ isc_task_t *task;
+ bool fail;
+ unsigned int valoptions = 0;
+ bool checknta = true;
+
+ FCTXTRACE("cache_name");
+
+ /*
+ * The appropriate bucket lock must be held.
+ */
+ task = res->buckets[fctx->bucketnum].task;
+
+ /*
+ * Is DNSSEC validation required for this name?
+ */
+ if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
+ valoptions |= DNS_VALIDATOR_NONTA;
+ checknta = false;
+ }
+
+ if (res->view->enablevalidation) {
+ result = issecuredomain(res->view, name, fctx->type, now,
+ checknta, NULL, &secure_domain);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
+ valoptions |= DNS_VALIDATOR_NOCDFLAG;
+ }
+
+ if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
+ need_validation = false;
+ } else {
+ need_validation = secure_domain;
+ }
+
+ if (((name->attributes & DNS_NAMEATTR_ANSWER) != 0) &&
+ (!need_validation))
+ {
+ have_answer = true;
+ event = ISC_LIST_HEAD(fctx->events);
+
+ if (event != NULL) {
+ adbp = &event->db;
+ aname = dns_fixedname_name(&event->foundname);
+ dns_name_copynf(name, aname);
+ anodep = &event->node;
+ /*
+ * If this is an ANY, SIG or RRSIG query, we're not
+ * going to return any rdatasets, unless we encountered
+ * a CNAME or DNAME as "the answer". In this case,
+ * we're going to return DNS_R_CNAME or DNS_R_DNAME
+ * and we must set up the rdatasets.
+ */
+ if ((fctx->type != dns_rdatatype_any &&
+ fctx->type != dns_rdatatype_rrsig &&
+ fctx->type != dns_rdatatype_sig) ||
+ (name->attributes & DNS_NAMEATTR_CHAINING) != 0)
+ {
+ ardataset = event->rdataset;
+ asigrdataset = event->sigrdataset;
+ }
+ }
+ }
+
+ /*
+ * Find or create the cache node.
+ */
+ node = NULL;
+ result = dns_db_findnode(fctx->cache, name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Cache or validate each cacheable rdataset.
+ */
+ fail = ((fctx->res->options & DNS_RESOLVER_CHECKNAMESFAIL) != 0);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (!CACHE(rdataset)) {
+ continue;
+ }
+ if (CHECKNAMES(rdataset)) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(rdataset->type, typebuf,
+ sizeof(typebuf));
+ dns_rdataclass_format(rdataset->rdclass, classbuf,
+ sizeof(classbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "check-names %s %s/%s/%s",
+ fail ? "failure" : "warning", namebuf,
+ typebuf, classbuf);
+ if (fail) {
+ if (ANSWER(rdataset)) {
+ dns_db_detachnode(fctx->cache, &node);
+ return (DNS_R_BADNAME);
+ }
+ continue;
+ }
+ }
+
+ /*
+ * Enforce the configure maximum cache TTL.
+ */
+ if (rdataset->ttl > res->view->maxcachettl) {
+ rdataset->ttl = res->view->maxcachettl;
+ }
+
+ /*
+ * Enforce configured minimum cache TTL.
+ */
+ if (rdataset->ttl < res->view->mincachettl) {
+ rdataset->ttl = res->view->mincachettl;
+ }
+
+ /*
+ * Mark the rdataset as being prefetch eligible.
+ */
+ if (rdataset->ttl >= fctx->res->view->prefetch_eligible) {
+ rdataset->attributes |= DNS_RDATASETATTR_PREFETCH;
+ }
+
+ /*
+ * Find the SIG for this rdataset, if we have it.
+ */
+ for (sigrdataset = ISC_LIST_HEAD(name->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (sigrdataset->type == dns_rdatatype_rrsig &&
+ sigrdataset->covers == rdataset->type)
+ {
+ break;
+ }
+ }
+
+ /*
+ * If this RRset is in a secure domain, is in bailiwick,
+ * and is not glue, attempt DNSSEC validation. (We do not
+ * attempt to validate glue or out-of-bailiwick data--even
+ * though there might be some performance benefit to doing
+ * so--because it makes it simpler and safer to ensure that
+ * records from a secure domain are only cached if validated
+ * within the context of a query to the domain that owns
+ * them.)
+ */
+ if (secure_domain && rdataset->trust != dns_trust_glue &&
+ !EXTERNAL(rdataset))
+ {
+ dns_trust_t trust;
+
+ /*
+ * RRSIGs are validated as part of validating the
+ * type they cover.
+ */
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ continue;
+ }
+
+ if (sigrdataset == NULL && need_validation &&
+ !ANSWER(rdataset))
+ {
+ /*
+ * Ignore unrelated non-answer
+ * rdatasets that are missing signatures.
+ */
+ continue;
+ }
+
+ /*
+ * Normalize the rdataset and sigrdataset TTLs.
+ */
+ if (sigrdataset != NULL) {
+ rdataset->ttl = ISC_MIN(rdataset->ttl,
+ sigrdataset->ttl);
+ sigrdataset->ttl = rdataset->ttl;
+ }
+
+ /*
+ * Mark the rdataset as being prefetch eligible.
+ */
+ if (rdataset->ttl >= fctx->res->view->prefetch_eligible)
+ {
+ rdataset->attributes |=
+ DNS_RDATASETATTR_PREFETCH;
+ }
+
+ /*
+ * Cache this rdataset/sigrdataset pair as
+ * pending data. Track whether it was additional
+ * or not. If this was a priming query, additional
+ * should be cached as glue.
+ */
+ if (rdataset->trust == dns_trust_additional) {
+ trust = dns_trust_pending_additional;
+ } else {
+ trust = dns_trust_pending_answer;
+ }
+
+ rdataset->trust = trust;
+ if (sigrdataset != NULL) {
+ sigrdataset->trust = trust;
+ }
+ if (!need_validation || !ANSWER(rdataset)) {
+ options = 0;
+ if (ANSWER(rdataset) &&
+ rdataset->type != dns_rdatatype_rrsig)
+ {
+ isc_result_t tresult;
+ dns_name_t *noqname = NULL;
+ tresult = findnoqname(
+ fctx, message, name,
+ rdataset->type, &noqname);
+ if (tresult == ISC_R_SUCCESS &&
+ noqname != NULL)
+ {
+ (void)dns_rdataset_addnoqname(
+ rdataset, noqname);
+ }
+ }
+ if ((fctx->options & DNS_FETCHOPT_PREFETCH) !=
+ 0)
+ {
+ options = DNS_DBADD_PREFETCH;
+ }
+ if ((fctx->options & DNS_FETCHOPT_NOCACHED) !=
+ 0)
+ {
+ options |= DNS_DBADD_FORCE;
+ }
+ addedrdataset = ardataset;
+ result = dns_db_addrdataset(
+ fctx->cache, node, NULL, now, rdataset,
+ options, addedrdataset);
+ if (result == DNS_R_UNCHANGED) {
+ result = ISC_R_SUCCESS;
+ if (!need_validation &&
+ ardataset != NULL &&
+ NEGATIVE(ardataset))
+ {
+ /*
+ * The answer in the cache is
+ * better than the answer we
+ * found, and is a negative
+ * cache entry, so we must set
+ * eresult appropriately.
+ */
+ if (NXDOMAIN(ardataset)) {
+ eresult =
+ DNS_R_NCACHENXDOMAIN;
+ } else {
+ eresult =
+ DNS_R_NCACHENXRRSET;
+ }
+ /*
+ * We have a negative response
+ * from the cache so don't
+ * attempt to add the RRSIG
+ * rrset.
+ */
+ continue;
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ if (sigrdataset != NULL) {
+ addedrdataset = asigrdataset;
+ result = dns_db_addrdataset(
+ fctx->cache, node, NULL, now,
+ sigrdataset, options,
+ addedrdataset);
+ if (result == DNS_R_UNCHANGED) {
+ result = ISC_R_SUCCESS;
+ }
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ } else if (!ANSWER(rdataset)) {
+ continue;
+ }
+ }
+
+ if (ANSWER(rdataset) && need_validation) {
+ if (fctx->type != dns_rdatatype_any &&
+ fctx->type != dns_rdatatype_rrsig &&
+ fctx->type != dns_rdatatype_sig)
+ {
+ /*
+ * This is The Answer. We will
+ * validate it, but first we cache
+ * the rest of the response - it may
+ * contain useful keys.
+ */
+ INSIST(valrdataset == NULL &&
+ valsigrdataset == NULL);
+ valrdataset = rdataset;
+ valsigrdataset = sigrdataset;
+ } else {
+ /*
+ * This is one of (potentially)
+ * multiple answers to an ANY
+ * or SIG query. To keep things
+ * simple, we just start the
+ * validator right away rather
+ * than caching first and
+ * having to remember which
+ * rdatasets needed validation.
+ */
+ result = valcreate(
+ fctx, message, addrinfo, name,
+ rdataset->type, rdataset,
+ sigrdataset, valoptions, task);
+ }
+ } else if (CHAINING(rdataset)) {
+ if (rdataset->type == dns_rdatatype_cname) {
+ eresult = DNS_R_CNAME;
+ } else {
+ INSIST(rdataset->type ==
+ dns_rdatatype_dname);
+ eresult = DNS_R_DNAME;
+ }
+ }
+ } else if (!EXTERNAL(rdataset)) {
+ /*
+ * It's OK to cache this rdataset now.
+ */
+ if (ANSWER(rdataset)) {
+ addedrdataset = ardataset;
+ } else if (ANSWERSIG(rdataset)) {
+ addedrdataset = asigrdataset;
+ } else {
+ addedrdataset = NULL;
+ }
+ if (CHAINING(rdataset)) {
+ if (rdataset->type == dns_rdatatype_cname) {
+ eresult = DNS_R_CNAME;
+ } else {
+ INSIST(rdataset->type ==
+ dns_rdatatype_dname);
+ eresult = DNS_R_DNAME;
+ }
+ }
+ if (rdataset->trust == dns_trust_glue &&
+ (rdataset->type == dns_rdatatype_ns ||
+ (rdataset->type == dns_rdatatype_rrsig &&
+ rdataset->covers == dns_rdatatype_ns)))
+ {
+ /*
+ * If the trust level is 'dns_trust_glue'
+ * then we are adding data from a referral
+ * we got while executing the search algorithm.
+ * New referral data always takes precedence
+ * over the existing cache contents.
+ */
+ options = DNS_DBADD_FORCE;
+ } else if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0)
+ {
+ options = DNS_DBADD_PREFETCH;
+ } else {
+ options = 0;
+ }
+
+ if (ANSWER(rdataset) &&
+ rdataset->type != dns_rdatatype_rrsig)
+ {
+ isc_result_t tresult;
+ dns_name_t *noqname = NULL;
+ tresult = findnoqname(fctx, message, name,
+ rdataset->type, &noqname);
+ if (tresult == ISC_R_SUCCESS && noqname != NULL)
+ {
+ (void)dns_rdataset_addnoqname(rdataset,
+ noqname);
+ }
+ }
+
+ /*
+ * Now we can add the rdataset.
+ */
+ result = dns_db_addrdataset(fctx->cache, node, NULL,
+ now, rdataset, options,
+ addedrdataset);
+
+ if (result == DNS_R_UNCHANGED) {
+ if (ANSWER(rdataset) && ardataset != NULL &&
+ NEGATIVE(ardataset))
+ {
+ /*
+ * The answer in the cache is better
+ * than the answer we found, and is
+ * a negative cache entry, so we
+ * must set eresult appropriately.
+ */
+ if (NXDOMAIN(ardataset)) {
+ eresult = DNS_R_NCACHENXDOMAIN;
+ } else {
+ eresult = DNS_R_NCACHENXRRSET;
+ }
+ }
+ result = ISC_R_SUCCESS;
+ } else if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ }
+
+ if (valrdataset != NULL) {
+ dns_rdatatype_t vtype = fctx->type;
+ if (CHAINING(valrdataset)) {
+ if (valrdataset->type == dns_rdatatype_cname) {
+ vtype = dns_rdatatype_cname;
+ } else {
+ vtype = dns_rdatatype_dname;
+ }
+ }
+
+ result = valcreate(fctx, message, addrinfo, name, vtype,
+ valrdataset, valsigrdataset, valoptions,
+ task);
+ }
+
+ if (result == ISC_R_SUCCESS && have_answer) {
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
+ if (event != NULL) {
+ /*
+ * Negative results must be indicated in event->result.
+ */
+ if (dns_rdataset_isassociated(event->rdataset) &&
+ NEGATIVE(event->rdataset))
+ {
+ INSIST(eresult == DNS_R_NCACHENXDOMAIN ||
+ eresult == DNS_R_NCACHENXRRSET);
+ }
+ event->result = eresult;
+ if (adbp != NULL && *adbp != NULL) {
+ if (anodep != NULL && *anodep != NULL) {
+ dns_db_detachnode(*adbp, anodep);
+ }
+ dns_db_detach(adbp);
+ }
+ dns_db_attach(fctx->cache, adbp);
+ dns_db_transfernode(fctx->cache, &node, anodep);
+ clone_results(fctx);
+ }
+ }
+
+ if (node != NULL) {
+ dns_db_detachnode(fctx->cache, &node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+cache_message(fetchctx_t *fctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) {
+ isc_result_t result;
+ dns_section_t section;
+ dns_name_t *name;
+
+ FCTXTRACE("cache_message");
+
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTCACHE);
+
+ LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+ for (section = DNS_SECTION_ANSWER; section <= DNS_SECTION_ADDITIONAL;
+ section++)
+ {
+ result = dns_message_firstname(message, section);
+ while (result == ISC_R_SUCCESS) {
+ name = NULL;
+ dns_message_currentname(message, section, &name);
+ if ((name->attributes & DNS_NAMEATTR_CACHE) != 0) {
+ result = cache_name(fctx, name, message,
+ addrinfo, now);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ result = dns_message_nextname(message, section);
+ }
+ if (result != ISC_R_NOMORE) {
+ break;
+ }
+ }
+ if (result == ISC_R_NOMORE) {
+ result = ISC_R_SUCCESS;
+ }
+
+ UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
+
+ return (result);
+}
+
+/*
+ * Do what dns_ncache_addoptout() does, and then compute an appropriate eresult.
+ */
+static isc_result_t
+ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
+ dns_ttl_t maxttl, bool optout, bool secure,
+ dns_rdataset_t *ardataset, isc_result_t *eresultp) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+
+ if (ardataset == NULL) {
+ dns_rdataset_init(&rdataset);
+ ardataset = &rdataset;
+ }
+ if (secure) {
+ result = dns_ncache_addoptout(message, cache, node, covers, now,
+ minttl, maxttl, optout,
+ ardataset);
+ } else {
+ result = dns_ncache_add(message, cache, node, covers, now,
+ minttl, maxttl, ardataset);
+ }
+ if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) {
+ /*
+ * If the cache now contains a negative entry and we
+ * care about whether it is DNS_R_NCACHENXDOMAIN or
+ * DNS_R_NCACHENXRRSET then extract it.
+ */
+ if (NEGATIVE(ardataset)) {
+ /*
+ * The cache data is a negative cache entry.
+ */
+ if (NXDOMAIN(ardataset)) {
+ *eresultp = DNS_R_NCACHENXDOMAIN;
+ } else {
+ *eresultp = DNS_R_NCACHENXRRSET;
+ }
+ } else {
+ /*
+ * Either we don't care about the nature of the
+ * cache rdataset (because no fetch is interested
+ * in the outcome), or the cache rdataset is not
+ * a negative cache entry. Whichever case it is,
+ * we can return success.
+ *
+ * XXXRTH There's a CNAME/DNAME problem here.
+ */
+ *eresultp = ISC_R_SUCCESS;
+ }
+ result = ISC_R_SUCCESS;
+ }
+ if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset)) {
+ dns_rdataset_disassociate(ardataset);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+ncache_message(fetchctx_t *fctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, dns_rdatatype_t covers,
+ isc_stdtime_t now) {
+ isc_result_t result, eresult;
+ dns_name_t *name;
+ dns_resolver_t *res;
+ dns_db_t **adbp;
+ dns_dbnode_t *node, **anodep;
+ dns_rdataset_t *ardataset;
+ bool need_validation, secure_domain;
+ dns_name_t *aname;
+ dns_fetchevent_t *event;
+ uint32_t ttl;
+ unsigned int valoptions = 0;
+ bool checknta = true;
+
+ FCTXTRACE("ncache_message");
+
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTNCACHE);
+
+ res = fctx->res;
+ need_validation = false;
+ POST(need_validation);
+ secure_domain = false;
+ eresult = ISC_R_SUCCESS;
+ name = &fctx->name;
+ node = NULL;
+
+ /*
+ * XXXMPA remove when we follow cnames and adjust the setting
+ * of FCTX_ATTR_WANTNCACHE in rctx_answer_none().
+ */
+ INSIST(message->counts[DNS_SECTION_ANSWER] == 0);
+
+ /*
+ * Is DNSSEC validation required for this name?
+ */
+ if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
+ valoptions |= DNS_VALIDATOR_NONTA;
+ checknta = false;
+ }
+
+ if (fctx->res->view->enablevalidation) {
+ result = issecuredomain(res->view, name, fctx->type, now,
+ checknta, NULL, &secure_domain);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
+ valoptions |= DNS_VALIDATOR_NOCDFLAG;
+ }
+
+ if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
+ need_validation = false;
+ } else {
+ need_validation = secure_domain;
+ }
+
+ if (secure_domain) {
+ /*
+ * Mark all rdatasets as pending.
+ */
+ dns_rdataset_t *trdataset;
+ dns_name_t *tname;
+
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ while (result == ISC_R_SUCCESS) {
+ tname = NULL;
+ dns_message_currentname(message, DNS_SECTION_AUTHORITY,
+ &tname);
+ for (trdataset = ISC_LIST_HEAD(tname->list);
+ trdataset != NULL;
+ trdataset = ISC_LIST_NEXT(trdataset, link))
+ {
+ trdataset->trust = dns_trust_pending_answer;
+ }
+ result = dns_message_nextname(message,
+ DNS_SECTION_AUTHORITY);
+ }
+ if (result != ISC_R_NOMORE) {
+ return (result);
+ }
+ }
+
+ if (need_validation) {
+ /*
+ * Do negative response validation.
+ */
+ result = valcreate(fctx, message, addrinfo, name, fctx->type,
+ NULL, NULL, valoptions,
+ res->buckets[fctx->bucketnum].task);
+ /*
+ * If validation is necessary, return now. Otherwise continue
+ * to process the message, letting the validation complete
+ * in its own good time.
+ */
+ return (result);
+ }
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+
+ adbp = NULL;
+ aname = NULL;
+ anodep = NULL;
+ ardataset = NULL;
+ if (!HAVE_ANSWER(fctx)) {
+ event = ISC_LIST_HEAD(fctx->events);
+ if (event != NULL) {
+ adbp = &event->db;
+ aname = dns_fixedname_name(&event->foundname);
+ dns_name_copynf(name, aname);
+ anodep = &event->node;
+ ardataset = event->rdataset;
+ }
+ } else {
+ event = NULL;
+ }
+
+ result = dns_db_findnode(fctx->cache, name, true, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+
+ /*
+ * If we are asking for a SOA record set the cache time
+ * to zero to facilitate locating the containing zone of
+ * a arbitrary zone.
+ */
+ ttl = fctx->res->view->maxncachettl;
+ if (fctx->type == dns_rdatatype_soa && covers == dns_rdatatype_any &&
+ fctx->res->zero_no_soa_ttl)
+ {
+ ttl = 0;
+ }
+
+ result = ncache_adderesult(message, fctx->cache, node, covers, now,
+ fctx->res->view->minncachettl, ttl, false,
+ false, ardataset, &eresult);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+
+ if (!HAVE_ANSWER(fctx)) {
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
+ if (event != NULL) {
+ event->result = eresult;
+ if (adbp != NULL && *adbp != NULL) {
+ if (anodep != NULL && *anodep != NULL) {
+ dns_db_detachnode(*adbp, anodep);
+ }
+ dns_db_detach(adbp);
+ }
+ dns_db_attach(fctx->cache, adbp);
+ dns_db_transfernode(fctx->cache, &node, anodep);
+ clone_results(fctx);
+ }
+ }
+
+unlock:
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+
+ if (node != NULL) {
+ dns_db_detachnode(fctx->cache, &node);
+ }
+
+ return (result);
+}
+
+static void
+mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external,
+ bool gluing) {
+ name->attributes |= DNS_NAMEATTR_CACHE;
+ if (gluing) {
+ rdataset->trust = dns_trust_glue;
+ /*
+ * Glue with 0 TTL causes problems. We force the TTL to
+ * 1 second to prevent this.
+ */
+ if (rdataset->ttl == 0) {
+ rdataset->ttl = 1;
+ }
+ } else {
+ rdataset->trust = dns_trust_additional;
+ }
+ /*
+ * Avoid infinite loops by only marking new rdatasets.
+ */
+ if (!CACHE(rdataset)) {
+ name->attributes |= DNS_NAMEATTR_CHASE;
+ rdataset->attributes |= DNS_RDATASETATTR_CHASE;
+ }
+ rdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ if (external) {
+ rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL;
+ }
+}
+
+/*
+ * Returns true if 'name' is external to the namespace for which
+ * the server being queried can answer, either because it's not a
+ * subdomain or because it's below a forward declaration or a
+ * locally served zone.
+ */
+static bool
+name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) {
+ isc_result_t result;
+ dns_forwarders_t *forwarders = NULL;
+ dns_fixedname_t fixed, zfixed;
+ dns_name_t *fname = dns_fixedname_initname(&fixed);
+ dns_name_t *zfname = dns_fixedname_initname(&zfixed);
+ dns_name_t *apex = NULL;
+ dns_name_t suffix;
+ dns_zone_t *zone = NULL;
+ unsigned int labels;
+ dns_namereln_t rel;
+
+ apex = (ISDUALSTACK(fctx->addrinfo) || !ISFORWARDER(fctx->addrinfo))
+ ? &fctx->domain
+ : fctx->fwdname;
+
+ /*
+ * The name is outside the queried namespace.
+ */
+ rel = dns_name_fullcompare(name, apex, &(int){ 0 },
+ &(unsigned int){ 0U });
+ if (rel != dns_namereln_subdomain && rel != dns_namereln_equal) {
+ return (true);
+ }
+
+ /*
+ * If the record lives in the parent zone, adjust the name so we
+ * look for the correct zone or forward clause.
+ */
+ labels = dns_name_countlabels(name);
+ if (dns_rdatatype_atparent(type) && labels > 1U) {
+ dns_name_init(&suffix, NULL);
+ dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
+ name = &suffix;
+ } else if (rel == dns_namereln_equal) {
+ /* If 'name' is 'apex', no further checking is needed. */
+ return (false);
+ }
+
+ /*
+ * If there is a locally served zone between 'apex' and 'name'
+ * then don't cache.
+ */
+ LOCK(&fctx->res->view->lock);
+ if (fctx->res->view->zonetable != NULL) {
+ unsigned int options = DNS_ZTFIND_NOEXACT | DNS_ZTFIND_MIRROR;
+ result = dns_zt_find(fctx->res->view->zonetable, name, options,
+ zfname, &zone);
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ if (dns_name_fullcompare(zfname, apex, &(int){ 0 },
+ &(unsigned int){ 0U }) ==
+ dns_namereln_subdomain)
+ {
+ UNLOCK(&fctx->res->view->lock);
+ return (true);
+ }
+ }
+ }
+ UNLOCK(&fctx->res->view->lock);
+
+ /*
+ * Look for a forward declaration below 'name'.
+ */
+ result = dns_fwdtable_find(fctx->res->view->fwdtable, name, fname,
+ &forwarders);
+
+ if (ISFORWARDER(fctx->addrinfo)) {
+ /*
+ * See if the forwarder declaration is better.
+ */
+ if (result == ISC_R_SUCCESS) {
+ return (!dns_name_equal(fname, fctx->fwdname));
+ }
+
+ /*
+ * If the lookup failed, the configuration must have
+ * changed: play it safe and don't cache.
+ */
+ return (true);
+ } else if (result == ISC_R_SUCCESS &&
+ forwarders->fwdpolicy == dns_fwdpolicy_only &&
+ !ISC_LIST_EMPTY(forwarders->fwdrs))
+ {
+ /*
+ * If 'name' is covered by a 'forward only' clause then we
+ * can't cache this repsonse.
+ */
+ return (true);
+ }
+
+ return (false);
+}
+
+static isc_result_t
+check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
+ dns_section_t section) {
+ respctx_t *rctx = arg;
+ fetchctx_t *fctx = rctx->fctx;
+ isc_result_t result;
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset = NULL;
+ bool external;
+ dns_rdatatype_t rtype;
+ bool gluing;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+#if CHECK_FOR_GLUE_IN_ANSWER
+ if (section == DNS_SECTION_ANSWER && type != dns_rdatatype_a) {
+ return (ISC_R_SUCCESS);
+ }
+#endif /* if CHECK_FOR_GLUE_IN_ANSWER */
+
+ gluing = (GLUING(fctx) || (fctx->type == dns_rdatatype_ns &&
+ dns_name_equal(&fctx->name, dns_rootname)));
+
+ result = dns_message_findname(rctx->query->rmessage, section, addname,
+ dns_rdatatype_any, 0, &name, NULL);
+ if (result == ISC_R_SUCCESS) {
+ external = name_external(name, type, fctx);
+ if (type == dns_rdatatype_a) {
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == dns_rdatatype_rrsig) {
+ rtype = rdataset->covers;
+ } else {
+ rtype = rdataset->type;
+ }
+ if (rtype == dns_rdatatype_a ||
+ rtype == dns_rdatatype_aaaa)
+ {
+ mark_related(name, rdataset, external,
+ gluing);
+ }
+ }
+ } else {
+ result = dns_message_findtype(name, type, 0, &rdataset);
+ if (result == ISC_R_SUCCESS) {
+ mark_related(name, rdataset, external, gluing);
+ /*
+ * Do we have its SIG too?
+ */
+ rdataset = NULL;
+ result = dns_message_findtype(
+ name, dns_rdatatype_rrsig, type,
+ &rdataset);
+ if (result == ISC_R_SUCCESS) {
+ mark_related(name, rdataset, external,
+ gluing);
+ }
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+check_related(void *arg, const dns_name_t *addname, dns_rdatatype_t type) {
+ return (check_section(arg, addname, type, DNS_SECTION_ADDITIONAL));
+}
+
+#ifndef CHECK_FOR_GLUE_IN_ANSWER
+#define CHECK_FOR_GLUE_IN_ANSWER 0
+#endif /* ifndef CHECK_FOR_GLUE_IN_ANSWER */
+
+#if CHECK_FOR_GLUE_IN_ANSWER
+static isc_result_t
+check_answer(void *arg, const dns_name_t *addname, dns_rdatatype_t type) {
+ return (check_section(arg, addname, type, DNS_SECTION_ANSWER));
+}
+#endif /* if CHECK_FOR_GLUE_IN_ANSWER */
+
+static bool
+is_answeraddress_allowed(dns_view_t *view, dns_name_t *name,
+ dns_rdataset_t *rdataset) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ struct in_addr ina;
+ struct in6_addr in6a;
+ isc_netaddr_t netaddr;
+ char addrbuf[ISC_NETADDR_FORMATSIZE];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[64];
+ char typebuf[64];
+ int match;
+
+ /* By default, we allow any addresses. */
+ if (view->denyansweracl == NULL) {
+ return (true);
+ }
+
+ /*
+ * If the owner name matches one in the exclusion list, either exactly
+ * or partially, allow it.
+ */
+ if (view->answeracl_exclude != NULL) {
+ dns_rbtnode_t *node = NULL;
+
+ result = dns_rbt_findnode(view->answeracl_exclude, name, NULL,
+ &node, NULL, 0, NULL, NULL);
+
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ return (true);
+ }
+ }
+
+ /*
+ * Otherwise, search the filter list for a match for each address
+ * record. If a match is found, the address should be filtered,
+ * so should the entire answer.
+ */
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(rdataset, &rdata);
+ if (rdataset->type == dns_rdatatype_a) {
+ INSIST(rdata.length == sizeof(ina.s_addr));
+ memmove(&ina.s_addr, rdata.data, sizeof(ina.s_addr));
+ isc_netaddr_fromin(&netaddr, &ina);
+ } else {
+ INSIST(rdata.length == sizeof(in6a.s6_addr));
+ memmove(in6a.s6_addr, rdata.data, sizeof(in6a.s6_addr));
+ isc_netaddr_fromin6(&netaddr, &in6a);
+ }
+
+ result = dns_acl_match(&netaddr, NULL, view->denyansweracl,
+ &view->aclenv, &match, NULL);
+ if (result == ISC_R_SUCCESS && match > 0) {
+ isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(rdataset->type, typebuf,
+ sizeof(typebuf));
+ dns_rdataclass_format(rdataset->rdclass, classbuf,
+ sizeof(classbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "answer address %s denied for %s/%s/%s",
+ addrbuf, namebuf, typebuf, classbuf);
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+static bool
+is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname,
+ dns_rdataset_t *rdataset, bool *chainingp) {
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+ char qnamebuf[DNS_NAME_FORMATSIZE];
+ char tnamebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[64];
+ char typebuf[64];
+ dns_name_t *tname = NULL;
+ dns_rdata_cname_t cname;
+ dns_rdata_dname_t dname;
+ dns_view_t *view = fctx->res->view;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned int nlabels;
+ dns_fixedname_t fixed;
+ dns_name_t prefix;
+ int order;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->type == dns_rdatatype_cname ||
+ rdataset->type == dns_rdatatype_dname);
+
+ /*
+ * By default, we allow any target name.
+ * If newqname != NULL we also need to extract the newqname.
+ */
+ if (chainingp == NULL && view->denyanswernames == NULL) {
+ return (true);
+ }
+
+ result = dns_rdataset_first(rdataset);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_current(rdataset, &rdata);
+ switch (rdataset->type) {
+ case dns_rdatatype_cname:
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ tname = &cname.cname;
+ break;
+ case dns_rdatatype_dname:
+ if (dns_name_fullcompare(qname, rname, &order, &nlabels) !=
+ dns_namereln_subdomain)
+ {
+ return (true);
+ }
+ result = dns_rdata_tostruct(&rdata, &dname, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_name_init(&prefix, NULL);
+ tname = dns_fixedname_initname(&fixed);
+ nlabels = dns_name_countlabels(rname);
+ dns_name_split(qname, nlabels, &prefix, NULL);
+ result = dns_name_concatenate(&prefix, &dname.dname, tname,
+ NULL);
+ if (result == DNS_R_NAMETOOLONG) {
+ if (chainingp != NULL) {
+ *chainingp = true;
+ }
+ return (true);
+ }
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (chainingp != NULL) {
+ *chainingp = true;
+ }
+
+ if (view->denyanswernames == NULL) {
+ return (true);
+ }
+
+ /*
+ * If the owner name matches one in the exclusion list, either exactly
+ * or partially, allow it.
+ */
+ if (view->answernames_exclude != NULL) {
+ result = dns_rbt_findnode(view->answernames_exclude, qname,
+ NULL, &node, NULL, 0, NULL, NULL);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ return (true);
+ }
+ }
+
+ /*
+ * If the target name is a subdomain of the search domain, allow it.
+ *
+ * Note that if BIND is configured as a forwarding DNS server, the
+ * search domain will always match the root domain ("."), so we
+ * must also check whether forwarding is enabled so that filters
+ * can be applied; see GL #1574.
+ */
+ if (!fctx->forwarding && dns_name_issubdomain(tname, &fctx->domain)) {
+ return (true);
+ }
+
+ /*
+ * Otherwise, apply filters.
+ */
+ result = dns_rbt_findnode(view->denyanswernames, tname, NULL, &node,
+ NULL, 0, NULL, NULL);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ dns_name_format(qname, qnamebuf, sizeof(qnamebuf));
+ dns_name_format(tname, tnamebuf, sizeof(tnamebuf));
+ dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
+ dns_rdataclass_format(view->rdclass, classbuf,
+ sizeof(classbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "%s target %s denied for %s/%s", typebuf,
+ tnamebuf, qnamebuf, classbuf);
+ return (false);
+ }
+
+ return (true);
+}
+
+static void
+trim_ns_ttl(fetchctx_t *fctx, dns_name_t *name, dns_rdataset_t *rdataset) {
+ char ns_namebuf[DNS_NAME_FORMATSIZE];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char tbuf[DNS_RDATATYPE_FORMATSIZE];
+
+ if (fctx->ns_ttl_ok && rdataset->ttl > fctx->ns_ttl) {
+ dns_name_format(name, ns_namebuf, sizeof(ns_namebuf));
+ dns_name_format(&fctx->name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(fctx->type, tbuf, sizeof(tbuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10),
+ "fctx %p: trimming ttl of %s/NS for %s/%s: "
+ "%u -> %u",
+ fctx, ns_namebuf, namebuf, tbuf, rdataset->ttl,
+ fctx->ns_ttl);
+ rdataset->ttl = fctx->ns_ttl;
+ }
+}
+
+static bool
+validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) {
+ if (rdataset->type == dns_rdatatype_nsec3) {
+ /*
+ * NSEC3 records are not allowed to
+ * appear in the answer section.
+ */
+ log_formerr(fctx, "NSEC3 in answer");
+ return (false);
+ }
+ if (rdataset->type == dns_rdatatype_tkey) {
+ /*
+ * TKEY is not a valid record in a
+ * response to any query we can make.
+ */
+ log_formerr(fctx, "TKEY in answer");
+ return (false);
+ }
+ if (rdataset->rdclass != fctx->res->rdclass) {
+ log_formerr(fctx, "Mismatched class in answer");
+ return (false);
+ }
+ return (true);
+}
+
+static void
+fctx_increference(fetchctx_t *fctx) {
+ REQUIRE(VALID_FCTX(fctx));
+
+ isc_refcount_increment0(&fctx->references);
+}
+
+/*
+ * Requires bucket lock to be held.
+ */
+static bool
+fctx_decreference(fetchctx_t *fctx) {
+ bool bucket_empty = false;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+ if (isc_refcount_decrement(&fctx->references) == 1) {
+ /*
+ * No one cares about the result of this fetch anymore.
+ */
+ if (fctx->pending == 0 && fctx->nqueries == 0 &&
+ ISC_LIST_EMPTY(fctx->validators) && SHUTTINGDOWN(fctx))
+ {
+ /*
+ * This fctx is already shutdown; we were just
+ * waiting for the last reference to go away.
+ */
+ bucket_empty = fctx_unlink(fctx);
+ fctx_destroy(fctx);
+ } else {
+ /*
+ * Initiate shutdown.
+ */
+ fctx_shutdown(fctx);
+ }
+ }
+ return (bucket_empty);
+}
+
+static void
+resume_dslookup(isc_task_t *task, isc_event_t *event) {
+ dns_fetchevent_t *fevent;
+ dns_resolver_t *res;
+ fetchctx_t *fctx;
+ isc_result_t result;
+ uint32_t bucketnum;
+ bool bucket_empty;
+ dns_rdataset_t nameservers;
+ dns_fixedname_t fixed;
+ dns_name_t *domain;
+
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+ fevent = (dns_fetchevent_t *)event;
+ fctx = event->ev_arg;
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+ bucketnum = fctx->bucketnum;
+
+ UNUSED(task);
+ FCTXTRACE("resume_dslookup");
+
+ if (fevent->node != NULL) {
+ dns_db_detachnode(fevent->db, &fevent->node);
+ }
+ if (fevent->db != NULL) {
+ dns_db_detach(&fevent->db);
+ }
+
+ dns_rdataset_init(&nameservers);
+
+ /*
+ * Note: fevent->rdataset must be disassociated and
+ * isc_event_free(&event) be called before resuming
+ * processing of the 'fctx' to prevent use-after-free.
+ * 'fevent' is set to NULL so as to not have a dangling
+ * pointer.
+ */
+ if (fevent->result == ISC_R_CANCELED) {
+ if (dns_rdataset_isassociated(fevent->rdataset)) {
+ dns_rdataset_disassociate(fevent->rdataset);
+ }
+ fevent = NULL;
+ isc_event_free(&event);
+
+ dns_resolver_destroyfetch(&fctx->nsfetch);
+ fctx_done(fctx, ISC_R_CANCELED, __LINE__);
+ } else if (fevent->result == ISC_R_SUCCESS) {
+ FCTXTRACE("resuming DS lookup");
+
+ dns_resolver_destroyfetch(&fctx->nsfetch);
+ if (dns_rdataset_isassociated(&fctx->nameservers)) {
+ dns_rdataset_disassociate(&fctx->nameservers);
+ }
+ dns_rdataset_clone(fevent->rdataset, &fctx->nameservers);
+ fctx->ns_ttl = fctx->nameservers.ttl;
+ fctx->ns_ttl_ok = true;
+ log_ns_ttl(fctx, "resume_dslookup");
+
+ if (dns_rdataset_isassociated(fevent->rdataset)) {
+ dns_rdataset_disassociate(fevent->rdataset);
+ }
+ fevent = NULL;
+ isc_event_free(&event);
+
+ fcount_decr(fctx);
+ dns_name_free(&fctx->domain, fctx->mctx);
+ dns_name_init(&fctx->domain, NULL);
+ dns_name_dup(&fctx->nsname, fctx->mctx, &fctx->domain);
+ result = fcount_incr(fctx, true);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ goto cleanup;
+ }
+ /*
+ * Try again.
+ */
+ fctx_try(fctx, true, false);
+ } else {
+ unsigned int n;
+ dns_rdataset_t *nsrdataset = NULL;
+
+ /*
+ * Retrieve state from fctx->nsfetch before we destroy it.
+ */
+ domain = dns_fixedname_initname(&fixed);
+ dns_name_copynf(&fctx->nsfetch->private->domain, domain);
+ if (dns_name_equal(&fctx->nsname, domain)) {
+ if (dns_rdataset_isassociated(fevent->rdataset)) {
+ dns_rdataset_disassociate(fevent->rdataset);
+ }
+ fevent = NULL;
+ isc_event_free(&event);
+
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ dns_resolver_destroyfetch(&fctx->nsfetch);
+ goto cleanup;
+ }
+ if (dns_rdataset_isassociated(
+ &fctx->nsfetch->private->nameservers))
+ {
+ dns_rdataset_clone(&fctx->nsfetch->private->nameservers,
+ &nameservers);
+ nsrdataset = &nameservers;
+ } else {
+ domain = NULL;
+ }
+ dns_resolver_destroyfetch(&fctx->nsfetch);
+ n = dns_name_countlabels(&fctx->nsname);
+ dns_name_getlabelsequence(&fctx->nsname, 1, n - 1,
+ &fctx->nsname);
+
+ if (dns_rdataset_isassociated(fevent->rdataset)) {
+ dns_rdataset_disassociate(fevent->rdataset);
+ }
+ fevent = NULL;
+ isc_event_free(&event);
+
+ FCTXTRACE("continuing to look for parent's NS records");
+
+ result = dns_resolver_createfetch(
+ fctx->res, &fctx->nsname, dns_rdatatype_ns, domain,
+ nsrdataset, NULL, NULL, 0, fctx->options, 0, NULL, task,
+ resume_dslookup, fctx, &fctx->nsrrset, NULL,
+ &fctx->nsfetch);
+ /*
+ * fevent->rdataset (a.k.a. fctx->nsrrset) must not be
+ * accessed below this point to prevent races with
+ * another thread concurrently processing the fetch.
+ */
+ if (result != ISC_R_SUCCESS) {
+ if (result == DNS_R_DUPLICATE) {
+ result = DNS_R_SERVFAIL;
+ }
+ fctx_done(fctx, result, __LINE__);
+ } else {
+ fctx_increference(fctx);
+ }
+ }
+
+cleanup:
+ INSIST(event == NULL);
+ INSIST(fevent == NULL);
+ if (dns_rdataset_isassociated(&nameservers)) {
+ dns_rdataset_disassociate(&nameservers);
+ }
+ LOCK(&res->buckets[bucketnum].lock);
+ bucket_empty = fctx_decreference(fctx);
+ UNLOCK(&res->buckets[bucketnum].lock);
+ if (bucket_empty) {
+ empty_bucket(res);
+ }
+}
+
+static void
+checknamessection(dns_message_t *message, dns_section_t section) {
+ isc_result_t result;
+ dns_name_t *name;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t *rdataset;
+
+ for (result = dns_message_firstname(message, section);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(message, section))
+ {
+ name = NULL;
+ dns_message_currentname(message, section, &name);
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ for (result = dns_rdataset_first(rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdataset_current(rdataset, &rdata);
+ if (!dns_rdata_checkowner(name, rdata.rdclass,
+ rdata.type, false) ||
+ !dns_rdata_checknames(&rdata, name, NULL))
+ {
+ rdataset->attributes |=
+ DNS_RDATASETATTR_CHECKNAMES;
+ }
+ dns_rdata_reset(&rdata);
+ }
+ }
+ }
+}
+
+static void
+checknames(dns_message_t *message) {
+ checknamessection(message, DNS_SECTION_ANSWER);
+ checknamessection(message, DNS_SECTION_AUTHORITY);
+ checknamessection(message, DNS_SECTION_ADDITIONAL);
+}
+
+/*
+ * Log server NSID at log level 'level'
+ */
+static void
+log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, int level,
+ isc_mem_t *mctx) {
+ static const char hex[17] = "0123456789abcdef";
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ size_t buflen;
+ unsigned char *p, *nsid;
+ unsigned char *buf = NULL, *pbuf = NULL;
+
+ REQUIRE(nsid_len <= UINT16_MAX);
+
+ /* Allocate buffer for storing hex version of the NSID */
+ buflen = nsid_len * 2 + 1;
+ buf = isc_mem_get(mctx, buflen);
+ pbuf = isc_mem_get(mctx, nsid_len + 1);
+
+ /* Convert to hex */
+ p = buf;
+ nsid = isc_buffer_current(opt);
+ for (size_t i = 0; i < nsid_len; i++) {
+ *p++ = hex[(nsid[i] >> 4) & 0xf];
+ *p++ = hex[nsid[i] & 0xf];
+ }
+ *p = '\0';
+
+ /* Make printable version */
+ p = pbuf;
+ for (size_t i = 0; i < nsid_len; i++) {
+ *p++ = isprint(nsid[i]) ? nsid[i] : '.';
+ }
+ *p = '\0';
+
+ isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
+ sizeof(addrbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_NSID, DNS_LOGMODULE_RESOLVER,
+ level, "received NSID %s (\"%s\") from %s", buf, pbuf,
+ addrbuf);
+
+ isc_mem_put(mctx, pbuf, nsid_len + 1);
+ isc_mem_put(mctx, buf, buflen);
+}
+
+static bool
+iscname(dns_message_t *message, dns_name_t *name) {
+ isc_result_t result;
+
+ result = dns_message_findname(message, DNS_SECTION_ANSWER, name,
+ dns_rdatatype_cname, 0, NULL, NULL);
+ return (result == ISC_R_SUCCESS ? true : false);
+}
+
+static bool
+betterreferral(respctx_t *rctx) {
+ isc_result_t result;
+ dns_name_t *name;
+ dns_rdataset_t *rdataset;
+
+ for (result = dns_message_firstname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY))
+ {
+ name = NULL;
+ dns_message_currentname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY, &name);
+ if (!isstrictsubdomain(name, &rctx->fctx->domain)) {
+ continue;
+ }
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == dns_rdatatype_ns) {
+ return (true);
+ }
+ }
+ }
+ return (false);
+}
+
+/*
+ * resquery_response():
+ * Handles responses received in response to iterative queries sent by
+ * resquery_send(). Sets up a response context (respctx_t).
+ */
+static void
+resquery_response(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result = ISC_R_SUCCESS;
+ resquery_t *query = event->ev_arg;
+ dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event;
+ fetchctx_t *fctx;
+ respctx_t rctx;
+
+ REQUIRE(VALID_QUERY(query));
+ fctx = query->fctx;
+ REQUIRE(VALID_FCTX(fctx));
+ REQUIRE(event->ev_type == DNS_EVENT_DISPATCH);
+
+ QTRACE("response");
+
+ if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET) {
+ inc_stats(fctx->res, dns_resstatscounter_responsev4);
+ } else {
+ inc_stats(fctx->res, dns_resstatscounter_responsev6);
+ }
+
+ (void)isc_timer_touch(fctx->timer);
+
+ rctx_respinit(task, devent, query, fctx, &rctx);
+
+ if (atomic_load_acquire(&fctx->res->exiting)) {
+ result = ISC_R_SHUTTINGDOWN;
+ FCTXTRACE("resolver shutting down");
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ fctx->timeouts = 0;
+ fctx->timeout = false;
+ fctx->addrinfo = query->addrinfo;
+
+ /*
+ * Check whether the dispatcher has failed; if so we're done
+ */
+ result = rctx_dispfail(&rctx);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+
+ if (query->tsig != NULL) {
+ result = dns_message_setquerytsig(query->rmessage, query->tsig);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("unable to set query tsig", result);
+ rctx_done(&rctx, result);
+ return;
+ }
+ }
+
+ if (query->tsigkey) {
+ result = dns_message_settsigkey(query->rmessage,
+ query->tsigkey);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("unable to set tsig key", result);
+ rctx_done(&rctx, result);
+ return;
+ }
+ }
+
+ dns_message_setclass(query->rmessage, fctx->res->rdclass);
+
+ if ((rctx.retryopts & DNS_FETCHOPT_TCP) == 0) {
+ if ((rctx.retryopts & DNS_FETCHOPT_NOEDNS0) == 0) {
+ dns_adb_setudpsize(
+ fctx->adb, query->addrinfo,
+ isc_buffer_usedlength(&devent->buffer));
+ } else {
+ dns_adb_plainresponse(fctx->adb, query->addrinfo);
+ }
+ }
+
+ /*
+ * Parse response message.
+ */
+ result = rctx_parse(&rctx);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+
+ /*
+ * Log the incoming packet.
+ */
+ rctx_logpacket(&rctx);
+
+ if (query->rmessage->rdclass != fctx->res->rdclass) {
+ rctx.resend = true;
+ FCTXTRACE("bad class");
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * Process receive opt record.
+ */
+ rctx.opt = dns_message_getopt(query->rmessage);
+ if (rctx.opt != NULL) {
+ rctx_opt(&rctx);
+ }
+
+ if (query->rmessage->cc_bad && (rctx.retryopts & DNS_FETCHOPT_TCP) == 0)
+ {
+ /*
+ * If the COOKIE is bad, assume it is an attack and
+ * keep listening for a good answer.
+ */
+ rctx.nextitem = true;
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) {
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
+ sizeof(addrbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "bad cookie from %s", addrbuf);
+ }
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * Is the question the same as the one we asked?
+ * NOERROR/NXDOMAIN/YXDOMAIN/REFUSED/SERVFAIL/BADCOOKIE must have
+ * the same question.
+ * FORMERR/NOTIMP if they have a question section then it must match.
+ */
+ switch (query->rmessage->rcode) {
+ case dns_rcode_notimp:
+ case dns_rcode_formerr:
+ if (query->rmessage->counts[DNS_SECTION_QUESTION] == 0) {
+ break;
+ }
+ FALLTHROUGH;
+ case dns_rcode_nxrrset: /* Not expected. */
+ case dns_rcode_badcookie:
+ case dns_rcode_noerror:
+ case dns_rcode_nxdomain:
+ case dns_rcode_yxdomain:
+ case dns_rcode_refused:
+ case dns_rcode_servfail:
+ default:
+ result = same_question(fctx, query->rmessage);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("response did not match question", result);
+ rctx.nextitem = true;
+ rctx_done(&rctx, result);
+ return;
+ }
+ break;
+ }
+
+ /*
+ * If the message is signed, check the signature. If not, this
+ * returns success anyway.
+ */
+ result = dns_message_checksig(query->rmessage, fctx->res->view);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("signature check failed", result);
+ if (result == DNS_R_UNEXPECTEDTSIG ||
+ result == DNS_R_EXPECTEDTSIG)
+ {
+ rctx.nextitem = true;
+ }
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * The dispatcher should ensure we only get responses with QR set.
+ */
+ INSIST((query->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0);
+ /*
+ * INSIST() that the message comes from the place we sent it to,
+ * since the dispatch code should ensure this.
+ *
+ * INSIST() that the message id is correct (this should also be
+ * ensured by the dispatch code).
+ */
+
+ /*
+ * If we have had a server cookie and don't get one retry over TCP.
+ * This may be a misconfigured anycast server or an attempt to send
+ * a spoofed response. Skip if we have a valid tsig.
+ */
+ if (dns_message_gettsig(query->rmessage, NULL) == NULL &&
+ !query->rmessage->cc_ok && !query->rmessage->cc_bad &&
+ (rctx.retryopts & DNS_FETCHOPT_TCP) == 0)
+ {
+ unsigned char cookie[COOKIE_BUFFER_SIZE];
+ if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie,
+ sizeof(cookie)) > CLIENT_COOKIE_SIZE)
+ {
+ if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) {
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_format(&query->addrinfo->sockaddr,
+ addrbuf, sizeof(addrbuf));
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "missing expected cookie from %s",
+ addrbuf);
+ }
+ rctx.retryopts |= DNS_FETCHOPT_TCP;
+ rctx.resend = true;
+ rctx_done(&rctx, result);
+ return;
+ }
+ /*
+ * XXXMPA When support for DNS COOKIE becomes ubiquitous, fall
+ * back to TCP for all non-COOKIE responses.
+ */
+ }
+
+ rctx_edns(&rctx);
+
+ /*
+ * Deal with truncated responses by retrying using TCP.
+ */
+ if ((query->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ rctx.truncated = true;
+ }
+
+ if (rctx.truncated) {
+ inc_stats(fctx->res, dns_resstatscounter_truncated);
+ if ((rctx.retryopts & DNS_FETCHOPT_TCP) != 0) {
+ rctx.broken_server = DNS_R_TRUNCATEDTCP;
+ rctx.next_server = true;
+ } else {
+ rctx.retryopts |= DNS_FETCHOPT_TCP;
+ rctx.resend = true;
+ }
+ FCTXTRACE3("message truncated", result);
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * Is it a query response?
+ */
+ if (query->rmessage->opcode != dns_opcode_query) {
+ rctx.broken_server = DNS_R_UNEXPECTEDOPCODE;
+ rctx.next_server = true;
+ FCTXTRACE("invalid message opcode");
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * Update statistics about erroneous responses.
+ */
+ switch (query->rmessage->rcode) {
+ case dns_rcode_noerror:
+ /* no error */
+ break;
+ case dns_rcode_nxdomain:
+ inc_stats(fctx->res, dns_resstatscounter_nxdomain);
+ break;
+ case dns_rcode_servfail:
+ inc_stats(fctx->res, dns_resstatscounter_servfail);
+ break;
+ case dns_rcode_formerr:
+ inc_stats(fctx->res, dns_resstatscounter_formerr);
+ break;
+ case dns_rcode_refused:
+ inc_stats(fctx->res, dns_resstatscounter_refused);
+ break;
+ case dns_rcode_badvers:
+ inc_stats(fctx->res, dns_resstatscounter_badvers);
+ break;
+ case dns_rcode_badcookie:
+ inc_stats(fctx->res, dns_resstatscounter_badcookie);
+ break;
+ default:
+ inc_stats(fctx->res, dns_resstatscounter_othererror);
+ break;
+ }
+
+ /*
+ * Bad server?
+ */
+ result = rctx_badserver(&rctx, result);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+
+ /*
+ * Lame server?
+ */
+ result = rctx_lameserver(&rctx);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+
+ /*
+ * Handle delegation-only zones like NET or COM.
+ */
+ rctx_delonly_zone(&rctx);
+
+ /*
+ * Optionally call dns_rdata_checkowner() and dns_rdata_checknames()
+ * to validate the names in the response message.
+ */
+ if ((fctx->res->options & DNS_RESOLVER_CHECKNAMES) != 0) {
+ checknames(query->rmessage);
+ }
+
+ /*
+ * Clear cache bits.
+ */
+ FCTX_ATTR_CLR(fctx, (FCTX_ATTR_WANTNCACHE | FCTX_ATTR_WANTCACHE));
+
+ /*
+ * Did we get any answers?
+ */
+ if (query->rmessage->counts[DNS_SECTION_ANSWER] > 0 &&
+ (query->rmessage->rcode == dns_rcode_noerror ||
+ query->rmessage->rcode == dns_rcode_yxdomain ||
+ query->rmessage->rcode == dns_rcode_nxdomain))
+ {
+ result = rctx_answer(&rctx);
+ if (result == ISC_R_COMPLETE) {
+ return;
+ }
+ } else if (query->rmessage->counts[DNS_SECTION_AUTHORITY] > 0 ||
+ query->rmessage->rcode == dns_rcode_noerror ||
+ query->rmessage->rcode == dns_rcode_nxdomain)
+ {
+ /*
+ * This might be an NXDOMAIN, NXRRSET, or referral.
+ * Call rctx_answer_none() to determine which it is.
+ */
+ result = rctx_answer_none(&rctx);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_CHASEDSSERVERS:
+ break;
+ case DNS_R_DELEGATION:
+ /* With NOFOLLOW we want to pass the result code */
+ if ((fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) {
+ result = ISC_R_SUCCESS;
+ }
+ break;
+ default:
+ /*
+ * Something has gone wrong.
+ */
+ if (result == DNS_R_FORMERR) {
+ rctx.next_server = true;
+ }
+ FCTXTRACE3("rctx_answer_none", result);
+ rctx_done(&rctx, result);
+ return;
+ }
+ } else {
+ /*
+ * The server is insane.
+ */
+ /* XXXRTH Log */
+ rctx.broken_server = DNS_R_UNEXPECTEDRCODE;
+ rctx.next_server = true;
+ FCTXTRACE("broken server: unexpected rcode");
+ rctx_done(&rctx, result);
+ return;
+ }
+
+ /*
+ * Follow additional section data chains.
+ */
+ rctx_additional(&rctx);
+
+ /*
+ * Cache the cacheable parts of the message. This may also cause
+ * work to be queued to the DNSSEC validator.
+ */
+ if (WANTCACHE(fctx)) {
+ isc_result_t tresult;
+ tresult = cache_message(fctx, query->rmessage, query->addrinfo,
+ rctx.now);
+ if (tresult != ISC_R_SUCCESS) {
+ FCTXTRACE3("cache_message complete", tresult);
+ rctx_done(&rctx, tresult);
+ return;
+ }
+ }
+
+ /*
+ * Negative caching
+ */
+ rctx_ncache(&rctx);
+
+ rctx_done(&rctx, result);
+}
+
+/*
+ * rctx_respinit():
+ * Initialize the response context structure 'rctx' to all zeroes, then set
+ * the task, event, query and fctx information from resquery_response().
+ */
+static void
+rctx_respinit(isc_task_t *task, dns_dispatchevent_t *devent, resquery_t *query,
+ fetchctx_t *fctx, respctx_t *rctx) {
+ memset(rctx, 0, sizeof(*rctx));
+
+ rctx->task = task;
+ rctx->devent = devent;
+ rctx->query = query;
+ rctx->fctx = fctx;
+ rctx->broken_type = badns_response;
+ rctx->retryopts = query->options;
+
+ /*
+ * XXXRTH We should really get the current time just once. We
+ * need a routine to convert from an isc_time_t to an
+ * isc_stdtime_t.
+ */
+ TIME_NOW(&rctx->tnow);
+ rctx->finish = &rctx->tnow;
+ isc_stdtime_get(&rctx->now);
+}
+
+/*
+ * rctx_answer_init():
+ * Clear and reinitialize those portions of 'rctx' that will be needed
+ * when scanning the answer section of the response message. This can be
+ * called more than once if scanning needs to be restarted (though currently
+ * there are no cases in which this occurs).
+ */
+static void
+rctx_answer_init(respctx_t *rctx) {
+ fetchctx_t *fctx = rctx->fctx;
+
+ rctx->aa = ((rctx->query->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0);
+ if (rctx->aa) {
+ rctx->trust = dns_trust_authanswer;
+ } else {
+ rctx->trust = dns_trust_answer;
+ }
+
+ /*
+ * There can be multiple RRSIG and SIG records at a name so
+ * we treat these types as a subset of ANY.
+ */
+ rctx->type = fctx->type;
+ if (rctx->type == dns_rdatatype_rrsig ||
+ rctx->type == dns_rdatatype_sig)
+ {
+ rctx->type = dns_rdatatype_any;
+ }
+
+ /*
+ * Bigger than any valid DNAME label count.
+ */
+ rctx->dname_labels = dns_name_countlabels(&fctx->name);
+ rctx->domain_labels = dns_name_countlabels(&fctx->domain);
+
+ rctx->found_type = dns_rdatatype_none;
+
+ rctx->aname = NULL;
+ rctx->ardataset = NULL;
+
+ rctx->cname = NULL;
+ rctx->crdataset = NULL;
+
+ rctx->dname = NULL;
+ rctx->drdataset = NULL;
+
+ rctx->ns_name = NULL;
+ rctx->ns_rdataset = NULL;
+
+ rctx->soa_name = NULL;
+ rctx->ds_name = NULL;
+ rctx->found_name = NULL;
+}
+
+/*
+ * rctx_dispfail():
+ * Handle the case where the dispatcher failed
+ */
+static isc_result_t
+rctx_dispfail(respctx_t *rctx) {
+ dns_dispatchevent_t *devent = rctx->devent;
+ fetchctx_t *fctx = rctx->fctx;
+ resquery_t *query = rctx->query;
+
+ if (devent->result == ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (devent->result == ISC_R_EOF &&
+ (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0)
+ {
+ /*
+ * The problem might be that they don't understand EDNS0.
+ * Turn it off and try again.
+ */
+ rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
+ rctx->resend = true;
+ add_bad_edns(fctx, &query->addrinfo->sockaddr);
+ } else {
+ /*
+ * There's no hope for this response.
+ */
+ rctx->next_server = true;
+
+ /*
+ * If this is a network error on an exclusive query
+ * socket, mark the server as bad so that we won't try
+ * it for this fetch again. Also adjust finish and
+ * no_response so that we penalize this address in SRTT
+ * adjustment later.
+ */
+ if (query->exclusivesocket &&
+ (devent->result == ISC_R_HOSTUNREACH ||
+ devent->result == ISC_R_NETUNREACH ||
+ devent->result == ISC_R_CONNREFUSED ||
+ devent->result == ISC_R_CANCELED))
+ {
+ rctx->broken_server = devent->result;
+ rctx->broken_type = badns_unreachable;
+ rctx->finish = NULL;
+ rctx->no_response = true;
+ }
+ }
+ FCTXTRACE3("dispatcher failure", devent->result);
+ rctx_done(rctx, ISC_R_SUCCESS);
+ return (ISC_R_COMPLETE);
+}
+
+/*
+ * rctx_parse():
+ * Parse the response message.
+ */
+static isc_result_t
+rctx_parse(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ resquery_t *query = rctx->query;
+
+ result = dns_message_parse(query->rmessage, &rctx->devent->buffer, 0);
+ if (result == ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ FCTXTRACE3("message failed to parse", result);
+
+ switch (result) {
+ case ISC_R_UNEXPECTEDEND:
+ if (query->rmessage->question_ok &&
+ (query->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0 &&
+ (rctx->retryopts & DNS_FETCHOPT_TCP) == 0)
+ {
+ /*
+ * We defer retrying via TCP for a bit so we can
+ * check out this message further.
+ */
+ rctx->truncated = true;
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Either the message ended prematurely,
+ * and/or wasn't marked as being truncated,
+ * and/or this is a response to a query we
+ * sent over TCP. In all of these cases,
+ * something is wrong with the remote
+ * server and we don't want to retry using
+ * TCP.
+ */
+ if ((rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) {
+ /*
+ * The problem might be that they
+ * don't understand EDNS0. Turn it
+ * off and try again.
+ */
+ rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
+ rctx->resend = true;
+ add_bad_edns(fctx, &query->addrinfo->sockaddr);
+ inc_stats(fctx->res, dns_resstatscounter_edns0fail);
+ } else {
+ rctx->broken_server = result;
+ rctx->next_server = true;
+ }
+
+ rctx_done(rctx, result);
+ break;
+ case DNS_R_FORMERR:
+ if ((rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) {
+ /*
+ * The problem might be that they
+ * don't understand EDNS0. Turn it
+ * off and try again.
+ */
+ rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
+ rctx->resend = true;
+ add_bad_edns(fctx, &query->addrinfo->sockaddr);
+ inc_stats(fctx->res, dns_resstatscounter_edns0fail);
+ } else {
+ rctx->broken_server = DNS_R_UNEXPECTEDRCODE;
+ rctx->next_server = true;
+ }
+
+ rctx_done(rctx, result);
+ break;
+ default:
+ /*
+ * Something bad has happened.
+ */
+ rctx_done(rctx, result);
+ break;
+ }
+
+ return (ISC_R_COMPLETE);
+}
+
+/*
+ * rctx_opt():
+ * Process the OPT record in the response.
+ */
+static void
+rctx_opt(respctx_t *rctx) {
+ resquery_t *query = rctx->query;
+ fetchctx_t *fctx = rctx->fctx;
+ dns_rdata_t rdata;
+ isc_buffer_t optbuf;
+ isc_result_t result;
+ uint16_t optcode;
+ uint16_t optlen;
+ unsigned char *optvalue;
+ dns_adbaddrinfo_t *addrinfo;
+ unsigned char cookie[CLIENT_COOKIE_SIZE];
+ bool seen_cookie = false;
+ bool seen_nsid = false;
+
+ result = dns_rdataset_first(rctx->opt);
+ if (result == ISC_R_SUCCESS) {
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(rctx->opt, &rdata);
+ isc_buffer_init(&optbuf, rdata.data, rdata.length);
+ isc_buffer_add(&optbuf, rdata.length);
+ while (isc_buffer_remaininglength(&optbuf) >= 4) {
+ optcode = isc_buffer_getuint16(&optbuf);
+ optlen = isc_buffer_getuint16(&optbuf);
+ INSIST(optlen <= isc_buffer_remaininglength(&optbuf));
+ switch (optcode) {
+ case DNS_OPT_NSID:
+ if (!seen_nsid && (query->options &
+ DNS_FETCHOPT_WANTNSID) != 0)
+ {
+ log_nsid(&optbuf, optlen, query,
+ ISC_LOG_INFO, fctx->res->mctx);
+ }
+ isc_buffer_forward(&optbuf, optlen);
+ seen_nsid = true;
+ break;
+ case DNS_OPT_COOKIE:
+ /*
+ * Only process the first cookie option.
+ */
+ if (seen_cookie) {
+ isc_buffer_forward(&optbuf, optlen);
+ break;
+ }
+ optvalue = isc_buffer_current(&optbuf);
+ compute_cc(query, cookie, sizeof(cookie));
+ INSIST(query->rmessage->cc_bad == 0 &&
+ query->rmessage->cc_ok == 0);
+ if (optlen >= CLIENT_COOKIE_SIZE &&
+ memcmp(cookie, optvalue,
+ CLIENT_COOKIE_SIZE) == 0)
+ {
+ query->rmessage->cc_ok = 1;
+ inc_stats(fctx->res,
+ dns_resstatscounter_cookieok);
+ addrinfo = query->addrinfo;
+ dns_adb_setcookie(fctx->adb, addrinfo,
+ optvalue, optlen);
+ } else {
+ query->rmessage->cc_bad = 1;
+ }
+ isc_buffer_forward(&optbuf, optlen);
+ inc_stats(fctx->res,
+ dns_resstatscounter_cookiein);
+ seen_cookie = true;
+ break;
+ default:
+ isc_buffer_forward(&optbuf, optlen);
+ break;
+ }
+ }
+ INSIST(isc_buffer_remaininglength(&optbuf) == 0U);
+ }
+}
+
+/*
+ * rctx_edns():
+ * Determine whether the remote server is using EDNS correctly or
+ * incorrectly and record that information if needed.
+ */
+static void
+rctx_edns(respctx_t *rctx) {
+ resquery_t *query = rctx->query;
+ fetchctx_t *fctx = rctx->fctx;
+
+ /*
+ * We have an affirmative response to the query and we have
+ * previously got a response from this server which indicated
+ * EDNS may not be supported so we can now cache the lack of
+ * EDNS support.
+ */
+ if (rctx->opt == NULL && !EDNSOK(query->addrinfo) &&
+ (query->rmessage->rcode == dns_rcode_noerror ||
+ query->rmessage->rcode == dns_rcode_nxdomain ||
+ query->rmessage->rcode == dns_rcode_refused ||
+ query->rmessage->rcode == dns_rcode_yxdomain) &&
+ bad_edns(fctx, &query->addrinfo->sockaddr))
+ {
+ dns_message_logpacket(
+ query->rmessage, "received packet (bad edns) from",
+ &query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ fctx->res->mctx);
+ dns_adb_changeflags(fctx->adb, query->addrinfo,
+ DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_NOEDNS0);
+ } else if (rctx->opt == NULL &&
+ (query->rmessage->flags & DNS_MESSAGEFLAG_TC) == 0 &&
+ !EDNSOK(query->addrinfo) &&
+ (query->rmessage->rcode == dns_rcode_noerror ||
+ query->rmessage->rcode == dns_rcode_nxdomain) &&
+ (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0)
+ {
+ /*
+ * We didn't get a OPT record in response to a EDNS query.
+ *
+ * Old versions of named incorrectly drop the OPT record
+ * when there is a signed, truncated response so we check
+ * that TC is not set.
+ *
+ * Record that the server is not talking EDNS. While this
+ * should be safe to do for any rcode we limit it to NOERROR
+ * and NXDOMAIN.
+ */
+ dns_message_logpacket(
+ query->rmessage, "received packet (no opt) from",
+ &query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+ fctx->res->mctx);
+ dns_adb_changeflags(fctx->adb, query->addrinfo,
+ DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_NOEDNS0);
+ }
+
+ /*
+ * If we get a non error EDNS response record the fact so we
+ * won't fallback to plain DNS in the future for this server.
+ */
+ if (rctx->opt != NULL && !EDNSOK(query->addrinfo) &&
+ (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0 &&
+ (query->rmessage->rcode == dns_rcode_noerror ||
+ query->rmessage->rcode == dns_rcode_nxdomain ||
+ query->rmessage->rcode == dns_rcode_refused ||
+ query->rmessage->rcode == dns_rcode_yxdomain))
+ {
+ dns_adb_changeflags(fctx->adb, query->addrinfo,
+ FCTX_ADDRINFO_EDNSOK, FCTX_ADDRINFO_EDNSOK);
+ }
+}
+
+/*
+ * rctx_answer():
+ * We might have answers, or we might have a malformed delegation with
+ * records in the answer section. Call rctx_answer_positive() or
+ * rctx_answer_none() as appropriate.
+ */
+static isc_result_t
+rctx_answer(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ resquery_t *query = rctx->query;
+
+ if ((query->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0 ||
+ ISFORWARDER(query->addrinfo))
+ {
+ result = rctx_answer_positive(rctx);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("rctx_answer_positive (AA/fwd)", result);
+ }
+ } else if (iscname(query->rmessage, &fctx->name) &&
+ fctx->type != dns_rdatatype_any &&
+ fctx->type != dns_rdatatype_cname)
+ {
+ /*
+ * A BIND8 server could return a non-authoritative
+ * answer when a CNAME is followed. We should treat
+ * it as a valid answer.
+ */
+ result = rctx_answer_positive(rctx);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("rctx_answer_positive (!ANY/!CNAME)",
+ result);
+ }
+ } else if (fctx->type != dns_rdatatype_ns && !betterreferral(rctx)) {
+ result = rctx_answer_positive(rctx);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("rctx_answer_positive (!NS)", result);
+ }
+ } else {
+ /*
+ * This may be a delegation. First let's check for
+ */
+
+ if (fctx->type == dns_rdatatype_ns) {
+ /*
+ * A BIND 8 server could incorrectly return a
+ * non-authoritative answer to an NS query
+ * instead of a referral. Since this answer
+ * lacks the SIGs necessary to do DNSSEC
+ * validation, we must invoke the following
+ * special kludge to treat it as a referral.
+ */
+ rctx->ns_in_answer = true;
+ result = rctx_answer_none(rctx);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("rctx_answer_none (NS)", result);
+ }
+ } else {
+ /*
+ * Some other servers may still somehow include
+ * an answer when it should return a referral
+ * with an empty answer. Check to see if we can
+ * treat this as a referral by ignoring the
+ * answer. Further more, there may be an
+ * implementation that moves A/AAAA glue records
+ * to the answer section for that type of
+ * delegation when the query is for that glue
+ * record. glue_in_answer will handle
+ * such a corner case.
+ */
+ rctx->glue_in_answer = true;
+ result = rctx_answer_none(rctx);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("rctx_answer_none", result);
+ }
+ }
+
+ if (result == DNS_R_DELEGATION) {
+ result = ISC_R_SUCCESS;
+ } else {
+ /*
+ * At this point, AA is not set, the response
+ * is not a referral, and the server is not a
+ * forwarder. It is technically lame and it's
+ * easier to treat it as such than to figure out
+ * some more elaborate course of action.
+ */
+ rctx->broken_server = DNS_R_LAME;
+ rctx->next_server = true;
+ rctx_done(rctx, result);
+ return (ISC_R_COMPLETE);
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ if (result == DNS_R_FORMERR) {
+ rctx->next_server = true;
+ }
+ rctx_done(rctx, result);
+ return (ISC_R_COMPLETE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_answer_positive():
+ * Handles positive responses. Depending which type of answer this is
+ * (matching QNAME/QTYPE, CNAME, DNAME, ANY) calls the proper routine
+ * to handle it (rctx_answer_match(), rctx_answer_cname(),
+ * rctx_answer_dname(), rctx_answer_any()).
+ */
+static isc_result_t
+rctx_answer_positive(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+
+ FCTXTRACE("rctx_answer");
+
+ rctx_answer_init(rctx);
+ rctx_answer_scan(rctx);
+
+ /*
+ * Determine which type of positive answer this is:
+ * type ANY, CNAME, DNAME, or an answer matching QNAME/QTYPE.
+ * Call the appropriate routine to handle the answer type.
+ */
+ if (rctx->aname != NULL && rctx->type == dns_rdatatype_any) {
+ result = rctx_answer_any(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+ } else if (rctx->aname != NULL) {
+ result = rctx_answer_match(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+ } else if (rctx->cname != NULL) {
+ result = rctx_answer_cname(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+ } else if (rctx->dname != NULL) {
+ result = rctx_answer_dname(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+ } else {
+ log_formerr(fctx, "reply has no answer");
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * This response is now potentially cacheable.
+ */
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTCACHE);
+
+ /*
+ * Did chaining end before we got the final answer?
+ */
+ if (rctx->chaining) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * We didn't end with an incomplete chain, so the rcode should be
+ * "no error".
+ */
+ if (rctx->query->rmessage->rcode != dns_rcode_noerror) {
+ log_formerr(fctx, "CNAME/DNAME chain complete, but RCODE "
+ "indicates error");
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * Cache records in the authority section, if
+ * there are any suitable for caching.
+ */
+ rctx_authority_positive(rctx);
+
+ log_ns_ttl(fctx, "rctx_answer");
+
+ if (rctx->ns_rdataset != NULL &&
+ dns_name_equal(&fctx->domain, rctx->ns_name) &&
+ !dns_name_equal(rctx->ns_name, dns_rootname))
+ {
+ trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_answer_scan():
+ * Perform a single pass over the answer section of a response, looking
+ * for an answer that matches QNAME/QTYPE, or a CNAME matching QNAME, or a
+ * covering DNAME. If more than one rdataset is found matching these
+ * criteria, then only one is kept. Order of preference is 1) the
+ * shortest DNAME, 2) the first matching answer, or 3) the first CNAME.
+ */
+static void
+rctx_answer_scan(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ dns_rdataset_t *rdataset = NULL;
+
+ for (result = dns_message_firstname(rctx->query->rmessage,
+ DNS_SECTION_ANSWER);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(rctx->query->rmessage,
+ DNS_SECTION_ANSWER))
+ {
+ int order;
+ unsigned int nlabels;
+ dns_namereln_t namereln;
+ dns_name_t *name = NULL;
+
+ dns_message_currentname(rctx->query->rmessage,
+ DNS_SECTION_ANSWER, &name);
+ namereln = dns_name_fullcompare(&fctx->name, name, &order,
+ &nlabels);
+ switch (namereln) {
+ case dns_namereln_equal:
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == rctx->type ||
+ rctx->type == dns_rdatatype_any)
+ {
+ rctx->aname = name;
+ if (rctx->type != dns_rdatatype_any) {
+ rctx->ardataset = rdataset;
+ }
+ break;
+ }
+ if (rdataset->type == dns_rdatatype_cname) {
+ rctx->cname = name;
+ rctx->crdataset = rdataset;
+ break;
+ }
+ }
+ break;
+
+ case dns_namereln_subdomain:
+ /*
+ * Don't accept DNAME from parent namespace.
+ */
+ if (name_external(name, dns_rdatatype_dname, fctx)) {
+ continue;
+ }
+
+ /*
+ * In-scope DNAME records must have at least
+ * as many labels as the domain being queried.
+ * They also must be less that qname's labels
+ * and any previously found dname.
+ */
+ if (nlabels >= rctx->dname_labels ||
+ nlabels < rctx->domain_labels)
+ {
+ continue;
+ }
+
+ /*
+ * We are looking for the shortest DNAME if there
+ * are multiple ones (which there shouldn't be).
+ */
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type != dns_rdatatype_dname) {
+ continue;
+ }
+ rctx->dname = name;
+ rctx->drdataset = rdataset;
+ rctx->dname_labels = nlabels;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * If a DNAME was found, then any CNAME or other answer matching
+ * QNAME that may also have been found must be ignored. Similarly,
+ * if a matching answer was found along with a CNAME, the CNAME
+ * must be ignored.
+ */
+ if (rctx->dname != NULL) {
+ rctx->aname = NULL;
+ rctx->ardataset = NULL;
+ rctx->cname = NULL;
+ rctx->crdataset = NULL;
+ } else if (rctx->aname != NULL) {
+ rctx->cname = NULL;
+ rctx->crdataset = NULL;
+ }
+}
+
+/*
+ * rctx_answer_any():
+ * Handle responses to queries of type ANY. Scan the answer section,
+ * and as long as each RRset is of a type that is valid in the answer
+ * section, and the rdata isn't filtered, cache it.
+ */
+static isc_result_t
+rctx_answer_any(respctx_t *rctx) {
+ dns_rdataset_t *rdataset = NULL;
+ fetchctx_t *fctx = rctx->fctx;
+
+ for (rdataset = ISC_LIST_HEAD(rctx->aname->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (!validinanswer(rdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if ((fctx->type == dns_rdatatype_sig ||
+ fctx->type == dns_rdatatype_rrsig) &&
+ rdataset->type != fctx->type)
+ {
+ continue;
+ }
+
+ if ((rdataset->type == dns_rdatatype_a ||
+ rdataset->type == dns_rdatatype_aaaa) &&
+ !is_answeraddress_allowed(fctx->res->view, rctx->aname,
+ rdataset))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+
+ if ((rdataset->type == dns_rdatatype_cname ||
+ rdataset->type == dns_rdatatype_dname) &&
+ !is_answertarget_allowed(fctx, &fctx->name, rctx->aname,
+ rdataset, NULL))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+
+ rctx->aname->attributes |= DNS_NAMEATTR_CACHE;
+ rctx->aname->attributes |= DNS_NAMEATTR_ANSWER;
+ rdataset->attributes |= DNS_RDATASETATTR_ANSWER;
+ rdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ rdataset->trust = rctx->trust;
+
+ (void)dns_rdataset_additionaldata(rdataset, check_related,
+ rctx);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_answer_match():
+ * Handle responses that match the QNAME/QTYPE of the resolver query.
+ * If QTYPE is valid in the answer section and the rdata isn't filtered,
+ * the answer can be cached. If there is additional section data related
+ * to the answer, it can be cached as well.
+ */
+static isc_result_t
+rctx_answer_match(respctx_t *rctx) {
+ dns_rdataset_t *sigrdataset = NULL;
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (!validinanswer(rctx->ardataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if ((rctx->ardataset->type == dns_rdatatype_a ||
+ rctx->ardataset->type == dns_rdatatype_aaaa) &&
+ !is_answeraddress_allowed(fctx->res->view, rctx->aname,
+ rctx->ardataset))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+ if ((rctx->ardataset->type == dns_rdatatype_cname ||
+ rctx->ardataset->type == dns_rdatatype_dname) &&
+ rctx->type != rctx->ardataset->type &&
+ rctx->type != dns_rdatatype_any &&
+ !is_answertarget_allowed(fctx, &fctx->name, rctx->aname,
+ rctx->ardataset, NULL))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+
+ rctx->aname->attributes |= DNS_NAMEATTR_CACHE;
+ rctx->aname->attributes |= DNS_NAMEATTR_ANSWER;
+ rctx->ardataset->attributes |= DNS_RDATASETATTR_ANSWER;
+ rctx->ardataset->attributes |= DNS_RDATASETATTR_CACHE;
+ rctx->ardataset->trust = rctx->trust;
+ (void)dns_rdataset_additionaldata(rctx->ardataset, check_related, rctx);
+
+ for (sigrdataset = ISC_LIST_HEAD(rctx->aname->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (!validinanswer(sigrdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (sigrdataset->type != dns_rdatatype_rrsig ||
+ sigrdataset->covers != rctx->type)
+ {
+ continue;
+ }
+
+ sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
+ sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ sigrdataset->trust = rctx->trust;
+ break;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_answer_cname():
+ * Handle answers containing a CNAME. Cache the CNAME, and flag that
+ * there may be additional chain answers to find.
+ */
+static isc_result_t
+rctx_answer_cname(respctx_t *rctx) {
+ dns_rdataset_t *sigrdataset = NULL;
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (!validinanswer(rctx->crdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (rctx->type == dns_rdatatype_rrsig ||
+ rctx->type == dns_rdatatype_key || rctx->type == dns_rdatatype_nsec)
+ {
+ char buf[DNS_RDATATYPE_FORMATSIZE];
+ dns_rdatatype_format(rctx->type, buf, sizeof(buf));
+ log_formerr(fctx, "CNAME response for %s RR", buf);
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (!is_answertarget_allowed(fctx, &fctx->name, rctx->cname,
+ rctx->crdataset, NULL))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+
+ rctx->cname->attributes |= DNS_NAMEATTR_CACHE;
+ rctx->cname->attributes |= DNS_NAMEATTR_ANSWER;
+ rctx->cname->attributes |= DNS_NAMEATTR_CHAINING;
+ rctx->crdataset->attributes |= DNS_RDATASETATTR_ANSWER;
+ rctx->crdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ rctx->crdataset->attributes |= DNS_RDATASETATTR_CHAINING;
+ rctx->crdataset->trust = rctx->trust;
+
+ for (sigrdataset = ISC_LIST_HEAD(rctx->cname->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (!validinanswer(sigrdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (sigrdataset->type != dns_rdatatype_rrsig ||
+ sigrdataset->covers != dns_rdatatype_cname)
+ {
+ continue;
+ }
+
+ sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
+ sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ sigrdataset->trust = rctx->trust;
+ break;
+ }
+
+ rctx->chaining = true;
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_answer_dname():
+ * Handle responses with covering DNAME records.
+ */
+static isc_result_t
+rctx_answer_dname(respctx_t *rctx) {
+ dns_rdataset_t *sigrdataset = NULL;
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (!validinanswer(rctx->drdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (!is_answertarget_allowed(fctx, &fctx->name, rctx->dname,
+ rctx->drdataset, &rctx->chaining))
+ {
+ rctx->result = DNS_R_SERVFAIL;
+ return (ISC_R_COMPLETE);
+ }
+
+ rctx->dname->attributes |= DNS_NAMEATTR_CACHE;
+ rctx->dname->attributes |= DNS_NAMEATTR_ANSWER;
+ rctx->dname->attributes |= DNS_NAMEATTR_CHAINING;
+ rctx->drdataset->attributes |= DNS_RDATASETATTR_ANSWER;
+ rctx->drdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ rctx->drdataset->attributes |= DNS_RDATASETATTR_CHAINING;
+ rctx->drdataset->trust = rctx->trust;
+
+ for (sigrdataset = ISC_LIST_HEAD(rctx->dname->list);
+ sigrdataset != NULL;
+ sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
+ {
+ if (!validinanswer(sigrdataset, fctx)) {
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (sigrdataset->type != dns_rdatatype_rrsig ||
+ sigrdataset->covers != dns_rdatatype_dname)
+ {
+ continue;
+ }
+
+ sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
+ sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ sigrdataset->trust = rctx->trust;
+ break;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_authority_positive():
+ * Examine the records in the authority section (if there are any) for a
+ * positive answer. We expect the names for all rdatasets in this section
+ * to be subdomains of the domain being queried; any that are not are
+ * skipped. We expect to find only *one* owner name; any names
+ * after the first one processed are ignored. We expect to find only
+ * rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever
+ * remains can be cached at trust level authauthority or additional
+ * (depending on whether the AA bit was set on the answer).
+ */
+static void
+rctx_authority_positive(respctx_t *rctx) {
+ fetchctx_t *fctx = rctx->fctx;
+ bool done = false;
+ isc_result_t result;
+
+ result = dns_message_firstname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY);
+ while (!done && result == ISC_R_SUCCESS) {
+ dns_name_t *name = NULL;
+
+ dns_message_currentname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY, &name);
+
+ if (!name_external(name, dns_rdatatype_ns, fctx)) {
+ dns_rdataset_t *rdataset = NULL;
+
+ /*
+ * We expect to find NS or SIG NS rdatasets, and
+ * nothing else.
+ */
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == dns_rdatatype_ns ||
+ (rdataset->type == dns_rdatatype_rrsig &&
+ rdataset->covers == dns_rdatatype_ns))
+ {
+ name->attributes |= DNS_NAMEATTR_CACHE;
+ rdataset->attributes |=
+ DNS_RDATASETATTR_CACHE;
+
+ if (rctx->aa) {
+ rdataset->trust =
+ dns_trust_authauthority;
+ } else {
+ rdataset->trust =
+ dns_trust_additional;
+ }
+
+ if (rdataset->type == dns_rdatatype_ns)
+ {
+ rctx->ns_name = name;
+ rctx->ns_rdataset = rdataset;
+ }
+ /*
+ * Mark any additional data related
+ * to this rdataset.
+ */
+ (void)dns_rdataset_additionaldata(
+ rdataset, check_related, rctx);
+ done = true;
+ }
+ }
+ }
+
+ result = dns_message_nextname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY);
+ }
+}
+
+/*
+ * rctx_answer_none():
+ * Handles a response without an answer: this is either a negative
+ * response (NXDOMAIN or NXRRSET) or a referral. Determine which it is,
+ * then either scan the authority section for negative caching and
+ * DNSSEC proof of nonexistence, or else call rctx_referral().
+ */
+static isc_result_t
+rctx_answer_none(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+
+ FCTXTRACE("rctx_answer_none");
+
+ rctx_answer_init(rctx);
+
+ /*
+ * Sometimes we can tell if its a negative response by looking at
+ * the message header.
+ */
+ if (rctx->query->rmessage->rcode == dns_rcode_nxdomain ||
+ (rctx->query->rmessage->counts[DNS_SECTION_ANSWER] == 0 &&
+ rctx->query->rmessage->counts[DNS_SECTION_AUTHORITY] == 0))
+ {
+ rctx->negative = true;
+ }
+
+ /*
+ * Process the authority section
+ */
+ result = rctx_authority_negative(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+
+ log_ns_ttl(fctx, "rctx_answer_none");
+
+ if (rctx->ns_rdataset != NULL &&
+ dns_name_equal(&fctx->domain, rctx->ns_name) &&
+ !dns_name_equal(rctx->ns_name, dns_rootname))
+ {
+ trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset);
+ }
+
+ /*
+ * A negative response has a SOA record (Type 2)
+ * and a optional NS RRset (Type 1) or it has neither
+ * a SOA or a NS RRset (Type 3, handled above) or
+ * rcode is NXDOMAIN (handled above) in which case
+ * the NS RRset is allowed (Type 4).
+ */
+ if (rctx->soa_name != NULL) {
+ rctx->negative = true;
+ }
+
+ if (!rctx->ns_in_answer && !rctx->glue_in_answer) {
+ /*
+ * Process DNSSEC records in the authority section.
+ */
+ result = rctx_authority_dnssec(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+ }
+
+ /*
+ * Trigger lookups for DNS nameservers.
+ */
+ if (rctx->negative &&
+ rctx->query->rmessage->rcode == dns_rcode_noerror &&
+ fctx->type == dns_rdatatype_ds && rctx->soa_name != NULL &&
+ dns_name_equal(rctx->soa_name, &fctx->name) &&
+ !dns_name_equal(&fctx->name, dns_rootname))
+ {
+ return (DNS_R_CHASEDSSERVERS);
+ }
+
+ /*
+ * Did we find anything?
+ */
+ if (!rctx->negative && rctx->ns_name == NULL) {
+ /*
+ * The responder is insane.
+ */
+ if (rctx->found_name == NULL) {
+ log_formerr(fctx, "invalid response");
+ return (DNS_R_FORMERR);
+ }
+ if (!dns_name_issubdomain(rctx->found_name, &fctx->domain)) {
+ char nbuf[DNS_NAME_FORMATSIZE];
+ char dbuf[DNS_NAME_FORMATSIZE];
+ char tbuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_rdatatype_format(rctx->found_type, tbuf,
+ sizeof(tbuf));
+ dns_name_format(rctx->found_name, nbuf, sizeof(nbuf));
+ dns_name_format(&fctx->domain, dbuf, sizeof(dbuf));
+
+ log_formerr(fctx,
+ "Name %s (%s) not subdomain"
+ " of zone %s -- invalid response",
+ nbuf, tbuf, dbuf);
+ } else {
+ log_formerr(fctx, "invalid response");
+ }
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * If we found both NS and SOA, they should be the same name.
+ */
+ if (rctx->ns_name != NULL && rctx->soa_name != NULL &&
+ rctx->ns_name != rctx->soa_name)
+ {
+ log_formerr(fctx, "NS/SOA mismatch");
+ return (DNS_R_FORMERR);
+ }
+
+ /*
+ * Handle a referral.
+ */
+ result = rctx_referral(rctx);
+ if (result == ISC_R_COMPLETE) {
+ return (rctx->result);
+ }
+
+ /*
+ * Since we're not doing a referral, we don't want to cache any
+ * NS RRs we may have found.
+ */
+ if (rctx->ns_name != NULL) {
+ rctx->ns_name->attributes &= ~DNS_NAMEATTR_CACHE;
+ }
+
+ if (rctx->negative) {
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTNCACHE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_authority_negative():
+ * Scan the authority section of a negative answer, handling
+ * NS and SOA records. (Note that this function does *not* handle
+ * DNSSEC records; those are addressed separately in
+ * rctx_authority_dnssec() below.)
+ */
+static isc_result_t
+rctx_authority_negative(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ dns_section_t section;
+ dns_rdataset_t *rdataset = NULL;
+ bool finished = false;
+
+ if (rctx->ns_in_answer) {
+ INSIST(fctx->type == dns_rdatatype_ns);
+ section = DNS_SECTION_ANSWER;
+ } else {
+ section = DNS_SECTION_AUTHORITY;
+ }
+
+ result = dns_message_firstname(rctx->query->rmessage, section);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ while (!finished) {
+ dns_name_t *name = NULL;
+
+ dns_message_currentname(rctx->query->rmessage, section, &name);
+ result = dns_message_nextname(rctx->query->rmessage, section);
+ if (result != ISC_R_SUCCESS) {
+ finished = true;
+ }
+
+ if (!dns_name_issubdomain(name, &fctx->domain)) {
+ continue;
+ }
+
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ dns_rdatatype_t type = rdataset->type;
+ if (type == dns_rdatatype_rrsig) {
+ type = rdataset->covers;
+ }
+ if (((type == dns_rdatatype_ns ||
+ type == dns_rdatatype_soa) &&
+ !dns_name_issubdomain(&fctx->name, name)))
+ {
+ char qbuf[DNS_NAME_FORMATSIZE];
+ char nbuf[DNS_NAME_FORMATSIZE];
+ char tbuf[DNS_RDATATYPE_FORMATSIZE];
+ dns_rdatatype_format(type, tbuf, sizeof(tbuf));
+ dns_name_format(name, nbuf, sizeof(nbuf));
+ dns_name_format(&fctx->name, qbuf,
+ sizeof(qbuf));
+ log_formerr(fctx,
+ "unrelated %s %s in "
+ "%s authority section",
+ tbuf, nbuf, qbuf);
+ break;
+ }
+
+ switch (type) {
+ case dns_rdatatype_ns:
+ /*
+ * NS or RRSIG NS.
+ *
+ * Only one set of NS RRs is allowed.
+ */
+ if (rdataset->type == dns_rdatatype_ns) {
+ if (rctx->ns_name != NULL &&
+ name != rctx->ns_name)
+ {
+ log_formerr(fctx, "multiple NS "
+ "RRsets "
+ "in "
+ "authority "
+ "section");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+ rctx->ns_name = name;
+ rctx->ns_rdataset = rdataset;
+ }
+ name->attributes |= DNS_NAMEATTR_CACHE;
+ rdataset->attributes |= DNS_RDATASETATTR_CACHE;
+ rdataset->trust = dns_trust_glue;
+ break;
+ case dns_rdatatype_soa:
+ /*
+ * SOA, or RRSIG SOA.
+ *
+ * Only one SOA is allowed.
+ */
+ if (rdataset->type == dns_rdatatype_soa) {
+ if (rctx->soa_name != NULL &&
+ name != rctx->soa_name)
+ {
+ log_formerr(fctx, "multiple "
+ "SOA RRs "
+ "in "
+ "authority "
+ "section");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+ rctx->soa_name = name;
+ }
+ name->attributes |= DNS_NAMEATTR_NCACHE;
+ rdataset->attributes |= DNS_RDATASETATTR_NCACHE;
+ if (rctx->aa) {
+ rdataset->trust =
+ dns_trust_authauthority;
+ } else if (ISFORWARDER(fctx->addrinfo)) {
+ rdataset->trust = dns_trust_answer;
+ } else {
+ rdataset->trust = dns_trust_additional;
+ }
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_ncache():
+ * Cache the negatively cacheable parts of the message. This may
+ * also cause work to be queued to the DNSSEC validator.
+ */
+static void
+rctx_ncache(respctx_t *rctx) {
+ isc_result_t result;
+ dns_rdatatype_t covers;
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (!WANTNCACHE(fctx)) {
+ return;
+ }
+
+ /*
+ * Cache DS NXDOMAIN separately to other types.
+ */
+ if (rctx->query->rmessage->rcode == dns_rcode_nxdomain &&
+ fctx->type != dns_rdatatype_ds)
+ {
+ covers = dns_rdatatype_any;
+ } else {
+ covers = fctx->type;
+ }
+
+ /*
+ * Cache any negative cache entries in the message.
+ */
+ result = ncache_message(fctx, rctx->query->rmessage,
+ rctx->query->addrinfo, covers, rctx->now);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE3("ncache_message complete", result);
+ }
+}
+
+/*
+ * rctx_authority_dnssec():
+ *
+ * Scan the authority section of a negative answer or referral,
+ * handling DNSSEC records (i.e. NSEC, NSEC3, DS).
+ */
+static isc_result_t
+rctx_authority_dnssec(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ dns_rdataset_t *rdataset = NULL;
+ bool finished = false;
+
+ REQUIRE(!rctx->ns_in_answer && !rctx->glue_in_answer);
+
+ result = dns_message_firstname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ while (!finished) {
+ dns_name_t *name = NULL;
+
+ dns_message_currentname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY, &name);
+ result = dns_message_nextname(rctx->query->rmessage,
+ DNS_SECTION_AUTHORITY);
+ if (result != ISC_R_SUCCESS) {
+ finished = true;
+ }
+
+ if (!dns_name_issubdomain(name, &fctx->domain)) {
+ /*
+ * Invalid name found; preserve it for logging
+ * later.
+ */
+ rctx->found_name = name;
+ rctx->found_type = ISC_LIST_HEAD(name->list)->type;
+ continue;
+ }
+
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ bool checknta = true;
+ bool secure_domain = false;
+ dns_rdatatype_t type = rdataset->type;
+
+ if (type == dns_rdatatype_rrsig) {
+ type = rdataset->covers;
+ }
+
+ switch (type) {
+ case dns_rdatatype_nsec:
+ case dns_rdatatype_nsec3:
+ if (rctx->negative) {
+ name->attributes |= DNS_NAMEATTR_NCACHE;
+ rdataset->attributes |=
+ DNS_RDATASETATTR_NCACHE;
+ } else if (type == dns_rdatatype_nsec) {
+ name->attributes |= DNS_NAMEATTR_CACHE;
+ rdataset->attributes |=
+ DNS_RDATASETATTR_CACHE;
+ }
+
+ if (rctx->aa) {
+ rdataset->trust =
+ dns_trust_authauthority;
+ } else if (ISFORWARDER(fctx->addrinfo)) {
+ rdataset->trust = dns_trust_answer;
+ } else {
+ rdataset->trust = dns_trust_additional;
+ }
+ /*
+ * No additional data needs to be marked.
+ */
+ break;
+ case dns_rdatatype_ds:
+ /*
+ * DS or SIG DS.
+ *
+ * These should only be here if this is a
+ * referral, and there should only be one
+ * DS RRset.
+ */
+ if (rctx->ns_name == NULL) {
+ log_formerr(fctx, "DS with no "
+ "referral");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ if (rdataset->type == dns_rdatatype_ds) {
+ if (rctx->ds_name != NULL &&
+ name != rctx->ds_name)
+ {
+ log_formerr(fctx, "DS doesn't "
+ "match "
+ "referral "
+ "(NS)");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+ rctx->ds_name = name;
+ }
+
+ name->attributes |= DNS_NAMEATTR_CACHE;
+ rdataset->attributes |= DNS_RDATASETATTR_CACHE;
+
+ if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
+ checknta = false;
+ }
+ if (fctx->res->view->enablevalidation) {
+ result = issecuredomain(
+ fctx->res->view, name,
+ dns_rdatatype_ds, fctx->now,
+ checknta, NULL, &secure_domain);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ if (secure_domain) {
+ rdataset->trust =
+ dns_trust_pending_answer;
+ } else if (rctx->aa) {
+ rdataset->trust =
+ dns_trust_authauthority;
+ } else if (ISFORWARDER(fctx->addrinfo)) {
+ rdataset->trust = dns_trust_answer;
+ } else {
+ rdataset->trust = dns_trust_additional;
+ }
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * rctx_referral():
+ * Handles referral responses. Check for sanity, find glue as needed,
+ * and update the fetch context to follow the delegation.
+ */
+static isc_result_t
+rctx_referral(respctx_t *rctx) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (rctx->negative || rctx->ns_name == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * We already know ns_name is a subdomain of fctx->domain.
+ * If ns_name is equal to fctx->domain, we're not making
+ * progress. We return DNS_R_FORMERR so that we'll keep
+ * trying other servers.
+ */
+ if (dns_name_equal(rctx->ns_name, &fctx->domain)) {
+ log_formerr(fctx, "non-improving referral");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ /*
+ * If the referral name is not a parent of the query
+ * name, consider the responder insane.
+ */
+ if (!dns_name_issubdomain(&fctx->name, rctx->ns_name)) {
+ /* Logged twice */
+ log_formerr(fctx, "referral to non-parent");
+ FCTXTRACE("referral to non-parent");
+ rctx->result = DNS_R_FORMERR;
+ return (ISC_R_COMPLETE);
+ }
+
+ /*
+ * Mark any additional data related to this rdataset.
+ * It's important that we do this before we change the
+ * query domain.
+ */
+ INSIST(rctx->ns_rdataset != NULL);
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_GLUING);
+ (void)dns_rdataset_additionaldata(rctx->ns_rdataset, check_related,
+ rctx);
+#if CHECK_FOR_GLUE_IN_ANSWER
+ /*
+ * Look in the answer section for "glue" that is incorrectly
+ * returned as a answer. This is needed if the server also
+ * minimizes the response size by not adding records to the
+ * additional section that are in the answer section or if
+ * the record gets dropped due to message size constraints.
+ */
+ if (rctx->glue_in_answer &&
+ (fctx->type == dns_rdatatype_aaaa || fctx->type == dns_rdatatype_a))
+ {
+ (void)dns_rdataset_additionaldata(rctx->ns_rdataset,
+ check_answer, fctx);
+ }
+#endif /* if CHECK_FOR_GLUE_IN_ANSWER */
+ FCTX_ATTR_CLR(fctx, FCTX_ATTR_GLUING);
+
+ /*
+ * NS rdatasets with 0 TTL cause problems.
+ * dns_view_findzonecut() will not find them when we
+ * try to follow the referral, and we'll SERVFAIL
+ * because the best nameservers are now above QDOMAIN.
+ * We force the TTL to 1 second to prevent this.
+ */
+ if (rctx->ns_rdataset->ttl == 0) {
+ rctx->ns_rdataset->ttl = 1;
+ }
+
+ /*
+ * Set the current query domain to the referral name.
+ *
+ * XXXRTH We should check if we're in forward-only mode, and
+ * if so we should bail out.
+ */
+ INSIST(dns_name_countlabels(&fctx->domain) > 0);
+ fcount_decr(fctx);
+
+ dns_name_free(&fctx->domain, fctx->mctx);
+ if (dns_rdataset_isassociated(&fctx->nameservers)) {
+ dns_rdataset_disassociate(&fctx->nameservers);
+ }
+
+ dns_name_init(&fctx->domain, NULL);
+ dns_name_dup(rctx->ns_name, fctx->mctx, &fctx->domain);
+
+ if ((fctx->options & DNS_FETCHOPT_QMINIMIZE) != 0) {
+ dns_name_free(&fctx->qmindcname, fctx->mctx);
+ dns_name_init(&fctx->qmindcname, NULL);
+ dns_name_dup(rctx->ns_name, fctx->mctx, &fctx->qmindcname);
+
+ result = fctx_minimize_qname(fctx);
+ if (result != ISC_R_SUCCESS) {
+ rctx->result = result;
+ return (ISC_R_COMPLETE);
+ }
+ }
+
+ result = fcount_incr(fctx, true);
+ if (result != ISC_R_SUCCESS) {
+ rctx->result = result;
+ return (ISC_R_COMPLETE);
+ }
+
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTCACHE);
+ fctx->ns_ttl_ok = false;
+ log_ns_ttl(fctx, "DELEGATION");
+ rctx->result = DNS_R_DELEGATION;
+
+ /*
+ * Reinitialize 'rctx' to prepare for following the delegation:
+ * set the get_nameservers and next_server flags appropriately and
+ * reset the fetch context counters.
+ *
+ */
+ if ((rctx->fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) {
+ rctx->get_nameservers = true;
+ rctx->next_server = true;
+ rctx->fctx->restarts = 0;
+ rctx->fctx->referrals++;
+ rctx->fctx->querysent = 0;
+ rctx->fctx->lamecount = 0;
+ rctx->fctx->quotacount = 0;
+ rctx->fctx->neterr = 0;
+ rctx->fctx->badresp = 0;
+ rctx->fctx->adberr = 0;
+ }
+
+ return (ISC_R_COMPLETE);
+}
+
+/*
+ * rctx_additional():
+ * Scan the additional section of a response to find records related
+ * to answers we were interested in.
+ */
+static void
+rctx_additional(respctx_t *rctx) {
+ bool rescan;
+ dns_section_t section = DNS_SECTION_ADDITIONAL;
+ isc_result_t result;
+
+again:
+ rescan = false;
+
+ for (result = dns_message_firstname(rctx->query->rmessage, section);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(rctx->query->rmessage, section))
+ {
+ dns_name_t *name = NULL;
+ dns_rdataset_t *rdataset;
+ dns_message_currentname(rctx->query->rmessage,
+ DNS_SECTION_ADDITIONAL, &name);
+ if ((name->attributes & DNS_NAMEATTR_CHASE) == 0) {
+ continue;
+ }
+ name->attributes &= ~DNS_NAMEATTR_CHASE;
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (CHASE(rdataset)) {
+ rdataset->attributes &= ~DNS_RDATASETATTR_CHASE;
+ (void)dns_rdataset_additionaldata(
+ rdataset, check_related, rctx);
+ rescan = true;
+ }
+ }
+ }
+ if (rescan) {
+ goto again;
+ }
+}
+
+/*
+ * rctx_nextserver():
+ * We found something wrong with the remote server, but it may be
+ * useful to try another one.
+ */
+static void
+rctx_nextserver(respctx_t *rctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_result_t result) {
+ fetchctx_t *fctx = rctx->fctx;
+
+ if (result == DNS_R_FORMERR) {
+ rctx->broken_server = DNS_R_FORMERR;
+ }
+ if (rctx->broken_server != ISC_R_SUCCESS) {
+ /*
+ * Add this server to the list of bad servers for
+ * this fctx.
+ */
+ add_bad(fctx, message, addrinfo, rctx->broken_server,
+ rctx->broken_type);
+ }
+
+ if (rctx->get_nameservers) {
+ dns_fixedname_t foundname, founddc;
+ dns_name_t *name, *fname, *dcname;
+ unsigned int findoptions = 0;
+
+ fname = dns_fixedname_initname(&foundname);
+ dcname = dns_fixedname_initname(&founddc);
+
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ return;
+ }
+ if (dns_rdatatype_atparent(fctx->type)) {
+ findoptions |= DNS_DBFIND_NOEXACT;
+ }
+ if ((rctx->retryopts & DNS_FETCHOPT_UNSHARED) == 0) {
+ name = &fctx->name;
+ } else {
+ name = &fctx->domain;
+ }
+ result = dns_view_findzonecut(
+ fctx->res->view, name, fname, dcname, fctx->now,
+ findoptions, true, true, &fctx->nameservers, NULL);
+ if (result != ISC_R_SUCCESS) {
+ FCTXTRACE("couldn't find a zonecut");
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ return;
+ }
+ if (!dns_name_issubdomain(fname, &fctx->domain)) {
+ /*
+ * The best nameservers are now above our QDOMAIN.
+ */
+ FCTXTRACE("nameservers now above QDOMAIN");
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ return;
+ }
+
+ fcount_decr(fctx);
+
+ dns_name_free(&fctx->domain, fctx->mctx);
+ dns_name_init(&fctx->domain, NULL);
+ dns_name_dup(fname, fctx->mctx, &fctx->domain);
+ dns_name_free(&fctx->qmindcname, fctx->mctx);
+ dns_name_init(&fctx->qmindcname, NULL);
+ dns_name_dup(dcname, fctx->mctx, &fctx->qmindcname);
+
+ result = fcount_incr(fctx, true);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ return;
+ }
+ fctx->ns_ttl = fctx->nameservers.ttl;
+ fctx->ns_ttl_ok = true;
+ fctx_cancelqueries(fctx, true, false);
+ fctx_cleanupall(fctx);
+ }
+
+ /*
+ * Try again.
+ */
+ fctx_try(fctx, !rctx->get_nameservers, false);
+}
+
+/*
+ * rctx_resend():
+ *
+ * Resend the query, probably with the options changed. Calls fctx_query(),
+ * passing rctx->retryopts (which is based on query->options, but may have been
+ * updated since the last time fctx_query() was called).
+ */
+static void
+rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) {
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ bool bucket_empty;
+ dns_resolver_t *res = fctx->res;
+ unsigned int bucketnum;
+
+ FCTXTRACE("resend");
+ inc_stats(fctx->res, dns_resstatscounter_retry);
+ fctx_increference(fctx);
+ result = fctx_query(fctx, addrinfo, rctx->retryopts);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+
+ bucketnum = fctx->bucketnum;
+ fctx_done(fctx, result, __LINE__);
+ LOCK(&res->buckets[bucketnum].lock);
+ bucket_empty = fctx_decreference(fctx);
+ UNLOCK(&res->buckets[bucketnum].lock);
+ if (bucket_empty) {
+ empty_bucket(res);
+ }
+}
+
+/*
+ * rctx_next():
+ * We got what appeared to be a response but it didn't match the question
+ * or the cookie; it may have been meant for someone else, or it may be a
+ * spoofing attack. Drop it and continue listening for the response we
+ * wanted.
+ */
+static void
+rctx_next(respctx_t *rctx) {
+#ifdef WANT_QUERYTRACE
+ fetchctx_t *fctx = rctx->fctx;
+#endif /* ifdef WANT_QUERYTRACE */
+ isc_result_t result;
+
+ FCTXTRACE("nextitem");
+ inc_stats(rctx->fctx->res, dns_resstatscounter_nextitem);
+ INSIST(rctx->query->dispentry != NULL);
+ dns_message_reset(rctx->query->rmessage, DNS_MESSAGE_INTENTPARSE);
+ result = dns_dispatch_getnext(rctx->query->dispentry, &rctx->devent);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(rctx->fctx, result, __LINE__);
+ }
+}
+
+/*
+ * rctx_chaseds():
+ * Look up the parent zone's NS records so that DS records can be fetched.
+ */
+static void
+rctx_chaseds(respctx_t *rctx, dns_message_t *message,
+ dns_adbaddrinfo_t *addrinfo, isc_result_t result) {
+ fetchctx_t *fctx = rctx->fctx;
+ unsigned int n;
+
+ add_bad(fctx, message, addrinfo, result, rctx->broken_type);
+ fctx_cancelqueries(fctx, true, false);
+ fctx_cleanupfinds(fctx);
+ fctx_cleanupforwaddrs(fctx);
+
+ n = dns_name_countlabels(&fctx->name);
+ dns_name_getlabelsequence(&fctx->name, 1, n - 1, &fctx->nsname);
+
+ FCTXTRACE("suspending DS lookup to find parent's NS records");
+
+ result = dns_resolver_createfetch(
+ fctx->res, &fctx->nsname, dns_rdatatype_ns, NULL, NULL, NULL,
+ NULL, 0, fctx->options, 0, NULL, rctx->task, resume_dslookup,
+ fctx, &fctx->nsrrset, NULL, &fctx->nsfetch);
+ if (result != ISC_R_SUCCESS) {
+ if (result == DNS_R_DUPLICATE) {
+ result = DNS_R_SERVFAIL;
+ }
+ fctx_done(fctx, result, __LINE__);
+ } else {
+ fctx_increference(fctx);
+ result = fctx_stopidletimer(fctx);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, result, __LINE__);
+ }
+ }
+}
+
+/*
+ * rctx_done():
+ * This resolver query response is finished, either because we encountered
+ * a problem or because we've gotten all the information from it that we
+ * can. We either wait for another response, resend the query to the
+ * same server, resend to a new server, or clean up and shut down the fetch.
+ */
+static void
+rctx_done(respctx_t *rctx, isc_result_t result) {
+ resquery_t *query = rctx->query;
+ fetchctx_t *fctx = rctx->fctx;
+ dns_adbaddrinfo_t *addrinfo = query->addrinfo;
+ /*
+ * Need to attach to the message until the scope
+ * of this function ends, since there are many places
+ * where te message is used and/or may be destroyed
+ * before this function ends.
+ */
+ dns_message_t *message = NULL;
+ dns_message_attach(query->rmessage, &message);
+
+ FCTXTRACE4("query canceled in response(); ",
+ rctx->no_response ? "no response" : "responding", result);
+
+ /*
+ * Cancel the query.
+ *
+ * XXXRTH Don't cancel the query if waiting for validation?
+ */
+ if (!rctx->nextitem) {
+ fctx_cancelquery(&query, &rctx->devent, rctx->finish,
+ rctx->no_response, false);
+ }
+
+#ifdef ENABLE_AFL
+ if (dns_fuzzing_resolver &&
+ (rctx->next_server || rctx->resend || rctx->nextitem))
+ {
+ if (rctx->nextitem) {
+ fctx_cancelquery(&query, &rctx->devent, rctx->finish,
+ rctx->no_response, false);
+ }
+ fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+ return;
+ } else
+#endif /* ifdef ENABLE_AFL */
+ if (rctx->next_server) {
+ rctx_nextserver(rctx, message, addrinfo, result);
+ } else if (rctx->resend) {
+ rctx_resend(rctx, addrinfo);
+ } else if (rctx->nextitem) {
+ rctx_next(rctx);
+ } else if (result == DNS_R_CHASEDSSERVERS) {
+ rctx_chaseds(rctx, message, addrinfo, result);
+ } else if (result == ISC_R_SUCCESS && !HAVE_ANSWER(fctx)) {
+ /*
+ * All has gone well so far, but we are waiting for the
+ * DNSSEC validator to validate the answer.
+ */
+ FCTXTRACE("wait for validator");
+ fctx_cancelqueries(fctx, true, false);
+ /*
+ * We must not retransmit while the validator is
+ * working; it has references to the current rmessage.
+ */
+ result = fctx_stopidletimer(fctx);
+ if (result != ISC_R_SUCCESS) {
+ fctx_done(fctx, result, __LINE__);
+ }
+ } else {
+ /*
+ * We're done.
+ */
+ fctx_done(fctx, result, __LINE__);
+ }
+
+ dns_message_detach(&message);
+}
+
+/*
+ * rctx_logpacket():
+ * Log the incoming packet; also log to DNSTAP if configured.
+ */
+static void
+rctx_logpacket(respctx_t *rctx) {
+#ifdef HAVE_DNSTAP
+ isc_result_t result;
+ fetchctx_t *fctx = rctx->fctx;
+ isc_socket_t *sock = NULL;
+ isc_sockaddr_t localaddr, *la = NULL;
+ unsigned char zone[DNS_NAME_MAXWIRE];
+ dns_dtmsgtype_t dtmsgtype;
+ dns_compress_t cctx;
+ isc_region_t zr;
+ isc_buffer_t zb;
+#endif /* HAVE_DNSTAP */
+
+ dns_message_logfmtpacket(
+ rctx->query->rmessage, "received packet from",
+ &rctx->query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_PACKETS, &dns_master_style_comment,
+ ISC_LOG_DEBUG(10), rctx->fctx->res->mctx);
+
+#ifdef HAVE_DNSTAP
+ /*
+ * Log the response via dnstap.
+ */
+ memset(&zr, 0, sizeof(zr));
+ result = dns_compress_init(&cctx, -1, fctx->res->mctx);
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_init(&zb, zone, sizeof(zone));
+ dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE);
+ result = dns_name_towire(&fctx->domain, &cctx, &zb);
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_usedregion(&zb, &zr);
+ }
+ dns_compress_invalidate(&cctx);
+ }
+
+ if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ dtmsgtype = DNS_DTTYPE_FR;
+ } else {
+ dtmsgtype = DNS_DTTYPE_RR;
+ }
+
+ sock = query2sock(rctx->query);
+
+ if (sock != NULL) {
+ result = isc_socket_getsockname(sock, &localaddr);
+ if (result == ISC_R_SUCCESS) {
+ la = &localaddr;
+ }
+ }
+
+ dns_dt_send(fctx->res->view, dtmsgtype, la,
+ &rctx->query->addrinfo->sockaddr,
+ ((rctx->query->options & DNS_FETCHOPT_TCP) != 0), &zr,
+ &rctx->query->start, NULL, &rctx->devent->buffer);
+#endif /* HAVE_DNSTAP */
+}
+
+/*
+ * rctx_badserver():
+ * Is the remote server broken, or does it dislike us?
+ */
+static isc_result_t
+rctx_badserver(respctx_t *rctx, isc_result_t result) {
+ fetchctx_t *fctx = rctx->fctx;
+ resquery_t *query = rctx->query;
+ isc_buffer_t b;
+ char code[64];
+ dns_rcode_t rcode = rctx->query->rmessage->rcode;
+
+ if (rcode == dns_rcode_noerror || rcode == dns_rcode_yxdomain ||
+ rcode == dns_rcode_nxdomain)
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ if ((rcode == dns_rcode_formerr) &&
+ (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0)
+ {
+ /*
+ * It's very likely they don't like EDNS0.
+ * If the response code is SERVFAIL, also check if the
+ * response contains an OPT RR and don't cache the
+ * failure since it can be returned for various other
+ * reasons.
+ *
+ * XXXRTH We should check if the question
+ * we're asking requires EDNS0, and
+ * if so, we should bail out.
+ */
+ rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
+ rctx->resend = true;
+ /*
+ * Remember that they may not like EDNS0.
+ */
+ add_bad_edns(fctx, &query->addrinfo->sockaddr);
+ inc_stats(fctx->res, dns_resstatscounter_edns0fail);
+ } else if (rcode == dns_rcode_formerr) {
+ if (ISFORWARDER(query->addrinfo)) {
+ /*
+ * This forwarder doesn't understand us,
+ * but other forwarders might. Keep trying.
+ */
+ rctx->broken_server = DNS_R_REMOTEFORMERR;
+ rctx->next_server = true;
+ } else {
+ /*
+ * The server doesn't understand us. Since
+ * all servers for a zone need similar
+ * capabilities, we assume that we will get
+ * FORMERR from all servers, and thus we
+ * cannot make any more progress with this
+ * fetch.
+ */
+ log_formerr(fctx, "server sent FORMERR");
+ result = DNS_R_FORMERR;
+ }
+ } else if (rcode == dns_rcode_badvers) {
+ unsigned int version;
+#if DNS_EDNS_VERSION > 0
+ unsigned int flags, mask;
+#endif /* if DNS_EDNS_VERSION > 0 */
+
+ INSIST(rctx->opt != NULL);
+ version = (rctx->opt->ttl >> 16) & 0xff;
+#if DNS_EDNS_VERSION > 0
+ flags = (version << DNS_FETCHOPT_EDNSVERSIONSHIFT) |
+ DNS_FETCHOPT_EDNSVERSIONSET;
+ mask = DNS_FETCHOPT_EDNSVERSIONMASK |
+ DNS_FETCHOPT_EDNSVERSIONSET;
+#endif /* if DNS_EDNS_VERSION > 0 */
+
+ /*
+ * Record that we got a good EDNS response.
+ */
+ if (query->ednsversion > (int)version &&
+ !EDNSOK(query->addrinfo))
+ {
+ dns_adb_changeflags(fctx->adb, query->addrinfo,
+ FCTX_ADDRINFO_EDNSOK,
+ FCTX_ADDRINFO_EDNSOK);
+ }
+
+ /*
+ * RFC 2671 was not clear that unknown options should
+ * be ignored. RFC 6891 is clear that that they
+ * should be ignored. If we are supporting the
+ * experimental EDNS > 0 then perform strict
+ * version checking of badvers responses. We won't
+ * be sending COOKIE etc. in that case.
+ */
+#if DNS_EDNS_VERSION > 0
+ if ((int)version < query->ednsversion) {
+ dns_adb_changeflags(fctx->adb, query->addrinfo, flags,
+ mask);
+ rctx->resend = true;
+ } else {
+ rctx->broken_server = DNS_R_BADVERS;
+ rctx->next_server = true;
+ }
+#else /* if DNS_EDNS_VERSION > 0 */
+ rctx->broken_server = DNS_R_BADVERS;
+ rctx->next_server = true;
+#endif /* if DNS_EDNS_VERSION > 0 */
+ } else if (rcode == dns_rcode_badcookie && rctx->query->rmessage->cc_ok)
+ {
+ /*
+ * We have recorded the new cookie.
+ */
+ if (BADCOOKIE(query->addrinfo)) {
+ rctx->retryopts |= DNS_FETCHOPT_TCP;
+ }
+ query->addrinfo->flags |= FCTX_ADDRINFO_BADCOOKIE;
+ rctx->resend = true;
+ } else {
+ rctx->broken_server = DNS_R_UNEXPECTEDRCODE;
+ rctx->next_server = true;
+ }
+
+ isc_buffer_init(&b, code, sizeof(code) - 1);
+ dns_rcode_totext(rcode, &b);
+ code[isc_buffer_usedlength(&b)] = '\0';
+ FCTXTRACE2("remote server broken: returned ", code);
+ rctx_done(rctx, result);
+
+ return (ISC_R_COMPLETE);
+}
+
+/*
+ * rctx_lameserver():
+ * Is the server lame?
+ */
+static isc_result_t
+rctx_lameserver(respctx_t *rctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ fetchctx_t *fctx = rctx->fctx;
+ resquery_t *query = rctx->query;
+
+ if (ISFORWARDER(query->addrinfo) || !is_lame(fctx, query->rmessage)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ inc_stats(fctx->res, dns_resstatscounter_lame);
+ log_lame(fctx, query->addrinfo);
+ if (fctx->res->lame_ttl != 0) {
+ result = dns_adb_marklame(fctx->adb, query->addrinfo,
+ &fctx->name, fctx->type,
+ rctx->now + fctx->res->lame_ttl);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR,
+ "could not mark server as lame: %s",
+ isc_result_totext(result));
+ }
+ }
+ rctx->broken_server = DNS_R_LAME;
+ rctx->next_server = true;
+ FCTXTRACE("lame server");
+ rctx_done(rctx, result);
+
+ return (ISC_R_COMPLETE);
+}
+
+/*
+ * rctx_delonly_zone():
+ * Handle delegation-only zones like NET and COM.
+ */
+static void
+rctx_delonly_zone(respctx_t *rctx) {
+ fetchctx_t *fctx = rctx->fctx;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char domainbuf[DNS_NAME_FORMATSIZE];
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ char classbuf[64];
+ char typebuf[64];
+
+ if (ISFORWARDER(rctx->query->addrinfo) ||
+ !dns_view_isdelegationonly(fctx->res->view, &fctx->domain) ||
+ dns_name_equal(&fctx->domain, &fctx->name) ||
+ !fix_mustbedelegationornxdomain(rctx->query->rmessage, fctx))
+ {
+ return;
+ }
+
+ dns_name_format(&fctx->name, namebuf, sizeof(namebuf));
+ dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf));
+ dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf));
+ dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf));
+ isc_sockaddr_format(&rctx->query->addrinfo->sockaddr, addrbuf,
+ sizeof(addrbuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DELEGATION_ONLY,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "enforced delegation-only for '%s' (%s/%s/%s) from %s",
+ domainbuf, namebuf, typebuf, classbuf, addrbuf);
+}
+
+/***
+ *** Resolver Methods
+ ***/
+static void
+destroy(dns_resolver_t *res) {
+ unsigned int i;
+ alternate_t *a;
+
+ isc_refcount_destroy(&res->references);
+ REQUIRE(!atomic_load_acquire(&res->priming));
+ REQUIRE(res->primefetch == NULL);
+
+ RTRACE("destroy");
+
+ REQUIRE(atomic_load_acquire(&res->nfctx) == 0);
+
+ isc_mutex_destroy(&res->primelock);
+ isc_mutex_destroy(&res->lock);
+ for (i = 0; i < res->nbuckets; i++) {
+ INSIST(ISC_LIST_EMPTY(res->buckets[i].fctxs));
+ isc_task_shutdown(res->buckets[i].task);
+ isc_task_detach(&res->buckets[i].task);
+ isc_mutex_destroy(&res->buckets[i].lock);
+ isc_mem_detach(&res->buckets[i].mctx);
+ }
+ isc_mem_put(res->mctx, res->buckets,
+ res->nbuckets * sizeof(fctxbucket_t));
+ for (i = 0; i < RES_DOMAIN_BUCKETS; i++) {
+ INSIST(ISC_LIST_EMPTY(res->dbuckets[i].list));
+ isc_mem_detach(&res->dbuckets[i].mctx);
+ isc_mutex_destroy(&res->dbuckets[i].lock);
+ }
+ isc_mem_put(res->mctx, res->dbuckets,
+ RES_DOMAIN_BUCKETS * sizeof(zonebucket_t));
+ if (res->dispatches4 != NULL) {
+ dns_dispatchset_destroy(&res->dispatches4);
+ }
+ if (res->dispatches6 != NULL) {
+ dns_dispatchset_destroy(&res->dispatches6);
+ }
+ while ((a = ISC_LIST_HEAD(res->alternates)) != NULL) {
+ ISC_LIST_UNLINK(res->alternates, a, link);
+ if (!a->isaddress) {
+ dns_name_free(&a->_u._n.name, res->mctx);
+ }
+ isc_mem_put(res->mctx, a, sizeof(*a));
+ }
+ dns_resolver_reset_algorithms(res);
+ dns_resolver_reset_ds_digests(res);
+ dns_badcache_destroy(&res->badcache);
+ dns_resolver_resetmustbesecure(res);
+#if USE_ALGLOCK
+ isc_rwlock_destroy(&res->alglock);
+#endif /* if USE_ALGLOCK */
+#if USE_MBSLOCK
+ isc_rwlock_destroy(&res->mbslock);
+#endif /* if USE_MBSLOCK */
+ isc_timer_destroy(&res->spillattimer);
+ res->magic = 0;
+ isc_mem_put(res->mctx, res, sizeof(*res));
+}
+
+static void
+send_shutdown_events(dns_resolver_t *res) {
+ isc_event_t *event, *next_event;
+ isc_task_t *etask;
+
+ /*
+ * Caller must be holding the resolver lock.
+ */
+
+ for (event = ISC_LIST_HEAD(res->whenshutdown); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ ISC_LIST_UNLINK(res->whenshutdown, event, ev_link);
+ etask = event->ev_sender;
+ event->ev_sender = res;
+ isc_task_sendanddetach(&etask, &event);
+ }
+}
+
+static void
+empty_bucket(dns_resolver_t *res) {
+ RTRACE("empty_bucket");
+
+ LOCK(&res->lock);
+
+ INSIST(res->activebuckets > 0);
+ res->activebuckets--;
+ if (res->activebuckets == 0) {
+ send_shutdown_events(res);
+ }
+
+ UNLOCK(&res->lock);
+}
+
+static void
+spillattimer_countdown(isc_task_t *task, isc_event_t *event) {
+ dns_resolver_t *res = event->ev_arg;
+ isc_result_t result;
+ unsigned int count;
+ bool logit = false;
+
+ REQUIRE(VALID_RESOLVER(res));
+
+ UNUSED(task);
+
+ LOCK(&res->lock);
+ INSIST(!atomic_load_acquire(&res->exiting));
+ if (res->spillat > res->spillatmin) {
+ res->spillat--;
+ logit = true;
+ }
+ if (res->spillat <= res->spillatmin) {
+ result = isc_timer_reset(res->spillattimer,
+ isc_timertype_inactive, NULL, NULL,
+ true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ count = res->spillat;
+ UNLOCK(&res->lock);
+ if (logit) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
+ "clients-per-query decreased to %u", count);
+ }
+
+ isc_event_free(&event);
+}
+
+isc_result_t
+dns_resolver_create(dns_view_t *view, isc_taskmgr_t *taskmgr,
+ unsigned int ntasks, unsigned int ndisp,
+ isc_socketmgr_t *socketmgr, isc_timermgr_t *timermgr,
+ unsigned int options, dns_dispatchmgr_t *dispatchmgr,
+ dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6,
+ dns_resolver_t **resp) {
+ dns_resolver_t *res;
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned int i, buckets_created = 0, dbuckets_created = 0;
+ isc_task_t *task = NULL;
+ char name[16];
+ unsigned dispattr;
+
+ /*
+ * Create a resolver.
+ */
+
+ REQUIRE(DNS_VIEW_VALID(view));
+ REQUIRE(ntasks > 0);
+ REQUIRE(ndisp > 0);
+ REQUIRE(resp != NULL && *resp == NULL);
+ REQUIRE(dispatchmgr != NULL);
+ REQUIRE(dispatchv4 != NULL || dispatchv6 != NULL);
+
+ res = isc_mem_get(view->mctx, sizeof(*res));
+ RTRACE("create");
+ res->mctx = view->mctx;
+ res->rdclass = view->rdclass;
+ res->socketmgr = socketmgr;
+ res->timermgr = timermgr;
+ res->taskmgr = taskmgr;
+ res->dispatchmgr = dispatchmgr;
+ res->view = view;
+ res->options = options;
+ res->lame_ttl = 0;
+ ISC_LIST_INIT(res->alternates);
+ res->udpsize = RECV_BUFFER_SIZE;
+ res->algorithms = NULL;
+ res->digests = NULL;
+ res->badcache = NULL;
+ result = dns_badcache_init(res->mctx, DNS_RESOLVER_BADCACHESIZE,
+ &res->badcache);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_res;
+ }
+ res->mustbesecure = NULL;
+ res->spillatmin = res->spillat = 10;
+ res->spillatmax = 100;
+ res->spillattimer = NULL;
+ atomic_init(&res->zspill, 0);
+ res->zero_no_soa_ttl = false;
+ res->retryinterval = 30000;
+ res->nonbackofftries = 3;
+ res->query_timeout = DEFAULT_QUERY_TIMEOUT;
+ res->maxdepth = DEFAULT_RECURSION_DEPTH;
+ res->maxqueries = DEFAULT_MAX_QUERIES;
+ res->quotaresp[dns_quotatype_zone] = DNS_R_DROP;
+ res->quotaresp[dns_quotatype_server] = DNS_R_SERVFAIL;
+ res->nbuckets = ntasks;
+ if (view->resstats != NULL) {
+ isc_stats_set(view->resstats, ntasks,
+ dns_resstatscounter_buckets);
+ }
+ res->activebuckets = ntasks;
+ res->buckets = isc_mem_get(view->mctx, ntasks * sizeof(fctxbucket_t));
+ for (i = 0; i < ntasks; i++) {
+ isc_mutex_init(&res->buckets[i].lock);
+
+ res->buckets[i].task = NULL;
+ /*
+ * Since we have a pool of tasks we bind them to task queues
+ * to spread the load evenly
+ */
+ result = isc_task_create_bound(taskmgr, 0,
+ &res->buckets[i].task, i);
+ if (result != ISC_R_SUCCESS) {
+ isc_mutex_destroy(&res->buckets[i].lock);
+ goto cleanup_buckets;
+ }
+ res->buckets[i].mctx = NULL;
+ snprintf(name, sizeof(name), "res%u", i);
+ /*
+ * Use a separate memory context for each bucket to reduce
+ * contention among multiple threads. Do this only when
+ * enabling threads because it will be require more memory.
+ */
+ isc_mem_create(&res->buckets[i].mctx);
+ isc_mem_setname(res->buckets[i].mctx, name, NULL);
+ isc_task_setname(res->buckets[i].task, name, res);
+ ISC_LIST_INIT(res->buckets[i].fctxs);
+ atomic_init(&res->buckets[i].exiting, false);
+ buckets_created++;
+ }
+
+ res->dbuckets = isc_mem_get(view->mctx,
+ RES_DOMAIN_BUCKETS * sizeof(zonebucket_t));
+ for (i = 0; i < RES_DOMAIN_BUCKETS; i++) {
+ ISC_LIST_INIT(res->dbuckets[i].list);
+ res->dbuckets[i].mctx = NULL;
+ isc_mem_attach(view->mctx, &res->dbuckets[i].mctx);
+ isc_mutex_init(&res->dbuckets[i].lock);
+ dbuckets_created++;
+ }
+
+ res->dispatches4 = NULL;
+ if (dispatchv4 != NULL) {
+ dns_dispatchset_create(view->mctx, socketmgr, taskmgr,
+ dispatchv4, &res->dispatches4, ndisp);
+ dispattr = dns_dispatch_getattributes(dispatchv4);
+ res->exclusivev4 = (dispattr & DNS_DISPATCHATTR_EXCLUSIVE);
+ }
+
+ res->dispatches6 = NULL;
+ if (dispatchv6 != NULL) {
+ dns_dispatchset_create(view->mctx, socketmgr, taskmgr,
+ dispatchv6, &res->dispatches6, ndisp);
+ dispattr = dns_dispatch_getattributes(dispatchv6);
+ res->exclusivev6 = (dispattr & DNS_DISPATCHATTR_EXCLUSIVE);
+ }
+
+ res->querydscp4 = -1;
+ res->querydscp6 = -1;
+ isc_refcount_init(&res->references, 1);
+ atomic_init(&res->exiting, false);
+ res->frozen = false;
+ ISC_LIST_INIT(res->whenshutdown);
+ atomic_init(&res->priming, false);
+ res->primefetch = NULL;
+
+ atomic_init(&res->nfctx, 0);
+
+ isc_mutex_init(&res->lock);
+ isc_mutex_init(&res->primelock);
+
+ task = NULL;
+ result = isc_task_create(taskmgr, 0, &task);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_primelock;
+ }
+ isc_task_setname(task, "resolver_task", NULL);
+
+ result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL,
+ task, spillattimer_countdown, res,
+ &res->spillattimer);
+ isc_task_detach(&task);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_primelock;
+ }
+
+#if USE_ALGLOCK
+ isc_rwlock_init(&res->alglock, 0, 0);
+#endif /* if USE_ALGLOCK */
+#if USE_MBSLOCK
+ isc_rwlock_init(&res->mbslock, 0, 0);
+#endif /* if USE_MBSLOCK */
+
+ res->magic = RES_MAGIC;
+
+ *resp = res;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_primelock:
+ isc_mutex_destroy(&res->primelock);
+ isc_mutex_destroy(&res->lock);
+
+ if (res->dispatches6 != NULL) {
+ dns_dispatchset_destroy(&res->dispatches6);
+ }
+ if (res->dispatches4 != NULL) {
+ dns_dispatchset_destroy(&res->dispatches4);
+ }
+
+ for (i = 0; i < dbuckets_created; i++) {
+ isc_mutex_destroy(&res->dbuckets[i].lock);
+ isc_mem_detach(&res->dbuckets[i].mctx);
+ }
+ isc_mem_put(view->mctx, res->dbuckets,
+ RES_DOMAIN_BUCKETS * sizeof(zonebucket_t));
+
+cleanup_buckets:
+ for (i = 0; i < buckets_created; i++) {
+ isc_mem_detach(&res->buckets[i].mctx);
+ isc_mutex_destroy(&res->buckets[i].lock);
+ isc_task_shutdown(res->buckets[i].task);
+ isc_task_detach(&res->buckets[i].task);
+ }
+ isc_mem_put(view->mctx, res->buckets,
+ res->nbuckets * sizeof(fctxbucket_t));
+
+ dns_badcache_destroy(&res->badcache);
+
+cleanup_res:
+ isc_mem_put(view->mctx, res, sizeof(*res));
+
+ return (result);
+}
+
+static void
+prime_done(isc_task_t *task, isc_event_t *event) {
+ dns_resolver_t *res;
+ dns_fetchevent_t *fevent;
+ dns_fetch_t *fetch;
+ dns_db_t *db = NULL;
+
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+ fevent = (dns_fetchevent_t *)event;
+ res = event->ev_arg;
+ REQUIRE(VALID_RESOLVER(res));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
+ "resolver priming query complete");
+
+ UNUSED(task);
+
+ LOCK(&res->primelock);
+ fetch = res->primefetch;
+ res->primefetch = NULL;
+ UNLOCK(&res->primelock);
+
+ INSIST(atomic_compare_exchange_strong_acq_rel(&res->priming,
+ &(bool){ true }, false));
+
+ if (fevent->result == ISC_R_SUCCESS && res->view->cache != NULL &&
+ res->view->hints != NULL)
+ {
+ dns_cache_attachdb(res->view->cache, &db);
+ dns_root_checkhints(res->view, res->view->hints, db);
+ dns_db_detach(&db);
+ }
+
+ if (fevent->node != NULL) {
+ dns_db_detachnode(fevent->db, &fevent->node);
+ }
+ if (fevent->db != NULL) {
+ dns_db_detach(&fevent->db);
+ }
+ if (dns_rdataset_isassociated(fevent->rdataset)) {
+ dns_rdataset_disassociate(fevent->rdataset);
+ }
+ INSIST(fevent->sigrdataset == NULL);
+
+ isc_mem_put(res->mctx, fevent->rdataset, sizeof(*fevent->rdataset));
+
+ isc_event_free(&event);
+ dns_resolver_destroyfetch(&fetch);
+}
+
+void
+dns_resolver_prime(dns_resolver_t *res) {
+ bool want_priming = false;
+ dns_rdataset_t *rdataset;
+ isc_result_t result;
+
+ REQUIRE(VALID_RESOLVER(res));
+ REQUIRE(res->frozen);
+
+ RTRACE("dns_resolver_prime");
+
+ if (!atomic_load_acquire(&res->exiting)) {
+ want_priming = atomic_compare_exchange_strong_acq_rel(
+ &res->priming, &(bool){ false }, true);
+ }
+
+ if (want_priming) {
+ /*
+ * To avoid any possible recursive locking problems, we
+ * start the priming fetch like any other fetch, and holding
+ * no resolver locks. No one else will try to start it
+ * because we're the ones who set res->priming to true.
+ * Any other callers of dns_resolver_prime() while we're
+ * running will see that res->priming is already true and
+ * do nothing.
+ */
+ RTRACE("priming");
+ rdataset = isc_mem_get(res->mctx, sizeof(*rdataset));
+ dns_rdataset_init(rdataset);
+
+ LOCK(&res->primelock);
+ INSIST(res->primefetch == NULL);
+ result = dns_resolver_createfetch(
+ res, dns_rootname, dns_rdatatype_ns, NULL, NULL, NULL,
+ NULL, 0, DNS_FETCHOPT_NOFORWARD, 0, NULL,
+ res->buckets[0].task, prime_done, res, rdataset, NULL,
+ &res->primefetch);
+ UNLOCK(&res->primelock);
+
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(res->mctx, rdataset, sizeof(*rdataset));
+ INSIST(atomic_compare_exchange_strong_acq_rel(
+ &res->priming, &(bool){ true }, false));
+ }
+ inc_stats(res, dns_resstatscounter_priming);
+ }
+}
+
+void
+dns_resolver_freeze(dns_resolver_t *res) {
+ /*
+ * Freeze resolver.
+ */
+
+ REQUIRE(VALID_RESOLVER(res));
+
+ res->frozen = true;
+}
+
+void
+dns_resolver_attach(dns_resolver_t *source, dns_resolver_t **targetp) {
+ REQUIRE(VALID_RESOLVER(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ RRTRACE(source, "attach");
+
+ LOCK(&source->lock);
+ REQUIRE(!atomic_load_acquire(&source->exiting));
+ isc_refcount_increment(&source->references);
+ UNLOCK(&source->lock);
+
+ *targetp = source;
+}
+
+void
+dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task,
+ isc_event_t **eventp) {
+ isc_task_t *tclone;
+ isc_event_t *event;
+
+ REQUIRE(VALID_RESOLVER(res));
+ REQUIRE(eventp != NULL);
+
+ event = *eventp;
+ *eventp = NULL;
+
+ LOCK(&res->lock);
+
+ if (atomic_load_acquire(&res->exiting) && res->activebuckets == 0) {
+ /*
+ * We're already shutdown. Send the event.
+ */
+ event->ev_sender = res;
+ isc_task_send(task, &event);
+ } else {
+ tclone = NULL;
+ isc_task_attach(task, &tclone);
+ event->ev_sender = tclone;
+ ISC_LIST_APPEND(res->whenshutdown, event, ev_link);
+ }
+
+ UNLOCK(&res->lock);
+}
+
+void
+dns_resolver_shutdown(dns_resolver_t *res) {
+ unsigned int i;
+ fetchctx_t *fctx;
+ isc_result_t result;
+ bool is_false = false;
+
+ REQUIRE(VALID_RESOLVER(res));
+
+ RTRACE("shutdown");
+
+ LOCK(&res->lock);
+ if (atomic_compare_exchange_strong(&res->exiting, &is_false, true)) {
+ RTRACE("exiting");
+
+ for (i = 0; i < res->nbuckets; i++) {
+ LOCK(&res->buckets[i].lock);
+ for (fctx = ISC_LIST_HEAD(res->buckets[i].fctxs);
+ fctx != NULL; fctx = ISC_LIST_NEXT(fctx, link))
+ {
+ fctx_shutdown(fctx);
+ }
+ if (res->dispatches4 != NULL && !res->exclusivev4) {
+ dns_dispatchset_cancelall(res->dispatches4,
+ res->buckets[i].task);
+ }
+ if (res->dispatches6 != NULL && !res->exclusivev6) {
+ dns_dispatchset_cancelall(res->dispatches6,
+ res->buckets[i].task);
+ }
+ atomic_store(&res->buckets[i].exiting, true);
+ if (ISC_LIST_EMPTY(res->buckets[i].fctxs)) {
+ INSIST(res->activebuckets > 0);
+ res->activebuckets--;
+ }
+ UNLOCK(&res->buckets[i].lock);
+ }
+ if (res->activebuckets == 0) {
+ send_shutdown_events(res);
+ }
+ result = isc_timer_reset(res->spillattimer,
+ isc_timertype_inactive, NULL, NULL,
+ true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ UNLOCK(&res->lock);
+}
+
+void
+dns_resolver_detach(dns_resolver_t **resp) {
+ dns_resolver_t *res;
+
+ REQUIRE(resp != NULL);
+ res = *resp;
+ *resp = NULL;
+ REQUIRE(VALID_RESOLVER(res));
+
+ RTRACE("detach");
+
+ if (isc_refcount_decrement(&res->references) == 1) {
+ LOCK(&res->lock);
+ INSIST(atomic_load_acquire(&res->exiting));
+ INSIST(res->activebuckets == 0);
+ UNLOCK(&res->lock);
+ destroy(res);
+ }
+}
+
+static bool
+fctx_match(fetchctx_t *fctx, const dns_name_t *name, dns_rdatatype_t type,
+ unsigned int options) {
+ /*
+ * Don't match fetch contexts that are shutting down.
+ */
+ if (fctx->cloned || fctx->state == fetchstate_done ||
+ ISC_LIST_EMPTY(fctx->events))
+ {
+ return (false);
+ }
+
+ if (fctx->type != type || fctx->options != options) {
+ return (false);
+ }
+ return (dns_name_equal(&fctx->name, name));
+}
+
+static void
+log_fetch(const dns_name_t *name, dns_rdatatype_t type) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ int level = ISC_LOG_DEBUG(1);
+
+ /*
+ * If there's no chance of logging it, don't render (format) the
+ * name and RDATA type (further below), and return early.
+ */
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(type, typebuf, sizeof(typebuf));
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, level, "fetch: %s/%s", namebuf,
+ typebuf);
+}
+
+static isc_result_t
+fctx_minimize_qname(fetchctx_t *fctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned int dlabels, nlabels;
+
+ REQUIRE(VALID_FCTX(fctx));
+
+ dlabels = dns_name_countlabels(&fctx->qmindcname);
+ nlabels = dns_name_countlabels(&fctx->name);
+ dns_name_free(&fctx->qminname, fctx->mctx);
+ dns_name_init(&fctx->qminname, NULL);
+
+ if (dlabels > fctx->qmin_labels) {
+ fctx->qmin_labels = dlabels + 1;
+ } else {
+ fctx->qmin_labels++;
+ }
+
+ if (fctx->ip6arpaskip) {
+ /*
+ * For ip6.arpa we want to skip some of the labels, with
+ * boundaries at /16, /32, /48, /56, /64 and /128
+ * In 'label count' terms that's equal to
+ * 7 11 15 17 19 35
+ * We fix fctx->qmin_labels to point to the nearest boundary
+ */
+ if (fctx->qmin_labels < 7) {
+ fctx->qmin_labels = 7;
+ } else if (fctx->qmin_labels < 11) {
+ fctx->qmin_labels = 11;
+ } else if (fctx->qmin_labels < 15) {
+ fctx->qmin_labels = 15;
+ } else if (fctx->qmin_labels < 17) {
+ fctx->qmin_labels = 17;
+ } else if (fctx->qmin_labels < 19) {
+ fctx->qmin_labels = 19;
+ } else if (fctx->qmin_labels < 35) {
+ fctx->qmin_labels = 35;
+ } else {
+ fctx->qmin_labels = nlabels;
+ }
+ } else if (fctx->qmin_labels > DNS_QMIN_MAXLABELS) {
+ fctx->qmin_labels = DNS_MAX_LABELS + 1;
+ }
+
+ if (fctx->qmin_labels < nlabels) {
+ /*
+ * We want to query for qmin_labels from fctx->name
+ */
+ dns_fixedname_t fname;
+ dns_name_t *name = dns_fixedname_initname(&fname);
+ dns_name_split(&fctx->name, fctx->qmin_labels, NULL,
+ dns_fixedname_name(&fname));
+ if ((fctx->options & DNS_FETCHOPT_QMIN_USE_A) != 0) {
+ isc_buffer_t dbuf;
+ dns_fixedname_t tmpname;
+ dns_name_t *tname = dns_fixedname_initname(&tmpname);
+ char ndata[DNS_NAME_MAXWIRE];
+
+ isc_buffer_init(&dbuf, ndata, DNS_NAME_MAXWIRE);
+ dns_fixedname_init(&tmpname);
+ result = dns_name_concatenate(&underscore_name, name,
+ tname, &dbuf);
+ if (result == ISC_R_SUCCESS) {
+ dns_name_dup(tname, fctx->mctx,
+ &fctx->qminname);
+ }
+ fctx->qmintype = dns_rdatatype_a;
+ } else {
+ dns_name_dup(dns_fixedname_name(&fname), fctx->mctx,
+ &fctx->qminname);
+ fctx->qmintype = dns_rdatatype_ns;
+ }
+ fctx->minimized = true;
+ } else {
+ /* Minimization is done, we'll ask for whole qname */
+ fctx->qmintype = fctx->type;
+ dns_name_dup(&fctx->name, fctx->mctx, &fctx->qminname);
+ fctx->minimized = false;
+ }
+
+ char domainbuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(&fctx->qminname, domainbuf, sizeof(domainbuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(5),
+ "QNAME minimization - %s minimized, qmintype %d "
+ "qminname %s",
+ fctx->minimized ? "" : "not", fctx->qmintype, domainbuf);
+
+ return (result);
+}
+
+isc_result_t
+dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
+ dns_rdatatype_t type, const dns_name_t *domain,
+ dns_rdataset_t *nameservers,
+ dns_forwarders_t *forwarders,
+ const isc_sockaddr_t *client, dns_messageid_t id,
+ unsigned int options, unsigned int depth,
+ isc_counter_t *qc, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_fetch_t **fetchp) {
+ dns_fetch_t *fetch;
+ fetchctx_t *fctx = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned int bucketnum;
+ bool new_fctx = false;
+ isc_event_t *event;
+ unsigned int count = 0;
+ unsigned int spillat;
+ unsigned int spillatmin;
+ bool dodestroy = false;
+
+ UNUSED(forwarders);
+
+ REQUIRE(VALID_RESOLVER(res));
+ REQUIRE(res->frozen);
+ /* XXXRTH Check for meta type */
+ if (domain != NULL) {
+ REQUIRE(DNS_RDATASET_VALID(nameservers));
+ REQUIRE(nameservers->type == dns_rdatatype_ns);
+ } else {
+ REQUIRE(nameservers == NULL);
+ }
+ REQUIRE(forwarders == NULL);
+ REQUIRE(!dns_rdataset_isassociated(rdataset));
+ REQUIRE(sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset));
+ REQUIRE(fetchp != NULL && *fetchp == NULL);
+
+ log_fetch(name, type);
+
+ /*
+ * XXXRTH use a mempool?
+ */
+ fetch = isc_mem_get(res->mctx, sizeof(*fetch));
+ fetch->mctx = NULL;
+ isc_mem_attach(res->mctx, &fetch->mctx);
+
+ bucketnum = dns_name_fullhash(name, false) % res->nbuckets;
+
+ LOCK(&res->lock);
+ spillat = res->spillat;
+ spillatmin = res->spillatmin;
+ UNLOCK(&res->lock);
+ LOCK(&res->buckets[bucketnum].lock);
+
+ if (atomic_load(&res->buckets[bucketnum].exiting)) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto unlock;
+ }
+
+ if ((options & DNS_FETCHOPT_UNSHARED) == 0) {
+ for (fctx = ISC_LIST_HEAD(res->buckets[bucketnum].fctxs);
+ fctx != NULL; fctx = ISC_LIST_NEXT(fctx, link))
+ {
+ if (fctx_match(fctx, name, type, options)) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * Is this a duplicate?
+ */
+ if (fctx != NULL && client != NULL) {
+ dns_fetchevent_t *fevent;
+ for (fevent = ISC_LIST_HEAD(fctx->events); fevent != NULL;
+ fevent = ISC_LIST_NEXT(fevent, ev_link))
+ {
+ if (fevent->client != NULL && fevent->id == id &&
+ isc_sockaddr_equal(fevent->client, client))
+ {
+ result = DNS_R_DUPLICATE;
+ goto unlock;
+ }
+ count++;
+ }
+ }
+ if (count >= spillatmin && spillatmin != 0) {
+ INSIST(fctx != NULL);
+ if (count >= spillat) {
+ fctx->spilled = true;
+ }
+ if (fctx->spilled) {
+ result = DNS_R_DROP;
+ goto unlock;
+ }
+ }
+
+ if (fctx == NULL) {
+ result = fctx_create(res, name, type, domain, nameservers,
+ client, id, options, bucketnum, depth, qc,
+ &fctx);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+ new_fctx = true;
+ } else if (fctx->depth > depth) {
+ fctx->depth = depth;
+ }
+
+ result = fctx_join(fctx, task, client, id, action, arg, rdataset,
+ sigrdataset, fetch);
+
+ if (result == ISC_R_SUCCESS &&
+ ((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0))
+ {
+ fctx_add_event(fctx, task, client, id, action, arg, fetch,
+ DNS_EVENT_TRYSTALE);
+ }
+
+ if (new_fctx) {
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * Launch this fctx.
+ */
+ event = &fctx->control_event;
+ ISC_EVENT_INIT(event, sizeof(*event), 0, NULL,
+ DNS_EVENT_FETCHCONTROL, fctx_start, fctx,
+ NULL, NULL, NULL);
+ isc_task_send(res->buckets[bucketnum].task, &event);
+ } else {
+ /*
+ * We don't care about the result of fctx_unlink()
+ * since we know we're not exiting.
+ */
+ (void)fctx_unlink(fctx);
+ dodestroy = true;
+ }
+ }
+
+unlock:
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ if (dodestroy) {
+ fctx_destroy(fctx);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ FTRACE("created");
+ *fetchp = fetch;
+ } else {
+ isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch));
+ }
+
+ return (result);
+}
+
+void
+dns_resolver_cancelfetch(dns_fetch_t *fetch) {
+ fetchctx_t *fctx;
+ dns_resolver_t *res;
+ dns_fetchevent_t *event = NULL;
+ dns_fetchevent_t *event_trystale = NULL;
+ dns_fetchevent_t *event_fetchdone = NULL;
+
+ REQUIRE(DNS_FETCH_VALID(fetch));
+ fctx = fetch->private;
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+
+ FTRACE("cancelfetch");
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+
+ /*
+ * Find the events for this fetch (as opposed
+ * to those for other fetches that have joined the same
+ * fctx) and send them with result = ISC_R_CANCELED.
+ */
+ if (fctx->state != fetchstate_done) {
+ dns_fetchevent_t *next_event = NULL;
+ for (event = ISC_LIST_HEAD(fctx->events); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ if (event->fetch == fetch) {
+ ISC_LIST_UNLINK(fctx->events, event, ev_link);
+ switch (event->ev_type) {
+ case DNS_EVENT_TRYSTALE:
+ INSIST(event_trystale == NULL);
+ event_trystale = event;
+ break;
+ case DNS_EVENT_FETCHDONE:
+ INSIST(event_fetchdone == NULL);
+ event_fetchdone = event;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ if (event_trystale != NULL &&
+ event_fetchdone != NULL)
+ {
+ break;
+ }
+ }
+ }
+ }
+ /*
+ * The "trystale" event must be sent before the "fetchdone" event,
+ * because the latter clears the "recursing" query attribute, which is
+ * required by both events (handled by the same callback function).
+ */
+ if (event_trystale != NULL) {
+ isc_task_t *etask = event_trystale->ev_sender;
+ event_trystale->ev_sender = fctx;
+ event_trystale->result = ISC_R_CANCELED;
+ isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_trystale));
+ }
+ if (event_fetchdone != NULL) {
+ isc_task_t *etask = event_fetchdone->ev_sender;
+ event_fetchdone->ev_sender = fctx;
+ event_fetchdone->result = ISC_R_CANCELED;
+ isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_fetchdone));
+ }
+
+ /*
+ * The fctx continues running even if no fetches remain;
+ * the answer is still cached.
+ */
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+}
+
+void
+dns_resolver_destroyfetch(dns_fetch_t **fetchp) {
+ dns_fetch_t *fetch;
+ dns_resolver_t *res;
+ dns_fetchevent_t *event, *next_event;
+ fetchctx_t *fctx;
+ unsigned int bucketnum;
+ bool bucket_empty;
+
+ REQUIRE(fetchp != NULL);
+ fetch = *fetchp;
+ *fetchp = NULL;
+ REQUIRE(DNS_FETCH_VALID(fetch));
+ fctx = fetch->private;
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+
+ FTRACE("destroyfetch");
+
+ bucketnum = fctx->bucketnum;
+ LOCK(&res->buckets[bucketnum].lock);
+
+ /*
+ * Sanity check: the caller should have gotten its event before
+ * trying to destroy the fetch.
+ */
+ event = NULL;
+ if (fctx->state != fetchstate_done) {
+ for (event = ISC_LIST_HEAD(fctx->events); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ RUNTIME_CHECK(event->fetch != fetch);
+ }
+ }
+
+ bucket_empty = fctx_decreference(fctx);
+ UNLOCK(&res->buckets[bucketnum].lock);
+
+ isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch));
+
+ if (bucket_empty) {
+ empty_bucket(res);
+ }
+}
+
+void
+dns_resolver_logfetch(dns_fetch_t *fetch, isc_log_t *lctx,
+ isc_logcategory_t *category, isc_logmodule_t *module,
+ int level, bool duplicateok) {
+ fetchctx_t *fctx;
+ dns_resolver_t *res;
+ char domainbuf[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_FETCH_VALID(fetch));
+ fctx = fetch->private;
+ REQUIRE(VALID_FCTX(fctx));
+ res = fctx->res;
+
+ LOCK(&res->buckets[fctx->bucketnum].lock);
+
+ INSIST(fctx->exitline >= 0);
+ if (!fctx->logged || duplicateok) {
+ dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf));
+ isc_log_write(lctx, category, module, level,
+ "fetch completed at %s:%d for %s in "
+ "%" PRIu64 "."
+ "%06" PRIu64 ": %s/%s "
+ "[domain:%s,referral:%u,restart:%u,qrysent:%u,"
+ "timeout:%u,lame:%u,quota:%u,neterr:%u,"
+ "badresp:%u,adberr:%u,findfail:%u,valfail:%u]",
+ __FILE__, fctx->exitline, fctx->info,
+ fctx->duration / US_PER_SEC,
+ fctx->duration % US_PER_SEC,
+ isc_result_totext(fctx->result),
+ isc_result_totext(fctx->vresult), domainbuf,
+ fctx->referrals, fctx->restarts, fctx->querysent,
+ fctx->timeouts, fctx->lamecount, fctx->quotacount,
+ fctx->neterr, fctx->badresp, fctx->adberr,
+ fctx->findfail, fctx->valfail);
+ fctx->logged = true;
+ }
+
+ UNLOCK(&res->buckets[fctx->bucketnum].lock);
+}
+
+dns_dispatchmgr_t *
+dns_resolver_dispatchmgr(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->dispatchmgr);
+}
+
+dns_dispatch_t *
+dns_resolver_dispatchv4(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (dns_dispatchset_get(resolver->dispatches4));
+}
+
+dns_dispatch_t *
+dns_resolver_dispatchv6(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (dns_dispatchset_get(resolver->dispatches6));
+}
+
+isc_socketmgr_t *
+dns_resolver_socketmgr(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->socketmgr);
+}
+
+isc_taskmgr_t *
+dns_resolver_taskmgr(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->taskmgr);
+}
+
+uint32_t
+dns_resolver_getlamettl(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->lame_ttl);
+}
+
+void
+dns_resolver_setlamettl(dns_resolver_t *resolver, uint32_t lame_ttl) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ resolver->lame_ttl = lame_ttl;
+}
+
+isc_result_t
+dns_resolver_addalternate(dns_resolver_t *resolver, const isc_sockaddr_t *alt,
+ const dns_name_t *name, in_port_t port) {
+ alternate_t *a;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(!resolver->frozen);
+ REQUIRE((alt == NULL) ^ (name == NULL));
+
+ a = isc_mem_get(resolver->mctx, sizeof(*a));
+ if (alt != NULL) {
+ a->isaddress = true;
+ a->_u.addr = *alt;
+ } else {
+ a->isaddress = false;
+ a->_u._n.port = port;
+ dns_name_init(&a->_u._n.name, NULL);
+ dns_name_dup(name, resolver->mctx, &a->_u._n.name);
+ }
+ ISC_LINK_INIT(a, link);
+ ISC_LIST_APPEND(resolver->alternates, a, link);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_resolver_setudpsize(dns_resolver_t *resolver, uint16_t udpsize) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ resolver->udpsize = udpsize;
+}
+
+uint16_t
+dns_resolver_getudpsize(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->udpsize);
+}
+
+void
+dns_resolver_flushbadcache(dns_resolver_t *resolver, const dns_name_t *name) {
+ if (name != NULL) {
+ dns_badcache_flushname(resolver->badcache, name);
+ } else {
+ dns_badcache_flush(resolver->badcache);
+ }
+}
+
+void
+dns_resolver_flushbadnames(dns_resolver_t *resolver, const dns_name_t *name) {
+ dns_badcache_flushtree(resolver->badcache, name);
+}
+
+void
+dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name,
+ dns_rdatatype_t type, isc_time_t *expire) {
+#ifdef ENABLE_AFL
+ if (!dns_fuzzing_resolver)
+#endif /* ifdef ENABLE_AFL */
+ {
+ dns_badcache_add(resolver->badcache, name, type, false, 0,
+ expire);
+ }
+}
+
+bool
+dns_resolver_getbadcache(dns_resolver_t *resolver, const dns_name_t *name,
+ dns_rdatatype_t type, isc_time_t *now) {
+ return (dns_badcache_find(resolver->badcache, name, type, NULL, now));
+}
+
+void
+dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp) {
+ (void)dns_badcache_print(resolver->badcache, "Bad cache", fp);
+}
+
+static void
+free_algorithm(void *node, void *arg) {
+ unsigned char *algorithms = node;
+ isc_mem_t *mctx = arg;
+
+ isc_mem_put(mctx, algorithms, *algorithms);
+}
+
+void
+dns_resolver_reset_algorithms(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+#if USE_ALGLOCK
+ RWLOCK(&resolver->alglock, isc_rwlocktype_write);
+#endif /* if USE_ALGLOCK */
+ if (resolver->algorithms != NULL) {
+ dns_rbt_destroy(&resolver->algorithms);
+ }
+#if USE_ALGLOCK
+ RWUNLOCK(&resolver->alglock, isc_rwlocktype_write);
+#endif /* if USE_ALGLOCK */
+}
+
+isc_result_t
+dns_resolver_disable_algorithm(dns_resolver_t *resolver, const dns_name_t *name,
+ unsigned int alg) {
+ unsigned int len, mask;
+ unsigned char *tmp;
+ unsigned char *algorithms;
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ /*
+ * Whether an algorithm is disabled (or not) is stored in a
+ * per-name bitfield that is stored as the node data of an
+ * RBT.
+ */
+
+ REQUIRE(VALID_RESOLVER(resolver));
+ if (alg > 255) {
+ return (ISC_R_RANGE);
+ }
+
+#if USE_ALGLOCK
+ RWLOCK(&resolver->alglock, isc_rwlocktype_write);
+#endif /* if USE_ALGLOCK */
+ if (resolver->algorithms == NULL) {
+ result = dns_rbt_create(resolver->mctx, free_algorithm,
+ resolver->mctx, &resolver->algorithms);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ len = alg / 8 + 2;
+ mask = 1 << (alg % 8);
+
+ result = dns_rbt_addnode(resolver->algorithms, name, &node);
+
+ if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) {
+ algorithms = node->data;
+ /*
+ * If algorithms is set, algorithms[0] contains its
+ * length.
+ */
+ if (algorithms == NULL || len > *algorithms) {
+ /*
+ * If no bitfield exists in the node data, or if
+ * it is not long enough, allocate a new
+ * bitfield and copy the old (smaller) bitfield
+ * into it if one exists.
+ */
+ tmp = isc_mem_get(resolver->mctx, len);
+ memset(tmp, 0, len);
+ if (algorithms != NULL) {
+ memmove(tmp, algorithms, *algorithms);
+ }
+ tmp[len - 1] |= mask;
+ /* 'tmp[0]' should contain the length of 'tmp'. */
+ *tmp = len;
+ node->data = tmp;
+ /* Free the older bitfield. */
+ if (algorithms != NULL) {
+ isc_mem_put(resolver->mctx, algorithms,
+ *algorithms);
+ }
+ } else {
+ algorithms[len - 1] |= mask;
+ }
+ }
+ result = ISC_R_SUCCESS;
+cleanup:
+#if USE_ALGLOCK
+ RWUNLOCK(&resolver->alglock, isc_rwlocktype_write);
+#endif /* if USE_ALGLOCK */
+ return (result);
+}
+
+bool
+dns_resolver_algorithm_supported(dns_resolver_t *resolver,
+ const dns_name_t *name, unsigned int alg) {
+ unsigned int len, mask;
+ unsigned char *algorithms;
+ void *data = NULL;
+ isc_result_t result;
+ bool found = false;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ /*
+ * DH is unsupported for DNSKEYs, see RFC 4034 sec. A.1.
+ */
+ if ((alg == DST_ALG_DH) || (alg == DST_ALG_INDIRECT)) {
+ return (false);
+ }
+
+#if USE_ALGLOCK
+ RWLOCK(&resolver->alglock, isc_rwlocktype_read);
+#endif /* if USE_ALGLOCK */
+ if (resolver->algorithms == NULL) {
+ goto unlock;
+ }
+ result = dns_rbt_findname(resolver->algorithms, name, 0, NULL, &data);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ len = alg / 8 + 2;
+ mask = 1 << (alg % 8);
+ algorithms = data;
+ if (len <= *algorithms && (algorithms[len - 1] & mask) != 0) {
+ found = true;
+ }
+ }
+unlock:
+#if USE_ALGLOCK
+ RWUNLOCK(&resolver->alglock, isc_rwlocktype_read);
+#endif /* if USE_ALGLOCK */
+ if (found) {
+ return (false);
+ }
+
+ return (dst_algorithm_supported(alg));
+}
+
+static void
+free_digest(void *node, void *arg) {
+ unsigned char *digests = node;
+ isc_mem_t *mctx = arg;
+
+ isc_mem_put(mctx, digests, *digests);
+}
+
+void
+dns_resolver_reset_ds_digests(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+#if USE_ALGLOCK
+ RWLOCK(&resolver->alglock, isc_rwlocktype_write);
+#endif /* if USE_ALGLOCK */
+ if (resolver->digests != NULL) {
+ dns_rbt_destroy(&resolver->digests);
+ }
+#if USE_ALGLOCK
+ RWUNLOCK(&resolver->alglock, isc_rwlocktype_write);
+#endif /* if USE_ALGLOCK */
+}
+
+isc_result_t
+dns_resolver_disable_ds_digest(dns_resolver_t *resolver, const dns_name_t *name,
+ unsigned int digest_type) {
+ unsigned int len, mask;
+ unsigned char *tmp;
+ unsigned char *digests;
+ isc_result_t result;
+ dns_rbtnode_t *node = NULL;
+
+ /*
+ * Whether a digest is disabled (or not) is stored in a per-name
+ * bitfield that is stored as the node data of an RBT.
+ */
+
+ REQUIRE(VALID_RESOLVER(resolver));
+ if (digest_type > 255) {
+ return (ISC_R_RANGE);
+ }
+
+#if USE_ALGLOCK
+ RWLOCK(&resolver->alglock, isc_rwlocktype_write);
+#endif /* if USE_ALGLOCK */
+ if (resolver->digests == NULL) {
+ result = dns_rbt_create(resolver->mctx, free_digest,
+ resolver->mctx, &resolver->digests);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ len = digest_type / 8 + 2;
+ mask = 1 << (digest_type % 8);
+
+ result = dns_rbt_addnode(resolver->digests, name, &node);
+
+ if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) {
+ digests = node->data;
+ /* If digests is set, digests[0] contains its length. */
+ if (digests == NULL || len > *digests) {
+ /*
+ * If no bitfield exists in the node data, or if
+ * it is not long enough, allocate a new
+ * bitfield and copy the old (smaller) bitfield
+ * into it if one exists.
+ */
+ tmp = isc_mem_get(resolver->mctx, len);
+ memset(tmp, 0, len);
+ if (digests != NULL) {
+ memmove(tmp, digests, *digests);
+ }
+ tmp[len - 1] |= mask;
+ /* tmp[0] should contain the length of 'tmp'. */
+ *tmp = len;
+ node->data = tmp;
+ /* Free the older bitfield. */
+ if (digests != NULL) {
+ isc_mem_put(resolver->mctx, digests, *digests);
+ }
+ } else {
+ digests[len - 1] |= mask;
+ }
+ }
+ result = ISC_R_SUCCESS;
+cleanup:
+#if USE_ALGLOCK
+ RWUNLOCK(&resolver->alglock, isc_rwlocktype_write);
+#endif /* if USE_ALGLOCK */
+ return (result);
+}
+
+bool
+dns_resolver_ds_digest_supported(dns_resolver_t *resolver,
+ const dns_name_t *name,
+ unsigned int digest_type) {
+ unsigned int len, mask;
+ unsigned char *digests;
+ void *data = NULL;
+ isc_result_t result;
+ bool found = false;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+
+#if USE_ALGLOCK
+ RWLOCK(&resolver->alglock, isc_rwlocktype_read);
+#endif /* if USE_ALGLOCK */
+ if (resolver->digests == NULL) {
+ goto unlock;
+ }
+ result = dns_rbt_findname(resolver->digests, name, 0, NULL, &data);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ len = digest_type / 8 + 2;
+ mask = 1 << (digest_type % 8);
+ digests = data;
+ if (len <= *digests && (digests[len - 1] & mask) != 0) {
+ found = true;
+ }
+ }
+unlock:
+#if USE_ALGLOCK
+ RWUNLOCK(&resolver->alglock, isc_rwlocktype_read);
+#endif /* if USE_ALGLOCK */
+ if (found) {
+ return (false);
+ }
+ return (dst_ds_digest_supported(digest_type));
+}
+
+void
+dns_resolver_resetmustbesecure(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+#if USE_MBSLOCK
+ RWLOCK(&resolver->mbslock, isc_rwlocktype_write);
+#endif /* if USE_MBSLOCK */
+ if (resolver->mustbesecure != NULL) {
+ dns_rbt_destroy(&resolver->mustbesecure);
+ }
+#if USE_MBSLOCK
+ RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write);
+#endif /* if USE_MBSLOCK */
+}
+
+static bool yes = true, no = false;
+
+isc_result_t
+dns_resolver_setmustbesecure(dns_resolver_t *resolver, const dns_name_t *name,
+ bool value) {
+ isc_result_t result;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+
+#if USE_MBSLOCK
+ RWLOCK(&resolver->mbslock, isc_rwlocktype_write);
+#endif /* if USE_MBSLOCK */
+ if (resolver->mustbesecure == NULL) {
+ result = dns_rbt_create(resolver->mctx, NULL, NULL,
+ &resolver->mustbesecure);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ result = dns_rbt_addname(resolver->mustbesecure, name,
+ value ? &yes : &no);
+cleanup:
+#if USE_MBSLOCK
+ RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write);
+#endif /* if USE_MBSLOCK */
+ return (result);
+}
+
+bool
+dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name) {
+ void *data = NULL;
+ bool value = false;
+ isc_result_t result;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+
+#if USE_MBSLOCK
+ RWLOCK(&resolver->mbslock, isc_rwlocktype_read);
+#endif /* if USE_MBSLOCK */
+ if (resolver->mustbesecure == NULL) {
+ goto unlock;
+ }
+ result = dns_rbt_findname(resolver->mustbesecure, name, 0, NULL, &data);
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ value = *(bool *)data;
+ }
+unlock:
+#if USE_MBSLOCK
+ RWUNLOCK(&resolver->mbslock, isc_rwlocktype_read);
+#endif /* if USE_MBSLOCK */
+ return (value);
+}
+
+void
+dns_resolver_getclientsperquery(dns_resolver_t *resolver, uint32_t *cur,
+ uint32_t *min, uint32_t *max) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ LOCK(&resolver->lock);
+ if (cur != NULL) {
+ *cur = resolver->spillat;
+ }
+ if (min != NULL) {
+ *min = resolver->spillatmin;
+ }
+ if (max != NULL) {
+ *max = resolver->spillatmax;
+ }
+ UNLOCK(&resolver->lock);
+}
+
+void
+dns_resolver_setclientsperquery(dns_resolver_t *resolver, uint32_t min,
+ uint32_t max) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ LOCK(&resolver->lock);
+ resolver->spillatmin = resolver->spillat = min;
+ resolver->spillatmax = max;
+ UNLOCK(&resolver->lock);
+}
+
+void
+dns_resolver_setfetchesperzone(dns_resolver_t *resolver, uint32_t clients) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ atomic_store_release(&resolver->zspill, clients);
+}
+
+bool
+dns_resolver_getzeronosoattl(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ return (resolver->zero_no_soa_ttl);
+}
+
+void
+dns_resolver_setzeronosoattl(dns_resolver_t *resolver, bool state) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ resolver->zero_no_soa_ttl = state;
+}
+
+unsigned int
+dns_resolver_getoptions(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ return (resolver->options);
+}
+
+unsigned int
+dns_resolver_gettimeout(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ return (resolver->query_timeout);
+}
+
+void
+dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ if (timeout <= 300) {
+ timeout *= 1000;
+ }
+
+ if (timeout == 0) {
+ timeout = DEFAULT_QUERY_TIMEOUT;
+ }
+ if (timeout > MAXIMUM_QUERY_TIMEOUT) {
+ timeout = MAXIMUM_QUERY_TIMEOUT;
+ }
+ if (timeout < MINIMUM_QUERY_TIMEOUT) {
+ timeout = MINIMUM_QUERY_TIMEOUT;
+ }
+
+ resolver->query_timeout = timeout;
+}
+
+void
+dns_resolver_setquerydscp4(dns_resolver_t *resolver, isc_dscp_t dscp) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ resolver->querydscp4 = dscp;
+}
+
+isc_dscp_t
+dns_resolver_getquerydscp4(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->querydscp4);
+}
+
+void
+dns_resolver_setquerydscp6(dns_resolver_t *resolver, isc_dscp_t dscp) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ resolver->querydscp6 = dscp;
+}
+
+isc_dscp_t
+dns_resolver_getquerydscp6(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->querydscp6);
+}
+
+void
+dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ resolver->maxdepth = maxdepth;
+}
+
+unsigned int
+dns_resolver_getmaxdepth(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->maxdepth);
+}
+
+void
+dns_resolver_setmaxqueries(dns_resolver_t *resolver, unsigned int queries) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ resolver->maxqueries = queries;
+}
+
+unsigned int
+dns_resolver_getmaxqueries(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ return (resolver->maxqueries);
+}
+
+void
+dns_resolver_dumpfetches(dns_resolver_t *resolver, isc_statsformat_t format,
+ FILE *fp) {
+ int i;
+
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(fp != NULL);
+ REQUIRE(format == isc_statsformat_file);
+
+ for (i = 0; i < RES_DOMAIN_BUCKETS; i++) {
+ fctxcount_t *fc;
+ LOCK(&resolver->dbuckets[i].lock);
+ for (fc = ISC_LIST_HEAD(resolver->dbuckets[i].list); fc != NULL;
+ fc = ISC_LIST_NEXT(fc, link))
+ {
+ dns_name_print(fc->domain, fp);
+ fprintf(fp, ": %u active (%u spilled, %u allowed)\n",
+ fc->count, fc->dropped, fc->allowed);
+ }
+ UNLOCK(&resolver->dbuckets[i].lock);
+ }
+}
+
+void
+dns_resolver_setquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which,
+ isc_result_t resp) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(which == dns_quotatype_zone || which == dns_quotatype_server);
+ REQUIRE(resp == DNS_R_DROP || resp == DNS_R_SERVFAIL);
+
+ resolver->quotaresp[which] = resp;
+}
+
+isc_result_t
+dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(which == dns_quotatype_zone || which == dns_quotatype_server);
+
+ return (resolver->quotaresp[which]);
+}
+
+unsigned int
+dns_resolver_getretryinterval(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ return (resolver->retryinterval);
+}
+
+void
+dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(interval > 0);
+
+ resolver->retryinterval = ISC_MIN(interval, 2000);
+}
+
+unsigned int
+dns_resolver_getnonbackofftries(dns_resolver_t *resolver) {
+ REQUIRE(VALID_RESOLVER(resolver));
+
+ return (resolver->nonbackofftries);
+}
+
+void
+dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries) {
+ REQUIRE(VALID_RESOLVER(resolver));
+ REQUIRE(tries > 0);
+
+ resolver->nonbackofftries = tries;
+}
diff --git a/lib/dns/result.c b/lib/dns/result.c
new file mode 100644
index 0000000..9921291
--- /dev/null
+++ b/lib/dns/result.c
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <isc/once.h>
+#include <isc/util.h>
+
+#include <dns/lib.h>
+#include <dns/result.h>
+
+static const char *text[DNS_R_NRESULTS] = {
+ "label too long", /*%< 0 DNS_R_LABELTOOLONG */
+ "bad escape", /*%< 1 DNS_R_BADESCAPE */
+ /*!
+ * Note that DNS_R_BADBITSTRING and DNS_R_BITSTRINGTOOLONG are
+ * deprecated.
+ */
+ "bad bitstring", /*%< 2 DNS_R_BADBITSTRING */
+ "bitstring too long", /*%< 3 DNS_R_BITSTRINGTOOLONG */
+ "empty label", /*%< 4 DNS_R_EMPTYLABEL */
+
+ "bad dotted quad", /*%< 5 DNS_R_BADDOTTEDQUAD */
+ "invalid NS owner name (wildcard)", /*%< 6 DNS_R_INVALIDNS */
+ "unknown class/type", /*%< 7 DNS_R_UNKNOWN */
+ "bad label type", /*%< 8 DNS_R_BADLABELTYPE */
+ "bad compression pointer", /*%< 9 DNS_R_BADPOINTER */
+
+ "too many hops", /*%< 10 DNS_R_TOOMANYHOPS */
+ "disallowed (by application policy)", /*%< 11 DNS_R_DISALLOWED */
+ "extra input text", /*%< 12 DNS_R_EXTRATOKEN */
+ "extra input data", /*%< 13 DNS_R_EXTRADATA */
+ "text too long", /*%< 14 DNS_R_TEXTTOOLONG */
+
+ "not at top of zone", /*%< 15 DNS_R_NOTZONETOP */
+ "syntax error", /*%< 16 DNS_R_SYNTAX */
+ "bad checksum", /*%< 17 DNS_R_BADCKSUM */
+ "bad IPv6 address", /*%< 18 DNS_R_BADAAAA */
+ "no owner", /*%< 19 DNS_R_NOOWNER */
+
+ "no ttl", /*%< 20 DNS_R_NOTTL */
+ "bad class", /*%< 21 DNS_R_BADCLASS */
+ "name too long", /*%< 22 DNS_R_NAMETOOLONG */
+ "partial match", /*%< 23 DNS_R_PARTIALMATCH */
+ "new origin", /*%< 24 DNS_R_NEWORIGIN */
+
+ "unchanged", /*%< 25 DNS_R_UNCHANGED */
+ "bad ttl", /*%< 26 DNS_R_BADTTL */
+ "more data needed/to be rendered", /*%< 27 DNS_R_NOREDATA */
+ "continue", /*%< 28 DNS_R_CONTINUE */
+ "delegation", /*%< 29 DNS_R_DELEGATION */
+
+ "glue", /*%< 30 DNS_R_GLUE */
+ "dname", /*%< 31 DNS_R_DNAME */
+ "cname", /*%< 32 DNS_R_CNAME */
+ "bad database", /*%< 33 DNS_R_BADDB */
+ "zonecut", /*%< 34 DNS_R_ZONECUT */
+
+ "bad zone", /*%< 35 DNS_R_BADZONE */
+ "more data", /*%< 36 DNS_R_MOREDATA */
+ "up to date", /*%< 37 DNS_R_UPTODATE */
+ "tsig verify failure", /*%< 38 DNS_R_TSIGVERIFYFAILURE */
+ "tsig indicates error", /*%< 39 DNS_R_TSIGERRORSET */
+
+ "RRSIG failed to verify", /*%< 40 DNS_R_SIGINVALID */
+ "RRSIG has expired", /*%< 41 DNS_R_SIGEXPIRED */
+ "RRSIG validity period has not begun", /*%< 42 DNS_R_SIGFUTURE */
+ "key is unauthorized to sign data", /*%< 43 DNS_R_KEYUNAUTHORIZED */
+ "invalid time", /*%< 44 DNS_R_INVALIDTIME */
+
+ "expected a TSIG or SIG(0)", /*%< 45 DNS_R_EXPECTEDTSIG */
+ "did not expect a TSIG or SIG(0)", /*%< 46 DNS_R_UNEXPECTEDTSIG */
+ "TKEY is unacceptable", /*%< 47 DNS_R_INVALIDTKEY */
+ "hint", /*%< 48 DNS_R_HINT */
+ "drop", /*%< 49 DNS_R_DROP */
+
+ "zone not loaded", /*%< 50 DNS_R_NOTLOADED */
+ "ncache nxdomain", /*%< 51 DNS_R_NCACHENXDOMAIN */
+ "ncache nxrrset", /*%< 52 DNS_R_NCACHENXRRSET */
+ "wait", /*%< 53 DNS_R_WAIT */
+ "not verified yet", /*%< 54 DNS_R_NOTVERIFIEDYET */
+
+ "no identity", /*%< 55 DNS_R_NOIDENTITY */
+ "no journal", /*%< 56 DNS_R_NOJOURNAL */
+ "alias", /*%< 57 DNS_R_ALIAS */
+ "use TCP", /*%< 58 DNS_R_USETCP */
+ "no valid RRSIG", /*%< 59 DNS_R_NOVALIDSIG */
+
+ "no valid NSEC", /*%< 60 DNS_R_NOVALIDNSEC */
+ "insecurity proof failed", /*%< 61 DNS_R_NOTINSECURE */
+ "unknown service", /*%< 62 DNS_R_UNKNOWNSERVICE */
+ "recoverable error occurred", /*%< 63 DNS_R_RECOVERABLE */
+ "unknown opt attribute record", /*%< 64 DNS_R_UNKNOWNOPT */
+
+ "unexpected message id", /*%< 65 DNS_R_UNEXPECTEDID */
+ "seen include file", /*%< 66 DNS_R_SEENINCLUDE */
+ "not exact", /*%< 67 DNS_R_NOTEXACT */
+ "address blackholed", /*%< 68 DNS_R_BLACKHOLED */
+ "bad algorithm", /*%< 69 DNS_R_BADALG */
+
+ "invalid use of a meta type", /*%< 70 DNS_R_METATYPE */
+ "CNAME and other data", /*%< 71 DNS_R_CNAMEANDOTHER */
+ "multiple RRs of singleton type", /*%< 72 DNS_R_SINGLETON */
+ "hint nxrrset", /*%< 73 DNS_R_HINTNXRRSET */
+ "no master file configured", /*%< 74 DNS_R_NOMASTERFILE */
+
+ "unknown protocol", /*%< 75 DNS_R_UNKNOWNPROTO */
+ "clocks are unsynchronized", /*%< 76 DNS_R_CLOCKSKEW */
+ "IXFR failed", /*%< 77 DNS_R_BADIXFR */
+ "not authoritative", /*%< 78 DNS_R_NOTAUTHORITATIVE */
+ "no valid KEY", /*%< 79 DNS_R_NOVALIDKEY */
+
+ "obsolete", /*%< 80 DNS_R_OBSOLETE */
+ "already frozen", /*%< 81 DNS_R_FROZEN */
+ "unknown flag", /*%< 82 DNS_R_UNKNOWNFLAG */
+ "expected a response", /*%< 83 DNS_R_EXPECTEDRESPONSE */
+ "no valid DS", /*%< 84 DNS_R_NOVALIDDS */
+
+ "NS is an address", /*%< 85 DNS_R_NSISADDRESS */
+ "received FORMERR", /*%< 86 DNS_R_REMOTEFORMERR */
+ "truncated TCP response", /*%< 87 DNS_R_TRUNCATEDTCP */
+ "lame server detected", /*%< 88 DNS_R_LAME */
+ "unexpected RCODE", /*%< 89 DNS_R_UNEXPECTEDRCODE */
+
+ "unexpected OPCODE", /*%< 90 DNS_R_UNEXPECTEDOPCODE */
+ "chase DS servers", /*%< 91 DNS_R_CHASEDSSERVERS */
+ "empty name", /*%< 92 DNS_R_EMPTYNAME */
+ "empty wild", /*%< 93 DNS_R_EMPTYWILD */
+ "bad bitmap", /*%< 94 DNS_R_BADBITMAP */
+
+ "from wildcard", /*%< 95 DNS_R_FROMWILDCARD */
+ "bad owner name (check-names)", /*%< 96 DNS_R_BADOWNERNAME */
+ "bad name (check-names)", /*%< 97 DNS_R_BADNAME */
+ "dynamic zone", /*%< 98 DNS_R_DYNAMIC */
+ "unknown command", /*%< 99 DNS_R_UNKNOWNCOMMAND */
+
+ "must-be-secure", /*%< 100 DNS_R_MUSTBESECURE */
+ "covering NSEC record returned", /*%< 101 DNS_R_COVERINGNSEC */
+ "MX is an address", /*%< 102 DNS_R_MXISADDRESS */
+ "duplicate query", /*%< 103 DNS_R_DUPLICATE */
+ "invalid NSEC3 owner name (wildcard)", /*%< 104 DNS_R_INVALIDNSEC3 */
+
+ "not master", /*%< 105 DNS_R_NOTMASTER */
+ "broken trust chain", /*%< 106 DNS_R_BROKENCHAIN */
+ "expired", /*%< 107 DNS_R_EXPIRED */
+ "not dynamic", /*%< 108 DNS_R_NOTDYNAMIC */
+ "bad EUI", /*%< 109 DNS_R_BADEUI */
+
+ "covered by negative trust anchor", /*%< 110 DNS_R_NTACOVERED */
+ "bad CDS", /*%< 111 DNS_R_BADCDS */
+ "bad CDNSKEY", /*%< 112 DNS_R_BADCDNSKEY */
+ "malformed OPT option", /*%< 113 DNS_R_OPTERR */
+ "malformed DNSTAP data", /*%< 114 DNS_R_BADDNSTAP */
+
+ "TSIG in wrong location", /*%< 115 DNS_R_BADTSIG */
+ "SIG(0) in wrong location", /*%< 116 DNS_R_BADSIG0 */
+ "too many records", /*%< 117 DNS_R_TOOMANYRECORDS */
+ "verify failure", /*%< 118 DNS_R_VERIFYFAILURE */
+ "at top of zone", /*%< 119 DNS_R_ATZONETOP */
+
+ "no matching key found", /*%< 120 DNS_R_NOKEYMATCH */
+ "too many keys matching", /*%< 121 DNS_R_TOOMANYKEYS */
+ "key is not actively signing", /*%< 122 DNS_R_KEYNOTACTIVE */
+ "NSEC3 iterations out of range", /*%< 123 DNS_R_NSEC3ITERRANGE */
+ "NSEC3 salt length too high", /*%< 124 DNS_R_NSEC3SALTRANGE */
+
+ "cannot use NSEC3 with key algorithm", /*%< 125 DNS_R_NSEC3BADALG */
+ "NSEC3 resalt", /*%< 126 DNS_R_NSEC3RESALT */
+ "inconsistent resource record", /*%< 127 DNS_R_INCONSISTENTRR */
+};
+
+static const char *ids[DNS_R_NRESULTS] = {
+ "DNS_R_LABELTOOLONG",
+ "DNS_R_BADESCAPE",
+ /*!
+ * Note that DNS_R_BADBITSTRING and DNS_R_BITSTRINGTOOLONG are
+ * deprecated.
+ */
+ "DNS_R_BADBITSTRING",
+ "DNS_R_BITSTRINGTOOLONG",
+ "DNS_R_EMPTYLABEL",
+ "DNS_R_BADDOTTEDQUAD",
+ "DNS_R_INVALIDNS",
+ "DNS_R_UNKNOWN",
+ "DNS_R_BADLABELTYPE",
+ "DNS_R_BADPOINTER",
+ "DNS_R_TOOMANYHOPS",
+ "DNS_R_DISALLOWED",
+ "DNS_R_EXTRATOKEN",
+ "DNS_R_EXTRADATA",
+ "DNS_R_TEXTTOOLONG",
+ "DNS_R_NOTZONETOP",
+ "DNS_R_SYNTAX",
+ "DNS_R_BADCKSUM",
+ "DNS_R_BADAAAA",
+ "DNS_R_NOOWNER",
+ "DNS_R_NOTTL",
+ "DNS_R_BADCLASS",
+ "DNS_R_NAMETOOLONG",
+ "DNS_R_PARTIALMATCH",
+ "DNS_R_NEWORIGIN",
+ "DNS_R_UNCHANGED",
+ "DNS_R_BADTTL",
+ "DNS_R_NOREDATA",
+ "DNS_R_CONTINUE",
+ "DNS_R_DELEGATION",
+ "DNS_R_GLUE",
+ "DNS_R_DNAME",
+ "DNS_R_CNAME",
+ "DNS_R_BADDB",
+ "DNS_R_ZONECUT",
+ "DNS_R_BADZONE",
+ "DNS_R_MOREDATA",
+ "DNS_R_UPTODATE",
+ "DNS_R_TSIGVERIFYFAILURE",
+ "DNS_R_TSIGERRORSET",
+ "DNS_R_SIGINVALID",
+ "DNS_R_SIGEXPIRED",
+ "DNS_R_SIGFUTURE",
+ "DNS_R_KEYUNAUTHORIZED",
+ "DNS_R_INVALIDTIME",
+ "DNS_R_EXPECTEDTSIG",
+ "DNS_R_UNEXPECTEDTSIG",
+ "DNS_R_INVALIDTKEY",
+ "DNS_R_HINT",
+ "DNS_R_DROP",
+ "DNS_R_NOTLOADED",
+ "DNS_R_NCACHENXDOMAIN",
+ "DNS_R_NCACHENXRRSET",
+ "DNS_R_WAIT",
+ "DNS_R_NOTVERIFIEDYET",
+ "DNS_R_NOIDENTITY",
+ "DNS_R_NOJOURNAL",
+ "DNS_R_ALIAS",
+ "DNS_R_USETCP",
+ "DNS_R_NOVALIDSIG",
+ "DNS_R_NOVALIDNSEC",
+ "DNS_R_NOTINSECURE",
+ "DNS_R_UNKNOWNSERVICE",
+ "DNS_R_RECOVERABLE",
+ "DNS_R_UNKNOWNOPT",
+ "DNS_R_UNEXPECTEDID",
+ "DNS_R_SEENINCLUDE",
+ "DNS_R_NOTEXACT",
+ "DNS_R_BLACKHOLED",
+ "DNS_R_BADALG",
+ "DNS_R_METATYPE",
+ "DNS_R_CNAMEANDOTHER",
+ "DNS_R_SINGLETON",
+ "DNS_R_HINTNXRRSET",
+ "DNS_R_NOMASTERFILE",
+ "DNS_R_UNKNOWNPROTO",
+ "DNS_R_CLOCKSKEW",
+ "DNS_R_BADIXFR",
+ "DNS_R_NOTAUTHORITATIVE",
+ "DNS_R_NOVALIDKEY",
+ "DNS_R_OBSOLETE",
+ "DNS_R_FROZEN",
+ "DNS_R_UNKNOWNFLAG",
+ "DNS_R_EXPECTEDRESPONSE",
+ "DNS_R_NOVALIDDS",
+ "DNS_R_NSISADDRESS",
+ "DNS_R_REMOTEFORMERR",
+ "DNS_R_TRUNCATEDTCP",
+ "DNS_R_LAME",
+ "DNS_R_UNEXPECTEDRCODE",
+ "DNS_R_UNEXPECTEDOPCODE",
+ "DNS_R_CHASEDSSERVERS",
+ "DNS_R_EMPTYNAME",
+ "DNS_R_EMPTYWILD",
+ "DNS_R_BADBITMAP",
+ "DNS_R_FROMWILDCARD",
+ "DNS_R_BADOWNERNAME",
+ "DNS_R_BADNAME",
+ "DNS_R_DYNAMIC",
+ "DNS_R_UNKNOWNCOMMAND",
+ "DNS_R_MUSTBESECURE",
+ "DNS_R_COVERINGNSEC",
+ "DNS_R_MXISADDRESS",
+ "DNS_R_DUPLICATE",
+ "DNS_R_INVALIDNSEC3",
+ "DNS_R_NOTMASTER",
+ "DNS_R_BROKENCHAIN",
+ "DNS_R_EXPIRED",
+ "DNS_R_NOTDYNAMIC",
+ "DNS_R_BADEUI",
+ "DNS_R_NTACOVERED",
+ "DNS_R_BADCDS",
+ "DNS_R_BADCDNSKEY",
+ "DNS_R_OPTERR",
+ "DNS_R_BADDNSTAP",
+ "DNS_R_BADTSIG",
+ "DNS_R_BADSIG0",
+ "DNS_R_TOOMANYRECORDS",
+ "DNS_R_VERIFYFAILURE",
+ "DNS_R_ATZONETOP",
+ "DNS_R_NOKEYMATCH",
+ "DNS_R_TOOMANYKEYS",
+ "DNS_R_KEYNOTACTIVE",
+ "DNS_R_NSEC3ITERRANGE",
+ "DNS_R_NSEC3SALTRANGE",
+ "DNS_R_NSEC3BADALG",
+ "DNS_R_NSEC3RESALT",
+ "DNS_R_INCONSISTENTRR",
+};
+
+static const char *rcode_text[DNS_R_NRCODERESULTS] = {
+ "NOERROR", /*%< 0 DNS_R_NOERROR */
+ "FORMERR", /*%< 1 DNS_R_FORMERR */
+ "SERVFAIL", /*%< 2 DNS_R_SERVFAIL */
+ "NXDOMAIN", /*%< 3 DNS_R_NXDOMAIN */
+ "NOTIMP", /*%< 4 DNS_R_NOTIMP */
+
+ "REFUSED", /*%< 5 DNS_R_REFUSED */
+ "YXDOMAIN", /*%< 6 DNS_R_YXDOMAIN */
+ "YXRRSET", /*%< 7 DNS_R_YXRRSET */
+ "NXRRSET", /*%< 8 DNS_R_NXRRSET */
+ "NOTAUTH", /*%< 9 DNS_R_NOTAUTH */
+
+ "NOTZONE", /*%< 10 DNS_R_NOTZONE */
+ "<rcode 11>", /*%< 11 DNS_R_RCODE11 */
+ "<rcode 12>", /*%< 12 DNS_R_RCODE12 */
+ "<rcode 13>", /*%< 13 DNS_R_RCODE13 */
+ "<rcode 14>", /*%< 14 DNS_R_RCODE14 */
+
+ "<rcode 15>", /*%< 15 DNS_R_RCODE15 */
+ "BADVERS", /*%< 16 DNS_R_BADVERS */
+};
+
+static const char *rcode_ids[DNS_R_NRCODERESULTS] = {
+ "DNS_R_NOERROR", "DNS_R_FORMERR", "DNS_R_SERVFAIL", "DNS_R_NXDOMAIN",
+ "DNS_R_NOTIMP", "DNS_R_REFUSED", "DNS_R_YXDOMAIN", "DNS_R_YXRRSET",
+ "DNS_R_NXRRSET", "DNS_R_NOTAUTH", "DNS_R_NOTZONE", "DNS_R_RCODE11",
+ "RNS_R_RCODE12", "DNS_R_RCODE13", "DNS_R_RCODE14", "DNS_R_RCODE15",
+ "DNS_R_BADVERS",
+};
+
+#define DNS_RESULT_RESULTSET 2
+#define DNS_RESULT_RCODERESULTSET 3
+
+static isc_once_t once = ISC_ONCE_INIT;
+
+static void
+initialize_action(void) {
+ isc_result_t result;
+
+ result = isc_result_register(ISC_RESULTCLASS_DNS, DNS_R_NRESULTS, text,
+ DNS_RESULT_RESULTSET);
+ if (result == ISC_R_SUCCESS) {
+ result = isc_result_register(ISC_RESULTCLASS_DNSRCODE,
+ DNS_R_NRCODERESULTS, rcode_text,
+ DNS_RESULT_RCODERESULTSET);
+ }
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_result_register() failed: %u", result);
+ }
+
+ result = isc_result_registerids(ISC_RESULTCLASS_DNS, DNS_R_NRESULTS,
+ ids, DNS_RESULT_RESULTSET);
+ if (result == ISC_R_SUCCESS) {
+ result = isc_result_registerids(ISC_RESULTCLASS_DNSRCODE,
+ DNS_R_NRCODERESULTS, rcode_ids,
+ DNS_RESULT_RCODERESULTSET);
+ }
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_result_registerids() failed: %u", result);
+ }
+}
+
+static void
+initialize(void) {
+ RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS);
+}
+
+const char *
+dns_result_totext(isc_result_t result) {
+ initialize();
+
+ return (isc_result_totext(result));
+}
+
+void
+dns_result_register(void) {
+ initialize();
+}
+
+dns_rcode_t
+dns_result_torcode(isc_result_t result) {
+ dns_rcode_t rcode = dns_rcode_servfail;
+
+ if (DNS_RESULT_ISRCODE(result)) {
+ /*
+ * Rcodes can't be bigger than 12 bits, which is why we
+ * AND with 0xFFF instead of 0xFFFF.
+ */
+ return ((dns_rcode_t)((result)&0xFFF));
+ }
+
+ /*
+ * Try to supply an appropriate rcode.
+ */
+ switch (result) {
+ case ISC_R_SUCCESS:
+ rcode = dns_rcode_noerror;
+ break;
+ case ISC_R_BADBASE64:
+ case ISC_R_RANGE:
+ case ISC_R_UNEXPECTEDEND:
+ case DNS_R_BADAAAA:
+ /* case DNS_R_BADBITSTRING: deprecated */
+ case DNS_R_BADCKSUM:
+ case DNS_R_BADCLASS:
+ case DNS_R_BADLABELTYPE:
+ case DNS_R_BADPOINTER:
+ case DNS_R_BADTTL:
+ case DNS_R_BADZONE:
+ /* case DNS_R_BITSTRINGTOOLONG: deprecated */
+ case DNS_R_EXTRADATA:
+ case DNS_R_LABELTOOLONG:
+ case DNS_R_NOREDATA:
+ case DNS_R_SYNTAX:
+ case DNS_R_TEXTTOOLONG:
+ case DNS_R_TOOMANYHOPS:
+ case DNS_R_TSIGERRORSET:
+ case DNS_R_UNKNOWN:
+ case DNS_R_NAMETOOLONG:
+ case DNS_R_OPTERR:
+ rcode = dns_rcode_formerr;
+ break;
+ case DNS_R_DISALLOWED:
+ rcode = dns_rcode_refused;
+ break;
+ case DNS_R_TSIGVERIFYFAILURE:
+ case DNS_R_CLOCKSKEW:
+ rcode = dns_rcode_notauth;
+ break;
+ default:
+ rcode = dns_rcode_servfail;
+ }
+
+ return (rcode);
+}
diff --git a/lib/dns/rootns.c b/lib/dns/rootns.c
new file mode 100644
index 0000000..69e2667
--- /dev/null
+++ b/lib/dns/rootns.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/buffer.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/result.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, dns_result_totext(result));
+ goto cleanup;
+ }
+
+ result = dns_db_find(db, dns_rootname, NULL, dns_rdatatype_ns, 0, now,
+ NULL, name, &rootns, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_HINTS, ISC_LOG_WARNING,
+ "checkhints%s%s: unable to get root NS rrset "
+ "from cache: %s",
+ sep, viewname, dns_result_totext(result));
+ goto cleanup;
+ }
+
+ /*
+ * Look for missing root NS names.
+ */
+ result = dns_rdataset_first(&rootns);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&rootns, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ result = in_rootns(&hintns, &ns.name);
+ if (result != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ /* missing from hints */
+ dns_name_format(&ns.name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_HINTS, ISC_LOG_WARNING,
+ "checkhints%s%s: unable to find root "
+ "NS '%s' in hints",
+ sep, viewname, namebuf);
+ } else {
+ check_address_records(view, hints, db, &ns.name, now);
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&rootns);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto cleanup;
+ }
+
+ /*
+ * Look for extra root NS names.
+ */
+ result = dns_rdataset_first(&hintns);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&hintns, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ns, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ result = in_rootns(&rootns, &ns.name);
+ if (result != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ /* extra entry in hints */
+ dns_name_format(&ns.name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_HINTS, ISC_LOG_WARNING,
+ "checkhints%s%s: extra NS '%s' in hints",
+ sep, viewname, namebuf);
+ }
+ dns_rdata_reset(&rdata);
+ result = dns_rdataset_next(&hintns);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto cleanup;
+ }
+
+cleanup:
+ if (dns_rdataset_isassociated(&rootns)) {
+ dns_rdataset_disassociate(&rootns);
+ }
+ if (dns_rdataset_isassociated(&hintns)) {
+ dns_rdataset_disassociate(&hintns);
+ }
+}
diff --git a/lib/dns/rpz.c b/lib/dns/rpz.c
new file mode 100644
index 0000000..20db72f
--- /dev/null
+++ b/lib/dns/rpz.c
@@ -0,0 +1,2891 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/net.h>
+#include <isc/netaddr.h>
+#include <isc/print.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/result.h>
+#include <dns/rpz.h>
+#include <dns/view.h>
+
+/*
+ * Parallel radix trees for databases of response policy IP addresses
+ *
+ * The radix or patricia trees are somewhat specialized to handle response
+ * policy addresses by representing the two sets of IP addresses and name
+ * server IP addresses in a single tree. One set of IP addresses is
+ * for rpz-ip policies or policies triggered by addresses in A or
+ * AAAA records in responses.
+ * The second set is for rpz-nsip policies or policies triggered by addresses
+ * in A or AAAA records for NS records that are authorities for responses.
+ *
+ * Each leaf indicates that an IP address is listed in the IP address or the
+ * name server IP address policy sub-zone (or both) of the corresponding
+ * response policy zone. The policy data such as a CNAME or an A record
+ * is kept in the policy zone. After an IP address has been found in a radix
+ * tree, the node in the policy zone's database is found by converting
+ * the IP address to a domain name in a canonical form.
+ *
+ *
+ * The response policy zone canonical form of an IPv6 address is one of:
+ * prefix.W.W.W.W.W.W.W.W
+ * prefix.WORDS.zz
+ * prefix.WORDS.zz.WORDS
+ * prefix.zz.WORDS
+ * where
+ * prefix is the prefix length of the IPv6 address between 1 and 128
+ * W is a number between 0 and 65535
+ * WORDS is one or more numbers W separated with "."
+ * zz corresponds to :: in the standard IPv6 text representation
+ *
+ * The canonical form of IPv4 addresses is:
+ * prefix.B.B.B.B
+ * where
+ * prefix is the prefix length of the address between 1 and 32
+ * B is a number between 0 and 255
+ *
+ * Names for IPv4 addresses are distinguished from IPv6 addresses by having
+ * 5 labels all of which are numbers, and a prefix between 1 and 32.
+ */
+
+/*
+ * Nodes hashtable calculation parameters
+ */
+#define DNS_RPZ_HTSIZE_MAX 24
+#define DNS_RPZ_HTSIZE_DIV 3
+
+/*
+ * Maximum number of nodes to process per quantum
+ */
+#define DNS_RPZ_QUANTUM 1024
+
+static void
+dns_rpz_update_from_db(dns_rpz_zone_t *rpz);
+
+static void
+dns_rpz_update_taskaction(isc_task_t *task, isc_event_t *event);
+
+/*
+ * Use a private definition of IPv6 addresses because s6_addr32 is not
+ * always defined and our IPv6 addresses are in non-standard byte order
+ */
+typedef uint32_t dns_rpz_cidr_word_t;
+#define DNS_RPZ_CIDR_WORD_BITS ((int)sizeof(dns_rpz_cidr_word_t) * 8)
+#define DNS_RPZ_CIDR_KEY_BITS ((int)sizeof(dns_rpz_cidr_key_t) * 8)
+#define DNS_RPZ_CIDR_WORDS (128 / DNS_RPZ_CIDR_WORD_BITS)
+typedef struct {
+ dns_rpz_cidr_word_t w[DNS_RPZ_CIDR_WORDS];
+} dns_rpz_cidr_key_t;
+
+#define ADDR_V4MAPPED 0xffff
+#define KEY_IS_IPV4(prefix, ip) \
+ ((prefix) >= 96 && (ip)->w[0] == 0 && (ip)->w[1] == 0 && \
+ (ip)->w[2] == ADDR_V4MAPPED)
+
+#define DNS_RPZ_WORD_MASK(b) \
+ ((b) == 0 ? (dns_rpz_cidr_word_t)(-1) \
+ : ((dns_rpz_cidr_word_t)(-1) \
+ << (DNS_RPZ_CIDR_WORD_BITS - (b))))
+
+/*
+ * Get bit #n from the array of words of an IP address.
+ */
+#define DNS_RPZ_IP_BIT(ip, n) \
+ (1 & ((ip)->w[(n) / DNS_RPZ_CIDR_WORD_BITS] >> \
+ (DNS_RPZ_CIDR_WORD_BITS - 1 - ((n) % DNS_RPZ_CIDR_WORD_BITS))))
+
+/*
+ * A triplet of arrays of bits flagging the existence of
+ * client-IP, IP, and NSIP policy triggers.
+ */
+typedef struct dns_rpz_addr_zbits dns_rpz_addr_zbits_t;
+struct dns_rpz_addr_zbits {
+ dns_rpz_zbits_t client_ip;
+ dns_rpz_zbits_t ip;
+ dns_rpz_zbits_t nsip;
+};
+
+/*
+ * A CIDR or radix tree node.
+ */
+struct dns_rpz_cidr_node {
+ dns_rpz_cidr_node_t *parent;
+ dns_rpz_cidr_node_t *child[2];
+ dns_rpz_cidr_key_t ip;
+ dns_rpz_prefix_t prefix;
+ dns_rpz_addr_zbits_t set;
+ dns_rpz_addr_zbits_t sum;
+};
+
+/*
+ * A pair of arrays of bits flagging the existence of
+ * QNAME and NSDNAME policy triggers.
+ */
+typedef struct dns_rpz_nm_zbits dns_rpz_nm_zbits_t;
+struct dns_rpz_nm_zbits {
+ dns_rpz_zbits_t qname;
+ dns_rpz_zbits_t ns;
+};
+
+/*
+ * The data in a RBT node has two pairs of bits for policy zones.
+ * One pair is for the corresponding name of the node such as example.com
+ * and the other pair is for a wildcard child such as *.example.com.
+ */
+typedef struct dns_rpz_nm_data dns_rpz_nm_data_t;
+struct dns_rpz_nm_data {
+ dns_rpz_nm_zbits_t set;
+ dns_rpz_nm_zbits_t wild;
+};
+
+static void
+rpz_detach(dns_rpz_zone_t **rpzp);
+
+static void
+rpz_detach_rpzs(dns_rpz_zones_t **rpzsp);
+
+#if 0
+/*
+ * Catch a name while debugging.
+ */
+static void
+catch_name(const dns_name_t *src_name, const char *tgt, const char *str) {
+ dns_fixedname_t tgt_namef;
+ dns_name_t *tgt_name;
+
+ tgt_name = dns_fixedname_initname(&tgt_namef);
+ dns_name_fromstring(tgt_name, tgt, DNS_NAME_DOWNCASE, NULL);
+ if (dns_name_equal(src_name, tgt_name)) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "rpz hit failed: %s %s", str, tgt);
+ }
+}
+#endif /* if 0 */
+
+const char *
+dns_rpz_type2str(dns_rpz_type_t type) {
+ switch (type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ return ("CLIENT-IP");
+ case DNS_RPZ_TYPE_QNAME:
+ return ("QNAME");
+ case DNS_RPZ_TYPE_IP:
+ return ("IP");
+ case DNS_RPZ_TYPE_NSIP:
+ return ("NSIP");
+ case DNS_RPZ_TYPE_NSDNAME:
+ return ("NSDNAME");
+ case DNS_RPZ_TYPE_BAD:
+ break;
+ }
+ FATAL_ERROR(__FILE__, __LINE__, "impossible rpz type %d", type);
+ return ("impossible");
+}
+
+dns_rpz_policy_t
+dns_rpz_str2policy(const char *str) {
+ static struct {
+ const char *str;
+ dns_rpz_policy_t policy;
+ } tbl[] = {
+ { "given", DNS_RPZ_POLICY_GIVEN },
+ { "disabled", DNS_RPZ_POLICY_DISABLED },
+ { "passthru", DNS_RPZ_POLICY_PASSTHRU },
+ { "drop", DNS_RPZ_POLICY_DROP },
+ { "tcp-only", DNS_RPZ_POLICY_TCP_ONLY },
+ { "nxdomain", DNS_RPZ_POLICY_NXDOMAIN },
+ { "nodata", DNS_RPZ_POLICY_NODATA },
+ { "cname", DNS_RPZ_POLICY_CNAME },
+ { "no-op", DNS_RPZ_POLICY_PASSTHRU }, /* old passthru */
+ };
+ unsigned int n;
+
+ if (str == NULL) {
+ return (DNS_RPZ_POLICY_ERROR);
+ }
+ for (n = 0; n < sizeof(tbl) / sizeof(tbl[0]); ++n) {
+ if (!strcasecmp(tbl[n].str, str)) {
+ return (tbl[n].policy);
+ }
+ }
+ return (DNS_RPZ_POLICY_ERROR);
+}
+
+const char *
+dns_rpz_policy2str(dns_rpz_policy_t policy) {
+ const char *str;
+
+ switch (policy) {
+ case DNS_RPZ_POLICY_PASSTHRU:
+ str = "PASSTHRU";
+ break;
+ case DNS_RPZ_POLICY_DROP:
+ str = "DROP";
+ break;
+ case DNS_RPZ_POLICY_TCP_ONLY:
+ str = "TCP-ONLY";
+ break;
+ case DNS_RPZ_POLICY_NXDOMAIN:
+ str = "NXDOMAIN";
+ break;
+ case DNS_RPZ_POLICY_NODATA:
+ str = "NODATA";
+ break;
+ case DNS_RPZ_POLICY_RECORD:
+ str = "Local-Data";
+ break;
+ case DNS_RPZ_POLICY_CNAME:
+ case DNS_RPZ_POLICY_WILDCNAME:
+ str = "CNAME";
+ break;
+ case DNS_RPZ_POLICY_MISS:
+ str = "MISS";
+ break;
+ case DNS_RPZ_POLICY_DNS64:
+ str = "DNS64";
+ break;
+ case DNS_RPZ_POLICY_ERROR:
+ str = "ERROR";
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return (str);
+}
+
+/*
+ * Return the bit number of the highest set bit in 'zbit'.
+ * (for example, 0x01 returns 0, 0xFF returns 7, etc.)
+ */
+static int
+zbit_to_num(dns_rpz_zbits_t zbit) {
+ dns_rpz_num_t rpz_num;
+
+ REQUIRE(zbit != 0);
+ rpz_num = 0;
+ if ((zbit & 0xffffffff00000000ULL) != 0) {
+ zbit >>= 32;
+ rpz_num += 32;
+ }
+ if ((zbit & 0xffff0000) != 0) {
+ zbit >>= 16;
+ rpz_num += 16;
+ }
+ if ((zbit & 0xff00) != 0) {
+ zbit >>= 8;
+ rpz_num += 8;
+ }
+ if ((zbit & 0xf0) != 0) {
+ zbit >>= 4;
+ rpz_num += 4;
+ }
+ if ((zbit & 0xc) != 0) {
+ zbit >>= 2;
+ rpz_num += 2;
+ }
+ if ((zbit & 2) != 0) {
+ ++rpz_num;
+ }
+ return (rpz_num);
+}
+
+/*
+ * Make a set of bit masks given one or more bits and their type.
+ */
+static void
+make_addr_set(dns_rpz_addr_zbits_t *tgt_set, dns_rpz_zbits_t zbits,
+ dns_rpz_type_t type) {
+ switch (type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ tgt_set->client_ip = zbits;
+ tgt_set->ip = 0;
+ tgt_set->nsip = 0;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ tgt_set->client_ip = 0;
+ tgt_set->ip = zbits;
+ tgt_set->nsip = 0;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ tgt_set->client_ip = 0;
+ tgt_set->ip = 0;
+ tgt_set->nsip = zbits;
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+static void
+make_nm_set(dns_rpz_nm_zbits_t *tgt_set, dns_rpz_num_t rpz_num,
+ dns_rpz_type_t type) {
+ switch (type) {
+ case DNS_RPZ_TYPE_QNAME:
+ tgt_set->qname = DNS_RPZ_ZBIT(rpz_num);
+ tgt_set->ns = 0;
+ break;
+ case DNS_RPZ_TYPE_NSDNAME:
+ tgt_set->qname = 0;
+ tgt_set->ns = DNS_RPZ_ZBIT(rpz_num);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+/*
+ * Mark a node and all of its parents as having client-IP, IP, or NSIP data
+ */
+static void
+set_sum_pair(dns_rpz_cidr_node_t *cnode) {
+ dns_rpz_cidr_node_t *child;
+ dns_rpz_addr_zbits_t sum;
+
+ do {
+ sum = cnode->set;
+
+ child = cnode->child[0];
+ if (child != NULL) {
+ sum.client_ip |= child->sum.client_ip;
+ sum.ip |= child->sum.ip;
+ sum.nsip |= child->sum.nsip;
+ }
+
+ child = cnode->child[1];
+ if (child != NULL) {
+ sum.client_ip |= child->sum.client_ip;
+ sum.ip |= child->sum.ip;
+ sum.nsip |= child->sum.nsip;
+ }
+
+ if (cnode->sum.client_ip == sum.client_ip &&
+ cnode->sum.ip == sum.ip && cnode->sum.nsip == sum.nsip)
+ {
+ break;
+ }
+ cnode->sum = sum;
+ cnode = cnode->parent;
+ } while (cnode != NULL);
+}
+
+/* Caller must hold rpzs->maint_lock */
+static void
+fix_qname_skip_recurse(dns_rpz_zones_t *rpzs) {
+ dns_rpz_zbits_t mask;
+
+ /*
+ * qname_wait_recurse and qname_skip_recurse are used to
+ * implement the "qname-wait-recurse" config option.
+ *
+ * When "qname-wait-recurse" is yes, no processing happens without
+ * recursion. In this case, qname_wait_recurse is true, and
+ * qname_skip_recurse (a bit field indicating which policy zones
+ * can be processed without recursion) is set to all 0's by
+ * fix_qname_skip_recurse().
+ *
+ * When "qname-wait-recurse" is no, qname_skip_recurse may be
+ * set to a non-zero value by fix_qname_skip_recurse(). The mask
+ * has to have bits set for the policy zones for which
+ * processing may continue without recursion, and bits cleared
+ * for the rest.
+ *
+ * (1) The ARM says:
+ *
+ * The "qname-wait-recurse no" option overrides that default
+ * behavior when recursion cannot change a non-error
+ * response. The option does not affect QNAME or client-IP
+ * triggers in policy zones listed after other zones
+ * containing IP, NSIP and NSDNAME triggers, because those may
+ * depend on the A, AAAA, and NS records that would be found
+ * during recursive resolution.
+ *
+ * Let's consider the following:
+ *
+ * zbits_req = (rpzs->have.ipv4 | rpzs->have.ipv6 |
+ * rpzs->have.nsdname |
+ * rpzs->have.nsipv4 | rpzs->have.nsipv6);
+ *
+ * zbits_req now contains bits set for zones which require
+ * recursion.
+ *
+ * But going by the description in the ARM, if the first policy
+ * zone requires recursion, then all zones after that (higher
+ * order bits) have to wait as well. If the Nth zone requires
+ * recursion, then (N+1)th zone onwards all need to wait.
+ *
+ * So mapping this, examples:
+ *
+ * zbits_req = 0b000 mask = 0xffffffff (no zones have to wait for
+ * recursion)
+ * zbits_req = 0b001 mask = 0x00000000 (all zones have to wait)
+ * zbits_req = 0b010 mask = 0x00000001 (the first zone doesn't have to
+ * wait, second zone onwards need
+ * to wait)
+ * zbits_req = 0b011 mask = 0x00000000 (all zones have to wait)
+ * zbits_req = 0b100 mask = 0x00000011 (the 1st and 2nd zones don't
+ * have to wait, third zone
+ * onwards need to wait)
+ *
+ * More generally, we have to count the number of trailing 0
+ * bits in zbits_req and only these can be processed without
+ * recursion. All the rest need to wait.
+ *
+ * (2) The ARM says that "qname-wait-recurse no" option
+ * overrides the default behavior when recursion cannot change a
+ * non-error response. So, in the order of listing of policy
+ * zones, within the first policy zone where recursion may be
+ * required, we should first allow CLIENT-IP and QNAME policy
+ * records to be attempted without recursion.
+ */
+
+ /*
+ * Get a mask covering all policy zones that are not subordinate to
+ * other policy zones containing triggers that require that the
+ * qname be resolved before they can be checked.
+ */
+ rpzs->have.client_ip = rpzs->have.client_ipv4 | rpzs->have.client_ipv6;
+ rpzs->have.ip = rpzs->have.ipv4 | rpzs->have.ipv6;
+ rpzs->have.nsip = rpzs->have.nsipv4 | rpzs->have.nsipv6;
+
+ if (rpzs->p.qname_wait_recurse) {
+ mask = 0;
+ } else {
+ dns_rpz_zbits_t zbits_req;
+ dns_rpz_zbits_t zbits_notreq;
+ dns_rpz_zbits_t mask2;
+ dns_rpz_zbits_t req_mask;
+
+ /*
+ * Get the masks of zones with policies that
+ * do/don't require recursion
+ */
+
+ zbits_req = (rpzs->have.ipv4 | rpzs->have.ipv6 |
+ rpzs->have.nsdname | rpzs->have.nsipv4 |
+ rpzs->have.nsipv6);
+ zbits_notreq = (rpzs->have.client_ip | rpzs->have.qname);
+
+ if (zbits_req == 0) {
+ mask = DNS_RPZ_ALL_ZBITS;
+ goto set;
+ }
+
+ /*
+ * req_mask is a mask covering used bits in
+ * zbits_req. (For instance, 0b1 => 0b1, 0b101 => 0b111,
+ * 0b11010101 => 0b11111111).
+ */
+ req_mask = zbits_req;
+ req_mask |= req_mask >> 1;
+ req_mask |= req_mask >> 2;
+ req_mask |= req_mask >> 4;
+ req_mask |= req_mask >> 8;
+ req_mask |= req_mask >> 16;
+ req_mask |= req_mask >> 32;
+
+ /*
+ * There's no point in skipping recursion for a later
+ * zone if it is required in a previous zone.
+ */
+ if ((zbits_notreq & req_mask) == 0) {
+ mask = 0;
+ goto set;
+ }
+
+ /*
+ * This bit arithmetic creates a mask of zones in which
+ * it is okay to skip recursion. After the first zone
+ * that has to wait for recursion, all the others have
+ * to wait as well, so we want to create a mask in which
+ * all the trailing zeroes in zbits_req are are 1, and
+ * more significant bits are 0. (For instance,
+ * 0x0700 => 0x00ff, 0x0007 => 0x0000)
+ */
+ mask = ~(zbits_req | ((~zbits_req) + 1));
+
+ /*
+ * As mentioned in (2) above, the zone corresponding to
+ * the least significant zero could have its CLIENT-IP
+ * and QNAME policies checked before recursion, if it
+ * has any of those policies. So if it does, we
+ * can set its 0 to 1.
+ *
+ * Locate the least significant 0 bit in the mask (for
+ * instance, 0xff => 0x100)...
+ */
+ mask2 = (mask << 1) & ~mask;
+
+ /*
+ * Also set the bit for zone 0, because if it's in
+ * zbits_notreq then it's definitely okay to attempt to
+ * skip recursion for zone 0...
+ */
+ mask2 |= 1;
+
+ /* Clear any bits *not* in zbits_notreq... */
+ mask2 &= zbits_notreq;
+
+ /* And merge the result into the skip-recursion mask */
+ mask |= mask2;
+ }
+
+set:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB,
+ DNS_RPZ_DEBUG_QUIET,
+ "computed RPZ qname_skip_recurse mask=0x%" PRIx64,
+ (uint64_t)mask);
+ rpzs->have.qname_skip_recurse = mask;
+}
+
+static void
+adj_trigger_cnt(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+ dns_rpz_type_t rpz_type, const dns_rpz_cidr_key_t *tgt_ip,
+ dns_rpz_prefix_t tgt_prefix, bool inc) {
+ dns_rpz_trigger_counter_t *cnt = NULL;
+ dns_rpz_zbits_t *have = NULL;
+
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ REQUIRE(tgt_ip != NULL);
+ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+ cnt = &rpzs->triggers[rpz_num].client_ipv4;
+ have = &rpzs->have.client_ipv4;
+ } else {
+ cnt = &rpzs->triggers[rpz_num].client_ipv6;
+ have = &rpzs->have.client_ipv6;
+ }
+ break;
+ case DNS_RPZ_TYPE_QNAME:
+ cnt = &rpzs->triggers[rpz_num].qname;
+ have = &rpzs->have.qname;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ REQUIRE(tgt_ip != NULL);
+ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+ cnt = &rpzs->triggers[rpz_num].ipv4;
+ have = &rpzs->have.ipv4;
+ } else {
+ cnt = &rpzs->triggers[rpz_num].ipv6;
+ have = &rpzs->have.ipv6;
+ }
+ break;
+ case DNS_RPZ_TYPE_NSDNAME:
+ cnt = &rpzs->triggers[rpz_num].nsdname;
+ have = &rpzs->have.nsdname;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ REQUIRE(tgt_ip != NULL);
+ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+ cnt = &rpzs->triggers[rpz_num].nsipv4;
+ have = &rpzs->have.nsipv4;
+ } else {
+ cnt = &rpzs->triggers[rpz_num].nsipv6;
+ have = &rpzs->have.nsipv6;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (inc) {
+ if (++*cnt == 1U) {
+ *have |= DNS_RPZ_ZBIT(rpz_num);
+ fix_qname_skip_recurse(rpzs);
+ }
+ } else {
+ REQUIRE(*cnt != 0U);
+ if (--*cnt == 0U) {
+ *have &= ~DNS_RPZ_ZBIT(rpz_num);
+ fix_qname_skip_recurse(rpzs);
+ }
+ }
+}
+
+static dns_rpz_cidr_node_t *
+new_node(dns_rpz_zones_t *rpzs, const dns_rpz_cidr_key_t *ip,
+ dns_rpz_prefix_t prefix, const dns_rpz_cidr_node_t *child) {
+ dns_rpz_cidr_node_t *node;
+ int i, words, wlen;
+
+ node = isc_mem_get(rpzs->mctx, sizeof(*node));
+ memset(node, 0, sizeof(*node));
+
+ if (child != NULL) {
+ node->sum = child->sum;
+ }
+
+ node->prefix = prefix;
+ words = prefix / DNS_RPZ_CIDR_WORD_BITS;
+ wlen = prefix % DNS_RPZ_CIDR_WORD_BITS;
+ i = 0;
+ while (i < words) {
+ node->ip.w[i] = ip->w[i];
+ ++i;
+ }
+ if (wlen != 0) {
+ node->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen);
+ ++i;
+ }
+ while (i < DNS_RPZ_CIDR_WORDS) {
+ node->ip.w[i++] = 0;
+ }
+
+ return (node);
+}
+
+static void
+badname(int level, const dns_name_t *name, const char *str1, const char *str2) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ /*
+ * bin/tests/system/rpz/tests.sh looks for "invalid rpz".
+ */
+ if (level < DNS_RPZ_DEBUG_QUIET && isc_log_wouldlog(dns_lctx, level)) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, level,
+ "invalid rpz IP address \"%s\"%s%s", namebuf,
+ str1, str2);
+ }
+}
+
+/*
+ * Convert an IP address from radix tree binary (host byte order) to
+ * to its canonical response policy domain name without the origin of the
+ * policy zone.
+ *
+ * Generate a name for an IPv6 address that fits RFC 5952, except that our
+ * reversed format requires that when the length of the consecutive 16-bit
+ * 0 fields are equal (e.g., 1.0.0.1.0.0.db8.2001 corresponding to
+ * 2001:db8:0:0:1:0:0:1), we shorted the last instead of the first
+ * (e.g., 1.0.0.1.zz.db8.2001 corresponding to 2001:db8::1:0:0:1).
+ */
+static isc_result_t
+ip2name(const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix,
+ const dns_name_t *base_name, dns_name_t *ip_name) {
+#ifndef INET6_ADDRSTRLEN
+#define INET6_ADDRSTRLEN 46
+#endif /* ifndef INET6_ADDRSTRLEN */
+ int w[DNS_RPZ_CIDR_WORDS * 2];
+ char str[1 + 8 + 1 + INET6_ADDRSTRLEN + 1];
+ isc_buffer_t buffer;
+ isc_result_t result;
+ int best_first, best_len, cur_first, cur_len;
+ int i, n, len;
+
+ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+ len = snprintf(str, sizeof(str), "%u.%u.%u.%u.%u",
+ tgt_prefix - 96U, tgt_ip->w[3] & 0xffU,
+ (tgt_ip->w[3] >> 8) & 0xffU,
+ (tgt_ip->w[3] >> 16) & 0xffU,
+ (tgt_ip->w[3] >> 24) & 0xffU);
+ if (len < 0 || (size_t)len >= sizeof(str)) {
+ return (ISC_R_FAILURE);
+ }
+ } else {
+ len = snprintf(str, sizeof(str), "%d", tgt_prefix);
+ if (len < 0 || (size_t)len >= sizeof(str)) {
+ return (ISC_R_FAILURE);
+ }
+
+ for (i = 0; i < DNS_RPZ_CIDR_WORDS; i++) {
+ w[i * 2 + 1] =
+ ((tgt_ip->w[DNS_RPZ_CIDR_WORDS - 1 - i] >> 16) &
+ 0xffff);
+ w[i * 2] = tgt_ip->w[DNS_RPZ_CIDR_WORDS - 1 - i] &
+ 0xffff;
+ }
+ /*
+ * Find the start and length of the first longest sequence
+ * of zeros in the address.
+ */
+ best_first = -1;
+ best_len = 0;
+ cur_first = -1;
+ cur_len = 0;
+ for (n = 0; n <= 7; ++n) {
+ if (w[n] != 0) {
+ cur_len = 0;
+ cur_first = -1;
+ } else {
+ ++cur_len;
+ if (cur_first < 0) {
+ cur_first = n;
+ } else if (cur_len >= best_len) {
+ best_first = cur_first;
+ best_len = cur_len;
+ }
+ }
+ }
+
+ for (n = 0; n <= 7; ++n) {
+ INSIST(len > 0 && (size_t)len < sizeof(str));
+ if (n == best_first) {
+ i = snprintf(str + len, sizeof(str) - len,
+ ".zz");
+ n += best_len - 1;
+ } else {
+ i = snprintf(str + len, sizeof(str) - len,
+ ".%x", w[n]);
+ }
+ if (i < 0 || (size_t)i >= (size_t)(sizeof(str) - len)) {
+ return (ISC_R_FAILURE);
+ }
+ len += i;
+ }
+ }
+
+ isc_buffer_init(&buffer, str, sizeof(str));
+ isc_buffer_add(&buffer, len);
+ result = dns_name_fromtext(ip_name, &buffer, base_name, 0, NULL);
+ return (result);
+}
+
+/*
+ * Determine the type of a name in a response policy zone.
+ */
+static dns_rpz_type_t
+type_from_name(const dns_rpz_zones_t *rpzs, dns_rpz_zone_t *rpz,
+ const dns_name_t *name) {
+ if (dns_name_issubdomain(name, &rpz->ip)) {
+ return (DNS_RPZ_TYPE_IP);
+ }
+
+ if (dns_name_issubdomain(name, &rpz->client_ip)) {
+ return (DNS_RPZ_TYPE_CLIENT_IP);
+ }
+
+ if ((rpzs->p.nsip_on & DNS_RPZ_ZBIT(rpz->num)) != 0 &&
+ dns_name_issubdomain(name, &rpz->nsip))
+ {
+ return (DNS_RPZ_TYPE_NSIP);
+ }
+
+ if ((rpzs->p.nsdname_on & DNS_RPZ_ZBIT(rpz->num)) != 0 &&
+ dns_name_issubdomain(name, &rpz->nsdname))
+ {
+ return (DNS_RPZ_TYPE_NSDNAME);
+ }
+
+ return (DNS_RPZ_TYPE_QNAME);
+}
+
+/*
+ * Convert an IP address from canonical response policy domain name form
+ * to radix tree binary (host byte order) for adding or deleting IP or NSIP
+ * data.
+ */
+static isc_result_t
+name2ipkey(int log_level, const dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+ dns_rpz_type_t rpz_type, const dns_name_t *src_name,
+ dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t *tgt_prefix,
+ dns_rpz_addr_zbits_t *new_set) {
+ dns_rpz_zone_t *rpz;
+ char ip_str[DNS_NAME_FORMATSIZE], ip2_str[DNS_NAME_FORMATSIZE];
+ dns_offsets_t ip_name_offsets;
+ dns_fixedname_t ip_name2f;
+ dns_name_t ip_name, *ip_name2;
+ const char *prefix_str, *cp, *end;
+ char *cp2;
+ int ip_labels;
+ dns_rpz_prefix_t prefix;
+ unsigned long prefix_num, l;
+ isc_result_t result;
+ int i;
+
+ REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
+ rpz = rpzs->zones[rpz_num];
+ REQUIRE(rpz != NULL);
+
+ make_addr_set(new_set, DNS_RPZ_ZBIT(rpz_num), rpz_type);
+
+ ip_labels = dns_name_countlabels(src_name);
+ if (rpz_type == DNS_RPZ_TYPE_QNAME) {
+ ip_labels -= dns_name_countlabels(&rpz->origin);
+ } else {
+ ip_labels -= dns_name_countlabels(&rpz->nsdname);
+ }
+ if (ip_labels < 2) {
+ badname(log_level, src_name, "; too short", "");
+ return (ISC_R_FAILURE);
+ }
+ dns_name_init(&ip_name, ip_name_offsets);
+ dns_name_getlabelsequence(src_name, 0, ip_labels, &ip_name);
+
+ /*
+ * Get text for the IP address
+ */
+ dns_name_format(&ip_name, ip_str, sizeof(ip_str));
+ end = &ip_str[strlen(ip_str) + 1];
+ prefix_str = ip_str;
+
+ prefix_num = strtoul(prefix_str, &cp2, 10);
+ if (*cp2 != '.') {
+ badname(log_level, src_name, "; invalid leading prefix length",
+ "");
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * Patch in trailing nul character to print just the length
+ * label (for various cases below).
+ */
+ *cp2 = '\0';
+ if (prefix_num < 1U || prefix_num > 128U) {
+ badname(log_level, src_name, "; invalid prefix length of ",
+ prefix_str);
+ return (ISC_R_FAILURE);
+ }
+ cp = cp2 + 1;
+
+ if (--ip_labels == 4 && !strchr(cp, 'z')) {
+ /*
+ * Convert an IPv4 address
+ * from the form "prefix.z.y.x.w"
+ */
+ if (prefix_num > 32U) {
+ badname(log_level, src_name,
+ "; invalid IPv4 prefix length of ", prefix_str);
+ return (ISC_R_FAILURE);
+ }
+ prefix_num += 96;
+ *tgt_prefix = (dns_rpz_prefix_t)prefix_num;
+ tgt_ip->w[0] = 0;
+ tgt_ip->w[1] = 0;
+ tgt_ip->w[2] = ADDR_V4MAPPED;
+ tgt_ip->w[3] = 0;
+ for (i = 0; i < 32; i += 8) {
+ l = strtoul(cp, &cp2, 10);
+ if (l > 255U || (*cp2 != '.' && *cp2 != '\0')) {
+ if (*cp2 == '.') {
+ *cp2 = '\0';
+ }
+ badname(log_level, src_name,
+ "; invalid IPv4 octet ", cp);
+ return (ISC_R_FAILURE);
+ }
+ tgt_ip->w[3] |= l << i;
+ cp = cp2 + 1;
+ }
+ } else {
+ /*
+ * Convert a text IPv6 address.
+ */
+ *tgt_prefix = (dns_rpz_prefix_t)prefix_num;
+ for (i = 0; ip_labels > 0 && i < DNS_RPZ_CIDR_WORDS * 2;
+ ip_labels--)
+ {
+ if (cp[0] == 'z' && cp[1] == 'z' &&
+ (cp[2] == '.' || cp[2] == '\0') && i <= 6)
+ {
+ do {
+ if ((i & 1) == 0) {
+ tgt_ip->w[3 - i / 2] = 0;
+ }
+ ++i;
+ } while (ip_labels + i <= 8);
+ cp += 3;
+ } else {
+ l = strtoul(cp, &cp2, 16);
+ if (l > 0xffffu ||
+ (*cp2 != '.' && *cp2 != '\0'))
+ {
+ if (*cp2 == '.') {
+ *cp2 = '\0';
+ }
+ badname(log_level, src_name,
+ "; invalid IPv6 word ", cp);
+ return (ISC_R_FAILURE);
+ }
+ if ((i & 1) == 0) {
+ tgt_ip->w[3 - i / 2] = l;
+ } else {
+ tgt_ip->w[3 - i / 2] |= l << 16;
+ }
+ i++;
+ cp = cp2 + 1;
+ }
+ }
+ }
+ if (cp != end) {
+ badname(log_level, src_name, "", "");
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * Check for 1s after the prefix length.
+ */
+ prefix = (dns_rpz_prefix_t)prefix_num;
+ while (prefix < DNS_RPZ_CIDR_KEY_BITS) {
+ dns_rpz_cidr_word_t aword;
+
+ i = prefix % DNS_RPZ_CIDR_WORD_BITS;
+ aword = tgt_ip->w[prefix / DNS_RPZ_CIDR_WORD_BITS];
+ if ((aword & ~DNS_RPZ_WORD_MASK(i)) != 0) {
+ badname(log_level, src_name,
+ "; too small prefix length of ", prefix_str);
+ return (ISC_R_FAILURE);
+ }
+ prefix -= i;
+ prefix += DNS_RPZ_CIDR_WORD_BITS;
+ }
+
+ /*
+ * Complain about bad names but be generous and accept them.
+ */
+ if (log_level < DNS_RPZ_DEBUG_QUIET &&
+ isc_log_wouldlog(dns_lctx, log_level))
+ {
+ /*
+ * Convert the address back to a canonical domain name
+ * to ensure that the original name is in canonical form.
+ */
+ ip_name2 = dns_fixedname_initname(&ip_name2f);
+ result = ip2name(tgt_ip, (dns_rpz_prefix_t)prefix_num, NULL,
+ ip_name2);
+ if (result != ISC_R_SUCCESS ||
+ !dns_name_equal(&ip_name, ip_name2))
+ {
+ dns_name_format(ip_name2, ip2_str, sizeof(ip2_str));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, log_level,
+ "rpz IP address \"%s\""
+ " is not the canonical \"%s\"",
+ ip_str, ip2_str);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Get trigger name and data bits for adding or deleting summary NSDNAME
+ * or QNAME data.
+ */
+static void
+name2data(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name, dns_name_t *trig_name,
+ dns_rpz_nm_data_t *new_data) {
+ dns_rpz_zone_t *rpz;
+ dns_offsets_t tmp_name_offsets;
+ dns_name_t tmp_name;
+ unsigned int prefix_len, n;
+
+ REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
+ rpz = rpzs->zones[rpz_num];
+ REQUIRE(rpz != NULL);
+
+ /*
+ * Handle wildcards by putting only the parent into the
+ * summary RBT. The summary database only causes a check of the
+ * real policy zone where wildcards will be handled.
+ */
+ if (dns_name_iswildcard(src_name)) {
+ prefix_len = 1;
+ memset(&new_data->set, 0, sizeof(new_data->set));
+ make_nm_set(&new_data->wild, rpz_num, rpz_type);
+ } else {
+ prefix_len = 0;
+ make_nm_set(&new_data->set, rpz_num, rpz_type);
+ memset(&new_data->wild, 0, sizeof(new_data->wild));
+ }
+
+ dns_name_init(&tmp_name, tmp_name_offsets);
+ n = dns_name_countlabels(src_name);
+ n -= prefix_len;
+ if (rpz_type == DNS_RPZ_TYPE_QNAME) {
+ n -= dns_name_countlabels(&rpz->origin);
+ } else {
+ n -= dns_name_countlabels(&rpz->nsdname);
+ }
+ dns_name_getlabelsequence(src_name, prefix_len, n, &tmp_name);
+ (void)dns_name_concatenate(&tmp_name, dns_rootname, trig_name, NULL);
+}
+
+#ifndef HAVE_BUILTIN_CLZ
+/**
+ * \brief Count Leading Zeros: Find the location of the left-most set
+ * bit.
+ */
+static unsigned int
+clz(dns_rpz_cidr_word_t w) {
+ unsigned int bit;
+
+ bit = DNS_RPZ_CIDR_WORD_BITS - 1;
+
+ if ((w & 0xffff0000) != 0) {
+ w >>= 16;
+ bit -= 16;
+ }
+
+ if ((w & 0xff00) != 0) {
+ w >>= 8;
+ bit -= 8;
+ }
+
+ if ((w & 0xf0) != 0) {
+ w >>= 4;
+ bit -= 4;
+ }
+
+ if ((w & 0xc) != 0) {
+ w >>= 2;
+ bit -= 2;
+ }
+
+ if ((w & 2) != 0) {
+ --bit;
+ }
+
+ return (bit);
+}
+#endif /* ifndef HAVE_BUILTIN_CLZ */
+
+/*
+ * Find the first differing bit in two keys (IP addresses).
+ */
+static int
+diff_keys(const dns_rpz_cidr_key_t *key1, dns_rpz_prefix_t prefix1,
+ const dns_rpz_cidr_key_t *key2, dns_rpz_prefix_t prefix2) {
+ dns_rpz_cidr_word_t delta;
+ dns_rpz_prefix_t maxbit, bit;
+ int i;
+
+ bit = 0;
+ maxbit = ISC_MIN(prefix1, prefix2);
+
+ /*
+ * find the first differing words
+ */
+ for (i = 0; bit < maxbit; i++, bit += DNS_RPZ_CIDR_WORD_BITS) {
+ delta = key1->w[i] ^ key2->w[i];
+ if (ISC_UNLIKELY(delta != 0)) {
+#ifdef HAVE_BUILTIN_CLZ
+ bit += __builtin_clz(delta);
+#else /* ifdef HAVE_BUILTIN_CLZ */
+ bit += clz(delta);
+#endif /* ifdef HAVE_BUILTIN_CLZ */
+ break;
+ }
+ }
+ return (ISC_MIN(bit, maxbit));
+}
+
+/*
+ * Given a hit while searching the radix trees,
+ * clear all bits for higher numbered zones.
+ */
+static dns_rpz_zbits_t
+trim_zbits(dns_rpz_zbits_t zbits, dns_rpz_zbits_t found) {
+ dns_rpz_zbits_t x;
+
+ /*
+ * Isolate the first or smallest numbered hit bit.
+ * Make a mask of that bit and all smaller numbered bits.
+ */
+ x = zbits & found;
+ x &= (~x + 1);
+ x = (x << 1) - 1;
+ zbits &= x;
+ return (zbits);
+}
+
+/*
+ * Search a radix tree for an IP address for ordinary lookup
+ * or for a CIDR block adding or deleting an entry
+ *
+ * Return ISC_R_SUCCESS, DNS_R_PARTIALMATCH, ISC_R_NOTFOUND,
+ * and *found=longest match node
+ * or with create==true, ISC_R_EXISTS or ISC_R_NOMEMORY
+ */
+static isc_result_t
+search(dns_rpz_zones_t *rpzs, const dns_rpz_cidr_key_t *tgt_ip,
+ dns_rpz_prefix_t tgt_prefix, const dns_rpz_addr_zbits_t *tgt_set,
+ bool create, dns_rpz_cidr_node_t **found) {
+ dns_rpz_cidr_node_t *cur, *parent, *child, *new_parent, *sibling;
+ dns_rpz_addr_zbits_t set;
+ int cur_num, child_num;
+ dns_rpz_prefix_t dbit;
+ isc_result_t find_result;
+
+ set = *tgt_set;
+ find_result = ISC_R_NOTFOUND;
+ *found = NULL;
+ cur = rpzs->cidr;
+ parent = NULL;
+ cur_num = 0;
+ for (;;) {
+ if (cur == NULL) {
+ /*
+ * No child so we cannot go down.
+ * Quit with whatever we already found
+ * or add the target as a child of the current parent.
+ */
+ if (!create) {
+ return (find_result);
+ }
+ child = new_node(rpzs, tgt_ip, tgt_prefix, NULL);
+ if (child == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ if (parent == NULL) {
+ rpzs->cidr = child;
+ } else {
+ parent->child[cur_num] = child;
+ }
+ child->parent = parent;
+ child->set.client_ip |= tgt_set->client_ip;
+ child->set.ip |= tgt_set->ip;
+ child->set.nsip |= tgt_set->nsip;
+ set_sum_pair(child);
+ *found = child;
+ return (ISC_R_SUCCESS);
+ }
+
+ if ((cur->sum.client_ip & set.client_ip) == 0 &&
+ (cur->sum.ip & set.ip) == 0 &&
+ (cur->sum.nsip & set.nsip) == 0)
+ {
+ /*
+ * This node has no relevant data
+ * and is in none of the target trees.
+ * Pretend it does not exist if we are not adding.
+ *
+ * If we are adding, continue down to eventually add
+ * a node and mark/put this node in the correct tree.
+ */
+ if (!create) {
+ return (find_result);
+ }
+ }
+
+ dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->prefix);
+ /*
+ * dbit <= tgt_prefix and dbit <= cur->prefix always.
+ * We are finished searching if we matched all of the target.
+ */
+ if (dbit == tgt_prefix) {
+ if (tgt_prefix == cur->prefix) {
+ /*
+ * The node's key matches the target exactly.
+ */
+ if ((cur->set.client_ip & set.client_ip) != 0 ||
+ (cur->set.ip & set.ip) != 0 ||
+ (cur->set.nsip & set.nsip) != 0)
+ {
+ /*
+ * It is the answer if it has data.
+ */
+ *found = cur;
+ if (create) {
+ find_result = ISC_R_EXISTS;
+ } else {
+ find_result = ISC_R_SUCCESS;
+ }
+ } else if (create) {
+ /*
+ * The node lacked relevant data,
+ * but will have it now.
+ */
+ cur->set.client_ip |=
+ tgt_set->client_ip;
+ cur->set.ip |= tgt_set->ip;
+ cur->set.nsip |= tgt_set->nsip;
+ set_sum_pair(cur);
+ *found = cur;
+ find_result = ISC_R_SUCCESS;
+ }
+ return (find_result);
+ }
+
+ /*
+ * We know tgt_prefix < cur->prefix which means that
+ * the target is shorter than the current node.
+ * Add the target as the current node's parent.
+ */
+ if (!create) {
+ return (find_result);
+ }
+
+ new_parent = new_node(rpzs, tgt_ip, tgt_prefix, cur);
+ if (new_parent == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ new_parent->parent = parent;
+ if (parent == NULL) {
+ rpzs->cidr = new_parent;
+ } else {
+ parent->child[cur_num] = new_parent;
+ }
+ child_num = DNS_RPZ_IP_BIT(&cur->ip, tgt_prefix);
+ new_parent->child[child_num] = cur;
+ cur->parent = new_parent;
+ new_parent->set = *tgt_set;
+ set_sum_pair(new_parent);
+ *found = new_parent;
+ return (ISC_R_SUCCESS);
+ }
+
+ if (dbit == cur->prefix) {
+ if ((cur->set.client_ip & set.client_ip) != 0 ||
+ (cur->set.ip & set.ip) != 0 ||
+ (cur->set.nsip & set.nsip) != 0)
+ {
+ /*
+ * We have a partial match between of all of the
+ * current node but only part of the target.
+ * Continue searching for other hits in the
+ * same or lower numbered trees.
+ */
+ find_result = DNS_R_PARTIALMATCH;
+ *found = cur;
+ set.client_ip = trim_zbits(set.client_ip,
+ cur->set.client_ip);
+ set.ip = trim_zbits(set.ip, cur->set.ip);
+ set.nsip = trim_zbits(set.nsip, cur->set.nsip);
+ }
+ parent = cur;
+ cur_num = DNS_RPZ_IP_BIT(tgt_ip, dbit);
+ cur = cur->child[cur_num];
+ continue;
+ }
+
+ /*
+ * dbit < tgt_prefix and dbit < cur->prefix,
+ * so we failed to match both the target and the current node.
+ * Insert a fork of a parent above the current node and
+ * add the target as a sibling of the current node
+ */
+ if (!create) {
+ return (find_result);
+ }
+
+ sibling = new_node(rpzs, tgt_ip, tgt_prefix, NULL);
+ if (sibling == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ new_parent = new_node(rpzs, tgt_ip, dbit, cur);
+ if (new_parent == NULL) {
+ isc_mem_put(rpzs->mctx, sibling, sizeof(*sibling));
+ return (ISC_R_NOMEMORY);
+ }
+ new_parent->parent = parent;
+ if (parent == NULL) {
+ rpzs->cidr = new_parent;
+ } else {
+ parent->child[cur_num] = new_parent;
+ }
+ child_num = DNS_RPZ_IP_BIT(tgt_ip, dbit);
+ new_parent->child[child_num] = sibling;
+ new_parent->child[1 - child_num] = cur;
+ cur->parent = new_parent;
+ sibling->parent = new_parent;
+ sibling->set = *tgt_set;
+ set_sum_pair(sibling);
+ *found = sibling;
+ return (ISC_R_SUCCESS);
+ }
+}
+
+/*
+ * Add an IP address to the radix tree.
+ */
+static isc_result_t
+add_cidr(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name) {
+ dns_rpz_cidr_key_t tgt_ip;
+ dns_rpz_prefix_t tgt_prefix;
+ dns_rpz_addr_zbits_t set;
+ dns_rpz_cidr_node_t *found;
+ isc_result_t result;
+
+ result = name2ipkey(DNS_RPZ_ERROR_LEVEL, rpzs, rpz_num, rpz_type,
+ src_name, &tgt_ip, &tgt_prefix, &set);
+ /*
+ * Log complaints about bad owner names but let the zone load.
+ */
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = search(rpzs, &tgt_ip, tgt_prefix, &set, true, &found);
+ if (result != ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ /*
+ * Do not worry if the radix tree already exists,
+ * because diff_apply() likes to add nodes before deleting.
+ */
+ if (result == ISC_R_EXISTS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+ */
+ dns_name_format(src_name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "rpz add_cidr(%s) failed: %s", namebuf,
+ isc_result_totext(result));
+ return (result);
+ }
+
+ adj_trigger_cnt(rpzs, rpz_num, rpz_type, &tgt_ip, tgt_prefix, true);
+ return (result);
+}
+
+static isc_result_t
+add_nm(dns_rpz_zones_t *rpzs, dns_name_t *trig_name,
+ const dns_rpz_nm_data_t *new_data) {
+ dns_rbtnode_t *nmnode;
+ dns_rpz_nm_data_t *nm_data;
+ isc_result_t result;
+
+ nmnode = NULL;
+ result = dns_rbt_addnode(rpzs->rbt, trig_name, &nmnode);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case ISC_R_EXISTS:
+ nm_data = nmnode->data;
+ if (nm_data == NULL) {
+ nm_data = isc_mem_get(rpzs->mctx, sizeof(*nm_data));
+ *nm_data = *new_data;
+ nmnode->data = nm_data;
+ return (ISC_R_SUCCESS);
+ }
+ break;
+ default:
+ return (result);
+ }
+
+ /*
+ * Do not count bits that are already present
+ */
+ if ((nm_data->set.qname & new_data->set.qname) != 0 ||
+ (nm_data->set.ns & new_data->set.ns) != 0 ||
+ (nm_data->wild.qname & new_data->wild.qname) != 0 ||
+ (nm_data->wild.ns & new_data->wild.ns) != 0)
+ {
+ return (ISC_R_EXISTS);
+ }
+
+ nm_data->set.qname |= new_data->set.qname;
+ nm_data->set.ns |= new_data->set.ns;
+ nm_data->wild.qname |= new_data->wild.qname;
+ nm_data->wild.ns |= new_data->wild.ns;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+add_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name) {
+ dns_rpz_nm_data_t new_data;
+ dns_fixedname_t trig_namef;
+ dns_name_t *trig_name;
+ isc_result_t result;
+
+ /*
+ * We need a summary database of names even with 1 policy zone,
+ * because wildcard triggers are handled differently.
+ */
+
+ trig_name = dns_fixedname_initname(&trig_namef);
+ name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &new_data);
+
+ result = add_nm(rpzs, trig_name, &new_data);
+
+ /*
+ * Do not worry if the node already exists,
+ * because diff_apply() likes to add nodes before deleting.
+ */
+ if (result == ISC_R_EXISTS) {
+ return (ISC_R_SUCCESS);
+ }
+ if (result == ISC_R_SUCCESS) {
+ adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, true);
+ }
+ return (result);
+}
+
+/*
+ * Callback to free the data for a node in the summary RBT database.
+ */
+static void
+rpz_node_deleter(void *nm_data, void *mctx) {
+ isc_mem_put(mctx, nm_data, sizeof(dns_rpz_nm_data_t));
+}
+
+/*
+ * Get ready for a new set of policy zones for a view.
+ */
+isc_result_t
+dns_rpz_new_zones(dns_rpz_zones_t **rpzsp, char *rps_cstr, size_t rps_cstr_size,
+ isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr) {
+ dns_rpz_zones_t *zones;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(rpzsp != NULL && *rpzsp == NULL);
+
+ zones = isc_mem_get(mctx, sizeof(*zones));
+ memset(zones, 0, sizeof(*zones));
+
+ isc_rwlock_init(&zones->search_lock, 0, 0);
+ isc_mutex_init(&zones->maint_lock);
+ isc_refcount_init(&zones->refs, 1);
+ isc_refcount_init(&zones->irefs, 1);
+
+ zones->rps_cstr = rps_cstr;
+ zones->rps_cstr_size = rps_cstr_size;
+#ifdef USE_DNSRPS
+ if (rps_cstr != NULL) {
+ result = dns_dnsrps_view_init(zones, rps_cstr);
+ }
+#else /* ifdef USE_DNSRPS */
+ INSIST(!zones->p.dnsrps_enabled);
+#endif /* ifdef USE_DNSRPS */
+ if (result == ISC_R_SUCCESS && !zones->p.dnsrps_enabled) {
+ result = dns_rbt_create(mctx, rpz_node_deleter, mctx,
+ &zones->rbt);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_rbt;
+ }
+
+ result = isc_task_create(taskmgr, 0, &zones->updater);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_task;
+ }
+
+ isc_mem_attach(mctx, &zones->mctx);
+ zones->timermgr = timermgr;
+ zones->taskmgr = taskmgr;
+
+ *rpzsp = zones;
+ return (ISC_R_SUCCESS);
+
+cleanup_task:
+ dns_rbt_destroy(&zones->rbt);
+
+cleanup_rbt:
+ isc_refcount_decrementz(&zones->irefs);
+ isc_refcount_destroy(&zones->irefs);
+ isc_refcount_decrementz(&zones->refs);
+ isc_refcount_destroy(&zones->refs);
+ isc_mutex_destroy(&zones->maint_lock);
+ isc_rwlock_destroy(&zones->search_lock);
+ isc_mem_put(mctx, zones, sizeof(*zones));
+
+ return (result);
+}
+
+isc_result_t
+dns_rpz_new_zone(dns_rpz_zones_t *rpzs, dns_rpz_zone_t **rpzp) {
+ dns_rpz_zone_t *zone;
+ isc_result_t result;
+
+ REQUIRE(rpzp != NULL && *rpzp == NULL);
+ REQUIRE(rpzs != NULL);
+ if (rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES) {
+ return (ISC_R_NOSPACE);
+ }
+
+ zone = isc_mem_get(rpzs->mctx, sizeof(*zone));
+
+ memset(zone, 0, sizeof(*zone));
+ isc_refcount_init(&zone->refs, 1);
+
+ result = isc_timer_create(rpzs->timermgr, isc_timertype_inactive, NULL,
+ NULL, rpzs->updater,
+ dns_rpz_update_taskaction, zone,
+ &zone->updatetimer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_timer;
+ }
+
+ /*
+ * This will never be used, but costs us nothing and
+ * simplifies update_from_db
+ */
+
+ isc_ht_init(&zone->nodes, rpzs->mctx, 1);
+
+ dns_name_init(&zone->origin, NULL);
+ dns_name_init(&zone->client_ip, NULL);
+ dns_name_init(&zone->ip, NULL);
+ dns_name_init(&zone->nsdname, NULL);
+ dns_name_init(&zone->nsip, NULL);
+ dns_name_init(&zone->passthru, NULL);
+ dns_name_init(&zone->drop, NULL);
+ dns_name_init(&zone->tcp_only, NULL);
+ dns_name_init(&zone->cname, NULL);
+
+ isc_time_settoepoch(&zone->lastupdated);
+ zone->updatepending = false;
+ zone->updaterunning = false;
+ zone->db = NULL;
+ zone->dbversion = NULL;
+ zone->updb = NULL;
+ zone->updbversion = NULL;
+ zone->updbit = NULL;
+ isc_refcount_increment(&rpzs->irefs);
+ zone->rpzs = rpzs;
+ zone->db_registered = false;
+ zone->addsoa = true;
+ ISC_EVENT_INIT(&zone->updateevent, sizeof(zone->updateevent), 0, NULL,
+ 0, NULL, NULL, NULL, NULL, NULL);
+
+ zone->num = rpzs->p.num_zones++;
+ rpzs->zones[zone->num] = zone;
+
+ *rpzp = zone;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_timer:
+ isc_refcount_decrementz(&zone->refs);
+ isc_refcount_destroy(&zone->refs);
+
+ isc_mem_put(rpzs->mctx, zone, sizeof(*zone));
+
+ return (result);
+}
+
+isc_result_t
+dns_rpz_dbupdate_callback(dns_db_t *db, void *fn_arg) {
+ dns_rpz_zone_t *zone = (dns_rpz_zone_t *)fn_arg;
+ isc_time_t now;
+ uint64_t tdiff;
+ isc_result_t result = ISC_R_SUCCESS;
+ char dname[DNS_NAME_FORMATSIZE];
+
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE(zone != NULL);
+
+ LOCK(&zone->rpzs->maint_lock);
+
+ /* New zone came as AXFR */
+ if (zone->db != NULL && zone->db != db) {
+ /* We need to clean up the old DB */
+ if (zone->dbversion != NULL) {
+ dns_db_closeversion(zone->db, &zone->dbversion, false);
+ }
+ dns_db_updatenotify_unregister(zone->db,
+ dns_rpz_dbupdate_callback, zone);
+ dns_db_detach(&zone->db);
+ }
+
+ if (zone->db == NULL) {
+ RUNTIME_CHECK(zone->dbversion == NULL);
+ dns_db_attach(db, &zone->db);
+ }
+
+ if (!zone->updatepending && !zone->updaterunning) {
+ zone->updatepending = true;
+ isc_time_now(&now);
+ tdiff = isc_time_microdiff(&now, &zone->lastupdated) / 1000000;
+ if (tdiff < zone->min_update_interval) {
+ uint64_t defer = zone->min_update_interval - tdiff;
+ isc_interval_t interval;
+ dns_name_format(&zone->origin, dname,
+ DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "rpz: %s: new zone version came "
+ "too soon, deferring update for "
+ "%" PRIu64 " seconds",
+ dname, defer);
+ isc_interval_set(&interval, (unsigned int)defer, 0);
+ dns_db_currentversion(zone->db, &zone->dbversion);
+ result = isc_timer_reset(zone->updatetimer,
+ isc_timertype_once, NULL,
+ &interval, true);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ } else {
+ isc_event_t *event;
+
+ dns_db_currentversion(zone->db, &zone->dbversion);
+ INSIST(!ISC_LINK_LINKED(&zone->updateevent, ev_link));
+ ISC_EVENT_INIT(&zone->updateevent,
+ sizeof(zone->updateevent), 0, NULL,
+ DNS_EVENT_RPZUPDATED,
+ dns_rpz_update_taskaction, zone, zone,
+ NULL, NULL);
+ event = &zone->updateevent;
+ isc_task_send(zone->rpzs->updater, &event);
+ }
+ } else {
+ zone->updatepending = true;
+ dns_name_format(&zone->origin, dname, DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
+ "rpz: %s: update already queued or running",
+ dname);
+ if (zone->dbversion != NULL) {
+ dns_db_closeversion(zone->db, &zone->dbversion, false);
+ }
+ dns_db_currentversion(zone->db, &zone->dbversion);
+ }
+
+cleanup:
+ UNLOCK(&zone->rpzs->maint_lock);
+
+ return (result);
+}
+
+static void
+dns_rpz_update_taskaction(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ dns_rpz_zone_t *zone;
+
+ REQUIRE(event != NULL);
+ REQUIRE(event->ev_arg != NULL);
+
+ UNUSED(task);
+ zone = (dns_rpz_zone_t *)event->ev_arg;
+ isc_event_free(&event);
+ LOCK(&zone->rpzs->maint_lock);
+ zone->updatepending = false;
+ zone->updaterunning = true;
+ dns_rpz_update_from_db(zone);
+ result = isc_timer_reset(zone->updatetimer, isc_timertype_inactive,
+ NULL, NULL, true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ result = isc_time_now(&zone->lastupdated);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ UNLOCK(&zone->rpzs->maint_lock);
+}
+
+static isc_result_t
+setup_update(dns_rpz_zone_t *rpz) {
+ isc_result_t result;
+ char domain[DNS_NAME_FORMATSIZE];
+ unsigned int nodecount;
+ uint32_t hashsize;
+
+ dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_INFO, "rpz: %s: reload start", domain);
+
+ nodecount = dns_db_nodecount(rpz->updb);
+ hashsize = 1;
+ while (nodecount != 0 &&
+ hashsize <= (DNS_RPZ_HTSIZE_MAX + DNS_RPZ_HTSIZE_DIV))
+ {
+ hashsize++;
+ nodecount >>= 1;
+ }
+
+ if (hashsize > DNS_RPZ_HTSIZE_DIV) {
+ hashsize -= DNS_RPZ_HTSIZE_DIV;
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
+ ISC_LOG_DEBUG(1), "rpz: %s: using hashtable size %d",
+ domain, hashsize);
+
+ isc_ht_init(&rpz->newnodes, rpz->rpzs->mctx, hashsize);
+
+ result = dns_db_createiterator(rpz->updb, DNS_DB_NONSEC3, &rpz->updbit);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: failed to create DB iterator - %s",
+ domain, isc_result_totext(result));
+ goto cleanup;
+ }
+
+ result = dns_dbiterator_first(rpz->updbit);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: failed to get db iterator - %s", domain,
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ result = dns_dbiterator_pause(rpz->updbit);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: failed to pause db iterator - %s",
+ domain, isc_result_totext(result));
+ goto cleanup;
+ }
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ if (rpz->updbit != NULL) {
+ dns_dbiterator_destroy(&rpz->updbit);
+ }
+ if (rpz->newnodes != NULL) {
+ isc_ht_destroy(&rpz->newnodes);
+ }
+ dns_db_closeversion(rpz->updb, &rpz->updbversion, false);
+ }
+
+ return (result);
+}
+
+static void
+finish_update(dns_rpz_zone_t *rpz) {
+ LOCK(&rpz->rpzs->maint_lock);
+ rpz->updaterunning = false;
+
+ /*
+ * If there's an update pending, schedule it.
+ */
+ if (rpz->updatepending) {
+ if (rpz->min_update_interval > 0) {
+ uint64_t defer = rpz->min_update_interval;
+ char dname[DNS_NAME_FORMATSIZE];
+ isc_interval_t interval;
+
+ dns_name_format(&rpz->origin, dname,
+ DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "rpz: %s: new zone version came "
+ "too soon, deferring update for "
+ "%" PRIu64 " seconds",
+ dname, defer);
+ isc_interval_set(&interval, (unsigned int)defer, 0);
+ isc_timer_reset(rpz->updatetimer, isc_timertype_once,
+ NULL, &interval, true);
+ } else {
+ isc_event_t *event = NULL;
+ INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
+ ISC_EVENT_INIT(&rpz->updateevent,
+ sizeof(rpz->updateevent), 0, NULL,
+ DNS_EVENT_RPZUPDATED,
+ dns_rpz_update_taskaction, rpz, rpz,
+ NULL, NULL);
+ event = &rpz->updateevent;
+ isc_task_send(rpz->rpzs->updater, &event);
+ }
+ }
+ UNLOCK(&rpz->rpzs->maint_lock);
+}
+
+static void
+cleanup_quantum(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result = ISC_R_SUCCESS;
+ char domain[DNS_NAME_FORMATSIZE];
+ dns_rpz_zone_t *rpz = NULL;
+ isc_ht_iter_t *iter = NULL;
+ dns_fixedname_t fname;
+ dns_name_t *name = NULL;
+ int count = 0;
+
+ UNUSED(task);
+
+ REQUIRE(event != NULL);
+ REQUIRE(event->ev_sender != NULL);
+
+ rpz = (dns_rpz_zone_t *)event->ev_sender;
+ iter = (isc_ht_iter_t *)event->ev_arg;
+ isc_event_free(&event);
+
+ if (iter == NULL) {
+ /*
+ * Iterate over old ht with existing nodes deleted to
+ * delete deleted nodes from RPZ
+ */
+ isc_ht_iter_create(rpz->nodes, &iter);
+ }
+
+ name = dns_fixedname_initname(&fname);
+
+ LOCK(&rpz->rpzs->maint_lock);
+
+ /* Check that we aren't shutting down. */
+ if (rpz->rpzs->zones[rpz->num] == NULL) {
+ UNLOCK(&rpz->rpzs->maint_lock);
+ goto cleanup;
+ }
+
+ for (result = isc_ht_iter_first(iter);
+ result == ISC_R_SUCCESS && count++ < DNS_RPZ_QUANTUM;
+ result = isc_ht_iter_delcurrent_next(iter))
+ {
+ isc_region_t region;
+ unsigned char *key = NULL;
+ size_t keysize;
+
+ isc_ht_iter_currentkey(iter, &key, &keysize);
+ region.base = key;
+ region.length = (unsigned int)keysize;
+ dns_name_fromregion(name, &region);
+ dns_rpz_delete(rpz->rpzs, rpz->num, name);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ isc_event_t *nevent = NULL;
+
+ /*
+ * We finished a quantum; trigger the next one and return.
+ */
+
+ INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
+ ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0,
+ NULL, DNS_EVENT_RPZUPDATED, cleanup_quantum,
+ iter, rpz, NULL, NULL);
+ nevent = &rpz->updateevent;
+ isc_task_send(rpz->rpzs->updater, &nevent);
+ UNLOCK(&rpz->rpzs->maint_lock);
+ return;
+ } else if (result == ISC_R_NOMORE) {
+ isc_ht_t *tmpht = NULL;
+
+ /*
+ * Done with cleanup of deleted nodes; finalize
+ * the update.
+ */
+ tmpht = rpz->nodes;
+ rpz->nodes = rpz->newnodes;
+ rpz->newnodes = tmpht;
+
+ UNLOCK(&rpz->rpzs->maint_lock);
+ finish_update(rpz);
+ dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
+ "rpz: %s: reload done", domain);
+ } else {
+ UNLOCK(&rpz->rpzs->maint_lock);
+ }
+
+ /*
+ * If we're here, we're finished or something went wrong.
+ */
+cleanup:
+ if (iter != NULL) {
+ isc_ht_iter_destroy(&iter);
+ }
+ if (rpz->newnodes != NULL) {
+ isc_ht_destroy(&rpz->newnodes);
+ }
+ dns_db_closeversion(rpz->updb, &rpz->updbversion, false);
+ dns_db_detach(&rpz->updb);
+ rpz_detach(&rpz);
+}
+
+static void
+update_quantum(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_dbnode_t *node = NULL;
+ dns_rpz_zone_t *rpz = NULL;
+ char domain[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t fixname;
+ dns_name_t *name = NULL;
+ isc_event_t *nevent = NULL;
+ int count = 0;
+
+ UNUSED(task);
+
+ REQUIRE(event != NULL);
+ REQUIRE(event->ev_arg != NULL);
+
+ rpz = (dns_rpz_zone_t *)event->ev_arg;
+ isc_event_free(&event);
+
+ REQUIRE(rpz->updbit != NULL);
+ REQUIRE(rpz->newnodes != NULL);
+
+ name = dns_fixedname_initname(&fixname);
+
+ dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE);
+
+ LOCK(&rpz->rpzs->maint_lock);
+
+ /* Check that we aren't shutting down. */
+ if (rpz->rpzs->zones[rpz->num] == NULL) {
+ UNLOCK(&rpz->rpzs->maint_lock);
+ goto cleanup;
+ }
+
+ while (result == ISC_R_SUCCESS && count++ < DNS_RPZ_QUANTUM) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_rdatasetiter_t *rdsiter = NULL;
+
+ result = dns_dbiterator_current(rpz->updbit, &node, name);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: failed to get dbiterator - %s",
+ domain, isc_result_totext(result));
+ dns_db_detachnode(rpz->updb, &node);
+ break;
+ }
+
+ result = dns_db_allrdatasets(rpz->updb, node, rpz->updbversion,
+ 0, 0, &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: failed to fetch "
+ "rrdatasets - %s",
+ domain, isc_result_totext(result));
+ dns_db_detachnode(rpz->updb, &node);
+ break;
+ }
+
+ result = dns_rdatasetiter_first(rdsiter);
+ dns_rdatasetiter_destroy(&rdsiter);
+ if (result != ISC_R_SUCCESS) { /* empty non-terminal */
+ if (result != ISC_R_NOMORE) {
+ isc_log_write(
+ dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s: error %s while creating "
+ "rdatasetiter",
+ domain, isc_result_totext(result));
+ }
+ dns_db_detachnode(rpz->updb, &node);
+ result = dns_dbiterator_next(rpz->updbit);
+ continue;
+ }
+
+ dns_name_downcase(name, name, NULL);
+ result = isc_ht_add(rpz->newnodes, name->ndata, name->length,
+ rpz);
+ if (result != ISC_R_SUCCESS) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
+ "rpz: %s, adding node %s to HT error %s",
+ domain, namebuf,
+ isc_result_totext(result));
+ dns_db_detachnode(rpz->updb, &node);
+ result = dns_dbiterator_next(rpz->updbit);
+ continue;
+ }
+
+ result = isc_ht_find(rpz->nodes, name->ndata, name->length,
+ NULL);
+ if (result == ISC_R_SUCCESS) {
+ isc_ht_delete(rpz->nodes, name->ndata, name->length);
+ } else { /* not found */
+ result = dns_rpz_add(rpz->rpzs, rpz->num, name);
+ if (result != ISC_R_SUCCESS) {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER,
+ ISC_LOG_ERROR,
+ "rpz: %s: adding node %s "
+ "to RPZ error %s",
+ domain, namebuf,
+ isc_result_totext(result));
+ } else {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_MASTER,
+ ISC_LOG_DEBUG(3),
+ "rpz: %s: adding node %s", domain,
+ namebuf);
+ }
+ }
+
+ dns_db_detachnode(rpz->updb, &node);
+ result = dns_dbiterator_next(rpz->updbit);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * Pause the iterator so that the DB is not locked.
+ */
+ dns_dbiterator_pause(rpz->updbit);
+
+ /*
+ * We finished a quantum; trigger the next one and return.
+ */
+ INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
+ ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0,
+ NULL, DNS_EVENT_RPZUPDATED, update_quantum, rpz,
+ rpz, NULL, NULL);
+ nevent = &rpz->updateevent;
+ isc_task_send(rpz->rpzs->updater, &nevent);
+ UNLOCK(&rpz->rpzs->maint_lock);
+ return;
+ } else if (result == ISC_R_NOMORE) {
+ /*
+ * Done with the new database; now we just need to
+ * clean up the old.
+ */
+ dns_dbiterator_destroy(&rpz->updbit);
+
+ INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
+ ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0,
+ NULL, DNS_EVENT_RPZUPDATED, cleanup_quantum,
+ NULL, rpz, NULL, NULL);
+ nevent = &rpz->updateevent;
+ isc_task_send(rpz->rpzs->updater, &nevent);
+ UNLOCK(&rpz->rpzs->maint_lock);
+ return;
+ }
+
+ /*
+ * If we're here, something went wrong, so clean up.
+ */
+ UNLOCK(&rpz->rpzs->maint_lock);
+
+cleanup:
+ if (rpz->updbit != NULL) {
+ dns_dbiterator_destroy(&rpz->updbit);
+ }
+ if (rpz->newnodes != NULL) {
+ isc_ht_destroy(&rpz->newnodes);
+ }
+ dns_db_closeversion(rpz->updb, &rpz->updbversion, false);
+ dns_db_detach(&rpz->updb);
+ rpz_detach(&rpz);
+}
+
+static void
+dns_rpz_update_from_db(dns_rpz_zone_t *rpz) {
+ isc_result_t result;
+ isc_event_t *event;
+
+ REQUIRE(rpz != NULL);
+ REQUIRE(DNS_DB_VALID(rpz->db));
+ REQUIRE(rpz->updb == NULL);
+ REQUIRE(rpz->updbversion == NULL);
+ REQUIRE(rpz->updbit == NULL);
+ REQUIRE(rpz->newnodes == NULL);
+
+ isc_refcount_increment(&rpz->refs);
+ dns_db_attach(rpz->db, &rpz->updb);
+ rpz->updbversion = rpz->dbversion;
+ rpz->dbversion = NULL;
+
+ result = setup_update(rpz);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ event = &rpz->updateevent;
+ INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
+ ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0, NULL,
+ DNS_EVENT_RPZUPDATED, update_quantum, rpz, rpz, NULL,
+ NULL);
+ isc_task_send(rpz->rpzs->updater, &event);
+ return;
+
+cleanup:
+ if (rpz->updbit != NULL) {
+ dns_dbiterator_destroy(&rpz->updbit);
+ }
+ if (rpz->newnodes != NULL) {
+ isc_ht_destroy(&rpz->newnodes);
+ }
+ dns_db_closeversion(rpz->updb, &rpz->updbversion, false);
+ dns_db_detach(&rpz->updb);
+ rpz_detach(&rpz);
+}
+
+/*
+ * Free the radix tree of a response policy database.
+ */
+static void
+cidr_free(dns_rpz_zones_t *rpzs) {
+ dns_rpz_cidr_node_t *cur, *child, *parent;
+
+ cur = rpzs->cidr;
+ while (cur != NULL) {
+ /* Depth first. */
+ child = cur->child[0];
+ if (child != NULL) {
+ cur = child;
+ continue;
+ }
+ child = cur->child[1];
+ if (child != NULL) {
+ cur = child;
+ continue;
+ }
+
+ /* Delete this leaf and go up. */
+ parent = cur->parent;
+ if (parent == NULL) {
+ rpzs->cidr = NULL;
+ } else {
+ parent->child[parent->child[1] == cur] = NULL;
+ }
+ isc_mem_put(rpzs->mctx, cur, sizeof(*cur));
+ cur = parent;
+ }
+}
+
+/*
+ * Discard a response policy zone blob
+ * before discarding the overall rpz structure.
+ */
+static void
+rpz_detach(dns_rpz_zone_t **rpzp) {
+ dns_rpz_zone_t *rpz;
+ dns_rpz_zones_t *rpzs;
+
+ REQUIRE(rpzp != NULL && *rpzp != NULL);
+
+ rpz = *rpzp;
+ *rpzp = NULL;
+
+ if (isc_refcount_decrement(&rpz->refs) == 1) {
+ isc_refcount_destroy(&rpz->refs);
+
+ rpzs = rpz->rpzs;
+ rpz->rpzs = NULL;
+
+ if (dns_name_dynamic(&rpz->origin)) {
+ dns_name_free(&rpz->origin, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->client_ip)) {
+ dns_name_free(&rpz->client_ip, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->ip)) {
+ dns_name_free(&rpz->ip, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->nsdname)) {
+ dns_name_free(&rpz->nsdname, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->nsip)) {
+ dns_name_free(&rpz->nsip, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->passthru)) {
+ dns_name_free(&rpz->passthru, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->drop)) {
+ dns_name_free(&rpz->drop, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->tcp_only)) {
+ dns_name_free(&rpz->tcp_only, rpzs->mctx);
+ }
+ if (dns_name_dynamic(&rpz->cname)) {
+ dns_name_free(&rpz->cname, rpzs->mctx);
+ }
+ if (rpz->db != NULL) {
+ if (rpz->dbversion != NULL) {
+ dns_db_closeversion(rpz->db, &rpz->dbversion,
+ false);
+ }
+ dns_db_updatenotify_unregister(
+ rpz->db, dns_rpz_dbupdate_callback, rpz);
+ dns_db_detach(&rpz->db);
+ }
+ if (rpz->updaterunning) {
+ isc_task_purgeevent(rpzs->updater, &rpz->updateevent);
+ if (rpz->updbit != NULL) {
+ dns_dbiterator_destroy(&rpz->updbit);
+ }
+ if (rpz->newnodes != NULL) {
+ isc_ht_destroy(&rpz->newnodes);
+ }
+ if (rpz->updb != NULL) {
+ if (rpz->updbversion != NULL) {
+ dns_db_closeversion(rpz->updb,
+ &rpz->updbversion,
+ false);
+ }
+ dns_db_detach(&rpz->updb);
+ }
+ }
+
+ isc_timer_reset(rpz->updatetimer, isc_timertype_inactive, NULL,
+ NULL, true);
+ isc_timer_destroy(&rpz->updatetimer);
+
+ isc_ht_destroy(&rpz->nodes);
+
+ isc_mem_put(rpzs->mctx, rpz, sizeof(*rpz));
+ rpz_detach_rpzs(&rpzs);
+ }
+}
+
+void
+dns_rpz_attach_rpzs(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **rpzsp) {
+ REQUIRE(rpzsp != NULL && *rpzsp == NULL);
+ isc_refcount_increment(&rpzs->refs);
+ *rpzsp = rpzs;
+}
+
+/*
+ * Forget a view's policy zones.
+ */
+void
+dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp) {
+ REQUIRE(rpzsp != NULL && *rpzsp != NULL);
+ dns_rpz_zones_t *rpzs = *rpzsp;
+ *rpzsp = NULL;
+
+ if (isc_refcount_decrement(&rpzs->refs) == 1) {
+ LOCK(&rpzs->maint_lock);
+ /*
+ * Forget the last of view's rpz machinery after
+ * the last reference.
+ */
+ for (dns_rpz_num_t rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES;
+ ++rpz_num)
+ {
+ dns_rpz_zone_t *rpz = rpzs->zones[rpz_num];
+ rpzs->zones[rpz_num] = NULL;
+ if (rpz != NULL) {
+ rpz_detach(&rpz);
+ }
+ }
+ UNLOCK(&rpzs->maint_lock);
+ rpz_detach_rpzs(&rpzs);
+ }
+}
+
+static void
+rpz_detach_rpzs(dns_rpz_zones_t **rpzsp) {
+ REQUIRE(rpzsp != NULL && *rpzsp != NULL);
+ dns_rpz_zones_t *rpzs = *rpzsp;
+ *rpzsp = NULL;
+
+ if (isc_refcount_decrement(&rpzs->irefs) == 1) {
+ if (rpzs->rps_cstr_size != 0) {
+#ifdef USE_DNSRPS
+ librpz->client_detach(&rpzs->rps_client);
+#endif /* ifdef USE_DNSRPS */
+ isc_mem_put(rpzs->mctx, rpzs->rps_cstr,
+ rpzs->rps_cstr_size);
+ }
+
+ cidr_free(rpzs);
+ if (rpzs->rbt != NULL) {
+ dns_rbt_destroy(&rpzs->rbt);
+ }
+ isc_task_destroy(&rpzs->updater);
+ isc_mutex_destroy(&rpzs->maint_lock);
+ isc_rwlock_destroy(&rpzs->search_lock);
+ isc_refcount_destroy(&rpzs->refs);
+ isc_mem_putanddetach(&rpzs->mctx, rpzs, sizeof(*rpzs));
+ }
+}
+
+/*
+ * Deprecated and removed.
+ */
+isc_result_t
+dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp, dns_rpz_zones_t *rpzs,
+ dns_rpz_num_t rpz_num) {
+ UNUSED(load_rpzsp);
+ UNUSED(rpzs);
+ UNUSED(rpz_num);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+/*
+ * Deprecated and removed.
+ */
+isc_result_t
+dns_rpz_ready(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **load_rpzsp,
+ dns_rpz_num_t rpz_num) {
+ UNUSED(rpzs);
+ UNUSED(load_rpzsp);
+ UNUSED(rpz_num);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+/*
+ * Add an IP address to the radix tree or a name to the summary database.
+ */
+isc_result_t
+dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+ const dns_name_t *src_name) {
+ dns_rpz_zone_t *rpz;
+ dns_rpz_type_t rpz_type;
+ isc_result_t result = ISC_R_FAILURE;
+
+ REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
+ rpz = rpzs->zones[rpz_num];
+ REQUIRE(rpz != NULL);
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_write);
+
+ rpz_type = type_from_name(rpzs, rpz, src_name);
+
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_QNAME:
+ case DNS_RPZ_TYPE_NSDNAME:
+ result = add_name(rpzs, rpz_num, rpz_type, src_name);
+ break;
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ case DNS_RPZ_TYPE_IP:
+ case DNS_RPZ_TYPE_NSIP:
+ result = add_cidr(rpzs, rpz_num, rpz_type, src_name);
+ break;
+ case DNS_RPZ_TYPE_BAD:
+ break;
+ }
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+/*
+ * Remove an IP address from the radix tree.
+ */
+static void
+del_cidr(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name) {
+ isc_result_t result;
+ dns_rpz_cidr_key_t tgt_ip;
+ dns_rpz_prefix_t tgt_prefix;
+ dns_rpz_addr_zbits_t tgt_set;
+ dns_rpz_cidr_node_t *tgt, *parent, *child;
+
+ /*
+ * Do not worry about invalid rpz IP address names. If we
+ * are here, then something relevant was added and so was
+ * valid. Invalid names here are usually internal RBTDB nodes.
+ */
+ result = name2ipkey(DNS_RPZ_DEBUG_QUIET, rpzs, rpz_num, rpz_type,
+ src_name, &tgt_ip, &tgt_prefix, &tgt_set);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ result = search(rpzs, &tgt_ip, tgt_prefix, &tgt_set, false, &tgt);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(result == ISC_R_NOTFOUND ||
+ result == DNS_R_PARTIALMATCH);
+ /*
+ * Do not worry about missing summary RBT nodes that probably
+ * correspond to RBTDB nodes that were implicit RBT nodes
+ * that were later added for (often empty) wildcards
+ * and then to the RBTDB deferred cleanup list.
+ */
+ return;
+ }
+
+ /*
+ * Mark the node and its parents to reflect the deleted IP address.
+ * Do not count bits that are already clear for internal RBTDB nodes.
+ */
+ tgt_set.client_ip &= tgt->set.client_ip;
+ tgt_set.ip &= tgt->set.ip;
+ tgt_set.nsip &= tgt->set.nsip;
+ tgt->set.client_ip &= ~tgt_set.client_ip;
+ tgt->set.ip &= ~tgt_set.ip;
+ tgt->set.nsip &= ~tgt_set.nsip;
+ set_sum_pair(tgt);
+
+ adj_trigger_cnt(rpzs, rpz_num, rpz_type, &tgt_ip, tgt_prefix, false);
+
+ /*
+ * We might need to delete 2 nodes.
+ */
+ do {
+ /*
+ * The node is now useless if it has no data of its own
+ * and 0 or 1 children. We are finished if it is not useless.
+ */
+ if ((child = tgt->child[0]) != NULL) {
+ if (tgt->child[1] != NULL) {
+ break;
+ }
+ } else {
+ child = tgt->child[1];
+ }
+ if (tgt->set.client_ip != 0 || tgt->set.ip != 0 ||
+ tgt->set.nsip != 0)
+ {
+ break;
+ }
+
+ /*
+ * Replace the pointer to this node in the parent with
+ * the remaining child or NULL.
+ */
+ parent = tgt->parent;
+ if (parent == NULL) {
+ rpzs->cidr = child;
+ } else {
+ parent->child[parent->child[1] == tgt] = child;
+ }
+ /*
+ * If the child exists fix up its parent pointer.
+ */
+ if (child != NULL) {
+ child->parent = parent;
+ }
+ isc_mem_put(rpzs->mctx, tgt, sizeof(*tgt));
+
+ tgt = parent;
+ } while (tgt != NULL);
+}
+
+static void
+del_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_rpz_type_t rpz_type,
+ const dns_name_t *src_name) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t trig_namef;
+ dns_name_t *trig_name;
+ dns_rbtnode_t *nmnode;
+ dns_rpz_nm_data_t *nm_data, del_data;
+ isc_result_t result;
+ bool exists;
+
+ /*
+ * We need a summary database of names even with 1 policy zone,
+ * because wildcard triggers are handled differently.
+ */
+
+ trig_name = dns_fixedname_initname(&trig_namef);
+ name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &del_data);
+
+ nmnode = NULL;
+ result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, NULL, 0,
+ NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Do not worry about missing summary RBT nodes that probably
+ * correspond to RBTDB nodes that were implicit RBT nodes
+ * that were later added for (often empty) wildcards
+ * and then to the RBTDB deferred cleanup list.
+ */
+ if (result == ISC_R_NOTFOUND || result == DNS_R_PARTIALMATCH) {
+ return;
+ }
+ dns_name_format(src_name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "rpz del_name(%s) node search failed: %s",
+ namebuf, isc_result_totext(result));
+ return;
+ }
+
+ nm_data = nmnode->data;
+ INSIST(nm_data != NULL);
+
+ /*
+ * Do not count bits that next existed for RBT nodes that would we
+ * would not have found in a summary for a single RBTDB tree.
+ */
+ del_data.set.qname &= nm_data->set.qname;
+ del_data.set.ns &= nm_data->set.ns;
+ del_data.wild.qname &= nm_data->wild.qname;
+ del_data.wild.ns &= nm_data->wild.ns;
+
+ exists = (del_data.set.qname != 0 || del_data.set.ns != 0 ||
+ del_data.wild.qname != 0 || del_data.wild.ns != 0);
+
+ nm_data->set.qname &= ~del_data.set.qname;
+ nm_data->set.ns &= ~del_data.set.ns;
+ nm_data->wild.qname &= ~del_data.wild.qname;
+ nm_data->wild.ns &= ~del_data.wild.ns;
+
+ if (nm_data->set.qname == 0 && nm_data->set.ns == 0 &&
+ nm_data->wild.qname == 0 && nm_data->wild.ns == 0)
+ {
+ result = dns_rbt_deletenode(rpzs->rbt, nmnode, false);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * bin/tests/system/rpz/tests.sh looks for
+ * "rpz.*failed".
+ */
+ dns_name_format(src_name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "rpz del_name(%s) node delete failed: %s",
+ namebuf, isc_result_totext(result));
+ }
+ }
+
+ if (exists) {
+ adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, false);
+ }
+}
+
+/*
+ * Remove an IP address from the radix tree or a name from the summary database.
+ */
+void
+dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+ const dns_name_t *src_name) {
+ dns_rpz_zone_t *rpz;
+ dns_rpz_type_t rpz_type;
+
+ REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
+ rpz = rpzs->zones[rpz_num];
+ REQUIRE(rpz != NULL);
+
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_write);
+
+ rpz_type = type_from_name(rpzs, rpz, src_name);
+
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_QNAME:
+ case DNS_RPZ_TYPE_NSDNAME:
+ del_name(rpzs, rpz_num, rpz_type, src_name);
+ break;
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ case DNS_RPZ_TYPE_IP:
+ case DNS_RPZ_TYPE_NSIP:
+ del_cidr(rpzs, rpz_num, rpz_type, src_name);
+ break;
+ case DNS_RPZ_TYPE_BAD:
+ break;
+ }
+
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write);
+}
+
+/*
+ * Search the summary radix tree to get a relative owner name in a
+ * policy zone relevant to a triggering IP address.
+ * rpz_type and zbits limit the search for IP address netaddr
+ * return the policy zone's number or DNS_RPZ_INVALID_NUM
+ * ip_name is the relative owner name found and
+ * *prefixp is its prefix length.
+ */
+dns_rpz_num_t
+dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+ dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr,
+ dns_name_t *ip_name, dns_rpz_prefix_t *prefixp) {
+ dns_rpz_cidr_key_t tgt_ip;
+ dns_rpz_addr_zbits_t tgt_set;
+ dns_rpz_cidr_node_t *found;
+ isc_result_t result;
+ dns_rpz_num_t rpz_num = 0;
+ dns_rpz_have_t have;
+ int i;
+
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+ have = rpzs->have;
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+
+ /*
+ * Convert IP address to CIDR tree key.
+ */
+ if (netaddr->family == AF_INET) {
+ tgt_ip.w[0] = 0;
+ tgt_ip.w[1] = 0;
+ tgt_ip.w[2] = ADDR_V4MAPPED;
+ tgt_ip.w[3] = ntohl(netaddr->type.in.s_addr);
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ zbits &= have.client_ipv4;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ zbits &= have.ipv4;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ zbits &= have.nsipv4;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ } else if (netaddr->family == AF_INET6) {
+ dns_rpz_cidr_key_t src_ip6;
+
+ /*
+ * Given the int aligned struct in_addr member of netaddr->type
+ * one could cast netaddr->type.in6 to dns_rpz_cidr_key_t *,
+ * but some people object.
+ */
+ memmove(src_ip6.w, &netaddr->type.in6, sizeof(src_ip6.w));
+ for (i = 0; i < 4; i++) {
+ tgt_ip.w[i] = ntohl(src_ip6.w[i]);
+ }
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ zbits &= have.client_ipv6;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ zbits &= have.ipv6;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ zbits &= have.nsipv6;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ } else {
+ return (DNS_RPZ_INVALID_NUM);
+ }
+
+ if (zbits == 0) {
+ return (DNS_RPZ_INVALID_NUM);
+ }
+ make_addr_set(&tgt_set, zbits, rpz_type);
+
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+ result = search(rpzs, &tgt_ip, 128, &tgt_set, false, &found);
+ if (result == ISC_R_NOTFOUND) {
+ /*
+ * There are no eligible zones for this IP address.
+ */
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+ return (DNS_RPZ_INVALID_NUM);
+ }
+
+ /*
+ * Construct the trigger name for the longest matching trigger
+ * in the first eligible zone with a match.
+ */
+ *prefixp = found->prefix;
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ rpz_num = zbit_to_num(found->set.client_ip & tgt_set.client_ip);
+ break;
+ case DNS_RPZ_TYPE_IP:
+ rpz_num = zbit_to_num(found->set.ip & tgt_set.ip);
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ rpz_num = zbit_to_num(found->set.nsip & tgt_set.nsip);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ result = ip2name(&found->ip, found->prefix, dns_rootname, ip_name);
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "rpz ip2name() failed: %s",
+ isc_result_totext(result));
+ return (DNS_RPZ_INVALID_NUM);
+ }
+ return (rpz_num);
+}
+
+/*
+ * Search the summary radix tree for policy zones with triggers matching
+ * a name.
+ */
+dns_rpz_zbits_t
+dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+ dns_rpz_zbits_t zbits, dns_name_t *trig_name) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_rbtnode_t *nmnode;
+ const dns_rpz_nm_data_t *nm_data;
+ dns_rpz_zbits_t found_zbits;
+ dns_rbtnodechain_t chain;
+ isc_result_t result;
+ int i;
+
+ if (zbits == 0) {
+ return (0);
+ }
+
+ found_zbits = 0;
+
+ dns_rbtnodechain_init(&chain);
+
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+
+ nmnode = NULL;
+ result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, &chain,
+ DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ nm_data = nmnode->data;
+ if (nm_data != NULL) {
+ if (rpz_type == DNS_RPZ_TYPE_QNAME) {
+ found_zbits = nm_data->set.qname;
+ } else {
+ found_zbits = nm_data->set.ns;
+ }
+ }
+ FALLTHROUGH;
+
+ case DNS_R_PARTIALMATCH:
+ i = chain.level_matches;
+ nmnode = chain.levels[chain.level_matches];
+
+ /*
+ * Whenever an exact match is found by dns_rbt_findnode(),
+ * the highest level node in the chain will not be put into
+ * chain->levels[] array, but instead the chain->end
+ * pointer will be adjusted to point to that node.
+ *
+ * Suppose we have the following entries in a rpz zone:
+ * example.com CNAME rpz-passthru.
+ * *.example.com CNAME rpz-passthru.
+ *
+ * A query for www.example.com would result in the
+ * following chain object returned by dns_rbt_findnode():
+ * chain->level_count = 2
+ * chain->level_matches = 2
+ * chain->levels[0] = .
+ * chain->levels[1] = example.com
+ * chain->levels[2] = NULL
+ * chain->end = www
+ *
+ * Since exact matches only care for testing rpz set bits,
+ * we need to test for rpz wild bits through iterating the
+ * nodechain, and that includes testing the rpz wild bits
+ * in the highest level node found. In the case of an exact
+ * match, chain->levels[chain->level_matches] will be NULL,
+ * to address that we must use chain->end as the start
+ * point, then iterate over the remaining levels in the
+ * chain.
+ */
+ if (nmnode == NULL) {
+ --i;
+ nmnode = chain.end;
+ }
+
+ while (nmnode != NULL) {
+ nm_data = nmnode->data;
+ if (nm_data != NULL) {
+ if (rpz_type == DNS_RPZ_TYPE_QNAME) {
+ found_zbits |= nm_data->wild.qname;
+ } else {
+ found_zbits |= nm_data->wild.ns;
+ }
+ }
+
+ if (i >= 0) {
+ nmnode = chain.levels[i];
+ --i;
+ } else {
+ break;
+ }
+ }
+ break;
+
+ case ISC_R_NOTFOUND:
+ break;
+
+ default:
+ /*
+ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+ */
+ dns_name_format(trig_name, namebuf, sizeof(namebuf));
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+ "dns_rpz_find_name(%s) failed: %s", namebuf,
+ isc_result_totext(result));
+ break;
+ }
+
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+
+ dns_rbtnodechain_invalidate(&chain);
+
+ return (zbits & found_zbits);
+}
+
+/*
+ * Translate CNAME rdata to a QNAME response policy action.
+ */
+dns_rpz_policy_t
+dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset,
+ dns_name_t *selfname) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_cname_t cname;
+ isc_result_t result;
+
+ result = dns_rdataset_first(rdataset);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+
+ /*
+ * CNAME . means NXDOMAIN
+ */
+ if (dns_name_equal(&cname.cname, dns_rootname)) {
+ return (DNS_RPZ_POLICY_NXDOMAIN);
+ }
+
+ if (dns_name_iswildcard(&cname.cname)) {
+ /*
+ * CNAME *. means NODATA
+ */
+ if (dns_name_countlabels(&cname.cname) == 2) {
+ return (DNS_RPZ_POLICY_NODATA);
+ }
+
+ /*
+ * A qname of www.evil.com and a policy of
+ * *.evil.com CNAME *.garden.net
+ * gives a result of
+ * evil.com CNAME evil.com.garden.net
+ */
+ if (dns_name_countlabels(&cname.cname) > 2) {
+ return (DNS_RPZ_POLICY_WILDCNAME);
+ }
+ }
+
+ /*
+ * CNAME rpz-tcp-only. means "send truncated UDP responses."
+ */
+ if (dns_name_equal(&cname.cname, &rpz->tcp_only)) {
+ return (DNS_RPZ_POLICY_TCP_ONLY);
+ }
+
+ /*
+ * CNAME rpz-drop. means "do not respond."
+ */
+ if (dns_name_equal(&cname.cname, &rpz->drop)) {
+ return (DNS_RPZ_POLICY_DROP);
+ }
+
+ /*
+ * CNAME rpz-passthru. means "do not rewrite."
+ */
+ if (dns_name_equal(&cname.cname, &rpz->passthru)) {
+ return (DNS_RPZ_POLICY_PASSTHRU);
+ }
+
+ /*
+ * 128.1.0.127.rpz-ip CNAME 128.1.0.0.127. is obsolete PASSTHRU
+ */
+ if (selfname != NULL && dns_name_equal(&cname.cname, selfname)) {
+ return (DNS_RPZ_POLICY_PASSTHRU);
+ }
+
+ /*
+ * Any other rdata gives a response consisting of the rdata.
+ */
+ return (DNS_RPZ_POLICY_RECORD);
+}
diff --git a/lib/dns/rriterator.c b/lib/dns/rriterator.c
new file mode 100644
index 0000000..9b40597
--- /dev/null
+++ b/lib/dns/rriterator.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.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/result.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..a8c2525
--- /dev/null
+++ b/lib/dns/rrl.c
@@ -0,0 +1,1367 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+/*
+ * Rate limit DNS responses.
+ */
+
+/* #define ISC_LIST_CHECKINIT */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/net.h>
+#include <isc/netaddr.h>
+#include <isc/print.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/result.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_copynf(qname, dns_fixedname_name(
+ &qbuf->qname));
+ }
+ }
+ if (qbuf != NULL) {
+ qname = dns_fixedname_name(&qbuf->qname);
+ }
+ if (qname != NULL) {
+ ADD_LOG_CSTR(&lb, " for ");
+ (void)dns_name_totext(qname, true, &lb);
+ } else {
+ ADD_LOG_CSTR(&lb, " for (?)");
+ }
+ if (e->key.s.rtype != DNS_RRL_RTYPE_NXDOMAIN) {
+ ADD_LOG_CSTR(&lb, " ");
+ (void)dns_rdataclass_totext(e->key.s.qclass, &lb);
+ if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY) {
+ ADD_LOG_CSTR(&lb, " ");
+ (void)dns_rdatatype_totext(e->key.s.qtype, &lb);
+ }
+ }
+ snprintf(strbuf, sizeof(strbuf), " (%08" PRIx32 ")",
+ e->key.s.qname_hash);
+ add_log_str(&lb, strbuf, strlen(strbuf));
+ }
+
+ /*
+ * We saved room for '\0'.
+ */
+ log_buf[isc_buffer_usedlength(&lb)] = '\0';
+}
+
+static void
+log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, bool early, char *log_buf,
+ unsigned int log_buf_len) {
+ if (e->logged) {
+ make_log_buf(rrl, e, early ? "*" : NULL,
+ rrl->log_only ? "would stop limiting "
+ : "stop limiting ",
+ true, NULL, false, DNS_RRL_RESULT_OK,
+ ISC_R_SUCCESS, log_buf, log_buf_len);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, "%s",
+ log_buf);
+ free_qname(rrl, e);
+ e->logged = false;
+ --rrl->num_logged;
+ }
+}
+
+/*
+ * Log messages for streams that have stopped being rate limited.
+ */
+static void
+log_stops(dns_rrl_t *rrl, isc_stdtime_t now, int limit, char *log_buf,
+ unsigned int log_buf_len) {
+ dns_rrl_entry_t *e;
+ int age;
+
+ for (e = rrl->last_logged; e != NULL; e = ISC_LIST_PREV(e, lru)) {
+ if (!e->logged) {
+ continue;
+ }
+ if (now != 0) {
+ age = get_age(rrl, e, now);
+ if (age < DNS_RRL_STOP_LOG_SECS ||
+ response_balance(rrl, e, age) < 0)
+ {
+ break;
+ }
+ }
+
+ log_end(rrl, e, now == 0, log_buf, log_buf_len);
+ if (rrl->num_logged <= 0) {
+ break;
+ }
+
+ /*
+ * Too many messages could stall real work.
+ */
+ if (--limit < 0) {
+ rrl->last_logged = ISC_LIST_PREV(e, lru);
+ return;
+ }
+ }
+ if (e == NULL) {
+ INSIST(rrl->num_logged == 0);
+ rrl->log_stops_time = now;
+ }
+ rrl->last_logged = e;
+}
+
+/*
+ * Main rate limit interface.
+ */
+dns_rrl_result_t
+dns_rrl(dns_view_t *view, dns_zone_t *zone, const isc_sockaddr_t *client_addr,
+ bool is_tcp, dns_rdataclass_t qclass, dns_rdatatype_t qtype,
+ const dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now,
+ bool wouldlog, char *log_buf, unsigned int log_buf_len) {
+ dns_rrl_t *rrl;
+ dns_rrl_rtype_t rtype;
+ dns_rrl_entry_t *e;
+ isc_netaddr_t netclient;
+ int secs;
+ double qps, scale;
+ int exempt_match;
+ isc_result_t result;
+ dns_rrl_result_t rrl_result;
+
+ INSIST(log_buf != NULL && log_buf_len > 0);
+
+ rrl = view->rrl;
+ if (rrl->exempt != NULL) {
+ isc_netaddr_fromsockaddr(&netclient, client_addr);
+ result = dns_acl_match(&netclient, NULL, rrl->exempt,
+ &view->aclenv, &exempt_match, NULL);
+ if (result == ISC_R_SUCCESS && exempt_match > 0) {
+ return (DNS_RRL_RESULT_OK);
+ }
+ }
+
+ LOCK(&rrl->lock);
+
+ /*
+ * Estimate total query per second rate when scaling by qps.
+ */
+ if (rrl->qps_scale == 0) {
+ qps = 0.0;
+ scale = 1.0;
+ } else {
+ ++rrl->qps_responses;
+ secs = delta_rrl_time(rrl->qps_time, now);
+ if (secs <= 0) {
+ qps = rrl->qps;
+ } else {
+ qps = (1.0 * rrl->qps_responses) / secs;
+ if (secs >= rrl->window) {
+ if (isc_log_wouldlog(dns_lctx,
+ DNS_RRL_LOG_DEBUG3))
+ {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST,
+ DNS_RRL_LOG_DEBUG3,
+ "%d responses/%d seconds"
+ " = %d qps",
+ rrl->qps_responses, secs,
+ (int)qps);
+ }
+ rrl->qps = qps;
+ rrl->qps_responses = 0;
+ rrl->qps_time = now;
+ } else if (qps < rrl->qps) {
+ qps = rrl->qps;
+ }
+ }
+ scale = rrl->qps_scale / qps;
+ }
+
+ /*
+ * Do maintenance once per second.
+ */
+ if (rrl->num_logged > 0 && rrl->log_stops_time != now) {
+ log_stops(rrl, now, 8, log_buf, log_buf_len);
+ }
+
+ /*
+ * Notice TCP responses when scaling limits by qps.
+ * Do not try to rate limit TCP responses.
+ */
+ if (is_tcp) {
+ if (scale < 1.0) {
+ e = get_entry(rrl, client_addr, NULL, 0,
+ dns_rdatatype_none, NULL,
+ DNS_RRL_RTYPE_TCP, now, true, log_buf,
+ log_buf_len);
+ if (e != NULL) {
+ e->responses = -(rrl->window + 1);
+ set_age(rrl, e, now);
+ }
+ }
+ UNLOCK(&rrl->lock);
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Find the right kind of entry, creating it if necessary.
+ * If that is impossible, then nothing more can be done
+ */
+ switch (resp_result) {
+ case ISC_R_SUCCESS:
+ rtype = DNS_RRL_RTYPE_QUERY;
+ break;
+ case DNS_R_DELEGATION:
+ rtype = DNS_RRL_RTYPE_REFERRAL;
+ break;
+ case DNS_R_NXRRSET:
+ rtype = DNS_RRL_RTYPE_NODATA;
+ break;
+ case DNS_R_NXDOMAIN:
+ rtype = DNS_RRL_RTYPE_NXDOMAIN;
+ break;
+ default:
+ rtype = DNS_RRL_RTYPE_ERROR;
+ break;
+ }
+ e = get_entry(rrl, client_addr, zone, qclass, qtype, qname, rtype, now,
+ true, log_buf, log_buf_len);
+ if (e == NULL) {
+ UNLOCK(&rrl->lock);
+ return (DNS_RRL_RESULT_OK);
+ }
+
+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) {
+ /*
+ * Do not worry about speed or releasing the lock.
+ * This message appears before messages from debit_rrl_entry().
+ */
+ make_log_buf(rrl, e, "consider limiting ", NULL, false, qname,
+ false, DNS_RRL_RESULT_OK, resp_result, log_buf,
+ log_buf_len);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, "%s",
+ log_buf);
+ }
+
+ rrl_result = debit_rrl_entry(rrl, e, qps, scale, client_addr, now,
+ log_buf, log_buf_len);
+
+ if (rrl->all_per_second.r != 0) {
+ /*
+ * We must debit the all-per-second token bucket if we have
+ * an all-per-second limit for the IP address.
+ * The all-per-second limit determines the log message
+ * when both limits are hit.
+ * The response limiting must continue if the
+ * all-per-second limiting lapses.
+ */
+ dns_rrl_entry_t *e_all;
+ dns_rrl_result_t rrl_all_result;
+
+ e_all = get_entry(rrl, client_addr, zone, 0, dns_rdatatype_none,
+ NULL, DNS_RRL_RTYPE_ALL, now, true, log_buf,
+ log_buf_len);
+ if (e_all == NULL) {
+ UNLOCK(&rrl->lock);
+ return (DNS_RRL_RESULT_OK);
+ }
+ rrl_all_result = debit_rrl_entry(rrl, e_all, qps, scale,
+ client_addr, now, log_buf,
+ log_buf_len);
+ if (rrl_all_result != DNS_RRL_RESULT_OK) {
+ e = e_all;
+ rrl_result = rrl_all_result;
+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) {
+ make_log_buf(rrl, e,
+ "prefer all-per-second limiting ",
+ NULL, true, qname, false,
+ DNS_RRL_RESULT_OK, resp_result,
+ log_buf, log_buf_len);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST,
+ DNS_RRL_LOG_DEBUG1, "%s",
+ log_buf);
+ }
+ }
+ }
+
+ if (rrl_result == DNS_RRL_RESULT_OK) {
+ UNLOCK(&rrl->lock);
+ return (DNS_RRL_RESULT_OK);
+ }
+
+ /*
+ * Log occasionally in the rate-limit category.
+ */
+ if ((!e->logged || e->log_secs >= DNS_RRL_MAX_LOG_SECS) &&
+ isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP))
+ {
+ make_log_buf(rrl, e, rrl->log_only ? "would " : NULL,
+ e->logged ? "continue limiting " : "limit ", true,
+ qname, true, DNS_RRL_RESULT_OK, resp_result,
+ log_buf, log_buf_len);
+ if (!e->logged) {
+ e->logged = true;
+ if (++rrl->num_logged <= 1) {
+ rrl->last_logged = e;
+ }
+ }
+ e->log_secs = 0;
+
+ /*
+ * Avoid holding the lock.
+ */
+ if (!wouldlog) {
+ UNLOCK(&rrl->lock);
+ e = NULL;
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, "%s",
+ log_buf);
+ }
+
+ /*
+ * Make a log message for the caller.
+ */
+ if (wouldlog) {
+ make_log_buf(rrl, e,
+ rrl->log_only ? "would rate limit "
+ : "rate limit ",
+ NULL, false, qname, false, rrl_result, resp_result,
+ log_buf, log_buf_len);
+ }
+
+ if (e != NULL) {
+ /*
+ * Do not save the qname unless we might need it for
+ * the ending log message.
+ */
+ if (!e->logged) {
+ free_qname(rrl, e);
+ }
+ UNLOCK(&rrl->lock);
+ }
+
+ return (rrl_result);
+}
+
+void
+dns_rrl_view_destroy(dns_view_t *view) {
+ dns_rrl_t *rrl;
+ dns_rrl_block_t *b;
+ dns_rrl_hash_t *h;
+ char log_buf[DNS_RRL_LOG_BUF_LEN];
+ int i;
+
+ rrl = view->rrl;
+ if (rrl == NULL) {
+ return;
+ }
+ view->rrl = NULL;
+
+ /*
+ * Assume the caller takes care of locking the view and anything else.
+ */
+
+ if (rrl->num_logged > 0) {
+ log_stops(rrl, 0, INT32_MAX, log_buf, sizeof(log_buf));
+ }
+
+ for (i = 0; i < DNS_RRL_QNAMES; ++i) {
+ if (rrl->qnames[i] == NULL) {
+ break;
+ }
+ isc_mem_put(rrl->mctx, rrl->qnames[i], sizeof(*rrl->qnames[i]));
+ }
+
+ if (rrl->exempt != NULL) {
+ dns_acl_detach(&rrl->exempt);
+ }
+
+ isc_mutex_destroy(&rrl->lock);
+
+ while (!ISC_LIST_EMPTY(rrl->blocks)) {
+ b = ISC_LIST_HEAD(rrl->blocks);
+ ISC_LIST_UNLINK(rrl->blocks, b, link);
+ isc_mem_put(rrl->mctx, b, b->size);
+ }
+
+ h = rrl->hash;
+ if (h != NULL) {
+ isc_mem_put(rrl->mctx, h,
+ sizeof(*h) + (h->length - 1) * sizeof(h->bins[0]));
+ }
+
+ h = rrl->old_hash;
+ if (h != NULL) {
+ isc_mem_put(rrl->mctx, h,
+ sizeof(*h) + (h->length - 1) * sizeof(h->bins[0]));
+ }
+
+ isc_mem_putanddetach(&rrl->mctx, rrl, sizeof(*rrl));
+}
+
+isc_result_t
+dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries) {
+ dns_rrl_t *rrl;
+ isc_result_t result;
+
+ *rrlp = NULL;
+
+ rrl = isc_mem_get(view->mctx, sizeof(*rrl));
+ memset(rrl, 0, sizeof(*rrl));
+ isc_mem_attach(view->mctx, &rrl->mctx);
+ isc_mutex_init(&rrl->lock);
+ isc_stdtime_get(&rrl->ts_bases[0]);
+
+ view->rrl = rrl;
+
+ result = expand_entries(rrl, min_entries);
+ if (result != ISC_R_SUCCESS) {
+ dns_rrl_view_destroy(view);
+ return (result);
+ }
+ result = expand_rrl_hash(rrl, 0);
+ if (result != ISC_R_SUCCESS) {
+ dns_rrl_view_destroy(view);
+ return (result);
+ }
+
+ *rrlp = rrl;
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c
new file mode 100644
index 0000000..2c6802f
--- /dev/null
+++ b/lib/dns/sdb.c
@@ -0,0 +1,1613 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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/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/result.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_copynf(xname, foundname);
+ }
+
+ if (nodep != NULL) {
+ *nodep = node;
+ } else if (node != NULL) {
+ detachnode(db, &node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
+ isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname,
+ dns_name_t *dcname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ UNUSED(db);
+ UNUSED(name);
+ UNUSED(options);
+ UNUSED(now);
+ UNUSED(nodep);
+ UNUSED(foundname);
+ UNUSED(dcname);
+ UNUSED(rdataset);
+ UNUSED(sigrdataset);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static void
+attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) {
+ dns_sdb_t *sdb = (dns_sdb_t *)db;
+ dns_sdbnode_t *node = (dns_sdbnode_t *)source;
+
+ REQUIRE(VALID_SDB(sdb));
+
+ UNUSED(sdb);
+
+ isc_refcount_increment(&node->references);
+
+ *targetp = source;
+}
+
+static void
+detachnode(dns_db_t *db, dns_dbnode_t **targetp) {
+ dns_sdb_t *sdb = (dns_sdb_t *)db;
+ dns_sdbnode_t *node;
+
+ REQUIRE(VALID_SDB(sdb));
+ REQUIRE(targetp != NULL && *targetp != NULL);
+
+ UNUSED(sdb);
+
+ node = (dns_sdbnode_t *)(*targetp);
+
+ *targetp = NULL;
+
+ if (isc_refcount_decrement(&node->references) == 1) {
+ destroynode(node);
+ }
+}
+
+static isc_result_t
+expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(now);
+ UNREACHABLE();
+}
+
+static void
+printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(out);
+ return;
+}
+
+static isc_result_t
+createiterator(dns_db_t *db, unsigned int options,
+ dns_dbiterator_t **iteratorp) {
+ dns_sdb_t *sdb = (dns_sdb_t *)db;
+ REQUIRE(VALID_SDB(sdb));
+
+ sdb_dbiterator_t *sdbiter;
+ isc_result_t result;
+ dns_sdbimplementation_t *imp = sdb->implementation;
+
+ if (imp->methods->allnodes == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ if ((options & DNS_DB_NSEC3ONLY) != 0 ||
+ (options & DNS_DB_NONSEC3) != 0)
+ {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ sdbiter = isc_mem_get(sdb->common.mctx, sizeof(sdb_dbiterator_t));
+
+ sdbiter->common.methods = &dbiterator_methods;
+ sdbiter->common.db = NULL;
+ dns_db_attach(db, &sdbiter->common.db);
+ sdbiter->common.relative_names = ((options & DNS_DB_RELATIVENAMES) !=
+ 0);
+ sdbiter->common.magic = DNS_DBITERATOR_MAGIC;
+ ISC_LIST_INIT(sdbiter->nodelist);
+ sdbiter->current = NULL;
+ sdbiter->origin = NULL;
+
+ MAYBE_LOCK(sdb);
+ result = imp->methods->allnodes(sdb->zone, sdb->dbdata, sdbiter);
+ MAYBE_UNLOCK(sdb);
+ if (result != ISC_R_SUCCESS) {
+ dbiterator_destroy((dns_dbiterator_t **)(void *)&sdbiter);
+ return (result);
+ }
+
+ if (sdbiter->origin != NULL) {
+ ISC_LIST_UNLINK(sdbiter->nodelist, sdbiter->origin, link);
+ ISC_LIST_PREPEND(sdbiter->nodelist, sdbiter->origin, link);
+ }
+
+ *iteratorp = (dns_dbiterator_t *)sdbiter;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers, isc_stdtime_t now,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ REQUIRE(VALID_SDBNODE(node));
+
+ dns_rdatalist_t *list;
+ dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)node;
+
+ UNUSED(db);
+ UNUSED(version);
+ UNUSED(covers);
+ UNUSED(now);
+ UNUSED(sigrdataset);
+
+ if (type == dns_rdatatype_rrsig) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ list = ISC_LIST_HEAD(sdbnode->lists);
+ while (list != NULL) {
+ if (list->type == type) {
+ break;
+ }
+ list = ISC_LIST_NEXT(list, link);
+ }
+ if (list == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ list_tordataset(list, db, node, rdataset);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ sdb_rdatasetiter_t *iterator;
+
+ REQUIRE(version == NULL || version == &dummy);
+
+ UNUSED(version);
+ UNUSED(now);
+
+ iterator = isc_mem_get(db->mctx, sizeof(sdb_rdatasetiter_t));
+
+ iterator->common.magic = DNS_RDATASETITER_MAGIC;
+ iterator->common.methods = &rdatasetiter_methods;
+ iterator->common.db = db;
+ iterator->common.node = NULL;
+ attachnode(db, node, &iterator->common.node);
+ iterator->common.version = version;
+ iterator->common.options = options;
+ iterator->common.now = now;
+
+ *iteratorp = (dns_rdatasetiter_t *)iterator;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *addedrdataset) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(version);
+ UNUSED(now);
+ UNUSED(rdataset);
+ UNUSED(options);
+ UNUSED(addedrdataset);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *newrdataset) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(version);
+ UNUSED(rdataset);
+ UNUSED(options);
+ UNUSED(newrdataset);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers) {
+ UNUSED(db);
+ UNUSED(node);
+ UNUSED(version);
+ UNUSED(type);
+ UNUSED(covers);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static bool
+issecure(dns_db_t *db) {
+ UNUSED(db);
+
+ return (false);
+}
+
+static unsigned int
+nodecount(dns_db_t *db) {
+ UNUSED(db);
+
+ return (0);
+}
+
+static bool
+ispersistent(dns_db_t *db) {
+ UNUSED(db);
+ return (true);
+}
+
+static void
+overmem(dns_db_t *db, bool over) {
+ UNUSED(db);
+ UNUSED(over);
+}
+
+static void
+settask(dns_db_t *db, isc_task_t *task) {
+ UNUSED(db);
+ UNUSED(task);
+}
+
+static dns_dbmethods_t sdb_methods = {
+ attach,
+ detach,
+ beginload,
+ endload,
+ NULL, /* serialize */
+ dump,
+ currentversion,
+ newversion,
+ attachversion,
+ closeversion,
+ NULL, /* findnode */
+ NULL, /* find */
+ findzonecut,
+ attachnode,
+ detachnode,
+ expirenode,
+ printnode,
+ createiterator,
+ findrdataset,
+ allrdatasets,
+ addrdataset,
+ subtractrdataset,
+ deleterdataset,
+ issecure,
+ nodecount,
+ ispersistent,
+ overmem,
+ settask,
+ getoriginnode, /* getoriginnode */
+ NULL, /* transfernode */
+ NULL, /* getnsec3parameters */
+ NULL, /* findnsec3node */
+ NULL, /* setsigningtime */
+ NULL, /* getsigningtime */
+ NULL, /* resigned */
+ NULL, /* isdnssec */
+ NULL, /* getrrsetstats */
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ findnodeext,
+ findext,
+ NULL, /* setcachestats */
+ NULL, /* hashsize */
+ NULL, /* nodefullname */
+ NULL, /* getsize */
+ NULL, /* setservestalettl */
+ NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
+ NULL, /* setgluecachestats */
+ NULL /* adjusthashsize */
+};
+
+static isc_result_t
+dns_sdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
+ dns_rdataclass_t rdclass, unsigned int argc, char *argv[],
+ void *driverarg, dns_db_t **dbp) {
+ dns_sdb_t *sdb;
+ isc_result_t result;
+ char zonestr[DNS_NAME_MAXTEXT + 1];
+ isc_buffer_t b;
+ dns_sdbimplementation_t *imp;
+
+ REQUIRE(driverarg != NULL);
+
+ imp = driverarg;
+
+ if (type != dns_dbtype_zone) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ sdb = isc_mem_get(mctx, sizeof(dns_sdb_t));
+ memset(sdb, 0, sizeof(dns_sdb_t));
+
+ dns_name_init(&sdb->common.origin, NULL);
+ sdb->common.attributes = 0;
+ sdb->common.methods = &sdb_methods;
+ sdb->common.rdclass = rdclass;
+ sdb->common.mctx = NULL;
+ sdb->implementation = imp;
+
+ isc_mem_attach(mctx, &sdb->common.mctx);
+
+ result = dns_name_dupwithoffsets(origin, mctx, &sdb->common.origin);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_lock;
+ }
+
+ isc_buffer_init(&b, zonestr, sizeof(zonestr));
+ result = dns_name_totext(origin, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_origin;
+ }
+ isc_buffer_putuint8(&b, 0);
+
+ sdb->zone = isc_mem_strdup(mctx, zonestr);
+
+ sdb->dbdata = NULL;
+ if (imp->methods->create != NULL) {
+ MAYBE_LOCK(sdb);
+ result = imp->methods->create(sdb->zone, argc, argv,
+ imp->driverdata, &sdb->dbdata);
+ MAYBE_UNLOCK(sdb);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_zonestr;
+ }
+ }
+
+ isc_refcount_init(&sdb->references, 1);
+
+ sdb->common.magic = DNS_DB_MAGIC;
+ sdb->common.impmagic = SDB_MAGIC;
+
+ *dbp = (dns_db_t *)sdb;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_zonestr:
+ isc_mem_free(mctx, sdb->zone);
+cleanup_origin:
+ dns_name_free(&sdb->common.origin, mctx);
+cleanup_lock:
+ isc_mem_putanddetach(&mctx, sdb, sizeof(dns_sdb_t));
+
+ return (result);
+}
+
+/*
+ * Rdataset Methods
+ */
+
+static void
+disassociate(dns_rdataset_t *rdataset) {
+ dns_dbnode_t *node = rdataset->private5;
+ dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)node;
+ dns_db_t *db = (dns_db_t *)sdbnode->sdb;
+
+ detachnode(db, &node);
+ isc__rdatalist_disassociate(rdataset);
+}
+
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ dns_dbnode_t *node = source->private5;
+ dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)node;
+ dns_db_t *db = (dns_db_t *)sdbnode->sdb;
+ dns_dbnode_t *tempdb = NULL;
+
+ isc__rdatalist_clone(source, target);
+ attachnode(db, node, &tempdb);
+ source->private5 = tempdb;
+}
+
+static dns_rdatasetmethods_t sdb_rdataset_methods = {
+ disassociate,
+ isc__rdatalist_first,
+ isc__rdatalist_next,
+ isc__rdatalist_current,
+ rdataset_clone,
+ isc__rdatalist_count,
+ isc__rdatalist_addnoqname,
+ isc__rdatalist_getnoqname,
+ NULL, /* addclosest */
+ NULL, /* getclosest */
+ NULL, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL, /* setownercase */
+ NULL, /* getownercase */
+ NULL /* addglue */
+};
+
+static void
+list_tordataset(dns_rdatalist_t *rdatalist, dns_db_t *db, dns_dbnode_t *node,
+ dns_rdataset_t *rdataset) {
+ /*
+ * The sdb rdataset is an rdatalist with some additions.
+ * - private1 & private2 are used by the rdatalist.
+ * - private3 & private 4 are unused.
+ * - private5 is the node.
+ */
+
+ /* This should never fail. */
+ RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) ==
+ ISC_R_SUCCESS);
+
+ rdataset->methods = &sdb_rdataset_methods;
+ dns_db_attachnode(db, node, &rdataset->private5);
+}
+
+/*
+ * Database Iterator Methods
+ */
+static void
+dbiterator_destroy(dns_dbiterator_t **iteratorp) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)(*iteratorp);
+ dns_sdb_t *sdb = (dns_sdb_t *)sdbiter->common.db;
+
+ while (!ISC_LIST_EMPTY(sdbiter->nodelist)) {
+ dns_sdbnode_t *node;
+ node = ISC_LIST_HEAD(sdbiter->nodelist);
+ ISC_LIST_UNLINK(sdbiter->nodelist, node, link);
+ destroynode(node);
+ }
+
+ dns_db_detach(&sdbiter->common.db);
+ isc_mem_put(sdb->common.mctx, sdbiter, sizeof(sdb_dbiterator_t));
+
+ *iteratorp = NULL;
+}
+
+static isc_result_t
+dbiterator_first(dns_dbiterator_t *iterator) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ sdbiter->current = ISC_LIST_HEAD(sdbiter->nodelist);
+ if (sdbiter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_last(dns_dbiterator_t *iterator) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ sdbiter->current = ISC_LIST_TAIL(sdbiter->nodelist);
+ if (sdbiter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ sdbiter->current = ISC_LIST_HEAD(sdbiter->nodelist);
+ while (sdbiter->current != NULL) {
+ if (dns_name_equal(sdbiter->current->name, name)) {
+ return (ISC_R_SUCCESS);
+ }
+ sdbiter->current = ISC_LIST_NEXT(sdbiter->current, link);
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+dbiterator_prev(dns_dbiterator_t *iterator) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ sdbiter->current = ISC_LIST_PREV(sdbiter->current, link);
+ if (sdbiter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_next(dns_dbiterator_t *iterator) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ sdbiter->current = ISC_LIST_NEXT(sdbiter->current, link);
+ if (sdbiter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name) {
+ sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator;
+
+ attachnode(iterator->db, sdbiter->current, nodep);
+ if (name != NULL) {
+ dns_name_copynf(sdbiter->current->name, name);
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dbiterator_pause(dns_dbiterator_t *iterator) {
+ UNUSED(iterator);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) {
+ UNUSED(iterator);
+ dns_name_copynf(dns_rootname, name);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Rdataset Iterator Methods
+ */
+
+static void
+rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
+ sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)(*iteratorp);
+ detachnode(sdbiterator->common.db, &sdbiterator->common.node);
+ isc_mem_put(sdbiterator->common.db->mctx, sdbiterator,
+ sizeof(sdb_rdatasetiter_t));
+ *iteratorp = NULL;
+}
+
+static isc_result_t
+rdatasetiter_first(dns_rdatasetiter_t *iterator) {
+ sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)iterator;
+ dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)iterator->node;
+
+ if (ISC_LIST_EMPTY(sdbnode->lists)) {
+ return (ISC_R_NOMORE);
+ }
+ sdbiterator->current = ISC_LIST_HEAD(sdbnode->lists);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+rdatasetiter_next(dns_rdatasetiter_t *iterator) {
+ sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)iterator;
+
+ sdbiterator->current = ISC_LIST_NEXT(sdbiterator->current, link);
+ if (sdbiterator->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static void
+rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset) {
+ sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)iterator;
+
+ list_tordataset(sdbiterator->current, iterator->db, iterator->node,
+ rdataset);
+}
diff --git a/lib/dns/sdlz.c b/lib/dns/sdlz.c
new file mode 100644
index 0000000..8ba7aae
--- /dev/null
+++ b/lib/dns/sdlz.c
@@ -0,0 +1,2108 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND ISC
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
+ * conceived and contributed by Rob Butler.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+#include <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/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/result.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_copynf(xname, foundname);
+ }
+
+ if (nodep != NULL) {
+ *nodep = node;
+ } else if (node != NULL) {
+ detachnode(db, &node);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
+ dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
+ dns_dbnode_t **nodep, dns_name_t *foundname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ return (findext(db, name, version, type, options, now, nodep, foundname,
+ NULL, NULL, rdataset, sigrdataset));
+}
+
+static isc_result_t
+allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ unsigned int options, isc_stdtime_t now,
+ dns_rdatasetiter_t **iteratorp) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ sdlz_rdatasetiter_t *iterator;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ REQUIRE(version == NULL || version == (void *)&sdlz->dummy_version ||
+ version == sdlz->future_version);
+
+ UNUSED(version);
+ UNUSED(now);
+
+ iterator = isc_mem_get(db->mctx, sizeof(sdlz_rdatasetiter_t));
+
+ iterator->common.magic = DNS_RDATASETITER_MAGIC;
+ iterator->common.methods = &rdatasetiter_methods;
+ iterator->common.db = db;
+ iterator->common.node = NULL;
+ attachnode(db, node, &iterator->common.node);
+ iterator->common.version = version;
+ iterator->common.options = options;
+ iterator->common.now = now;
+
+ *iteratorp = (dns_rdatasetiter_t *)iterator;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+modrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdataset_t *rdataset, unsigned int options,
+ dns_sdlzmodrdataset_t mod_function) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ dns_master_style_t *style = NULL;
+ isc_result_t result;
+ isc_buffer_t *buffer = NULL;
+ isc_mem_t *mctx;
+ dns_sdlznode_t *sdlznode;
+ char *rdatastr = NULL;
+ char name[DNS_NAME_MAXTEXT + 1];
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ if (mod_function == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ sdlznode = (dns_sdlznode_t *)node;
+
+ UNUSED(options);
+
+ dns_name_format(sdlznode->name, name, sizeof(name));
+
+ mctx = sdlz->common.mctx;
+
+ isc_buffer_allocate(mctx, &buffer, 1024);
+
+ result = dns_master_stylecreate(&style, 0, 0, 0, 0, 0, 0, 1, 0xffffffff,
+ mctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_master_rdatasettotext(sdlznode->name, rdataset, style,
+ NULL, buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (isc_buffer_usedlength(buffer) < 1) {
+ result = ISC_R_BADADDRESSFORM;
+ goto cleanup;
+ }
+
+ rdatastr = isc_buffer_base(buffer);
+ if (rdatastr == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ rdatastr[isc_buffer_usedlength(buffer) - 1] = 0;
+
+ MAYBE_LOCK(sdlz->dlzimp);
+ result = mod_function(name, rdatastr, sdlz->dlzimp->driverarg,
+ sdlz->dbdata, version);
+ MAYBE_UNLOCK(sdlz->dlzimp);
+
+cleanup:
+ isc_buffer_free(&buffer);
+ if (style != NULL) {
+ dns_master_styledestroy(&style, mctx);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *addedrdataset) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ isc_result_t result;
+
+ UNUSED(now);
+ UNUSED(addedrdataset);
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ if (sdlz->dlzimp->methods->addrdataset == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ result = modrdataset(db, node, version, rdataset, options,
+ sdlz->dlzimp->methods->addrdataset);
+ return (result);
+}
+
+static isc_result_t
+subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdataset_t *rdataset, unsigned int options,
+ dns_rdataset_t *newrdataset) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ isc_result_t result;
+
+ UNUSED(newrdataset);
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ if (sdlz->dlzimp->methods->subtractrdataset == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ result = modrdataset(db, node, version, rdataset, options,
+ sdlz->dlzimp->methods->subtractrdataset);
+ return (result);
+}
+
+static isc_result_t
+deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdatatype_t type, dns_rdatatype_t covers) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ char name[DNS_NAME_MAXTEXT + 1];
+ char b_type[DNS_RDATATYPE_FORMATSIZE];
+ dns_sdlznode_t *sdlznode;
+ isc_result_t result;
+
+ UNUSED(covers);
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+
+ if (sdlz->dlzimp->methods->delrdataset == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ sdlznode = (dns_sdlznode_t *)node;
+ dns_name_format(sdlznode->name, name, sizeof(name));
+ dns_rdatatype_format(type, b_type, sizeof(b_type));
+
+ MAYBE_LOCK(sdlz->dlzimp);
+ result = sdlz->dlzimp->methods->delrdataset(
+ name, b_type, sdlz->dlzimp->driverarg, sdlz->dbdata, version);
+ MAYBE_UNLOCK(sdlz->dlzimp);
+
+ return (result);
+}
+
+static bool
+issecure(dns_db_t *db) {
+ UNUSED(db);
+
+ return (false);
+}
+
+static unsigned int
+nodecount(dns_db_t *db) {
+ UNUSED(db);
+
+ return (0);
+}
+
+static bool
+ispersistent(dns_db_t *db) {
+ UNUSED(db);
+ return (true);
+}
+
+static void
+overmem(dns_db_t *db, bool over) {
+ UNUSED(db);
+ UNUSED(over);
+}
+
+static void
+settask(dns_db_t *db, isc_task_t *task) {
+ UNUSED(db);
+ UNUSED(task);
+}
+
+/*
+ * getoriginnode() is used by the update code to find the
+ * dns_rdatatype_dnskey record for a zone
+ */
+static isc_result_t
+getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) {
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
+ isc_result_t result;
+
+ REQUIRE(VALID_SDLZDB(sdlz));
+ if (sdlz->dlzimp->methods->newversion == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ result = getnodedata(db, &sdlz->common.origin, false, 0, NULL, NULL,
+ nodep);
+ if (result != ISC_R_SUCCESS) {
+ sdlz_log(ISC_LOG_ERROR, "sdlz getoriginnode failed: %s",
+ isc_result_totext(result));
+ }
+ return (result);
+}
+
+static dns_dbmethods_t sdlzdb_methods = {
+ attach,
+ detach,
+ beginload,
+ endload,
+ NULL, /* serialize */
+ dump,
+ currentversion,
+ newversion,
+ attachversion,
+ closeversion,
+ findnode,
+ find,
+ findzonecut,
+ attachnode,
+ detachnode,
+ expirenode,
+ printnode,
+ createiterator,
+ findrdataset,
+ allrdatasets,
+ addrdataset,
+ subtractrdataset,
+ deleterdataset,
+ issecure,
+ nodecount,
+ ispersistent,
+ overmem,
+ settask,
+ getoriginnode,
+ NULL, /* transfernode */
+ NULL, /* getnsec3parameters */
+ NULL, /* findnsec3node */
+ NULL, /* setsigningtime */
+ NULL, /* getsigningtime */
+ NULL, /* resigned */
+ NULL, /* isdnssec */
+ NULL, /* getrrsetstats */
+ NULL, /* rpz_attach */
+ NULL, /* rpz_ready */
+ findnodeext,
+ findext,
+ NULL, /* setcachestats */
+ NULL, /* hashsize */
+ NULL, /* nodefullname */
+ NULL, /* getsize */
+ NULL, /* setservestalettl */
+ NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
+ NULL, /* setgluecachestats */
+ NULL /* adjusthashsize */
+};
+
+/*
+ * Database Iterator Methods. These methods were "borrowed" from the SDB
+ * driver interface. See the SDB driver interface documentation for more info.
+ */
+
+static void
+dbiterator_destroy(dns_dbiterator_t **iteratorp) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)(*iteratorp);
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)sdlziter->common.db;
+
+ while (!ISC_LIST_EMPTY(sdlziter->nodelist)) {
+ dns_sdlznode_t *node;
+ node = ISC_LIST_HEAD(sdlziter->nodelist);
+ ISC_LIST_UNLINK(sdlziter->nodelist, node, link);
+ isc_refcount_decrementz(&node->references);
+ destroynode(node);
+ }
+
+ dns_db_detach(&sdlziter->common.db);
+ isc_mem_put(sdlz->common.mctx, sdlziter, sizeof(sdlz_dbiterator_t));
+
+ *iteratorp = NULL;
+}
+
+static isc_result_t
+dbiterator_first(dns_dbiterator_t *iterator) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ sdlziter->current = ISC_LIST_HEAD(sdlziter->nodelist);
+ if (sdlziter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_last(dns_dbiterator_t *iterator) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ sdlziter->current = ISC_LIST_TAIL(sdlziter->nodelist);
+ if (sdlziter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ sdlziter->current = ISC_LIST_HEAD(sdlziter->nodelist);
+ while (sdlziter->current != NULL) {
+ if (dns_name_equal(sdlziter->current->name, name)) {
+ return (ISC_R_SUCCESS);
+ }
+ sdlziter->current = ISC_LIST_NEXT(sdlziter->current, link);
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+dbiterator_prev(dns_dbiterator_t *iterator) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ sdlziter->current = ISC_LIST_PREV(sdlziter->current, link);
+ if (sdlziter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_next(dns_dbiterator_t *iterator) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ sdlziter->current = ISC_LIST_NEXT(sdlziter->current, link);
+ if (sdlziter->current == NULL) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep,
+ dns_name_t *name) {
+ sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator;
+
+ attachnode(iterator->db, sdlziter->current, nodep);
+ if (name != NULL) {
+ dns_name_copynf(sdlziter->current->name, name);
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dbiterator_pause(dns_dbiterator_t *iterator) {
+ UNUSED(iterator);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) {
+ UNUSED(iterator);
+ dns_name_copynf(dns_rootname, name);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Rdataset Methods. These methods were "borrowed" from the SDB driver
+ * interface. See the SDB driver interface documentation for more info.
+ */
+
+static void
+disassociate(dns_rdataset_t *rdataset) {
+ dns_dbnode_t *node = rdataset->private5;
+ dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)node;
+ dns_db_t *db = (dns_db_t *)sdlznode->sdlz;
+
+ detachnode(db, &node);
+ isc__rdatalist_disassociate(rdataset);
+}
+
+static void
+rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
+ dns_dbnode_t *node = source->private5;
+ dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)node;
+ dns_db_t *db = (dns_db_t *)sdlznode->sdlz;
+ dns_dbnode_t *tempdb = NULL;
+
+ isc__rdatalist_clone(source, target);
+ attachnode(db, node, &tempdb);
+ source->private5 = tempdb;
+}
+
+static dns_rdatasetmethods_t rdataset_methods = {
+ disassociate,
+ isc__rdatalist_first,
+ isc__rdatalist_next,
+ isc__rdatalist_current,
+ rdataset_clone,
+ isc__rdatalist_count,
+ isc__rdatalist_addnoqname,
+ isc__rdatalist_getnoqname,
+ NULL, /* addclosest */
+ NULL, /* getclosest */
+ NULL, /* settrust */
+ NULL, /* expire */
+ NULL, /* clearprefetch */
+ NULL, /* setownercase */
+ NULL, /* getownercase */
+ NULL /* addglue */
+};
+
+static void
+list_tordataset(dns_rdatalist_t *rdatalist, dns_db_t *db, dns_dbnode_t *node,
+ dns_rdataset_t *rdataset) {
+ /*
+ * The sdlz rdataset is an rdatalist with some additions.
+ * - private1 & private2 are used by the rdatalist.
+ * - private3 & private 4 are unused.
+ * - private5 is the node.
+ */
+
+ /* This should never fail. */
+ RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) ==
+ ISC_R_SUCCESS);
+
+ rdataset->methods = &rdataset_methods;
+ dns_db_attachnode(db, node, &rdataset->private5);
+}
+
+/*
+ * SDLZ core methods. This is the core of the new DLZ functionality.
+ */
+
+/*%
+ * Build a 'bind' database driver structure to be returned by
+ * either the find zone or the allow zone transfer method.
+ * This method is only available in this source file, it is
+ * not made available anywhere else.
+ */
+
+static isc_result_t
+dns_sdlzcreateDBP(isc_mem_t *mctx, void *driverarg, void *dbdata,
+ const dns_name_t *name, dns_rdataclass_t rdclass,
+ dns_db_t **dbp) {
+ isc_result_t result;
+ dns_sdlz_db_t *sdlzdb;
+ dns_sdlzimplementation_t *imp;
+
+ /* check that things are as we expect */
+ REQUIRE(dbp != NULL && *dbp == NULL);
+ REQUIRE(name != NULL);
+
+ imp = (dns_sdlzimplementation_t *)driverarg;
+
+ /* allocate and zero memory for driver structure */
+ sdlzdb = isc_mem_get(mctx, sizeof(dns_sdlz_db_t));
+ memset(sdlzdb, 0, sizeof(dns_sdlz_db_t));
+
+ /* initialize and set origin */
+ dns_name_init(&sdlzdb->common.origin, NULL);
+ result = dns_name_dupwithoffsets(name, mctx, &sdlzdb->common.origin);
+ if (result != ISC_R_SUCCESS) {
+ goto mem_cleanup;
+ }
+
+ /* set the rest of the database structure attributes */
+ sdlzdb->dlzimp = imp;
+ sdlzdb->common.methods = &sdlzdb_methods;
+ sdlzdb->common.attributes = 0;
+ sdlzdb->common.rdclass = rdclass;
+ sdlzdb->common.mctx = NULL;
+ sdlzdb->dbdata = dbdata;
+ isc_refcount_init(&sdlzdb->references, 1);
+
+ /* attach to the memory context */
+ isc_mem_attach(mctx, &sdlzdb->common.mctx);
+
+ /* mark structure as valid */
+ sdlzdb->common.magic = DNS_DB_MAGIC;
+ sdlzdb->common.impmagic = SDLZDB_MAGIC;
+ *dbp = (dns_db_t *)sdlzdb;
+
+ return (result);
+mem_cleanup:
+ isc_mem_put(mctx, sdlzdb, sizeof(dns_sdlz_db_t));
+ return (result);
+}
+
+static isc_result_t
+dns_sdlzallowzonexfr(void *driverarg, void *dbdata, isc_mem_t *mctx,
+ dns_rdataclass_t rdclass, const dns_name_t *name,
+ const isc_sockaddr_t *clientaddr, dns_db_t **dbp) {
+ isc_buffer_t b;
+ isc_buffer_t b2;
+ char namestr[DNS_NAME_MAXTEXT + 1];
+ char clientstr[(sizeof "xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255."
+ "255") +
+ 1];
+ isc_netaddr_t netaddr;
+ isc_result_t result;
+ dns_sdlzimplementation_t *imp;
+
+ /*
+ * Perform checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(driverarg != NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(clientaddr != NULL);
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ imp = (dns_sdlzimplementation_t *)driverarg;
+
+ /* Convert DNS name to ascii text */
+ isc_buffer_init(&b, namestr, sizeof(namestr));
+ result = dns_name_totext(name, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_putuint8(&b, 0);
+
+ /* convert client address to ascii text */
+ isc_buffer_init(&b2, clientstr, sizeof(clientstr));
+ isc_netaddr_fromsockaddr(&netaddr, clientaddr);
+ result = isc_netaddr_totext(&netaddr, &b2);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_putuint8(&b2, 0);
+
+ /* make sure strings are always lowercase */
+ dns_sdlz_tolower(namestr);
+ dns_sdlz_tolower(clientstr);
+
+ /* Call SDLZ driver's find zone method */
+ if (imp->methods->allowzonexfr != NULL) {
+ isc_result_t rresult = ISC_R_SUCCESS;
+
+ MAYBE_LOCK(imp);
+ result = imp->methods->allowzonexfr(imp->driverarg, dbdata,
+ namestr, clientstr);
+ MAYBE_UNLOCK(imp);
+ /*
+ * if zone is supported and transfers are (or might be)
+ * allowed, build a 'bind' database driver
+ */
+ if (result == ISC_R_SUCCESS || result == ISC_R_DEFAULT) {
+ rresult = dns_sdlzcreateDBP(mctx, driverarg, dbdata,
+ name, rdclass, dbp);
+ }
+ if (rresult != ISC_R_SUCCESS) {
+ result = rresult;
+ }
+ return (result);
+ }
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static isc_result_t
+dns_sdlzcreate(isc_mem_t *mctx, const char *dlzname, unsigned int argc,
+ char *argv[], void *driverarg, void **dbdata) {
+ dns_sdlzimplementation_t *imp;
+ isc_result_t result = ISC_R_NOTFOUND;
+
+ /* Write debugging message to log */
+ sdlz_log(ISC_LOG_DEBUG(2), "Loading SDLZ driver.");
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(driverarg != NULL);
+ REQUIRE(dlzname != NULL);
+ REQUIRE(dbdata != NULL);
+ UNUSED(mctx);
+
+ imp = driverarg;
+
+ /* If the create method exists, call it. */
+ if (imp->methods->create != NULL) {
+ MAYBE_LOCK(imp);
+ result = imp->methods->create(dlzname, argc, argv,
+ imp->driverarg, dbdata);
+ MAYBE_UNLOCK(imp);
+ }
+
+ /* Write debugging message to log */
+ if (result == ISC_R_SUCCESS) {
+ sdlz_log(ISC_LOG_DEBUG(2), "SDLZ driver loaded successfully.");
+ } else {
+ sdlz_log(ISC_LOG_ERROR, "SDLZ driver failed to load.");
+ }
+
+ return (result);
+}
+
+static void
+dns_sdlzdestroy(void *driverdata, void **dbdata) {
+ dns_sdlzimplementation_t *imp;
+
+ /* Write debugging message to log */
+ sdlz_log(ISC_LOG_DEBUG(2), "Unloading SDLZ driver.");
+
+ imp = driverdata;
+
+ /* If the destroy method exists, call it. */
+ if (imp->methods->destroy != NULL) {
+ MAYBE_LOCK(imp);
+ imp->methods->destroy(imp->driverarg, dbdata);
+ MAYBE_UNLOCK(imp);
+ }
+}
+
+static isc_result_t
+dns_sdlzfindzone(void *driverarg, void *dbdata, isc_mem_t *mctx,
+ dns_rdataclass_t rdclass, const dns_name_t *name,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
+ dns_db_t **dbp) {
+ isc_buffer_t b;
+ char namestr[DNS_NAME_MAXTEXT + 1];
+ isc_result_t result;
+ dns_sdlzimplementation_t *imp;
+
+ /*
+ * Perform checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(driverarg != NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ imp = (dns_sdlzimplementation_t *)driverarg;
+
+ /* Convert DNS name to ascii text */
+ isc_buffer_init(&b, namestr, sizeof(namestr));
+ result = dns_name_totext(name, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_putuint8(&b, 0);
+
+ /* make sure strings are always lowercase */
+ dns_sdlz_tolower(namestr);
+
+ /* Call SDLZ driver's find zone method */
+ MAYBE_LOCK(imp);
+ result = imp->methods->findzone(imp->driverarg, dbdata, namestr,
+ methods, clientinfo);
+ MAYBE_UNLOCK(imp);
+
+ /*
+ * if zone is supported build a 'bind' database driver
+ * structure to return
+ */
+ if (result == ISC_R_SUCCESS) {
+ result = dns_sdlzcreateDBP(mctx, driverarg, dbdata, name,
+ rdclass, dbp);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+dns_sdlzconfigure(void *driverarg, void *dbdata, dns_view_t *view,
+ dns_dlzdb_t *dlzdb) {
+ isc_result_t result;
+ dns_sdlzimplementation_t *imp;
+
+ REQUIRE(driverarg != NULL);
+
+ imp = (dns_sdlzimplementation_t *)driverarg;
+
+ /* Call SDLZ driver's configure method */
+ if (imp->methods->configure != NULL) {
+ MAYBE_LOCK(imp);
+ result = imp->methods->configure(view, dlzdb, imp->driverarg,
+ dbdata);
+ MAYBE_UNLOCK(imp);
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
+
+static bool
+dns_sdlzssumatch(const dns_name_t *signer, const dns_name_t *name,
+ const isc_netaddr_t *tcpaddr, dns_rdatatype_t type,
+ const dst_key_t *key, void *driverarg, void *dbdata) {
+ dns_sdlzimplementation_t *imp;
+ char b_signer[DNS_NAME_FORMATSIZE];
+ char b_name[DNS_NAME_FORMATSIZE];
+ char b_addr[ISC_NETADDR_FORMATSIZE];
+ char b_type[DNS_RDATATYPE_FORMATSIZE];
+ char b_key[DST_KEY_FORMATSIZE];
+ isc_buffer_t *tkey_token = NULL;
+ isc_region_t token_region = { NULL, 0 };
+ uint32_t token_len = 0;
+ bool ret;
+
+ REQUIRE(driverarg != NULL);
+
+ imp = (dns_sdlzimplementation_t *)driverarg;
+ if (imp->methods->ssumatch == NULL) {
+ return (false);
+ }
+
+ /*
+ * Format the request elements. sdlz operates on strings, not
+ * structures
+ */
+ if (signer != NULL) {
+ dns_name_format(signer, b_signer, sizeof(b_signer));
+ } else {
+ b_signer[0] = 0;
+ }
+
+ dns_name_format(name, b_name, sizeof(b_name));
+
+ if (tcpaddr != NULL) {
+ isc_netaddr_format(tcpaddr, b_addr, sizeof(b_addr));
+ } else {
+ b_addr[0] = 0;
+ }
+
+ dns_rdatatype_format(type, b_type, sizeof(b_type));
+
+ if (key != NULL) {
+ dst_key_format(key, b_key, sizeof(b_key));
+ tkey_token = dst_key_tkeytoken(key);
+ } else {
+ b_key[0] = 0;
+ }
+
+ if (tkey_token != NULL) {
+ isc_buffer_region(tkey_token, &token_region);
+ token_len = token_region.length;
+ }
+
+ MAYBE_LOCK(imp);
+ ret = imp->methods->ssumatch(b_signer, b_name, b_addr, b_type, b_key,
+ token_len,
+ token_len != 0 ? token_region.base : NULL,
+ imp->driverarg, dbdata);
+ MAYBE_UNLOCK(imp);
+ return (ret);
+}
+
+static dns_dlzmethods_t sdlzmethods = { dns_sdlzcreate, dns_sdlzdestroy,
+ dns_sdlzfindzone, dns_sdlzallowzonexfr,
+ dns_sdlzconfigure, dns_sdlzssumatch };
+
+/*
+ * Public functions.
+ */
+
+isc_result_t
+dns_sdlz_putrr(dns_sdlzlookup_t *lookup, const char *type, dns_ttl_t ttl,
+ const char *data) {
+ dns_rdatalist_t *rdatalist;
+ dns_rdata_t *rdata;
+ dns_rdatatype_t typeval;
+ isc_consttextregion_t r;
+ isc_buffer_t b;
+ isc_buffer_t *rdatabuf = NULL;
+ isc_lex_t *lex;
+ isc_result_t result;
+ unsigned int size;
+ isc_mem_t *mctx;
+ const dns_name_t *origin;
+
+ REQUIRE(VALID_SDLZLOOKUP(lookup));
+ REQUIRE(type != NULL);
+ REQUIRE(data != NULL);
+
+ mctx = lookup->sdlz->common.mctx;
+
+ r.base = type;
+ r.length = strlen(type);
+ result = dns_rdatatype_fromtext(&typeval, (void *)&r);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ rdatalist = ISC_LIST_HEAD(lookup->lists);
+ while (rdatalist != NULL) {
+ if (rdatalist->type == typeval) {
+ break;
+ }
+ rdatalist = ISC_LIST_NEXT(rdatalist, link);
+ }
+
+ if (rdatalist == NULL) {
+ rdatalist = isc_mem_get(mctx, sizeof(dns_rdatalist_t));
+ dns_rdatalist_init(rdatalist);
+ rdatalist->rdclass = lookup->sdlz->common.rdclass;
+ rdatalist->type = typeval;
+ rdatalist->ttl = ttl;
+ ISC_LIST_APPEND(lookup->lists, rdatalist, link);
+ } else if (rdatalist->ttl > ttl) {
+ /*
+ * BIND9 doesn't enforce all RRs in an RRset
+ * having the same TTL, as per RFC 2136,
+ * section 7.12. If a DLZ backend has
+ * different TTLs, then the best
+ * we can do is return the lowest.
+ */
+ rdatalist->ttl = ttl;
+ }
+
+ rdata = isc_mem_get(mctx, sizeof(dns_rdata_t));
+ dns_rdata_init(rdata);
+
+ if ((lookup->sdlz->dlzimp->flags & DNS_SDLZFLAG_RELATIVERDATA) != 0) {
+ origin = &lookup->sdlz->common.origin;
+ } else {
+ origin = dns_rootname;
+ }
+
+ lex = NULL;
+ result = isc_lex_create(mctx, 64, &lex);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ size = initial_size(data);
+ do {
+ isc_buffer_constinit(&b, data, strlen(data));
+ isc_buffer_add(&b, strlen(data));
+
+ result = isc_lex_openbuffer(lex, &b);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ rdatabuf = NULL;
+ isc_buffer_allocate(mctx, &rdatabuf, size);
+
+ result = dns_rdata_fromtext(rdata, rdatalist->rdclass,
+ rdatalist->type, lex, origin, false,
+ mctx, rdatabuf, &lookup->callbacks);
+ if (result != ISC_R_SUCCESS) {
+ isc_buffer_free(&rdatabuf);
+ }
+ if (size >= 65535) {
+ break;
+ }
+ size *= 2;
+ if (size >= 65535) {
+ size = 65535;
+ }
+ } while (result == ISC_R_NOSPACE);
+
+ if (result != ISC_R_SUCCESS) {
+ result = DNS_R_SERVFAIL;
+ goto failure;
+ }
+
+ ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
+ ISC_LIST_APPEND(lookup->buffers, rdatabuf, link);
+
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (rdatabuf != NULL) {
+ isc_buffer_free(&rdatabuf);
+ }
+ if (lex != NULL) {
+ isc_lex_destroy(&lex);
+ }
+ isc_mem_put(mctx, rdata, sizeof(dns_rdata_t));
+
+ return (result);
+}
+
+isc_result_t
+dns_sdlz_putnamedrr(dns_sdlzallnodes_t *allnodes, const char *name,
+ const char *type, dns_ttl_t ttl, const char *data) {
+ dns_name_t *newname;
+ const dns_name_t *origin;
+ dns_fixedname_t fnewname;
+ dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)allnodes->common.db;
+ dns_sdlznode_t *sdlznode;
+ isc_mem_t *mctx = sdlz->common.mctx;
+ isc_buffer_t b;
+ isc_result_t result;
+
+ newname = dns_fixedname_initname(&fnewname);
+
+ if ((sdlz->dlzimp->flags & DNS_SDLZFLAG_RELATIVERDATA) != 0) {
+ origin = &sdlz->common.origin;
+ } else {
+ origin = dns_rootname;
+ }
+ isc_buffer_constinit(&b, name, strlen(name));
+ isc_buffer_add(&b, strlen(name));
+
+ result = dns_name_fromtext(newname, &b, origin, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (allnodes->common.relative_names) {
+ /* All names are relative to the root */
+ unsigned int nlabels = dns_name_countlabels(newname);
+ dns_name_getlabelsequence(newname, 0, nlabels - 1, newname);
+ }
+
+ sdlznode = ISC_LIST_HEAD(allnodes->nodelist);
+ if (sdlznode == NULL || !dns_name_equal(sdlznode->name, newname)) {
+ sdlznode = NULL;
+ result = createnode(sdlz, &sdlznode);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ sdlznode->name = isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(sdlznode->name, NULL);
+ dns_name_dup(newname, mctx, sdlznode->name);
+ ISC_LIST_PREPEND(allnodes->nodelist, sdlznode, link);
+ if (allnodes->origin == NULL &&
+ dns_name_equal(newname, &sdlz->common.origin))
+ {
+ allnodes->origin = sdlznode;
+ }
+ }
+ return (dns_sdlz_putrr(sdlznode, type, ttl, data));
+}
+
+isc_result_t
+dns_sdlz_putsoa(dns_sdlzlookup_t *lookup, const char *mname, const char *rname,
+ uint32_t serial) {
+ char str[2 * DNS_NAME_MAXTEXT + 5 * (sizeof("2147483647")) + 7];
+ int n;
+
+ REQUIRE(mname != NULL);
+ REQUIRE(rname != NULL);
+
+ n = snprintf(str, sizeof str, "%s %s %u %u %u %u %u", mname, rname,
+ serial, SDLZ_DEFAULT_REFRESH, SDLZ_DEFAULT_RETRY,
+ SDLZ_DEFAULT_EXPIRE, SDLZ_DEFAULT_MINIMUM);
+ if (n >= (int)sizeof(str) || n < 0) {
+ return (ISC_R_NOSPACE);
+ }
+ return (dns_sdlz_putrr(lookup, "SOA", SDLZ_DEFAULT_TTL, str));
+}
+
+isc_result_t
+dns_sdlzregister(const char *drivername, const dns_sdlzmethods_t *methods,
+ void *driverarg, unsigned int flags, isc_mem_t *mctx,
+ dns_sdlzimplementation_t **sdlzimp) {
+ dns_sdlzimplementation_t *imp;
+ isc_result_t result;
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(drivername != NULL);
+ REQUIRE(methods != NULL);
+ REQUIRE(methods->findzone != NULL);
+ REQUIRE(methods->lookup != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(sdlzimp != NULL && *sdlzimp == NULL);
+ REQUIRE((flags &
+ ~(DNS_SDLZFLAG_RELATIVEOWNER | DNS_SDLZFLAG_RELATIVERDATA |
+ DNS_SDLZFLAG_THREADSAFE)) == 0);
+
+ /* Write debugging message to log */
+ sdlz_log(ISC_LOG_DEBUG(2), "Registering SDLZ driver '%s'", drivername);
+
+ /*
+ * Allocate memory for a sdlz_implementation object. Error if
+ * we cannot.
+ */
+ imp = isc_mem_get(mctx, sizeof(dns_sdlzimplementation_t));
+
+ /* Make sure memory region is set to all 0's */
+ memset(imp, 0, sizeof(dns_sdlzimplementation_t));
+
+ /* Store the data passed into this method */
+ imp->methods = methods;
+ imp->driverarg = driverarg;
+ imp->flags = flags;
+ imp->mctx = NULL;
+
+ /* attach the new sdlz_implementation object to a memory context */
+ isc_mem_attach(mctx, &imp->mctx);
+
+ /*
+ * initialize the driver lock, error if we cannot
+ * (used if a driver does not support multiple threads)
+ */
+ isc_mutex_init(&imp->driverlock);
+
+ imp->dlz_imp = NULL;
+
+ /*
+ * register the DLZ driver. Pass in our "extra" sdlz information as
+ * a driverarg. (that's why we stored the passed in driver arg in our
+ * sdlz_implementation structure) Also, store the dlz_implementation
+ * structure in our sdlz_implementation.
+ */
+ result = dns_dlzregister(drivername, &sdlzmethods, imp, mctx,
+ &imp->dlz_imp);
+
+ /* if registration fails, cleanup and get outta here. */
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_mutex;
+ }
+
+ *sdlzimp = imp;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_mutex:
+ /* destroy the driver lock, we don't need it anymore */
+ isc_mutex_destroy(&imp->driverlock);
+
+ /*
+ * return the memory back to the available memory pool and
+ * remove it from the memory context.
+ */
+ isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_sdlzimplementation_t));
+ return (result);
+}
+
+void
+dns_sdlzunregister(dns_sdlzimplementation_t **sdlzimp) {
+ dns_sdlzimplementation_t *imp;
+
+ /* Write debugging message to log */
+ sdlz_log(ISC_LOG_DEBUG(2), "Unregistering SDLZ driver.");
+
+ /*
+ * Performs checks to make sure data is as we expect it to be.
+ */
+ REQUIRE(sdlzimp != NULL && *sdlzimp != NULL);
+
+ imp = *sdlzimp;
+ *sdlzimp = NULL;
+
+ /* Unregister the DLZ driver implementation */
+ dns_dlzunregister(&imp->dlz_imp);
+
+ /* destroy the driver lock, we don't need it anymore */
+ isc_mutex_destroy(&imp->driverlock);
+
+ /*
+ * return the memory back to the available memory pool and
+ * remove it from the memory context.
+ */
+ isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_sdlzimplementation_t));
+}
+
+isc_result_t
+dns_sdlz_setdb(dns_dlzdb_t *dlzdatabase, dns_rdataclass_t rdclass,
+ const dns_name_t *name, dns_db_t **dbp) {
+ isc_result_t result;
+
+ result = dns_sdlzcreateDBP(dlzdatabase->mctx,
+ dlzdatabase->implementation->driverarg,
+ dlzdatabase->dbdata, name, rdclass, dbp);
+ return (result);
+}
diff --git a/lib/dns/soa.c b/lib/dns/soa.c
new file mode 100644
index 0000000..ca5ca88
--- /dev/null
+++ b/lib/dns/soa.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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..1529a61
--- /dev/null
+++ b/lib/dns/ssu.c
@@ -0,0 +1,667 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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_rdatatype_t *types; /*%< the data types. Can include */
+ /* ANY. if NULL, defaults to all */
+ /* types except SIG, SOA, and NS */
+ ISC_LINK(dns_ssurule_t) link;
+};
+
+struct dns_ssutable {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t references;
+ dns_dlzdb_t *dlzdatabase;
+ ISC_LIST(dns_ssurule_t) rules;
+};
+
+isc_result_t
+dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **tablep) {
+ dns_ssutable_t *table;
+
+ REQUIRE(tablep != NULL && *tablep == NULL);
+ REQUIRE(mctx != NULL);
+
+ table = isc_mem_get(mctx, sizeof(dns_ssutable_t));
+ isc_refcount_init(&table->references, 1);
+ table->mctx = NULL;
+ isc_mem_attach(mctx, &table->mctx);
+ ISC_LIST_INIT(table->rules);
+ table->magic = SSUTABLEMAGIC;
+ *tablep = table;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+destroy(dns_ssutable_t *table) {
+ isc_mem_t *mctx;
+
+ REQUIRE(VALID_SSUTABLE(table));
+
+ mctx = table->mctx;
+ while (!ISC_LIST_EMPTY(table->rules)) {
+ dns_ssurule_t *rule = ISC_LIST_HEAD(table->rules);
+ if (rule->identity != NULL) {
+ dns_name_free(rule->identity, mctx);
+ isc_mem_put(mctx, rule->identity, sizeof(dns_name_t));
+ }
+ if (rule->name != NULL) {
+ dns_name_free(rule->name, mctx);
+ isc_mem_put(mctx, rule->name, sizeof(dns_name_t));
+ }
+ if (rule->types != NULL) {
+ isc_mem_put(mctx, rule->types,
+ rule->ntypes * sizeof(dns_rdatatype_t));
+ }
+ ISC_LIST_UNLINK(table->rules, rule, link);
+ rule->magic = 0;
+ isc_mem_put(mctx, rule, sizeof(dns_ssurule_t));
+ }
+ isc_refcount_destroy(&table->references);
+ table->magic = 0;
+ isc_mem_putanddetach(&table->mctx, table, sizeof(dns_ssutable_t));
+}
+
+void
+dns_ssutable_attach(dns_ssutable_t *source, dns_ssutable_t **targetp) {
+ REQUIRE(VALID_SSUTABLE(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+void
+dns_ssutable_detach(dns_ssutable_t **tablep) {
+ dns_ssutable_t *table;
+
+ REQUIRE(tablep != NULL);
+ table = *tablep;
+ *tablep = NULL;
+ REQUIRE(VALID_SSUTABLE(table));
+
+ if (isc_refcount_decrement(&table->references) == 1) {
+ destroy(table);
+ }
+}
+
+isc_result_t
+dns_ssutable_addrule(dns_ssutable_t *table, bool grant,
+ const dns_name_t *identity, dns_ssumatchtype_t matchtype,
+ const dns_name_t *name, unsigned int ntypes,
+ dns_rdatatype_t *types) {
+ dns_ssurule_t *rule;
+ isc_mem_t *mctx;
+
+ REQUIRE(VALID_SSUTABLE(table));
+ REQUIRE(dns_name_isabsolute(identity));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(matchtype <= dns_ssumatchtype_max);
+ if (matchtype == dns_ssumatchtype_wildcard) {
+ REQUIRE(dns_name_iswildcard(name));
+ }
+ if (ntypes > 0) {
+ REQUIRE(types != NULL);
+ }
+
+ mctx = table->mctx;
+ rule = isc_mem_get(mctx, sizeof(dns_ssurule_t));
+
+ rule->identity = NULL;
+ rule->name = NULL;
+ rule->types = NULL;
+
+ rule->grant = grant;
+
+ rule->identity = isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(rule->identity, NULL);
+ dns_name_dup(identity, mctx, rule->identity);
+
+ rule->name = isc_mem_get(mctx, sizeof(dns_name_t));
+ dns_name_init(rule->name, NULL);
+ dns_name_dup(name, mctx, rule->name);
+
+ rule->matchtype = matchtype;
+
+ rule->ntypes = ntypes;
+ if (ntypes > 0) {
+ rule->types = isc_mem_get(mctx,
+ ntypes * sizeof(dns_rdatatype_t));
+ memmove(rule->types, types, ntypes * sizeof(dns_rdatatype_t));
+ } else {
+ rule->types = NULL;
+ }
+
+ rule->magic = SSURULEMAGIC;
+ ISC_LIST_INITANDAPPEND(table->rules, rule, link);
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+isusertype(dns_rdatatype_t type) {
+ return (type != dns_rdatatype_ns && type != dns_rdatatype_soa &&
+ type != dns_rdatatype_rrsig);
+}
+
+static void
+reverse_from_address(dns_name_t *tcpself, const isc_netaddr_t *tcpaddr) {
+ char buf[16 * 4 + sizeof("IP6.ARPA.")];
+ isc_result_t result;
+ const unsigned char *ap;
+ isc_buffer_t b;
+ unsigned long l;
+
+ switch (tcpaddr->family) {
+ case AF_INET:
+ l = ntohl(tcpaddr->type.in.s_addr);
+ result = snprintf(buf, sizeof(buf),
+ "%lu.%lu.%lu.%lu.IN-ADDR.ARPA.",
+ (l >> 0) & 0xff, (l >> 8) & 0xff,
+ (l >> 16) & 0xff, (l >> 24) & 0xff);
+ RUNTIME_CHECK(result < sizeof(buf));
+ break;
+ case AF_INET6:
+ ap = tcpaddr->type.in6.s6_addr;
+ result = snprintf(
+ buf, sizeof(buf),
+ "%x.%x.%x.%x.%x.%x.%x.%x."
+ "%x.%x.%x.%x.%x.%x.%x.%x."
+ "%x.%x.%x.%x.%x.%x.%x.%x."
+ "%x.%x.%x.%x.%x.%x.%x.%x."
+ "IP6.ARPA.",
+ ap[15] & 0x0f, (ap[15] >> 4) & 0x0f, ap[14] & 0x0f,
+ (ap[14] >> 4) & 0x0f, ap[13] & 0x0f,
+ (ap[13] >> 4) & 0x0f, ap[12] & 0x0f,
+ (ap[12] >> 4) & 0x0f, ap[11] & 0x0f,
+ (ap[11] >> 4) & 0x0f, ap[10] & 0x0f,
+ (ap[10] >> 4) & 0x0f, ap[9] & 0x0f, (ap[9] >> 4) & 0x0f,
+ ap[8] & 0x0f, (ap[8] >> 4) & 0x0f, ap[7] & 0x0f,
+ (ap[7] >> 4) & 0x0f, ap[6] & 0x0f, (ap[6] >> 4) & 0x0f,
+ ap[5] & 0x0f, (ap[5] >> 4) & 0x0f, ap[4] & 0x0f,
+ (ap[4] >> 4) & 0x0f, ap[3] & 0x0f, (ap[3] >> 4) & 0x0f,
+ ap[2] & 0x0f, (ap[2] >> 4) & 0x0f, ap[1] & 0x0f,
+ (ap[1] >> 4) & 0x0f, ap[0] & 0x0f, (ap[0] >> 4) & 0x0f);
+ RUNTIME_CHECK(result < sizeof(buf));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ isc_buffer_init(&b, buf, strlen(buf));
+ isc_buffer_add(&b, strlen(buf));
+ result = dns_name_fromtext(tcpself, &b, dns_rootname, 0, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+}
+
+static void
+stf_from_address(dns_name_t *stfself, const isc_netaddr_t *tcpaddr) {
+ char buf[sizeof("X.X.X.X.Y.Y.Y.Y.2.0.0.2.IP6.ARPA.")];
+ isc_result_t result;
+ const unsigned char *ap;
+ isc_buffer_t b;
+ unsigned long l;
+
+ switch (tcpaddr->family) {
+ case AF_INET:
+ l = ntohl(tcpaddr->type.in.s_addr);
+ result = snprintf(buf, sizeof(buf),
+ "%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx"
+ "2.0.0.2.IP6.ARPA.",
+ l & 0xf, (l >> 4) & 0xf, (l >> 8) & 0xf,
+ (l >> 12) & 0xf, (l >> 16) & 0xf,
+ (l >> 20) & 0xf, (l >> 24) & 0xf,
+ (l >> 28) & 0xf);
+ RUNTIME_CHECK(result < sizeof(buf));
+ break;
+ case AF_INET6:
+ ap = tcpaddr->type.in6.s6_addr;
+ result = snprintf(
+ buf, sizeof(buf),
+ "%x.%x.%x.%x.%x.%x.%x.%x."
+ "%x.%x.%x.%x.IP6.ARPA.",
+ ap[5] & 0x0f, (ap[5] >> 4) & 0x0f, ap[4] & 0x0f,
+ (ap[4] >> 4) & 0x0f, ap[3] & 0x0f, (ap[3] >> 4) & 0x0f,
+ ap[2] & 0x0f, (ap[2] >> 4) & 0x0f, ap[1] & 0x0f,
+ (ap[1] >> 4) & 0x0f, ap[0] & 0x0f, (ap[0] >> 4) & 0x0f);
+ RUNTIME_CHECK(result < sizeof(buf));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ isc_buffer_init(&b, buf, strlen(buf));
+ isc_buffer_add(&b, strlen(buf));
+ result = dns_name_fromtext(stfself, &b, dns_rootname, 0, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+}
+
+bool
+dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *addr,
+ bool tcp, const dns_aclenv_t *env, dns_rdatatype_t type,
+ const dst_key_t *key) {
+ dns_ssurule_t *rule;
+ unsigned int i;
+ dns_fixedname_t fixed;
+ dns_name_t *wildcard;
+ dns_name_t *tcpself;
+ dns_name_t *stfself;
+ isc_result_t result;
+ int match;
+
+ REQUIRE(VALID_SSUTABLE(table));
+ REQUIRE(signer == NULL || dns_name_isabsolute(signer));
+ REQUIRE(dns_name_isabsolute(name));
+ REQUIRE(addr == NULL || env != NULL);
+
+ if (signer == NULL && addr == NULL) {
+ return (false);
+ }
+
+ for (rule = ISC_LIST_HEAD(table->rules); rule != NULL;
+ rule = ISC_LIST_NEXT(rule, link))
+ {
+ switch (rule->matchtype) {
+ case dns_ssumatchtype_name:
+ case dns_ssumatchtype_local:
+ case dns_ssumatchtype_subdomain:
+ case dns_ssumatchtype_wildcard:
+ case dns_ssumatchtype_self:
+ case dns_ssumatchtype_selfsub:
+ case dns_ssumatchtype_selfwild:
+ if (signer == NULL) {
+ continue;
+ }
+ if (dns_name_iswildcard(rule->identity)) {
+ if (!dns_name_matcheswildcard(signer,
+ rule->identity))
+ {
+ continue;
+ }
+ } else {
+ if (!dns_name_equal(signer, rule->identity)) {
+ continue;
+ }
+ }
+ break;
+ case dns_ssumatchtype_selfkrb5:
+ case dns_ssumatchtype_selfms:
+ case dns_ssumatchtype_selfsubkrb5:
+ case dns_ssumatchtype_selfsubms:
+ case dns_ssumatchtype_subdomainkrb5:
+ case dns_ssumatchtype_subdomainms:
+ if (signer == NULL) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_tcpself:
+ case dns_ssumatchtype_6to4self:
+ if (!tcp || addr == NULL) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_external:
+ case dns_ssumatchtype_dlz:
+ break;
+ }
+
+ switch (rule->matchtype) {
+ case dns_ssumatchtype_name:
+ if (!dns_name_equal(name, rule->name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_subdomain:
+ if (!dns_name_issubdomain(name, rule->name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_local:
+ if (addr == NULL) {
+ continue;
+ }
+ if (!dns_name_issubdomain(name, rule->name)) {
+ continue;
+ }
+ dns_acl_match(addr, NULL, env->localhost, NULL, &match,
+ NULL);
+ if (match == 0) {
+ if (signer != NULL) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_GENERAL,
+ DNS_LOGMODULE_SSU,
+ ISC_LOG_WARNING,
+ "update-policy local: "
+ "match on session "
+ "key not from "
+ "localhost");
+ }
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_wildcard:
+ if (!dns_name_matcheswildcard(name, rule->name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_self:
+ if (!dns_name_equal(signer, name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_selfsub:
+ if (!dns_name_issubdomain(name, signer)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_selfwild:
+ wildcard = dns_fixedname_initname(&fixed);
+ result = dns_name_concatenate(dns_wildcardname, signer,
+ wildcard, NULL);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (!dns_name_matcheswildcard(name, wildcard)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_selfkrb5:
+ if (dst_gssapi_identitymatchesrealmkrb5(
+ signer, name, rule->identity, false))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_selfms:
+ if (dst_gssapi_identitymatchesrealmms(
+ signer, name, rule->identity, false))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_selfsubkrb5:
+ if (dst_gssapi_identitymatchesrealmkrb5(
+ signer, name, rule->identity, true))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_selfsubms:
+ if (dst_gssapi_identitymatchesrealmms(
+ signer, name, rule->identity, true))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_subdomainkrb5:
+ if (!dns_name_issubdomain(name, rule->name)) {
+ continue;
+ }
+ if (dst_gssapi_identitymatchesrealmkrb5(
+ signer, NULL, rule->identity, false))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_subdomainms:
+ if (!dns_name_issubdomain(name, rule->name)) {
+ continue;
+ }
+ if (dst_gssapi_identitymatchesrealmms(
+ signer, NULL, rule->identity, false))
+ {
+ break;
+ }
+ continue;
+ case dns_ssumatchtype_tcpself:
+ tcpself = dns_fixedname_initname(&fixed);
+ reverse_from_address(tcpself, addr);
+ if (dns_name_iswildcard(rule->identity)) {
+ if (!dns_name_matcheswildcard(tcpself,
+ rule->identity))
+ {
+ continue;
+ }
+ } else {
+ if (!dns_name_equal(tcpself, rule->identity)) {
+ continue;
+ }
+ }
+ if (!dns_name_equal(tcpself, name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_6to4self:
+ stfself = dns_fixedname_initname(&fixed);
+ stf_from_address(stfself, addr);
+ if (dns_name_iswildcard(rule->identity)) {
+ if (!dns_name_matcheswildcard(stfself,
+ rule->identity))
+ {
+ continue;
+ }
+ } else {
+ if (!dns_name_equal(stfself, rule->identity)) {
+ continue;
+ }
+ }
+ if (!dns_name_equal(stfself, name)) {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_external:
+ if (!dns_ssu_external_match(rule->identity, signer,
+ name, addr, type, key,
+ table->mctx))
+ {
+ continue;
+ }
+ break;
+ case dns_ssumatchtype_dlz:
+ if (!dns_dlz_ssumatch(table->dlzdatabase, signer, name,
+ addr, type, key))
+ {
+ continue;
+ }
+ break;
+ }
+
+ if (rule->ntypes == 0) {
+ /*
+ * If this is a DLZ rule, then the DLZ ssu
+ * checks will have already checked
+ * the type.
+ */
+ if (rule->matchtype != dns_ssumatchtype_dlz &&
+ !isusertype(type))
+ {
+ continue;
+ }
+ } else {
+ for (i = 0; i < rule->ntypes; i++) {
+ if (rule->types[i] == dns_rdatatype_any ||
+ rule->types[i] == type)
+ {
+ break;
+ }
+ }
+ if (i == rule->ntypes) {
+ continue;
+ }
+ }
+ return (rule->grant);
+ }
+
+ return (false);
+}
+
+bool
+dns_ssurule_isgrant(const dns_ssurule_t *rule) {
+ REQUIRE(VALID_SSURULE(rule));
+ return (rule->grant);
+}
+
+dns_name_t *
+dns_ssurule_identity(const dns_ssurule_t *rule) {
+ REQUIRE(VALID_SSURULE(rule));
+ return (rule->identity);
+}
+
+unsigned int
+dns_ssurule_matchtype(const dns_ssurule_t *rule) {
+ REQUIRE(VALID_SSURULE(rule));
+ return (rule->matchtype);
+}
+
+dns_name_t *
+dns_ssurule_name(const dns_ssurule_t *rule) {
+ REQUIRE(VALID_SSURULE(rule));
+ return (rule->name);
+}
+
+unsigned int
+dns_ssurule_types(const dns_ssurule_t *rule, dns_rdatatype_t **types) {
+ REQUIRE(VALID_SSURULE(rule));
+ REQUIRE(types != NULL && *types != NULL);
+ *types = rule->types;
+ return (rule->ntypes);
+}
+
+isc_result_t
+dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule) {
+ REQUIRE(VALID_SSUTABLE(table));
+ REQUIRE(rule != NULL && *rule == NULL);
+ *rule = ISC_LIST_HEAD(table->rules);
+ return (*rule != NULL ? ISC_R_SUCCESS : ISC_R_NOMORE);
+}
+
+isc_result_t
+dns_ssutable_nextrule(dns_ssurule_t *rule, dns_ssurule_t **nextrule) {
+ REQUIRE(VALID_SSURULE(rule));
+ REQUIRE(nextrule != NULL && *nextrule == NULL);
+ *nextrule = ISC_LIST_NEXT(rule, link);
+ return (*nextrule != NULL ? ISC_R_SUCCESS : ISC_R_NOMORE);
+}
+
+/*
+ * Create a specialised SSU table that points at an external DLZ database
+ */
+isc_result_t
+dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep,
+ dns_dlzdb_t *dlzdatabase) {
+ isc_result_t result;
+ dns_ssurule_t *rule;
+ dns_ssutable_t *table = NULL;
+
+ REQUIRE(tablep != NULL && *tablep == NULL);
+
+ result = dns_ssutable_create(mctx, &table);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ table->dlzdatabase = dlzdatabase;
+
+ rule = isc_mem_get(table->mctx, sizeof(dns_ssurule_t));
+
+ rule->identity = NULL;
+ rule->name = NULL;
+ rule->types = NULL;
+ rule->grant = true;
+ rule->matchtype = dns_ssumatchtype_dlz;
+ rule->ntypes = 0;
+ rule->types = NULL;
+ rule->magic = SSURULEMAGIC;
+
+ ISC_LIST_INITANDAPPEND(table->rules, rule, link);
+ *tablep = table;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_ssu_mtypefromstring(const char *str, dns_ssumatchtype_t *mtype) {
+ REQUIRE(str != NULL);
+ REQUIRE(mtype != NULL);
+
+ if (strcasecmp(str, "name") == 0) {
+ *mtype = dns_ssumatchtype_name;
+ } else if (strcasecmp(str, "subdomain") == 0) {
+ *mtype = dns_ssumatchtype_subdomain;
+ } else if (strcasecmp(str, "wildcard") == 0) {
+ *mtype = dns_ssumatchtype_wildcard;
+ } else if (strcasecmp(str, "self") == 0) {
+ *mtype = dns_ssumatchtype_self;
+ } else if (strcasecmp(str, "selfsub") == 0) {
+ *mtype = dns_ssumatchtype_selfsub;
+ } else if (strcasecmp(str, "selfwild") == 0) {
+ *mtype = dns_ssumatchtype_selfwild;
+ } else if (strcasecmp(str, "ms-self") == 0) {
+ *mtype = dns_ssumatchtype_selfms;
+ } else if (strcasecmp(str, "ms-selfsub") == 0) {
+ *mtype = dns_ssumatchtype_selfsubms;
+ } else if (strcasecmp(str, "krb5-self") == 0) {
+ *mtype = dns_ssumatchtype_selfkrb5;
+ } else if (strcasecmp(str, "krb5-selfsub") == 0) {
+ *mtype = dns_ssumatchtype_selfsubkrb5;
+ } else if (strcasecmp(str, "ms-subdomain") == 0) {
+ *mtype = dns_ssumatchtype_subdomainms;
+ } else if (strcasecmp(str, "krb5-subdomain") == 0) {
+ *mtype = dns_ssumatchtype_subdomainkrb5;
+ } else if (strcasecmp(str, "tcp-self") == 0) {
+ *mtype = dns_ssumatchtype_tcpself;
+ } else if (strcasecmp(str, "6to4-self") == 0) {
+ *mtype = dns_ssumatchtype_6to4self;
+ } else if (strcasecmp(str, "zonesub") == 0) {
+ *mtype = dns_ssumatchtype_subdomain;
+ } else if (strcasecmp(str, "external") == 0) {
+ *mtype = dns_ssumatchtype_external;
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/dns/ssu_external.c b/lib/dns/ssu_external.c
new file mode 100644
index 0000000..d5e4715
--- /dev/null
+++ b/lib/dns/ssu_external.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * This implements external update-policy rules. This allows permission
+ * to update a zone to be checked by consulting an external daemon (e.g.,
+ * kerberos).
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+#ifdef ISC_PLATFORM_HAVESYSUNH
+#include <sys/socket.h>
+#include <sys/un.h>
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+
+#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;
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ struct sockaddr_un addr;
+
+ REQUIRE(path != NULL);
+
+ if (strlen(path) > sizeof(addr.sun_path)) {
+ ssu_e_log(3,
+ "ssu_external: socket path '%s' "
+ "longer than system maximum %zu",
+ path, sizeof(addr.sun_path));
+ return (-1);
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ ssu_e_log(3, "ssu_external: unable to create socket - %s",
+ strbuf);
+ return (-1);
+ }
+
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ ssu_e_log(3,
+ "ssu_external: unable to connect to "
+ "socket '%s' - %s",
+ path, strbuf);
+ close(fd);
+ return (-1);
+ }
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ return (fd);
+}
+
+/* Change this version if you update the format of the request */
+#define SSU_EXTERNAL_VERSION 1
+
+/*
+ * Perform an update-policy rule check against an external application
+ * over a socket.
+ *
+ * This currently only supports local: for unix domain datagram sockets.
+ *
+ * Note that by using a datagram socket and creating a new socket each
+ * time we avoid the need for locking and allow for parallel access to
+ * the authorization server.
+ */
+bool
+dns_ssu_external_match(const dns_name_t *identity, const dns_name_t *signer,
+ const dns_name_t *name, const isc_netaddr_t *tcpaddr,
+ dns_rdatatype_t type, const dst_key_t *key,
+ isc_mem_t *mctx) {
+ char b_identity[DNS_NAME_FORMATSIZE];
+ char b_signer[DNS_NAME_FORMATSIZE];
+ char b_name[DNS_NAME_FORMATSIZE];
+ char b_addr[ISC_NETADDR_FORMATSIZE];
+ char b_type[DNS_RDATATYPE_FORMATSIZE];
+ char b_key[DST_KEY_FORMATSIZE];
+ isc_buffer_t *tkey_token = NULL;
+ int fd;
+ const char *sock_path;
+ unsigned int req_len;
+ isc_region_t token_region = { NULL, 0 };
+ unsigned char *data;
+ isc_buffer_t buf;
+ uint32_t token_len = 0;
+ uint32_t reply;
+ ssize_t ret;
+
+ /* The identity contains local:/path/to/socket */
+ dns_name_format(identity, b_identity, sizeof(b_identity));
+
+ /* For now only local: is supported */
+ if (strncmp(b_identity, "local:", 6) != 0) {
+ ssu_e_log(3, "ssu_external: invalid socket path '%s'",
+ b_identity);
+ return (false);
+ }
+ sock_path = &b_identity[6];
+
+ fd = ux_socket_connect(sock_path);
+ if (fd == -1) {
+ return (false);
+ }
+
+ if (key != NULL) {
+ dst_key_format(key, b_key, sizeof(b_key));
+ tkey_token = dst_key_tkeytoken(key);
+ } else {
+ b_key[0] = 0;
+ }
+
+ if (tkey_token != NULL) {
+ isc_buffer_region(tkey_token, &token_region);
+ token_len = token_region.length;
+ }
+
+ /* Format the request elements */
+ if (signer != NULL) {
+ dns_name_format(signer, b_signer, sizeof(b_signer));
+ } else {
+ b_signer[0] = 0;
+ }
+
+ dns_name_format(name, b_name, sizeof(b_name));
+
+ if (tcpaddr != NULL) {
+ isc_netaddr_format(tcpaddr, b_addr, sizeof(b_addr));
+ } else {
+ b_addr[0] = 0;
+ }
+
+ dns_rdatatype_format(type, b_type, sizeof(b_type));
+
+ /* Work out how big the request will be */
+ req_len = sizeof(uint32_t) + /* Format version */
+ sizeof(uint32_t) + /* Length */
+ strlen(b_signer) + 1 + /* Signer */
+ strlen(b_name) + 1 + /* Name */
+ strlen(b_addr) + 1 + /* Address */
+ strlen(b_type) + 1 + /* Type */
+ strlen(b_key) + 1 + /* Key */
+ sizeof(uint32_t) + /* tkey_token length */
+ token_len; /* tkey_token */
+
+ /* format the buffer */
+ data = isc_mem_allocate(mctx, req_len);
+
+ isc_buffer_init(&buf, data, req_len);
+ isc_buffer_putuint32(&buf, SSU_EXTERNAL_VERSION);
+ isc_buffer_putuint32(&buf, req_len);
+
+ /* Strings must be null-terminated */
+ isc_buffer_putstr(&buf, b_signer);
+ isc_buffer_putuint8(&buf, 0);
+ isc_buffer_putstr(&buf, b_name);
+ isc_buffer_putuint8(&buf, 0);
+ isc_buffer_putstr(&buf, b_addr);
+ isc_buffer_putuint8(&buf, 0);
+ isc_buffer_putstr(&buf, b_type);
+ isc_buffer_putuint8(&buf, 0);
+ isc_buffer_putstr(&buf, b_key);
+ isc_buffer_putuint8(&buf, 0);
+
+ isc_buffer_putuint32(&buf, token_len);
+ if (tkey_token && token_len != 0) {
+ isc_buffer_putmem(&buf, token_region.base, token_len);
+ }
+
+ ENSURE(isc_buffer_availablelength(&buf) == 0);
+
+ /* Send the request */
+ ret = write(fd, data, req_len);
+ isc_mem_free(mctx, data);
+ if (ret != (ssize_t)req_len) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ ssu_e_log(3, "ssu_external: unable to send request - %s",
+ strbuf);
+ close(fd);
+ return (false);
+ }
+
+ /* Receive the reply */
+ ret = read(fd, &reply, sizeof(uint32_t));
+ if (ret != (ssize_t)sizeof(uint32_t)) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ ssu_e_log(3, "ssu_external: unable to receive reply - %s",
+ strbuf);
+ close(fd);
+ return (false);
+ }
+
+ close(fd);
+
+ reply = ntohl(reply);
+
+ if (reply == 0) {
+ ssu_e_log(3, "ssu_external: denied external auth for '%s'",
+ b_name);
+ return (false);
+ } else if (reply == 1) {
+ ssu_e_log(3, "ssu_external: allowed external auth for '%s'",
+ b_name);
+ return (true);
+ }
+
+ ssu_e_log(3, "ssu_external: invalid reply 0x%08x", reply);
+
+ return (false);
+}
diff --git a/lib/dns/stats.c b/lib/dns/stats.c
new file mode 100644
index 0000000..bdd98c5
--- /dev/null
+++ b/lib/dns/stats.c
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <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:
+ ***/
+LIBDNS_EXTERNAL_DATA const char *dns_statscounter_names[DNS_STATS_NCOUNTERS] = {
+ "success", "referral", "nxrrset", "nxdomain",
+ "recursion", "failure", "duplicate", "dropped"
+};
+
+isc_result_t
+dns_stats_alloccounters(isc_mem_t *mctx, uint64_t **ctrp) {
+ int i;
+ uint64_t *p = isc_mem_get(mctx, DNS_STATS_NCOUNTERS * sizeof(uint64_t));
+ if (p == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ for (i = 0; i < DNS_STATS_NCOUNTERS; i++) {
+ p[i] = 0;
+ }
+ *ctrp = p;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_stats_freecounters(isc_mem_t *mctx, uint64_t **ctrp) {
+ isc_mem_put(mctx, *ctrp, DNS_STATS_NCOUNTERS * sizeof(uint64_t));
+ *ctrp = NULL;
+}
diff --git a/lib/dns/tcpmsg.c b/lib/dns/tcpmsg.c
new file mode 100644
index 0000000..44694d8
--- /dev/null
+++ b/lib/dns/tcpmsg.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/events.h>
+#include <dns/result.h>
+#include <dns/tcpmsg.h>
+
+#ifdef TCPMSG_DEBUG
+#include <stdio.h> /* Required for printf. */
+#define XDEBUG(x) printf x
+#else /* ifdef TCPMSG_DEBUG */
+#define XDEBUG(x)
+#endif /* ifdef TCPMSG_DEBUG */
+
+#define TCPMSG_MAGIC ISC_MAGIC('T', 'C', 'P', 'm')
+#define VALID_TCPMSG(foo) ISC_MAGIC_VALID(foo, TCPMSG_MAGIC)
+
+static void
+recv_length(isc_task_t *, isc_event_t *);
+static void
+recv_message(isc_task_t *, isc_event_t *);
+
+static void
+recv_length(isc_task_t *task, isc_event_t *ev_in) {
+ isc_socketevent_t *ev = (isc_socketevent_t *)ev_in;
+ isc_event_t *dev;
+ dns_tcpmsg_t *tcpmsg = ev_in->ev_arg;
+ isc_region_t region;
+ isc_result_t result;
+
+ INSIST(VALID_TCPMSG(tcpmsg));
+
+ dev = &tcpmsg->event;
+ tcpmsg->address = ev->address;
+
+ if (ev->result != ISC_R_SUCCESS) {
+ tcpmsg->result = ev->result;
+ goto send_and_free;
+ }
+
+ /*
+ * Success.
+ */
+ tcpmsg->size = ntohs(tcpmsg->size);
+ if (tcpmsg->size == 0) {
+ tcpmsg->result = ISC_R_UNEXPECTEDEND;
+ goto send_and_free;
+ }
+ if (tcpmsg->size > tcpmsg->maxsize) {
+ tcpmsg->result = ISC_R_RANGE;
+ goto send_and_free;
+ }
+
+ region.base = isc_mem_get(tcpmsg->mctx, tcpmsg->size);
+ region.length = tcpmsg->size;
+ if (region.base == NULL) {
+ tcpmsg->result = ISC_R_NOMEMORY;
+ goto send_and_free;
+ }
+ XDEBUG(("Allocated %d bytes\n", tcpmsg->size));
+
+ isc_buffer_init(&tcpmsg->buffer, region.base, region.length);
+ result = isc_socket_recv(tcpmsg->sock, &region, 0, task, recv_message,
+ tcpmsg);
+ if (result != ISC_R_SUCCESS) {
+ tcpmsg->result = result;
+ goto send_and_free;
+ }
+
+ isc_event_free(&ev_in);
+ return;
+
+send_and_free:
+ isc_task_send(tcpmsg->task, &dev);
+ tcpmsg->task = NULL;
+ isc_event_free(&ev_in);
+ return;
+}
+
+static void
+recv_message(isc_task_t *task, isc_event_t *ev_in) {
+ isc_socketevent_t *ev = (isc_socketevent_t *)ev_in;
+ isc_event_t *dev;
+ dns_tcpmsg_t *tcpmsg = ev_in->ev_arg;
+
+ (void)task;
+
+ INSIST(VALID_TCPMSG(tcpmsg));
+
+ dev = &tcpmsg->event;
+ tcpmsg->address = ev->address;
+
+ if (ev->result != ISC_R_SUCCESS) {
+ tcpmsg->result = ev->result;
+ goto send_and_free;
+ }
+
+ tcpmsg->result = ISC_R_SUCCESS;
+ isc_buffer_add(&tcpmsg->buffer, ev->n);
+
+ XDEBUG(("Received %u bytes (of %d)\n", ev->n, tcpmsg->size));
+
+send_and_free:
+ isc_task_send(tcpmsg->task, &dev);
+ tcpmsg->task = NULL;
+ isc_event_free(&ev_in);
+}
+
+void
+dns_tcpmsg_init(isc_mem_t *mctx, isc_socket_t *sock, dns_tcpmsg_t *tcpmsg) {
+ REQUIRE(mctx != NULL);
+ REQUIRE(sock != NULL);
+ REQUIRE(tcpmsg != NULL);
+
+ tcpmsg->magic = TCPMSG_MAGIC;
+ tcpmsg->size = 0;
+ tcpmsg->buffer.base = NULL;
+ tcpmsg->buffer.length = 0;
+ tcpmsg->maxsize = 65535; /* Largest message possible. */
+ tcpmsg->mctx = mctx;
+ tcpmsg->sock = sock;
+ tcpmsg->task = NULL; /* None yet. */
+ tcpmsg->result = ISC_R_UNEXPECTED; /* None yet. */
+
+ /* Should probably initialize the event here, but it can wait. */
+}
+
+void
+dns_tcpmsg_setmaxsize(dns_tcpmsg_t *tcpmsg, unsigned int maxsize) {
+ REQUIRE(VALID_TCPMSG(tcpmsg));
+ REQUIRE(maxsize < 65536);
+
+ tcpmsg->maxsize = maxsize;
+}
+
+isc_result_t
+dns_tcpmsg_readmessage(dns_tcpmsg_t *tcpmsg, isc_task_t *task,
+ isc_taskaction_t action, void *arg) {
+ isc_result_t result;
+ isc_region_t region;
+
+ REQUIRE(VALID_TCPMSG(tcpmsg));
+ REQUIRE(task != NULL);
+ REQUIRE(tcpmsg->task == NULL); /* not currently in use */
+
+ if (tcpmsg->buffer.base != NULL) {
+ isc_mem_put(tcpmsg->mctx, tcpmsg->buffer.base,
+ tcpmsg->buffer.length);
+ tcpmsg->buffer.base = NULL;
+ tcpmsg->buffer.length = 0;
+ }
+
+ tcpmsg->task = task;
+ tcpmsg->action = action;
+ tcpmsg->arg = arg;
+ tcpmsg->result = ISC_R_UNEXPECTED; /* unknown right now */
+
+ ISC_EVENT_INIT(&tcpmsg->event, sizeof(isc_event_t), 0, 0,
+ DNS_EVENT_TCPMSG, action, arg, tcpmsg, NULL, NULL);
+
+ region.base = (unsigned char *)&tcpmsg->size;
+ region.length = 2; /* uint16_t */
+ result = isc_socket_recv(tcpmsg->sock, &region, 0, tcpmsg->task,
+ recv_length, tcpmsg);
+
+ if (result != ISC_R_SUCCESS) {
+ tcpmsg->task = NULL;
+ }
+
+ return (result);
+}
+
+void
+dns_tcpmsg_cancelread(dns_tcpmsg_t *tcpmsg) {
+ REQUIRE(VALID_TCPMSG(tcpmsg));
+
+ isc_socket_cancel(tcpmsg->sock, NULL, ISC_SOCKCANCEL_RECV);
+}
+
+void
+dns_tcpmsg_keepbuffer(dns_tcpmsg_t *tcpmsg, isc_buffer_t *buffer) {
+ REQUIRE(VALID_TCPMSG(tcpmsg));
+ REQUIRE(buffer != NULL);
+
+ *buffer = tcpmsg->buffer;
+ tcpmsg->buffer.base = NULL;
+ tcpmsg->buffer.length = 0;
+}
+
+#if 0
+void
+dns_tcpmsg_freebuffer(dns_tcpmsg_t *tcpmsg) {
+ REQUIRE(VALID_TCPMSG(tcpmsg));
+
+ if (tcpmsg->buffer.base == NULL) {
+ return;
+ }
+
+ isc_mem_put(tcpmsg->mctx, tcpmsg->buffer.base, tcpmsg->buffer.length);
+ tcpmsg->buffer.base = NULL;
+ tcpmsg->buffer.length = 0;
+}
+#endif /* if 0 */
+
+void
+dns_tcpmsg_invalidate(dns_tcpmsg_t *tcpmsg) {
+ REQUIRE(VALID_TCPMSG(tcpmsg));
+
+ tcpmsg->magic = 0;
+
+ if (tcpmsg->buffer.base != NULL) {
+ isc_mem_put(tcpmsg->mctx, tcpmsg->buffer.base,
+ tcpmsg->buffer.length);
+ tcpmsg->buffer.base = NULL;
+ tcpmsg->buffer.length = 0;
+ }
+}
diff --git a/lib/dns/tests/Kdh.+002+18602.key b/lib/dns/tests/Kdh.+002+18602.key
new file mode 100644
index 0000000..09b4cf5
--- /dev/null
+++ b/lib/dns/tests/Kdh.+002+18602.key
@@ -0,0 +1 @@
+dh. IN KEY 0 2 2 AAEBAAAAYIHI/wjtOagNga9GILSoS02IVelgLilPE/TfhtvShsiDAXqb IfxQcj2JkuOnNLs5ttb2WZXWl5/jsSjIxHMwMF2XY4gwt/lwHBf/vgYH r7aIxnKXov1jk9rymTLHGKIOtg==
diff --git a/lib/dns/tests/Krsa.+008+29238.key b/lib/dns/tests/Krsa.+008+29238.key
new file mode 100644
index 0000000..8a09067
--- /dev/null
+++ b/lib/dns/tests/Krsa.+008+29238.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 29235, for rsa.
+; Created: 20160819191802 (Fri Aug 19 21:18:02 2016)
+; Publish: 20160819191802 (Fri Aug 19 21:18:02 2016)
+; Activate: 20160819191802 (Fri Aug 19 21:18:02 2016)
+rsa. IN DNSKEY 256 3 8 AwEAAdLT1R3qiqCqll3Xzh2qFMvehQ9FODsPftw5U4UjB3QwnJ/3+dph 9kZBBeaJagUBVYzoArk6XNydpp3HhSCFDcIiepL6r8XAifW3SqI1KCne OD38kSCl/Qm9P0+3CFWokGVubsSQ+3dpQZxqx5bzOXthbuzAr6X+gDUE LAyHtCQNmJ+4ktdCoj3DNYW0z/xLvrcB2Lns7H+/qWnGPL4f3hr7Vbak Oeay+4J4KGdY2LFxJUVts6QrgAA8gz4mV9YIJFP+C4B3b/Z7qgqZRxmT 0pic+fJC5+sq0l8KwavPn0n+HqVuJNvppVKMdTbsmmuk69RFGMjbFkP7 tnCiqC9Zi6s=
diff --git a/lib/dns/tests/Kyuafile b/lib/dns/tests/Kyuafile
new file mode 100644
index 0000000..53b4686
--- /dev/null
+++ b/lib/dns/tests/Kyuafile
@@ -0,0 +1,45 @@
+-- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+--
+-- SPDX-License-Identifier: MPL-2.0
+--
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, you can obtain one at https://mozilla.org/MPL/2.0/.
+--
+-- See the COPYRIGHT file distributed with this work for additional
+-- information regarding copyright ownership.
+
+syntax(2)
+test_suite('bind9')
+
+tap_test_program{name='acl_test'}
+tap_test_program{name='db_test'}
+tap_test_program{name='dbdiff_test'}
+tap_test_program{name='dbiterator_test'}
+tap_test_program{name='dbversion_test'}
+tap_test_program{name='dh_test'}
+tap_test_program{name='dispatch_test'}
+tap_test_program{name='dnstap_test'}
+tap_test_program{name='dst_test'}
+tap_test_program{name='geoip_test'}
+tap_test_program{name='keytable_test'}
+tap_test_program{name='master_test'}
+tap_test_program{name='name_test'}
+tap_test_program{name='nsec3_test'}
+tap_test_program{name='peer_test'}
+tap_test_program{name='private_test'}
+tap_test_program{name='rbt_serialize_test', is_exclusive=true}
+tap_test_program{name='rbt_test'}
+tap_test_program{name='rbtdb_test'}
+tap_test_program{name='rdata_test'}
+tap_test_program{name='rdataset_test'}
+tap_test_program{name='rdatasetstats_test'}
+tap_test_program{name='resolver_test'}
+tap_test_program{name='result_test'}
+tap_test_program{name='rsa_test'}
+tap_test_program{name='sigs_test'}
+tap_test_program{name='time_test'}
+tap_test_program{name='tsig_test'}
+tap_test_program{name='update_test'}
+tap_test_program{name='zonemgr_test'}
+tap_test_program{name='zt_test'}
diff --git a/lib/dns/tests/Makefile.in b/lib/dns/tests/Makefile.in
new file mode 100644
index 0000000..90c1371
--- /dev/null
+++ b/lib/dns/tests/Makefile.in
@@ -0,0 +1,287 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. -Iinclude ${DNS_INCLUDES} ${ISC_INCLUDES} \
+ ${FSTRM_CFLAGS} ${OPENSSL_CFLAGS} \
+ ${PROTOBUF_C_CFLAGS} ${MAXMINDDB_CFLAGS} @CMOCKA_CFLAGS@
+CDEFINES = -DTESTS="\"${top_builddir}/lib/dns/tests/\""
+
+ISCLIBS = ../../isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@
+ISCDEPLIBS = ../../isc/libisc.@A@
+DNSLIBS = ../libdns.@A@ @NO_LIBTOOL_DNSLIBS@
+DNSDEPLIBS = ../libdns.@A@
+
+LIBS = @LIBS@ @CMOCKA_LIBS@
+
+OBJS = dnstest.@O@
+SRCS = acl_test.c \
+ db_test.c \
+ dbdiff_test.c \
+ dbiterator_test.c \
+ dh_test.c \
+ dispatch_test.c \
+ dnstap_test.c \
+ dst_test.c \
+ dnstest.c \
+ geoip_test.c \
+ keytable_test.c \
+ master_test.c \
+ name_test.c \
+ nsec3_test.c \
+ nsec3param_test.c \
+ peer_test.c \
+ private_test.c \
+ rbt_test.c \
+ rbt_serialize_test.c \
+ rbtdb_test.c \
+ rdata_test.c \
+ rdataset_test.c \
+ rdatasetstats_test.c \
+ resolver_test.c \
+ result_test.c \
+ rsa_test.c \
+ sigs_test.c \
+ time_test.c \
+ tsig_test.c \
+ update_test.c \
+ zonemgr_test.c \
+ zt_test.c
+
+SUBDIRS =
+TARGETS = acl_test@EXEEXT@ \
+ db_test@EXEEXT@ \
+ dbdiff_test@EXEEXT@ \
+ dbiterator_test@EXEEXT@ \
+ dbversion_test@EXEEXT@ \
+ dh_test@EXEEXT@ \
+ dispatch_test@EXEEXT@ \
+ dnstap_test@EXEEXT@ \
+ dst_test@EXEEXT@ \
+ geoip_test@EXEEXT@ \
+ keytable_test@EXEEXT@ \
+ master_test@EXEEXT@ \
+ name_test@EXEEXT@ \
+ nsec3_test@EXEEXT@ \
+ nsec3param_test@EXEEXT@ \
+ peer_test@EXEEXT@ \
+ private_test@EXEEXT@ \
+ rbt_test@EXEEXT@ \
+ rbt_serialize_test@EXEEXT@ \
+ rbtdb_test@EXEEXT@ \
+ rdata_test@EXEEXT@ \
+ rdataset_test@EXEEXT@ \
+ rdatasetstats_test@EXEEXT@ \
+ resolver_test@EXEEXT@ \
+ result_test@EXEEXT@ \
+ rsa_test@EXEEXT@ \
+ sigs_test@EXEEXT@ \
+ time_test@EXEEXT@ \
+ tsig_test@EXEEXT@ \
+ update_test@EXEEXT@ \
+ zonemgr_test@EXEEXT@ \
+ zt_test@EXEEXT@
+
+@BIND9_MAKE_RULES@
+
+LD_WRAP_TESTS=@LD_WRAP_TESTS@
+
+acl_test@EXEEXT@: acl_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ acl_test.@O@ dnstest.@O@ ${DNSLIBS} \
+ ${ISCLIBS} ${LIBS}
+
+db_test@EXEEXT@: db_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ db_test.@O@ dnstest.@O@ ${DNSLIBS} \
+ ${ISCLIBS} ${LIBS}
+
+dbdiff_test@EXEEXT@: dbdiff_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ dbdiff_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+dbiterator_test@EXEEXT@: dbiterator_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ dbiterator_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+dbversion_test@EXEEXT@: dbversion_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ dbversion_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+dh_test@EXEEXT@: dh_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ dh_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+dispatch_test@EXEEXT@: dispatch_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ dispatch_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+dnstap_test@EXEEXT@: dnstap_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ dnstap_test.@O@ dnstest.@O@ \
+ ${FSTRM_LIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+dst_test@EXEEXT@: dst_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ dst_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+geoip_test@EXEEXT@: geoip_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ geoip_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${MAXMINDDB_LIBS} ${ISCLIBS} ${LIBS}
+
+keytable_test@EXEEXT@: keytable_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ keytable_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+master_test@EXEEXT@: master_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ test -d testdata || mkdir testdata
+ test -d testdata/master || mkdir testdata/master
+ ${PERL} ${srcdir}/mkraw.pl < ${srcdir}/testdata/master/master12.data.in \
+ > testdata/master/master12.data
+ ${PERL} ${srcdir}/mkraw.pl < ${srcdir}/testdata/master/master13.data.in \
+ > testdata/master/master13.data
+ ${PERL} ${srcdir}/mkraw.pl < ${srcdir}/testdata/master/master14.data.in \
+ > testdata/master/master14.data
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ master_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+name_test@EXEEXT@: name_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ name_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+nsec3_test@EXEEXT@: nsec3_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ nsec3_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+nsec3param_test@EXEEXT@: nsec3param_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ nsec3param_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+peer_test@EXEEXT@: peer_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ peer_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+private_test@EXEEXT@: private_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ private_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+rbt_serialize_test@EXEEXT@: rbt_serialize_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ rbt_serialize_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+rbt_test@EXEEXT@: rbt_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ rbt_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+rbtdb_test@EXEEXT@: rbtdb_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ rbtdb_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+rdata_test@EXEEXT@: rdata_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ rdata_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+rdataset_test@EXEEXT@: rdataset_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ rdataset_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+rdatasetstats_test@EXEEXT@: rdatasetstats_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ rdatasetstats_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+resolver_test@EXEEXT@: resolver_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ resolver_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+result_test@EXEEXT@: result_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ result_test.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+rsa_test@EXEEXT@: rsa_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ rsa_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+sigs_test@EXEEXT@: sigs_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ sigs_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+time_test@EXEEXT@: time_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ time_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+WRAP_OPTIONS = \
+ -Wl,--wrap=isc__mem_put \
+ -Wl,--wrap=isc__mem_get \
+ -Wl,--wrap=isc_mem_attach \
+ -Wl,--wrap=isc_mem_detach \
+ -Wl,--wrap=isc__mem_putanddetach
+
+tsig_test@EXEEXT@: tsig_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ tsig_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+update_test@EXEEXT@: update_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ update_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+zonemgr_test@EXEEXT@: zonemgr_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ zonemgr_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+zt_test@EXEEXT@: zt_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ zt_test.@O@ dnstest.@O@ \
+ ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+unit::
+ sh ${top_builddir}/unit/unittest.sh
+
+clean distclean::
+ rm -f ${TARGETS}
+ rm -f atf.out
+ rm -f testdata/master/master12.data testdata/master/master13.data \
+ testdata/master/master14.data
+ rm -f zone.bin
diff --git a/lib/dns/tests/acl_test.c b/lib/dns/tests/acl_test.c
new file mode 100644
index 0000000..21941a2
--- /dev/null
+++ b/lib/dns/tests/acl_test.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+#define BUFLEN 255
+#define BIGBUFLEN (70 * 1024)
+#define TEST_ORIGIN "test"
+
+/* test that dns_acl_isinsecure works */
+static void
+dns_acl_isinsecure_test(void **state) {
+ isc_result_t result;
+ dns_acl_t *any = NULL;
+ dns_acl_t *none = NULL;
+ dns_acl_t *notnone = NULL;
+ dns_acl_t *notany = NULL;
+#if defined(HAVE_GEOIP2)
+ dns_acl_t *geoip = NULL;
+ dns_acl_t *notgeoip = NULL;
+ dns_aclelement_t *de;
+#endif /* HAVE_GEOIP2 */
+
+ UNUSED(state);
+
+ result = dns_acl_any(dt_mctx, &any);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_acl_none(dt_mctx, &none);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_acl_create(dt_mctx, 1, &notnone);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_acl_create(dt_mctx, 1, &notany);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_acl_merge(notnone, none, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_acl_merge(notany, any, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+#if defined(HAVE_GEOIP2)
+ result = dns_acl_create(dt_mctx, 1, &geoip);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ de = geoip->elements;
+ assert_non_null(de);
+ strlcpy(de->geoip_elem.as_string, "AU",
+ sizeof(de->geoip_elem.as_string));
+ de->geoip_elem.subtype = dns_geoip_country_code;
+ de->type = dns_aclelementtype_geoip;
+ de->negative = false;
+ assert_true(geoip->length < geoip->alloc);
+ dns_acl_node_count(geoip)++;
+ de->node_num = dns_acl_node_count(geoip);
+ geoip->length++;
+
+ result = dns_acl_create(dt_mctx, 1, &notgeoip);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_acl_merge(notgeoip, geoip, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+#endif /* HAVE_GEOIP2 */
+
+ assert_true(dns_acl_isinsecure(any)); /* any; */
+ assert_false(dns_acl_isinsecure(none)); /* none; */
+ assert_false(dns_acl_isinsecure(notany)); /* !any; */
+ assert_false(dns_acl_isinsecure(notnone)); /* !none; */
+
+#if defined(HAVE_GEOIP2)
+ assert_true(dns_acl_isinsecure(geoip)); /* geoip; */
+ assert_false(dns_acl_isinsecure(notgeoip)); /* !geoip; */
+#endif /* HAVE_GEOIP2 */
+
+ dns_acl_detach(&any);
+ dns_acl_detach(&none);
+ dns_acl_detach(&notany);
+ dns_acl_detach(&notnone);
+#if defined(HAVE_GEOIP2)
+ dns_acl_detach(&geoip);
+ dns_acl_detach(&notgeoip);
+#endif /* HAVE_GEOIP2 */
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(dns_acl_isinsecure_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/comparekeys/Kexample-d.+008+53461.key b/lib/dns/tests/comparekeys/Kexample-d.+008+53461.key
new file mode 100644
index 0000000..5c43165
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-d.+008+53461.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 53461, for example-d.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example-d. IN DNSKEY 256 3 8 AwEAAaKYSOPDzZvfue5sU71xPCJKJpB5kZGl4vTp3OI8W+nN1YFtmVe2 2gM666AEutDAEB7cLkyoKCOH0+4Lh1ucPr6OmdWkHfk7uZv58eH0kOAV tNz2xhEF/YHSD7cnBEU9g0knGwpWuzSJKRhGhNoaVus9g1MaAn8efptz HIduIwgAeXV3BDCUpY6HbpwjDxOGCzCUYDRgcex37kYuCyW0PvlO5FQ0 DT0LpjcgBmIBpXol7sYpmKdOKJrm4x2lwGntr4K+bCdNYI2PRPJjPqAf jlvIvJylGUaqFJasw7PSMQIkgcQ4OQXVrhE8uGLdYvP1cusLuROIjdYp Pdqc5K9lCQE=
diff --git a/lib/dns/tests/comparekeys/Kexample-d.+008+53461.private b/lib/dns/tests/comparekeys/Kexample-d.+008+53461.private
new file mode 100644
index 0000000..a693428
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-d.+008+53461.private
@@ -0,0 +1,13 @@
+Private-key-format: v1.3
+Algorithm: 8 (RSASHA256)
+Modulus: ophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2ZV7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7kVDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN1ik92pzkr2UJAQ==
+PublicExponent: AQAB
+PrivateExponent: AhR3VvVoV6OGOjiiNUt728hidEMoX4PJWtHNWqinyRek5tSnqgaXeKC3NuU0mUIjDvBps9oH4lK3yNa5fBr/nodwP4wNyTd3obR/z6JcLersxJjHi4nYX2ju8vjdsBSIulNudqlrsPhLJe0+Tff3FRfClSQmQ/JtakHo4lIx8zxiOJY8aWFeHGdWJDkAf6NStt3eVYyOyAwISfv3muaGPZKShiIOfLyTvqFqzwYFgdTWmvFqTdwgjIMc5XAwqw73WP2BPCN+fdCiMtrw0fCrhWzw/gfMJBHdOPH0diUZysAJhM0vdVKQzEi/g3YOo00fahZiPzaxNtZnLNj2mA54YQ==
+Prime1: z08i0sCcEpr4MZi4TReohPWp3F5vMQYVux8B3ltmJ3kKraXEmVEVmujhWa+ZDxhJmwKoba65vNEsUbSJN6WwJd7PVyskHb2GnWGK8NtlainFEuiS5CDxwULR4o2SI+Pij9thMQoA13ZTKc9s3E57VgcvJ7vaoD/1ZtpP7tdaerE=
+Prime2: yMid465M6bCXXUfWg7oq6A4MZUULbEPKvs+qGIersdiHfrFRGJ0Lviujs8KHaPS5rt4YmbpQU9tGbJBauY17T03qr/mQOBDx5gDkAJcJ0EUHudFslwqyn50THlJsKrFOxBYl7laY0v6CGCMyuZok8qyhiPHv5dhzSc9zwKaXZ1E=
+Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE=
+Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE=
+Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample-e.+008+53973.key b/lib/dns/tests/comparekeys/Kexample-e.+008+53973.key
new file mode 100644
index 0000000..a4b0d03
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-e.+008+53973.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 53973, for example-e.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example-e. IN DNSKEY 256 3 8 BQEAAAABophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2Z V7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ 4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+ m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7k VDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+ oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN 1ik92pzkr2UJAQ==
diff --git a/lib/dns/tests/comparekeys/Kexample-e.+008+53973.private b/lib/dns/tests/comparekeys/Kexample-e.+008+53973.private
new file mode 100644
index 0000000..765ca1a
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-e.+008+53973.private
@@ -0,0 +1,13 @@
+Private-key-format: v1.3
+Algorithm: 8 (RSASHA256)
+Modulus: ophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2ZV7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7kVDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN1ik92pzkr2UJAQ==
+PublicExponent: AQAAAAE=
+PrivateExponent: lFgeQHf3klxXlfkNmczDEYHXl37i2iCgZdUsqtho/3LFdfWZrxZr6ACM040dKLHiw1UdhODy5h/Zstif4Ww3LsKKBgpbMnZUTMOI9R+eQmRrhCI96XAur5AIuJCHa+jIbCiamh8xY6g0byp/sUHQxYV02I/lcTdQSeGHSOSqX3QjB835OVa18hyW6txAxM4DVGo/NvIJw2ItSl2qwHTMDHK45t4YbnKEd6suriUiveyax5dU1JtpviwHJiAFPy+L68jMo8cfr+JCLWW2OJYkrBXb8kwqaPsV0RCGZ59sePyRdSYRgNi1brBStesctVc5UfSxH6p2A6C28LdrubcXAQ==
+Prime1: z08i0sCcEpr4MZi4TReohPWp3F5vMQYVux8B3ltmJ3kKraXEmVEVmujhWa+ZDxhJmwKoba65vNEsUbSJN6WwJd7PVyskHb2GnWGK8NtlainFEuiS5CDxwULR4o2SI+Pij9thMQoA13ZTKc9s3E57VgcvJ7vaoD/1ZtpP7tdaerE=
+Prime2: yMid465M6bCXXUfWg7oq6A4MZUULbEPKvs+qGIersdiHfrFRGJ0Lviujs8KHaPS5rt4YmbpQU9tGbJBauY17T03qr/mQOBDx5gDkAJcJ0EUHudFslwqyn50THlJsKrFOxBYl7laY0v6CGCMyuZok8qyhiPHv5dhzSc9zwKaXZ1E=
+Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE=
+Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE=
+Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample-n.+008+37464.key b/lib/dns/tests/comparekeys/Kexample-n.+008+37464.key
new file mode 100644
index 0000000..da2e16a
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-n.+008+37464.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 37464, for example-n.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example-n. IN DNSKEY 256 3 8 AwEAAbxHOF8G0xw9ekCodhL8KivuZ3o0jmGlycLiXBjBN8c5R5fjLjUh D0gy3IDbDC+kLaPhHGF/MwrSEjrgSowxZ8nrxDzsq5ZdpeUsYaNrbQEY /mqf35T/9/Ulm4v06x58v/NTugWd05Xq04aAyfm7EViyGFzmVOVfPnll h9xQtvWEWoRWPseFw+dY5/nc/+xB/IsQMihoH2rO+cek/lsP3R9DsHCG RbQ/ks/+rrp6/O+QJZyZrzsONl7mlMDXNy3Pz9J4qMW2W6Mz702LN324 7/9UsetDGGbuZfrCLMpKWXzdsJm36DOk4aMooS9111plfXaXQgQNcL5G 021utpTau+8=
diff --git a/lib/dns/tests/comparekeys/Kexample-n.+008+37464.private b/lib/dns/tests/comparekeys/Kexample-n.+008+37464.private
new file mode 100644
index 0000000..65689a2
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-n.+008+37464.private
@@ -0,0 +1,13 @@
+Private-key-format: v1.3
+Algorithm: 8 (RSASHA256)
+Modulus: vEc4XwbTHD16QKh2EvwqK+5nejSOYaXJwuJcGME3xzlHl+MuNSEPSDLcgNsML6Qto+EcYX8zCtISOuBKjDFnyevEPOyrll2l5Sxho2ttARj+ap/flP/39SWbi/TrHny/81O6BZ3TlerThoDJ+bsRWLIYXOZU5V8+eWWH3FC29YRahFY+x4XD51jn+dz/7EH8ixAyKGgfas75x6T+Ww/dH0OwcIZFtD+Sz/6uunr875AlnJmvOw42XuaUwNc3Lc/P0nioxbZbozPvTYs3fbjv/1Sx60MYZu5l+sIsykpZfN2wmbfoM6ThoyihL3XXWmV9dpdCBA1wvkbTbW62lNq77w==
+PublicExponent: AQAB
+PrivateExponent: lFgeQHf3klxXlfkNmczDEYHXl37i2iCgZdUsqtho/3LFdfWZrxZr6ACM040dKLHiw1UdhODy5h/Zstif4Ww3LsKKBgpbMnZUTMOI9R+eQmRrhCI96XAur5AIuJCHa+jIbCiamh8xY6g0byp/sUHQxYV02I/lcTdQSeGHSOSqX3QjB835OVa18hyW6txAxM4DVGo/NvIJw2ItSl2qwHTMDHK45t4YbnKEd6suriUiveyax5dU1JtpviwHJiAFPy+L68jMo8cfr+JCLWW2OJYkrBXb8kwqaPsV0RCGZ59sePyRdSYRgNi1brBStesctVc5UfSxH6p2A6C28LdrubcXAQ==
+Prime1: z08i0sCcEpr4MZi4TReohPWp3F5vMQYVux8B3ltmJ3kKraXEmVEVmujhWa+ZDxhJmwKoba65vNEsUbSJN6WwJd7PVyskHb2GnWGK8NtlainFEuiS5CDxwULR4o2SI+Pij9thMQoA13ZTKc9s3E57VgcvJ7vaoD/1ZtpP7tdaerE=
+Prime2: yMid465M6bCXXUfWg7oq6A4MZUULbEPKvs+qGIersdiHfrFRGJ0Lviujs8KHaPS5rt4YmbpQU9tGbJBauY17T03qr/mQOBDx5gDkAJcJ0EUHudFslwqyn50THlJsKrFOxBYl7laY0v6CGCMyuZok8qyhiPHv5dhzSc9zwKaXZ1E=
+Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE=
+Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE=
+Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample-p.+008+53461.key b/lib/dns/tests/comparekeys/Kexample-p.+008+53461.key
new file mode 100644
index 0000000..20ffcfd
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-p.+008+53461.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 53461, for example-p.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example-p. IN DNSKEY 256 3 8 AwEAAaKYSOPDzZvfue5sU71xPCJKJpB5kZGl4vTp3OI8W+nN1YFtmVe2 2gM666AEutDAEB7cLkyoKCOH0+4Lh1ucPr6OmdWkHfk7uZv58eH0kOAV tNz2xhEF/YHSD7cnBEU9g0knGwpWuzSJKRhGhNoaVus9g1MaAn8efptz HIduIwgAeXV3BDCUpY6HbpwjDxOGCzCUYDRgcex37kYuCyW0PvlO5FQ0 DT0LpjcgBmIBpXol7sYpmKdOKJrm4x2lwGntr4K+bCdNYI2PRPJjPqAf jlvIvJylGUaqFJasw7PSMQIkgcQ4OQXVrhE8uGLdYvP1cusLuROIjdYp Pdqc5K9lCQE=
diff --git a/lib/dns/tests/comparekeys/Kexample-p.+008+53461.private b/lib/dns/tests/comparekeys/Kexample-p.+008+53461.private
new file mode 100644
index 0000000..063c925
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-p.+008+53461.private
@@ -0,0 +1,13 @@
+Private-key-format: v1.3
+Algorithm: 8 (RSASHA256)
+Modulus: ophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2ZV7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7kVDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN1ik92pzkr2UJAQ==
+PublicExponent: AQAB
+PrivateExponent: lFgeQHf3klxXlfkNmczDEYHXl37i2iCgZdUsqtho/3LFdfWZrxZr6ACM040dKLHiw1UdhODy5h/Zstif4Ww3LsKKBgpbMnZUTMOI9R+eQmRrhCI96XAur5AIuJCHa+jIbCiamh8xY6g0byp/sUHQxYV02I/lcTdQSeGHSOSqX3QjB835OVa18hyW6txAxM4DVGo/NvIJw2ItSl2qwHTMDHK45t4YbnKEd6suriUiveyax5dU1JtpviwHJiAFPy+L68jMo8cfr+JCLWW2OJYkrBXb8kwqaPsV0RCGZ59sePyRdSYRgNi1brBStesctVc5UfSxH6p2A6C28LdrubcXAQ==
+Prime1: 5YpfVjEtL1owW9gSFbIMx65POr+fiktxirgy1bc5fSsVqUgG6zhbaN/VpWcNZG0Zg5xd6S7C8V3djGlnJN8wZIyjIh7+Z3WWjqbOD9oY7rC1fR+W0OvbCmZiEzOpRJ5qoMOh1MzkkanhMy0/ICpaa8eQ9zEb80oTIQpFgoLn7K0=
+Prime2: yMid465M6bCXXUfWg7oq6A4MZUULbEPKvs+qGIersdiHfrFRGJ0Lviujs8KHaPS5rt4YmbpQU9tGbJBauY17T03qr/mQOBDx5gDkAJcJ0EUHudFslwqyn50THlJsKrFOxBYl7laY0v6CGCMyuZok8qyhiPHv5dhzSc9zwKaXZ1E=
+Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE=
+Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE=
+Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample-private.+002+65316.key b/lib/dns/tests/comparekeys/Kexample-private.+002+65316.key
new file mode 100644
index 0000000..7cc002d
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-private.+002+65316.key
@@ -0,0 +1 @@
+example-private. IN KEY 512 3 2 AAECAAAAgKVXnUOFKMvLvwO/VdY9bq+eOPBxrRWsDpcL9FJ9+hklVvii pcLOIhiKLeHI/u9vM2nhd8+opIW92+j2pB185MRgSrINQcC+XpI/xiDG HwE78bQ+2Ykb/memG+ctkVyrFGHtaJLCUGWrUHy1jbtvYeaKeS92jR/2 4oryt3N851u5
diff --git a/lib/dns/tests/comparekeys/Kexample-private.+002+65316.private b/lib/dns/tests/comparekeys/Kexample-private.+002+65316.private
new file mode 100644
index 0000000..1f00fa9
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-private.+002+65316.private
@@ -0,0 +1,9 @@
+Private-key-format: v1.3
+Algorithm: 2 (DH)
+Prime(p): ///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5lOB//////////8=
+Generator(g): Ag==
+Private_value(x): dLr0sfk/P1V0DfQ7Ke3IIaSM8nHjtrBRlMcQXRMVrLhbbKeCodvpSRtI0Nwtt38Df8dbGGtP676my2Ht2UHyL7rO0+ASv98NCysL0Xp6q2a7fn67iGFUBTg3jzXC89FYv4sYNeVLDGrKC3EjtGkalzgDVuzEC8CqRkWKeys3ufc=
+Public_value(y): pVedQ4Uoy8u/A79V1j1ur5448HGtFawOlwv0Un36GSVW+KKlws4iGIot4cj+728zaeF3z6ikhb3b6PakHXzkxGBKsg1BwL5ekj/GIMYfATvxtD7ZiRv+Z6Yb5y2RXKsUYe1oksJQZatQfLWNu29h5op5L3aNH/biivK3c3znW7k=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample-q.+008+53461.key b/lib/dns/tests/comparekeys/Kexample-q.+008+53461.key
new file mode 100644
index 0000000..5d4a0e7
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-q.+008+53461.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 53461, for example-q.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example-q. IN DNSKEY 256 3 8 AwEAAaKYSOPDzZvfue5sU71xPCJKJpB5kZGl4vTp3OI8W+nN1YFtmVe2 2gM666AEutDAEB7cLkyoKCOH0+4Lh1ucPr6OmdWkHfk7uZv58eH0kOAV tNz2xhEF/YHSD7cnBEU9g0knGwpWuzSJKRhGhNoaVus9g1MaAn8efptz HIduIwgAeXV3BDCUpY6HbpwjDxOGCzCUYDRgcex37kYuCyW0PvlO5FQ0 DT0LpjcgBmIBpXol7sYpmKdOKJrm4x2lwGntr4K+bCdNYI2PRPJjPqAf jlvIvJylGUaqFJasw7PSMQIkgcQ4OQXVrhE8uGLdYvP1cusLuROIjdYp Pdqc5K9lCQE=
diff --git a/lib/dns/tests/comparekeys/Kexample-q.+008+53461.private b/lib/dns/tests/comparekeys/Kexample-q.+008+53461.private
new file mode 100644
index 0000000..6b2e563
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample-q.+008+53461.private
@@ -0,0 +1,13 @@
+Private-key-format: v1.3
+Algorithm: 8 (RSASHA256)
+Modulus: ophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2ZV7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7kVDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN1ik92pzkr2UJAQ==
+PublicExponent: AQAB
+PrivateExponent: lFgeQHf3klxXlfkNmczDEYHXl37i2iCgZdUsqtho/3LFdfWZrxZr6ACM040dKLHiw1UdhODy5h/Zstif4Ww3LsKKBgpbMnZUTMOI9R+eQmRrhCI96XAur5AIuJCHa+jIbCiamh8xY6g0byp/sUHQxYV02I/lcTdQSeGHSOSqX3QjB835OVa18hyW6txAxM4DVGo/NvIJw2ItSl2qwHTMDHK45t4YbnKEd6suriUiveyax5dU1JtpviwHJiAFPy+L68jMo8cfr+JCLWW2OJYkrBXb8kwqaPsV0RCGZ59sePyRdSYRgNi1brBStesctVc5UfSxH6p2A6C28LdrubcXAQ==
+Prime1: z08i0sCcEpr4MZi4TReohPWp3F5vMQYVux8B3ltmJ3kKraXEmVEVmujhWa+ZDxhJmwKoba65vNEsUbSJN6WwJd7PVyskHb2GnWGK8NtlainFEuiS5CDxwULR4o2SI+Pij9thMQoA13ZTKc9s3E57VgcvJ7vaoD/1ZtpP7tdaerE=
+Prime2: 0fs3ncL5/2qzq2dmPXLYcOfc1EGSuESO0VpREP8EpTkyPKeVw5LaF9TgZRqPWlRf2T0LPoZ766xLAn090u0pLQ5fWM96NMas7kS+rxtRssat6MiQo3YfoU3ysk3xuPzrMBHyn/N42CjSG+bJEToHR7V16KsCT6dBIPkI3tj/Yos=
+Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE=
+Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE=
+Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample.+002+65316.key b/lib/dns/tests/comparekeys/Kexample.+002+65316.key
new file mode 100644
index 0000000..c2f4703
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample.+002+65316.key
@@ -0,0 +1 @@
+example. IN KEY 512 3 2 AAECAAAAgKVXnUOFKMvLvwO/VdY9bq+eOPBxrRWsDpcL9FJ9+hklVvii pcLOIhiKLeHI/u9vM2nhd8+opIW92+j2pB185MRgSrINQcC+XpI/xiDG HwE78bQ+2Ykb/memG+ctkVyrFGHtaJLCUGWrUHy1jbtvYeaKeS92jR/2 4oryt3N851u5
diff --git a/lib/dns/tests/comparekeys/Kexample.+002+65316.private b/lib/dns/tests/comparekeys/Kexample.+002+65316.private
new file mode 100644
index 0000000..e872834
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample.+002+65316.private
@@ -0,0 +1,9 @@
+Private-key-format: v1.3
+Algorithm: 2 (DH)
+Prime(p): ///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5lOB//////////8=
+Generator(g): Ag==
+Private_value(x): bUMVaaSCAPT0NK7AkIa0JA1SSw83x8WxS+iePECQwr4xDDMnevNHWK1nIofUM2qNbpDe2KvFIt9tu+1UgZgOTLoQFipePtHKOjoRX6XsGNzKmL8WZOlw/QJw0D5RIn7l7tvmBCeNHINl9IWVgMLTi+wgzrJxSeGe406q23Jn4Uc=
+Public_value(y): pVedQ4Uoy8u/A79V1j1ur5448HGtFawOlwv0Un36GSVW+KKlws4iGIot4cj+728zaeF3z6ikhb3b6PakHXzkxGBKsg1BwL5ekj/GIMYfATvxtD7ZiRv+Z6Yb5y2RXKsUYe1oksJQZatQfLWNu29h5op5L3aNH/biivK3c3znW7k=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample.+008+53461.key b/lib/dns/tests/comparekeys/Kexample.+008+53461.key
new file mode 100644
index 0000000..33c8188
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample.+008+53461.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 53461, for example.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example. IN DNSKEY 256 3 8 AwEAAaKYSOPDzZvfue5sU71xPCJKJpB5kZGl4vTp3OI8W+nN1YFtmVe2 2gM666AEutDAEB7cLkyoKCOH0+4Lh1ucPr6OmdWkHfk7uZv58eH0kOAV tNz2xhEF/YHSD7cnBEU9g0knGwpWuzSJKRhGhNoaVus9g1MaAn8efptz HIduIwgAeXV3BDCUpY6HbpwjDxOGCzCUYDRgcex37kYuCyW0PvlO5FQ0 DT0LpjcgBmIBpXol7sYpmKdOKJrm4x2lwGntr4K+bCdNYI2PRPJjPqAf jlvIvJylGUaqFJasw7PSMQIkgcQ4OQXVrhE8uGLdYvP1cusLuROIjdYp Pdqc5K9lCQE=
diff --git a/lib/dns/tests/comparekeys/Kexample.+008+53461.private b/lib/dns/tests/comparekeys/Kexample.+008+53461.private
new file mode 100644
index 0000000..dd4d9a4
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample.+008+53461.private
@@ -0,0 +1,13 @@
+Private-key-format: v1.3
+Algorithm: 8 (RSASHA256)
+Modulus: ophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2ZV7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7kVDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN1ik92pzkr2UJAQ==
+PublicExponent: AQAB
+PrivateExponent: lFgeQHf3klxXlfkNmczDEYHXl37i2iCgZdUsqtho/3LFdfWZrxZr6ACM040dKLHiw1UdhODy5h/Zstif4Ww3LsKKBgpbMnZUTMOI9R+eQmRrhCI96XAur5AIuJCHa+jIbCiamh8xY6g0byp/sUHQxYV02I/lcTdQSeGHSOSqX3QjB835OVa18hyW6txAxM4DVGo/NvIJw2ItSl2qwHTMDHK45t4YbnKEd6suriUiveyax5dU1JtpviwHJiAFPy+L68jMo8cfr+JCLWW2OJYkrBXb8kwqaPsV0RCGZ59sePyRdSYRgNi1brBStesctVc5UfSxH6p2A6C28LdrubcXAQ==
+Prime1: z08i0sCcEpr4MZi4TReohPWp3F5vMQYVux8B3ltmJ3kKraXEmVEVmujhWa+ZDxhJmwKoba65vNEsUbSJN6WwJd7PVyskHb2GnWGK8NtlainFEuiS5CDxwULR4o2SI+Pij9thMQoA13ZTKc9s3E57VgcvJ7vaoD/1ZtpP7tdaerE=
+Prime2: yMid465M6bCXXUfWg7oq6A4MZUULbEPKvs+qGIersdiHfrFRGJ0Lviujs8KHaPS5rt4YmbpQU9tGbJBauY17T03qr/mQOBDx5gDkAJcJ0EUHudFslwqyn50THlJsKrFOxBYl7laY0v6CGCMyuZok8qyhiPHv5dhzSc9zwKaXZ1E=
+Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE=
+Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE=
+Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample.+013+19786.key b/lib/dns/tests/comparekeys/Kexample.+013+19786.key
new file mode 100644
index 0000000..ccfcc97
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample.+013+19786.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 19786, for example.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example. IN DNSKEY 256 3 13 S35Z1XtGlnnU7BBahMJwAZXXff+JupyIDssNfJyrugLKq5R10TJ5tU3W r3VuP6aJNs6+uL2cMPVTVT1vr1Aqwg==
diff --git a/lib/dns/tests/comparekeys/Kexample.+013+19786.private b/lib/dns/tests/comparekeys/Kexample.+013+19786.private
new file mode 100644
index 0000000..0d72cf1
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample.+013+19786.private
@@ -0,0 +1,6 @@
+Private-key-format: v1.3
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: ZYcYhR5f98vI1+BFGKLIrarZrqxJM4mRy9tvwntdYoo=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample.+015+63663.key b/lib/dns/tests/comparekeys/Kexample.+015+63663.key
new file mode 100644
index 0000000..92db9fb
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample.+015+63663.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 63663, for example.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example. IN DNSKEY 256 3 15 ZLlkI5q8XDkP3D7Zxdbmuqh4yp90mbvdcNT0xSGLDtI=
diff --git a/lib/dns/tests/comparekeys/Kexample.+015+63663.private b/lib/dns/tests/comparekeys/Kexample.+015+63663.private
new file mode 100644
index 0000000..c2c48f3
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample.+015+63663.private
@@ -0,0 +1,6 @@
+Private-key-format: v1.3
+Algorithm: 15 (ED25519)
+PrivateKey: rGYsnf8nPlg7kg7qRcIXYShPsTiMHTeWJInNrW9GwSQ=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample2.+002+19823.key b/lib/dns/tests/comparekeys/Kexample2.+002+19823.key
new file mode 100644
index 0000000..9d521f7
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample2.+002+19823.key
@@ -0,0 +1 @@
+example2. IN KEY 512 3 2 AAECAAAAgCxVfxiyTe8C83ou8KXSu9WmzGwCYWB2NkdS87Kz0PgTuBay JkDDAEeR6CIYClA6PXBp2GXUPHoYWag9zVOVU85PYu0KRZF69EN0IVsA OCtgikOcr5yD4esSMwTTPk/OQ8qW/yGf1DvdpXuiu3P/wSpzVGL8tHFQ 2XURydYytol0
diff --git a/lib/dns/tests/comparekeys/Kexample2.+002+19823.private b/lib/dns/tests/comparekeys/Kexample2.+002+19823.private
new file mode 100644
index 0000000..f6722f6
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample2.+002+19823.private
@@ -0,0 +1,9 @@
+Private-key-format: v1.3
+Algorithm: 2 (DH)
+Prime(p): ///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5lOB//////////8=
+Generator(g): Ag==
+Private_value(x): W0EpuIMltmMuZAKcCmRe/Ix9WsHPU/GLfqbjHKCjgYdRFzwqHyVp6z+uf8EgmHBD1bbBjwfcnRse8xfqqmt/wZIRdDzjRq/oZdKtJHqFZSO+MQZ5DKrdojKU7UEl/j44heJzVO0qFkrPvWglRt+780LP0awkfetecXDxvJT+HIw=
+Public_value(y): LFV/GLJN7wLzei7wpdK71abMbAJhYHY2R1LzsrPQ+BO4FrImQMMAR5HoIhgKUDo9cGnYZdQ8ehhZqD3NU5VTzk9i7QpFkXr0Q3QhWwA4K2CKQ5yvnIPh6xIzBNM+T85Dypb/IZ/UO92le6K7c//BKnNUYvy0cVDZdRHJ1jK2iXQ=
+Created: 20211027221355
+Publish: 20211027221355
+Activate: 20211027221355
diff --git a/lib/dns/tests/comparekeys/Kexample2.+008+37993.key b/lib/dns/tests/comparekeys/Kexample2.+008+37993.key
new file mode 100644
index 0000000..c0e09a1
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample2.+008+37993.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 37993, for example2.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example2. IN DNSKEY 256 3 8 AwEAAaSRhPf0XhYR52Kpi0RZJgEnpidvuz2Ywdyh8k5CKwal9nM15PNc 4ZoPEVGO+ize53hq0iUkVlBhAfhQE31Fhf4zU544fezBEaz33hiajEzL ZITux9N83WfoYSNnyufvSGzNcpNM6LHKdDwMr1kr9tTgNeuiTAlPv5z9 BNtfv2B25moVm1DoxMCd8WH0jYC452a2lGM+Fbd45o02OO7V8balPwJh MM2bbeWg5G+tbvCAot93KxtavyOMKV4siv3ZH639J0dIb10L8nNrN0Ge UjkX8yU3fgeWB4Oldtzx0SHxG75NWjRLnpVzBq5GeacLc4RsN+S+nhYW 4Wv2A066w70=
diff --git a/lib/dns/tests/comparekeys/Kexample2.+008+37993.private b/lib/dns/tests/comparekeys/Kexample2.+008+37993.private
new file mode 100644
index 0000000..887ad2b
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample2.+008+37993.private
@@ -0,0 +1,13 @@
+Private-key-format: v1.3
+Algorithm: 8 (RSASHA256)
+Modulus: pJGE9/ReFhHnYqmLRFkmASemJ2+7PZjB3KHyTkIrBqX2czXk81zhmg8RUY76LN7neGrSJSRWUGEB+FATfUWF/jNTnjh97MERrPfeGJqMTMtkhO7H03zdZ+hhI2fK5+9IbM1yk0zoscp0PAyvWSv21OA166JMCU+/nP0E21+/YHbmahWbUOjEwJ3xYfSNgLjnZraUYz4Vt3jmjTY47tXxtqU/AmEwzZtt5aDkb61u8ICi33crG1q/I4wpXiyK/dkfrf0nR0hvXQvyc2s3QZ5SORfzJTd+B5YHg6V23PHRIfEbvk1aNEuelXMGrkZ5pwtzhGw35L6eFhbha/YDTrrDvQ==
+PublicExponent: AQAB
+PrivateExponent: O/HFvYwFuYRMBGQ9lmfisAkBPNw2F/nMo9FZsafohENvwgefngX3J2bVqB+sgSuwpOxEH8NcrWqojQqeDsOES1Pm4XsyY0rwZVDkVZH2CQMNWl6f6ylQfMjomTz1bAZ9GyS612zsVdapADaeqJybDG+fNHWpvLqP0V9YpY/65efTvrA3Qu+XpDvLaJ34yjkeEGUgysNP3KkDTeJTY/ksKi6ODtdzbKpufjZS8b6BL97XcFcNGiwu/gNPCvtmm/H+tXaNYyijG7bNGPOpHFhlMCT13o8XLrR/OGty6VY6PpjaEnvlZUZnWHUwn/JmNoZRJoXAAEerk3nS+tOhRmcrAQ==
+Prime1: 2W8JHYaTn7XefxxwaDZWFrVtHnnd0vUZvBBNA1PJeRfDr+yPxyWcgYx1OBxKkJsYGiob0i992W2HXuz2KS81yBCtH/uLK1Y+mkjgme4MWZupZ0RsKA1TkgIrJs174Dv3P/yqc+/R4eiwUGt10493MS1PJFF0CmisDzgjai/JLIM=
+Prime2: wcIKikgzOsq2A2Hl7qPCeA3oKTc66eFVNvB/KH91/hNFKNm0kAvhHrNe9rSoU+JywCNbX/Fs7X6SuHHJaRs+KpSBadnqfwEIngCq2Y00nT3sbETx4VNbXFD6MPPo3MWDi61/TCyrtBujutavo5ghj2oVzNGqMT3UyhaVNrJp2r8=
+Exponent1: GsEO3hMxFvXJ6toU+r202hZ41scoBE0kXX+j+kTVBZFnAr6Y8mguWcJuqfjRM/nhfVaxFavCUH6pqYR+xZKJi5SBuO26shpqmZFeEZK48k21Cn/gzwzUu6KIrL2cAHtgcP8l+h4INUPsbfjLBr0gbWyl0FI1dRJsGXNO6EH4/wE=
+Exponent2: WrgcsUQ+4E8bS5ghzUtVeVqhkfKvHeSIPpH6J58OQukI36iXNz6op/Q6CW7qxWPocHfdh52Fb+lsjvmP4SuFPvCLa2FBvzdfroMHe5b2xIzCzqq1Sdf6lc3AZv080WmVPuf8C1F7D3hFf+yXDhTj2b9E98JPWoDlyb0rHhIJKAc=
+Coefficient: aVDpGheH0UJ8aWPRIRHyjMTCIPB8zmhfwugpV11Z/OXNb2uaNRcnVKujs1mlSydoIfFQSuFf3iPs7ytaJUfcQJ+k1QAtssJC1HXF14t0p5o99QxuQLgmNtPHD7m2aeAyFJoycF24UmDnFmOPSNm1fnJs9LrBPFZFdTBGhl8plEo=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample2.+013+16384.key b/lib/dns/tests/comparekeys/Kexample2.+013+16384.key
new file mode 100644
index 0000000..b6351ad
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample2.+013+16384.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 16384, for example2.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example2. IN DNSKEY 256 3 13 p+ohRFVh6wmdAhbU/cF2FYoE/i49FxnvKwif0Co0D7RhBui4AMFOsFYu 9AIqEBaCGurjGYl7WDYRrjRMRjWW1g==
diff --git a/lib/dns/tests/comparekeys/Kexample2.+013+16384.private b/lib/dns/tests/comparekeys/Kexample2.+013+16384.private
new file mode 100644
index 0000000..74a371c
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample2.+013+16384.private
@@ -0,0 +1,6 @@
+Private-key-format: v1.3
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: ZNsU73iCEeC837TA59nT/QDtd3oYsrYDWy8jfazZQkA=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample2.+015+37529.key b/lib/dns/tests/comparekeys/Kexample2.+015+37529.key
new file mode 100644
index 0000000..9a8cf77
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample2.+015+37529.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 37529, for example2.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example2. IN DNSKEY 256 3 15 zyiLjDymEPN90rwi/y0mWXLnUm0Nq7T9Kc8VoubF2Io=
diff --git a/lib/dns/tests/comparekeys/Kexample2.+015+37529.private b/lib/dns/tests/comparekeys/Kexample2.+015+37529.private
new file mode 100644
index 0000000..aa70804
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample2.+015+37529.private
@@ -0,0 +1,6 @@
+Private-key-format: v1.3
+Algorithm: 15 (ED25519)
+PrivateKey: pUR2VLqbi4XtBNImbVDRHrjugMMmaXmy6noV0/jy/rA=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/comparekeys/Kexample3.+002+17187.key b/lib/dns/tests/comparekeys/Kexample3.+002+17187.key
new file mode 100644
index 0000000..0260293
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample3.+002+17187.key
@@ -0,0 +1 @@
+example3. IN KEY 512 3 2 AIDzVP5SeKpxZmlbok6AbdFT5aCVAg3AnQln24CKoc0IXRyTdnhmIEWw VatzRrUY0V6SSwwe3740yk46/TdSvVmZkakw52yn3M661ra5L8kouAK4 rpyM6uulsS0dyjyRzomHJg2zwOukzHBVINFheKsJ25MvdPpp9E64IVji jp/x3wABBQCAFb1Je3cKpt/4gS+KRx9hxFOF5c64ytC9tf0hp2hP4OiP YCu8o5C3qh+PexZAx58m6cSaFlzf3DZ3GGsvamDj5H8YpvP4FeRVva9V jH4VeU4/VtbdNweDUHwAguGJ77MXw+bruHhpbsFVSxHrnNg99WHghaRy YzfFzphJHKBxACo=
diff --git a/lib/dns/tests/comparekeys/Kexample3.+002+17187.private b/lib/dns/tests/comparekeys/Kexample3.+002+17187.private
new file mode 100644
index 0000000..47ef4bc
--- /dev/null
+++ b/lib/dns/tests/comparekeys/Kexample3.+002+17187.private
@@ -0,0 +1,9 @@
+Private-key-format: v1.3
+Algorithm: 2 (DH)
+Prime(p): 81T+UniqcWZpW6JOgG3RU+WglQINwJ0JZ9uAiqHNCF0ck3Z4ZiBFsFWrc0a1GNFekksMHt++NMpOOv03Ur1ZmZGpMOdsp9zOuta2uS/JKLgCuK6cjOrrpbEtHco8kc6JhyYNs8DrpMxwVSDRYXirCduTL3T6afROuCFY4o6f8d8=
+Generator(g): BQ==
+Private_value(x): ccA7JRCvjAE1ASWTtObkvO5k58oKdJ+bzcd/H3cOQsPAhItUc8Pfca2ILWYzDfs+nl+WKLfODQ9cRUabp4SUh0GKPnqJVM1UgDXwme/98NEtVFhs2VawT40wHLkcdPN9jACH11l28u1qsDVb7MRj2UXGC/oszRwQ7s3rN1UlHO8=
+Public_value(y): Fb1Je3cKpt/4gS+KRx9hxFOF5c64ytC9tf0hp2hP4OiPYCu8o5C3qh+PexZAx58m6cSaFlzf3DZ3GGsvamDj5H8YpvP4FeRVva9VjH4VeU4/VtbdNweDUHwAguGJ77MXw+bruHhpbsFVSxHrnNg99WHghaRyYzfFzphJHKBxACo=
+Created: 20211027221447
+Publish: 20211027221447
+Activate: 20211027221447
diff --git a/lib/dns/tests/db_test.c b/lib/dns/tests/db_test.c
new file mode 100644
index 0000000..5b7ba64
--- /dev/null
+++ b/lib/dns/tests/db_test.c
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/journal.h>
+#include <dns/name.h>
+#include <dns/rdatalist.h>
+
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+#define BUFLEN 255
+#define BIGBUFLEN (64 * 1024)
+#define TEST_ORIGIN "test"
+
+/*
+ * Individual unit tests
+ */
+
+/* test multiple calls to dns_db_getoriginnode */
+static void
+getoriginnode_test(void **state) {
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+ isc_mem_t *mctx = NULL;
+ isc_result_t result;
+
+ UNUSED(state);
+
+ isc_mem_create(&mctx);
+
+ result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone,
+ dns_rdataclass_in, 0, NULL, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_getoriginnode(db, &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_db_detachnode(db, &node);
+
+ result = dns_db_getoriginnode(db, &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_db_detachnode(db, &node);
+
+ dns_db_detach(&db);
+ isc_mem_detach(&mctx);
+}
+
+/* test getservestalettl and setservestalettl */
+static void
+getsetservestalettl_test(void **state) {
+ dns_db_t *db = NULL;
+ isc_mem_t *mctx = NULL;
+ isc_result_t result;
+ dns_ttl_t ttl;
+
+ UNUSED(state);
+
+ isc_mem_create(&mctx);
+
+ result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_cache,
+ dns_rdataclass_in, 0, NULL, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ ttl = 5000;
+ result = dns_db_getservestalettl(db, &ttl);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(ttl, 0);
+
+ ttl = 6 * 3600;
+ result = dns_db_setservestalettl(db, ttl);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ ttl = 5000;
+ result = dns_db_getservestalettl(db, &ttl);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(ttl, 6 * 3600);
+
+ dns_db_detach(&db);
+ isc_mem_detach(&mctx);
+}
+
+/* check DNS_DBFIND_STALEOK works */
+static void
+dns_dbfind_staleok_test(void **state) {
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_fixedname_t example_fixed;
+ dns_fixedname_t found_fixed;
+ dns_name_t *example;
+ dns_name_t *found;
+ dns_rdatalist_t rdatalist;
+ dns_rdataset_t rdataset;
+ int count;
+ int pass;
+ isc_mem_t *mctx = NULL;
+ isc_result_t result;
+ unsigned char data[] = { 0x0a, 0x00, 0x00, 0x01 };
+
+ UNUSED(state);
+
+ isc_mem_create(&mctx);
+
+ result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_cache,
+ dns_rdataclass_in, 0, NULL, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ example = dns_fixedname_initname(&example_fixed);
+ found = dns_fixedname_initname(&found_fixed);
+
+ result = dns_name_fromstring(example, "example", 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Pass 0: default; no stale processing permitted.
+ * Pass 1: stale processing for 1 second.
+ * Pass 2: stale turned off after being on.
+ */
+ for (pass = 0; pass < 3; pass++) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ /* 10.0.0.1 */
+ rdata.data = data;
+ rdata.length = 4;
+ rdata.rdclass = dns_rdataclass_in;
+ rdata.type = dns_rdatatype_a;
+
+ dns_rdatalist_init(&rdatalist);
+ rdatalist.ttl = 2;
+ rdatalist.type = dns_rdatatype_a;
+ rdatalist.rdclass = dns_rdataclass_in;
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
+
+ switch (pass) {
+ case 0:
+ /* default: stale processing off */
+ break;
+ case 1:
+ /* turn on stale processing */
+ result = dns_db_setservestalettl(db, 1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ break;
+ case 2:
+ /* turn off stale processing */
+ result = dns_db_setservestalettl(db, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ break;
+ }
+
+ dns_rdataset_init(&rdataset);
+ result = dns_rdatalist_tordataset(&rdatalist, &rdataset);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_findnode(db, example, true, &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_addrdataset(db, node, NULL, 0, &rdataset, 0,
+ NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_db_detachnode(db, &node);
+ dns_rdataset_disassociate(&rdataset);
+
+ result = dns_db_find(db, example, NULL, dns_rdatatype_a, 0, 0,
+ &node, found, &rdataset, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * May loop for up to 2 seconds performing non stale lookups.
+ */
+ count = 0;
+ do {
+ count++;
+ assert_in_range(count, 1, 21); /* loop sanity */
+ assert_int_equal(rdataset.attributes &
+ DNS_RDATASETATTR_STALE,
+ 0);
+ assert_true(rdataset.ttl > 0);
+ dns_db_detachnode(db, &node);
+ dns_rdataset_disassociate(&rdataset);
+
+ usleep(100000); /* 100 ms */
+
+ result = dns_db_find(db, example, NULL, dns_rdatatype_a,
+ 0, 0, &node, found, &rdataset,
+ NULL);
+ } while (result == ISC_R_SUCCESS);
+
+ assert_int_equal(result, ISC_R_NOTFOUND);
+
+ /*
+ * Check whether we can get stale data.
+ */
+ result = dns_db_find(db, example, NULL, dns_rdatatype_a,
+ DNS_DBFIND_STALEOK, 0, &node, found,
+ &rdataset, NULL);
+ switch (pass) {
+ case 0:
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ break;
+ case 1:
+ /*
+ * Should loop for 1 second with stale lookups then
+ * stop.
+ */
+ count = 0;
+ do {
+ count++;
+ assert_in_range(count, 0, 49); /* loop sanity */
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(rdataset.attributes &
+ DNS_RDATASETATTR_STALE,
+ DNS_RDATASETATTR_STALE);
+ dns_db_detachnode(db, &node);
+ dns_rdataset_disassociate(&rdataset);
+
+ usleep(100000); /* 100 ms */
+
+ result = dns_db_find(
+ db, example, NULL, dns_rdatatype_a,
+ DNS_DBFIND_STALEOK, 0, &node, found,
+ &rdataset, NULL);
+ } while (result == ISC_R_SUCCESS);
+ /*
+ * usleep(100000) can be slightly less than 10ms so
+ * allow the count to reach 11.
+ */
+ assert_in_range(count, 1, 11);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ break;
+ case 2:
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ break;
+ }
+ }
+
+ dns_db_detach(&db);
+ isc_mem_detach(&mctx);
+}
+
+/* database class */
+static void
+class_test(void **state) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+
+ UNUSED(state);
+
+ result = dns_db_create(dt_mctx, "rbt", dns_rootname, dns_dbtype_zone,
+ dns_rdataclass_in, 0, NULL, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_load(db, "testdata/db/data.db", dns_masterformat_text,
+ 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_int_equal(dns_db_class(db), dns_rdataclass_in);
+
+ dns_db_detach(&db);
+}
+
+/* database type */
+static void
+dbtype_test(void **state) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+
+ UNUSED(state);
+
+ /* DB has zone semantics */
+ result = dns_db_create(dt_mctx, "rbt", dns_rootname, dns_dbtype_zone,
+ dns_rdataclass_in, 0, NULL, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_db_load(db, "testdata/db/data.db", dns_masterformat_text,
+ 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(dns_db_iszone(db));
+ assert_false(dns_db_iscache(db));
+ dns_db_detach(&db);
+
+ /* DB has cache semantics */
+ result = dns_db_create(dt_mctx, "rbt", dns_rootname, dns_dbtype_cache,
+ dns_rdataclass_in, 0, NULL, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_db_load(db, "testdata/db/data.db", dns_masterformat_text,
+ 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(dns_db_iscache(db));
+ assert_false(dns_db_iszone(db));
+ dns_db_detach(&db);
+}
+
+/* database versions */
+static void
+version_test(void **state) {
+ isc_result_t result;
+ dns_fixedname_t fname, ffound;
+ dns_name_t *name, *foundname;
+ dns_db_t *db = NULL;
+ dns_dbversion_t *ver = NULL, *new = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+
+ UNUSED(state);
+
+ result = dns_test_loaddb(&db, dns_dbtype_zone, "test.test",
+ "testdata/db/data.db");
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Open current version for reading */
+ dns_db_currentversion(db, &ver);
+ dns_test_namefromstring("b.test.test", &fname);
+ name = dns_fixedname_name(&fname);
+ foundname = dns_fixedname_initname(&ffound);
+ dns_rdataset_init(&rdataset);
+ result = dns_db_find(db, name, ver, dns_rdatatype_a, 0, 0, &node,
+ foundname, &rdataset, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(db, &node);
+ dns_db_closeversion(db, &ver, false);
+
+ /* Open new version for writing */
+ dns_db_currentversion(db, &ver);
+ dns_test_namefromstring("b.test.test", &fname);
+ name = dns_fixedname_name(&fname);
+ foundname = dns_fixedname_initname(&ffound);
+ dns_rdataset_init(&rdataset);
+ result = dns_db_find(db, name, ver, dns_rdatatype_a, 0, 0, &node,
+ foundname, &rdataset, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_newversion(db, &new);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Delete the rdataset from the new version */
+ result = dns_db_deleterdataset(db, node, new, dns_rdatatype_a, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(db, &node);
+
+ /* This should fail now */
+ result = dns_db_find(db, name, new, dns_rdatatype_a, 0, 0, &node,
+ foundname, &rdataset, NULL);
+ assert_int_equal(result, DNS_R_NXDOMAIN);
+
+ dns_db_closeversion(db, &new, true);
+
+ /* But this should still succeed */
+ result = dns_db_find(db, name, ver, dns_rdatatype_a, 0, 0, &node,
+ foundname, &rdataset, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(db, &node);
+ dns_db_closeversion(db, &ver, false);
+
+ dns_db_detach(&db);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(getoriginnode_test),
+ cmocka_unit_test(getsetservestalettl_test),
+ cmocka_unit_test(dns_dbfind_staleok_test),
+ cmocka_unit_test_setup_teardown(class_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(dbtype_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(version_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/dbdiff_test.c b/lib/dns/tests/dbdiff_test.c
new file mode 100644
index 0000000..05bb761
--- /dev/null
+++ b/lib/dns/tests/dbdiff_test.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/journal.h>
+#include <dns/name.h>
+
+#include "dnstest.h"
+
+#define BUFLEN 255
+#define BIGBUFLEN (64 * 1024)
+#define TEST_ORIGIN "test"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static void
+test_create(const char *oldfile, dns_db_t **old, const char *newfile,
+ dns_db_t **newdb) {
+ isc_result_t result;
+
+ result = dns_test_loaddb(old, dns_dbtype_zone, TEST_ORIGIN, oldfile);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_test_loaddb(newdb, dns_dbtype_zone, TEST_ORIGIN, newfile);
+ assert_int_equal(result, ISC_R_SUCCESS);
+}
+
+/* dns_db_diffx of identical content */
+static void
+diffx_same(void **state) {
+ dns_db_t *newdb = NULL, *olddb = NULL;
+ isc_result_t result;
+ dns_diff_t diff;
+
+ UNUSED(state);
+
+ test_create("testdata/diff/zone1.data", &olddb,
+ "testdata/diff/zone1.data", &newdb);
+
+ dns_diff_init(dt_mctx, &diff);
+
+ result = dns_db_diffx(&diff, newdb, NULL, olddb, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_true(ISC_LIST_EMPTY(diff.tuples));
+
+ dns_diff_clear(&diff);
+ dns_db_detach(&newdb);
+ dns_db_detach(&olddb);
+}
+
+/* dns_db_diffx of zone with record added */
+static void
+diffx_add(void **state) {
+ dns_db_t *newdb = NULL, *olddb = NULL;
+ dns_difftuple_t *tuple;
+ isc_result_t result;
+ dns_diff_t diff;
+ int count = 0;
+
+ UNUSED(state);
+
+ test_create("testdata/diff/zone1.data", &olddb,
+ "testdata/diff/zone2.data", &newdb);
+
+ dns_diff_init(dt_mctx, &diff);
+
+ result = dns_db_diffx(&diff, newdb, NULL, olddb, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_false(ISC_LIST_EMPTY(diff.tuples));
+ for (tuple = ISC_LIST_HEAD(diff.tuples); tuple != NULL;
+ tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ assert_int_equal(tuple->op, DNS_DIFFOP_ADD);
+ count++;
+ }
+ assert_int_equal(count, 1);
+
+ dns_diff_clear(&diff);
+ dns_db_detach(&newdb);
+ dns_db_detach(&olddb);
+}
+
+/* dns_db_diffx of zone with record removed */
+static void
+diffx_remove(void **state) {
+ dns_db_t *newdb = NULL, *olddb = NULL;
+ dns_difftuple_t *tuple;
+ isc_result_t result;
+ dns_diff_t diff;
+ int count = 0;
+
+ UNUSED(state);
+
+ test_create("testdata/diff/zone1.data", &olddb,
+ "testdata/diff/zone3.data", &newdb);
+
+ dns_diff_init(dt_mctx, &diff);
+
+ result = dns_db_diffx(&diff, newdb, NULL, olddb, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_false(ISC_LIST_EMPTY(diff.tuples));
+ for (tuple = ISC_LIST_HEAD(diff.tuples); tuple != NULL;
+ tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ assert_int_equal(tuple->op, DNS_DIFFOP_DEL);
+ count++;
+ }
+ assert_int_equal(count, 1);
+
+ dns_diff_clear(&diff);
+ dns_db_detach(&newdb);
+ dns_db_detach(&olddb);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(diffx_same, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(diffx_add, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(diffx_remove, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/dbiterator_test.c b/lib/dns/tests/dbiterator_test.c
new file mode 100644
index 0000000..2182672
--- /dev/null
+++ b/lib/dns/tests/dbiterator_test.c
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/name.h>
+
+#include "dnstest.h"
+
+#define BUFLEN 255
+#define BIGBUFLEN (64 * 1024)
+#define TEST_ORIGIN "test"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static isc_result_t
+make_name(const char *src, dns_name_t *name) {
+ isc_buffer_t b;
+ isc_buffer_constinit(&b, src, strlen(src));
+ isc_buffer_add(&b, strlen(src));
+ return (dns_name_fromtext(name, &b, dns_rootname, 0, NULL));
+}
+
+/* create: make sure we can create a dbiterator */
+static void
+test_create(const char *filename) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ dns_dbiterator_t *iter = NULL;
+
+ result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_createiterator(db, 0, &iter);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_dbiterator_destroy(&iter);
+ dns_db_detach(&db);
+}
+
+static void
+create(void **state) {
+ UNUSED(state);
+
+ test_create("testdata/dbiterator/zone1.data");
+}
+
+static void
+create_nsec3(void **state) {
+ UNUSED(state);
+
+ test_create("testdata/dbiterator/zone2.data");
+}
+
+/* walk: walk a database */
+static void
+test_walk(const char *filename, int nodes) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ dns_dbiterator_t *iter = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_name_t *name;
+ dns_fixedname_t f;
+ int i = 0;
+
+ name = dns_fixedname_initname(&f);
+
+ result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_createiterator(db, 0, &iter);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ for (result = dns_dbiterator_first(iter); result == ISC_R_SUCCESS;
+ result = dns_dbiterator_next(iter))
+ {
+ result = dns_dbiterator_current(iter, &node, name);
+ if (result == DNS_R_NEWORIGIN) {
+ result = ISC_R_SUCCESS;
+ }
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_db_detachnode(db, &node);
+ i++;
+ }
+
+ assert_int_equal(i, nodes);
+
+ dns_dbiterator_destroy(&iter);
+ dns_db_detach(&db);
+}
+
+static void
+walk(void **state) {
+ UNUSED(state);
+
+ test_walk("testdata/dbiterator/zone1.data", 12);
+}
+
+static void
+walk_nsec3(void **state) {
+ UNUSED(state);
+
+ test_walk("testdata/dbiterator/zone2.data", 33);
+}
+
+/* reverse: walk database backwards */
+static void
+test_reverse(const char *filename) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ dns_dbiterator_t *iter = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_name_t *name;
+ dns_fixedname_t f;
+ int i = 0;
+
+ name = dns_fixedname_initname(&f);
+
+ result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_createiterator(db, 0, &iter);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ for (result = dns_dbiterator_last(iter); result == ISC_R_SUCCESS;
+ result = dns_dbiterator_prev(iter))
+ {
+ result = dns_dbiterator_current(iter, &node, name);
+ if (result == DNS_R_NEWORIGIN) {
+ result = ISC_R_SUCCESS;
+ }
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_db_detachnode(db, &node);
+ i++;
+ }
+
+ assert_int_equal(i, 12);
+
+ dns_dbiterator_destroy(&iter);
+ dns_db_detach(&db);
+}
+
+static void
+reverse(void **state) {
+ UNUSED(state);
+
+ test_reverse("testdata/dbiterator/zone1.data");
+}
+
+static void
+reverse_nsec3(void **state) {
+ UNUSED(state);
+
+ test_reverse("testdata/dbiterator/zone2.data");
+}
+
+/* seek: walk database starting at a particular node */
+static void
+test_seek_node(const char *filename, int nodes) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ dns_dbiterator_t *iter = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_name_t *name, *seekname;
+ dns_fixedname_t f1, f2;
+ int i = 0;
+
+ name = dns_fixedname_initname(&f1);
+ seekname = dns_fixedname_initname(&f2);
+
+ result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_createiterator(db, 0, &iter);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = make_name("c." TEST_ORIGIN, seekname);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_dbiterator_seek(iter, seekname);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ while (result == ISC_R_SUCCESS) {
+ result = dns_dbiterator_current(iter, &node, name);
+ if (result == DNS_R_NEWORIGIN) {
+ result = ISC_R_SUCCESS;
+ }
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_db_detachnode(db, &node);
+ result = dns_dbiterator_next(iter);
+ i++;
+ }
+
+ assert_int_equal(i, nodes);
+
+ dns_dbiterator_destroy(&iter);
+ dns_db_detach(&db);
+}
+
+static void
+seek_node(void **state) {
+ UNUSED(state);
+
+ test_seek_node("testdata/dbiterator/zone1.data", 9);
+}
+
+static void
+seek_node_nsec3(void **state) {
+ UNUSED(state);
+
+ test_seek_node("testdata/dbiterator/zone2.data", 30);
+}
+
+/*
+ * seek_emty: walk database starting at an empty nonterminal node
+ * (should fail)
+ */
+static void
+test_seek_empty(const char *filename) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ dns_dbiterator_t *iter = NULL;
+ dns_name_t *seekname;
+ dns_fixedname_t f1;
+
+ seekname = dns_fixedname_initname(&f1);
+
+ result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_createiterator(db, 0, &iter);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = make_name("d." TEST_ORIGIN, seekname);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_dbiterator_seek(iter, seekname);
+ assert_int_equal(result, DNS_R_PARTIALMATCH);
+
+ dns_dbiterator_destroy(&iter);
+ dns_db_detach(&db);
+}
+
+static void
+seek_empty(void **state) {
+ UNUSED(state);
+
+ test_seek_empty("testdata/dbiterator/zone1.data");
+}
+
+static void
+seek_empty_nsec3(void **state) {
+ UNUSED(state);
+
+ test_seek_empty("testdata/dbiterator/zone2.data");
+}
+
+/*
+ * seek_nx: walk database starting at a nonexistent node
+ */
+static void
+test_seek_nx(const char *filename) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ dns_dbiterator_t *iter = NULL;
+ dns_name_t *seekname;
+ dns_fixedname_t f1;
+
+ seekname = dns_fixedname_initname(&f1);
+
+ result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_createiterator(db, 0, &iter);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = make_name("nonexistent." TEST_ORIGIN, seekname);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_dbiterator_seek(iter, seekname);
+ assert_int_equal(result, DNS_R_PARTIALMATCH);
+
+ result = make_name("nonexistent.", seekname);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_dbiterator_seek(iter, seekname);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+
+ dns_dbiterator_destroy(&iter);
+ dns_db_detach(&db);
+}
+
+static void
+seek_nx(void **state) {
+ UNUSED(state);
+
+ test_seek_nx("testdata/dbiterator/zone1.data");
+}
+
+static void
+seek_nx_nsec3(void **state) {
+ UNUSED(state);
+
+ test_seek_nx("testdata/dbiterator/zone2.data");
+}
+
+/*
+ * XXX:
+ * dns_dbiterator API calls that are not yet part of this unit test:
+ *
+ * dns_dbiterator_pause
+ * dns_dbiterator_origin
+ * dns_dbiterator_setcleanmode
+ */
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(create, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(create_nsec3, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(walk, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(walk_nsec3, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(reverse, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(reverse_nsec3, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(seek_node, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(seek_node_nsec3, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(seek_empty, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(seek_empty_nsec3, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(seek_nx, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(seek_nx_nsec3, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/dbversion_test.c b/lib/dns/tests/dbversion_test.c
new file mode 100644
index 0000000..70a5124
--- /dev/null
+++ b/lib/dns/tests/dbversion_test.c
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/file.h>
+#include <isc/result.h>
+#include <isc/serial.h>
+#include <isc/stdtime.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/nsec3.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+
+#include "dnstest.h"
+
+static char tempname[11] = "dtXXXXXXXX";
+static dns_db_t *db1 = NULL, *db2 = NULL;
+static dns_dbversion_t *v1 = NULL, *v2 = NULL;
+
+/*
+ * The code below enables us to trap assertion failures for testing
+ * purposes. local_callback() is set as the callback function for
+ * isc_assertion_failed(). It calls mock_assert() so that CMOCKA
+ * will be able to see it, then returns to the calling function via
+ * longjmp() so that the abort() call in isc_assertion_failed() will
+ * never be reached. Use check_assertion() to check for assertions
+ * instead of expect_assert_failure().
+ */
+jmp_buf assertion;
+
+#define check_assertion(function_call) \
+ do { \
+ const int r = setjmp(assertion); \
+ if (r == 0) { \
+ expect_assert_failure(function_call); \
+ } \
+ } while (false);
+
+static void
+local_callback(const char *file, int line, isc_assertiontype_t type,
+ const char *cond) {
+ UNUSED(type);
+
+ mock_assert(1, cond, file, line);
+ longjmp(assertion, 1);
+}
+
+static int
+_setup(void **state) {
+ isc_result_t res;
+
+ UNUSED(state);
+
+ isc_assertion_setcallback(local_callback);
+
+ res = dns_test_begin(NULL, false);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ res = dns_db_create(dt_mctx, "rbt", dns_rootname, dns_dbtype_zone,
+ dns_rdataclass_in, 0, NULL, &db1);
+ assert_int_equal(res, ISC_R_SUCCESS);
+ dns_db_newversion(db1, &v1);
+ assert_non_null(v1);
+
+ res = dns_db_create(dt_mctx, "rbt", dns_rootname, dns_dbtype_zone,
+ dns_rdataclass_in, 0, NULL, &db2);
+ assert_int_equal(res, ISC_R_SUCCESS);
+ dns_db_newversion(db2, &v2);
+ assert_non_null(v1);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ if (strcmp(tempname, "dtXXXXXXXX") != 0) {
+ unlink(tempname);
+ }
+
+ if (v1 != NULL) {
+ dns_db_closeversion(db1, &v1, false);
+ assert_null(v1);
+ }
+ if (db1 != NULL) {
+ dns_db_detach(&db1);
+ assert_null(db1);
+ }
+
+ if (v2 != NULL) {
+ dns_db_closeversion(db2, &v2, false);
+ assert_null(v2);
+ }
+ if (db2 != NULL) {
+ dns_db_detach(&db2);
+ assert_null(db2);
+ }
+
+ dns_test_end();
+
+ return (0);
+}
+
+/*
+ * Check dns_db_attachversion() passes with matching db and version, and
+ * asserts with mis-matching db and version.
+ */
+static void
+attachversion(void **state) {
+ dns_dbversion_t *v = NULL;
+
+ UNUSED(state);
+
+ dns_db_attachversion(db1, v1, &v);
+ assert_ptr_equal(v, v1);
+ dns_db_closeversion(db1, &v, false);
+ assert_null(v);
+
+ check_assertion(dns_db_attachversion(db1, v2, &v));
+}
+
+/*
+ * Check dns_db_closeversion() passes with matching db and version, and
+ * asserts with mis-matching db and version.
+ */
+static void
+closeversion(void **state) {
+ UNUSED(state);
+
+ assert_non_null(v1);
+ dns_db_closeversion(db1, &v1, false);
+ assert_null(v1);
+
+ check_assertion(dns_db_closeversion(db1, &v2, false));
+}
+
+/*
+ * Check dns_db_find() passes with matching db and version, and
+ * asserts with mis-matching db and version.
+ */
+static void
+find(void **state) {
+ isc_result_t res;
+ dns_rdataset_t rdataset;
+ dns_fixedname_t fixed;
+ dns_name_t *name = NULL;
+
+ UNUSED(state);
+
+ name = dns_fixedname_initname(&fixed);
+
+ dns_rdataset_init(&rdataset);
+ res = dns_db_find(db1, dns_rootname, v1, dns_rdatatype_soa, 0, 0, NULL,
+ name, &rdataset, NULL);
+ assert_int_equal(res, DNS_R_NXDOMAIN);
+
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ dns_rdataset_init(&rdataset);
+ check_assertion((void)dns_db_find(db1, dns_rootname, v2,
+ dns_rdatatype_soa, 0, 0, NULL, name,
+ &rdataset, NULL));
+}
+
+/*
+ * Check dns_db_allrdatasets() passes with matching db and version, and
+ * asserts with mis-matching db and version.
+ */
+static void
+allrdatasets(void **state) {
+ isc_result_t res;
+ dns_dbnode_t *node = NULL;
+ dns_rdatasetiter_t *iterator = NULL;
+
+ UNUSED(state);
+
+ res = dns_db_findnode(db1, dns_rootname, false, &node);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ res = dns_db_allrdatasets(db1, node, v1, 0, 0, &iterator);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ check_assertion(dns_db_allrdatasets(db1, node, v2, 0, 0, &iterator));
+
+ dns_rdatasetiter_destroy(&iterator);
+ assert_null(iterator);
+
+ dns_db_detachnode(db1, &node);
+ assert_null(node);
+}
+
+/*
+ * Check dns_db_findrdataset() passes with matching db and version, and
+ * asserts with mis-matching db and version.
+ */
+static void
+findrdataset(void **state) {
+ isc_result_t res;
+ dns_rdataset_t rdataset;
+ dns_dbnode_t *node = NULL;
+
+ UNUSED(state);
+
+ res = dns_db_findnode(db1, dns_rootname, false, &node);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ dns_rdataset_init(&rdataset);
+ res = dns_db_findrdataset(db1, node, v1, dns_rdatatype_soa, 0, 0,
+ &rdataset, NULL);
+ assert_int_equal(res, ISC_R_NOTFOUND);
+
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ dns_rdataset_init(&rdataset);
+ check_assertion(dns_db_findrdataset(db1, node, v2, dns_rdatatype_soa, 0,
+ 0, &rdataset, NULL));
+
+ dns_db_detachnode(db1, &node);
+ assert_null(node);
+}
+
+/*
+ * Check dns_db_deleterdataset() passes with matching db and version, and
+ * asserts with mis-matching db and version.
+ */
+static void
+deleterdataset(void **state) {
+ isc_result_t res;
+ dns_dbnode_t *node = NULL;
+
+ UNUSED(state);
+
+ res = dns_db_findnode(db1, dns_rootname, false, &node);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ res = dns_db_deleterdataset(db1, node, v1, dns_rdatatype_soa, 0);
+ assert_int_equal(res, DNS_R_UNCHANGED);
+
+ check_assertion(
+ dns_db_deleterdataset(db1, node, v2, dns_rdatatype_soa, 0));
+ dns_db_detachnode(db1, &node);
+ assert_null(node);
+}
+
+/*
+ * Check dns_db_subtractrdataset() passes with matching db and version, and
+ * asserts with mis-matching db and version.
+ */
+static void
+subtract(void **state) {
+ isc_result_t res;
+ dns_rdataset_t rdataset;
+ dns_rdatalist_t rdatalist;
+ dns_dbnode_t *node = NULL;
+
+ UNUSED(state);
+
+ dns_rdataset_init(&rdataset);
+ dns_rdatalist_init(&rdatalist);
+
+ rdatalist.rdclass = dns_rdataclass_in;
+
+ res = dns_rdatalist_tordataset(&rdatalist, &rdataset);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ res = dns_db_findnode(db1, dns_rootname, false, &node);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ res = dns_db_subtractrdataset(db1, node, v1, &rdataset, 0, NULL);
+ assert_int_equal(res, DNS_R_UNCHANGED);
+
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+
+ dns_rdataset_init(&rdataset);
+ res = dns_rdatalist_tordataset(&rdatalist, &rdataset);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ check_assertion(
+ dns_db_subtractrdataset(db1, node, v2, &rdataset, 0, NULL));
+
+ dns_db_detachnode(db1, &node);
+ assert_null(node);
+}
+
+/*
+ * Check dns_db_dump() passes with matching db and version, and
+ * asserts with mis-matching db and version.
+ */
+static void
+dump(void **state) {
+ isc_result_t res;
+ FILE *f = NULL;
+
+ UNUSED(state);
+
+ res = isc_file_openunique(tempname, &f);
+ fclose(f);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ res = dns_db_dump(db1, v1, tempname);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ check_assertion(dns_db_dump(db1, v2, tempname));
+}
+
+/*
+ * Check dns_db_addrdataset() passes with matching db and version, and
+ * asserts with mis-matching db and version.
+ */
+static void
+addrdataset(void **state) {
+ isc_result_t res;
+ dns_rdataset_t rdataset;
+ dns_dbnode_t *node = NULL;
+ dns_rdatalist_t rdatalist;
+
+ UNUSED(state);
+
+ dns_rdataset_init(&rdataset);
+ dns_rdatalist_init(&rdatalist);
+
+ rdatalist.rdclass = dns_rdataclass_in;
+
+ res = dns_rdatalist_tordataset(&rdatalist, &rdataset);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ res = dns_db_findnode(db1, dns_rootname, false, &node);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ res = dns_db_addrdataset(db1, node, v1, 0, &rdataset, 0, NULL);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ check_assertion(
+ dns_db_addrdataset(db1, node, v2, 0, &rdataset, 0, NULL));
+
+ dns_db_detachnode(db1, &node);
+ assert_null(node);
+}
+
+/*
+ * Check dns_db_getnsec3parameters() passes with matching db and version,
+ * and asserts with mis-matching db and version.
+ */
+static void
+getnsec3parameters(void **state) {
+ isc_result_t res;
+ dns_hash_t hash;
+ uint8_t flags;
+ uint16_t iterations;
+ unsigned char salt[DNS_NSEC3_SALTSIZE];
+ size_t salt_length = sizeof(salt);
+
+ UNUSED(state);
+
+ res = dns_db_getnsec3parameters(db1, v1, &hash, &flags, &iterations,
+ salt, &salt_length);
+ assert_int_equal(res, ISC_R_NOTFOUND);
+
+ check_assertion(dns_db_getnsec3parameters(
+ db1, v2, &hash, &flags, &iterations, salt, &salt_length));
+}
+
+/*
+ * Check dns_db_resigned() passes with matching db and version, and
+ * asserts with mis-matching db and version.
+ */
+static void
+resigned(void **state) {
+ isc_result_t res;
+ dns_rdataset_t rdataset, added;
+ dns_dbnode_t *node = NULL;
+ dns_rdatalist_t rdatalist;
+ dns_rdata_rrsig_t rrsig;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_buffer_t b;
+ unsigned char buf[1024];
+
+ UNUSED(state);
+
+ /*
+ * Create a dummy RRSIG record and set a resigning time.
+ */
+ dns_rdataset_init(&added);
+ dns_rdataset_init(&rdataset);
+ dns_rdatalist_init(&rdatalist);
+ isc_buffer_init(&b, buf, sizeof(buf));
+
+ DNS_RDATACOMMON_INIT(&rrsig, dns_rdatatype_rrsig, dns_rdataclass_in);
+ rrsig.covered = dns_rdatatype_a;
+ rrsig.algorithm = 100;
+ rrsig.labels = 0;
+ rrsig.originalttl = 0;
+ rrsig.timeexpire = 3600;
+ rrsig.timesigned = 0;
+ rrsig.keyid = 0;
+ dns_name_init(&rrsig.signer, NULL);
+ dns_name_clone(dns_rootname, &rrsig.signer);
+ rrsig.siglen = 0;
+ rrsig.signature = NULL;
+
+ res = dns_rdata_fromstruct(&rdata, dns_rdataclass_in,
+ dns_rdatatype_rrsig, &rrsig, &b);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ rdatalist.rdclass = dns_rdataclass_in;
+ rdatalist.type = dns_rdatatype_rrsig;
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
+
+ res = dns_rdatalist_tordataset(&rdatalist, &rdataset);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ rdataset.attributes |= DNS_RDATASETATTR_RESIGN;
+ rdataset.resign = 7200;
+
+ res = dns_db_findnode(db1, dns_rootname, false, &node);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ res = dns_db_addrdataset(db1, node, v1, 0, &rdataset, 0, &added);
+ assert_int_equal(res, ISC_R_SUCCESS);
+
+ dns_db_detachnode(db1, &node);
+ assert_null(node);
+
+ check_assertion(dns_db_resigned(db1, &added, v2));
+
+ dns_db_resigned(db1, &added, v1);
+
+ dns_rdataset_disassociate(&added);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(dump, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(find, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(allrdatasets, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(findrdataset, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(deleterdataset, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(subtract, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(addrdataset, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(getnsec3parameters, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(resigned, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(attachversion, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(closeversion, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/dh_test.c b/lib/dns/tests/dh_test.c
new file mode 100644
index 0000000..bd60d6d
--- /dev/null
+++ b/lib/dns/tests/dh_test.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <pk11/site.h>
+
+#include <dns/name.h>
+
+#include <dst/result.h>
+
+#include "../dst_internal.h"
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+/* OpenSSL DH_compute_key() failure */
+static void
+dh_computesecret(void **state) {
+ dst_key_t *key = NULL;
+ isc_buffer_t buf;
+ unsigned char array[1024];
+ isc_result_t result;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ UNUSED(state);
+
+ name = dns_fixedname_initname(&fname);
+ isc_buffer_constinit(&buf, "dh.", 3);
+ isc_buffer_add(&buf, 3);
+ result = dns_name_fromtext(name, &buf, NULL, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dst_key_fromfile(name, 18602, DST_ALG_DH,
+ DST_TYPE_PUBLIC | DST_TYPE_KEY, "./", dt_mctx,
+ &key);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_init(&buf, array, sizeof(array));
+ result = dst_key_computesecret(key, key, &buf);
+ assert_int_equal(result, DST_R_NOTPRIVATEKEY);
+ result = key->func->computesecret(key, key, &buf);
+ assert_int_equal(result, DST_R_COMPUTESECRETFAILURE);
+
+ dst_key_free(&key);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(dh_computesecret, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/dispatch_test.c b/lib/dns/tests/dispatch_test.c
new file mode 100644
index 0000000..9f9737b
--- /dev/null
+++ b/lib/dns/tests/dispatch_test.c
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/app.h>
+#include <isc/buffer.h>
+#include <isc/refcount.h>
+#include <isc/socket.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/dispatch.h>
+#include <dns/name.h>
+#include <dns/view.h>
+
+#include "dnstest.h"
+
+dns_dispatchmgr_t *dispatchmgr = NULL;
+dns_dispatchset_t *dset = NULL;
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static isc_result_t
+make_dispatchset(unsigned int ndisps) {
+ isc_result_t result;
+ isc_sockaddr_t any;
+ unsigned int attrs;
+ dns_dispatch_t *disp = NULL;
+
+ result = dns_dispatchmgr_create(dt_mctx, &dispatchmgr);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ isc_sockaddr_any(&any);
+ attrs = DNS_DISPATCHATTR_IPV4 | DNS_DISPATCHATTR_UDP;
+ result = dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, &any, 512,
+ 6, 1024, 17, 19, attrs, attrs, &disp);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_dispatchset_create(dt_mctx, socketmgr, taskmgr, disp,
+ &dset, ndisps);
+ dns_dispatch_detach(&disp);
+
+ return (result);
+}
+
+static void
+reset(void) {
+ if (dset != NULL) {
+ dns_dispatchset_destroy(&dset);
+ }
+ if (dispatchmgr != NULL) {
+ dns_dispatchmgr_destroy(&dispatchmgr);
+ }
+}
+
+/* create dispatch set */
+static void
+dispatchset_create(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = make_dispatchset(1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ reset();
+
+ result = make_dispatchset(10);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ reset();
+}
+
+/* test dispatch set round-robin */
+static void
+dispatchset_get(void **state) {
+ isc_result_t result;
+ dns_dispatch_t *d1, *d2, *d3, *d4, *d5;
+
+ UNUSED(state);
+
+ result = make_dispatchset(1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ d1 = dns_dispatchset_get(dset);
+ d2 = dns_dispatchset_get(dset);
+ d3 = dns_dispatchset_get(dset);
+ d4 = dns_dispatchset_get(dset);
+ d5 = dns_dispatchset_get(dset);
+
+ assert_ptr_equal(d1, d2);
+ assert_ptr_equal(d2, d3);
+ assert_ptr_equal(d3, d4);
+ assert_ptr_equal(d4, d5);
+
+ reset();
+
+ result = make_dispatchset(4);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ d1 = dns_dispatchset_get(dset);
+ d2 = dns_dispatchset_get(dset);
+ d3 = dns_dispatchset_get(dset);
+ d4 = dns_dispatchset_get(dset);
+ d5 = dns_dispatchset_get(dset);
+
+ assert_ptr_equal(d1, d5);
+ assert_ptr_not_equal(d1, d2);
+ assert_ptr_not_equal(d2, d3);
+ assert_ptr_not_equal(d3, d4);
+ assert_ptr_not_equal(d4, d5);
+
+ reset();
+}
+
+static void
+senddone(isc_task_t *task, isc_event_t *event) {
+ isc_socket_t *sock = event->ev_arg;
+
+ UNUSED(task);
+
+ isc_socket_detach(&sock);
+ isc_event_free(&event);
+}
+
+static void
+nameserver(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ isc_region_t region;
+ isc_socket_t *dummy;
+ isc_socket_t *sock = event->ev_arg;
+ isc_socketevent_t *ev = (isc_socketevent_t *)event;
+ static unsigned char buf1[16];
+ static unsigned char buf2[16];
+
+ memmove(buf1, ev->region.base, 12);
+ memset(buf1 + 12, 0, 4);
+ buf1[2] |= 0x80; /* qr=1 */
+
+ memmove(buf2, ev->region.base, 12);
+ memset(buf2 + 12, 1, 4);
+ buf2[2] |= 0x80; /* qr=1 */
+
+ /*
+ * send message to be discarded.
+ */
+ region.base = buf1;
+ region.length = sizeof(buf1);
+ dummy = NULL;
+ isc_socket_attach(sock, &dummy);
+ result = isc_socket_sendto(sock, &region, task, senddone, sock,
+ &ev->address, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_socket_detach(&dummy);
+ }
+
+ /*
+ * send nextitem message.
+ */
+ region.base = buf2;
+ region.length = sizeof(buf2);
+ dummy = NULL;
+ isc_socket_attach(sock, &dummy);
+ result = isc_socket_sendto(sock, &region, task, senddone, sock,
+ &ev->address, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_socket_detach(&dummy);
+ }
+ isc_event_free(&event);
+}
+
+static dns_dispatch_t *dispatch = NULL;
+static dns_dispentry_t *dispentry = NULL;
+static atomic_bool first = true;
+static isc_sockaddr_t local;
+static atomic_uint_fast32_t responses;
+
+static void
+response(isc_task_t *task, isc_event_t *event) {
+ dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event;
+ bool exp_true = true;
+
+ UNUSED(task);
+
+ atomic_fetch_add_relaxed(&responses, 1);
+ if (atomic_compare_exchange_strong(&first, &exp_true, false)) {
+ isc_result_t result = dns_dispatch_getnext(dispentry, &devent);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ } else {
+ dns_dispatch_removeresponse(&dispentry, &devent);
+ isc_app_shutdown();
+ }
+}
+
+static void
+startit(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ isc_socket_t *sock = NULL;
+
+ isc_socket_attach(dns_dispatch_getsocket(dispatch), &sock);
+ result = isc_socket_sendto(sock, event->ev_arg, task, senddone, sock,
+ &local, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_event_free(&event);
+}
+
+/* test dispatch getnext */
+static void
+dispatch_getnext(void **state) {
+ isc_region_t region;
+ isc_result_t result;
+ isc_socket_t *sock = NULL;
+ isc_task_t *task = NULL;
+ uint16_t id;
+ struct in_addr ina;
+ unsigned char message[12];
+ unsigned int attrs;
+ unsigned char rbuf[12];
+
+ UNUSED(state);
+
+ atomic_init(&responses, 0);
+
+ result = isc_task_create(taskmgr, 0, &task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_dispatchmgr_create(dt_mctx, &dispatchmgr);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ ina.s_addr = htonl(INADDR_LOOPBACK);
+ isc_sockaddr_fromin(&local, &ina, 0);
+ attrs = DNS_DISPATCHATTR_IPV4 | DNS_DISPATCHATTR_UDP;
+ result = dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, &local,
+ 512, 6, 1024, 17, 19, attrs, attrs,
+ &dispatch);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Create a local udp nameserver on the loopback.
+ */
+ result = isc_socket_create(socketmgr, AF_INET, isc_sockettype_udp,
+ &sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ ina.s_addr = htonl(INADDR_LOOPBACK);
+ isc_sockaddr_fromin(&local, &ina, 0);
+ result = isc_socket_bind(sock, &local, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_socket_getsockname(sock, &local);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ region.base = rbuf;
+ region.length = sizeof(rbuf);
+ result = isc_socket_recv(sock, &region, 1, task, nameserver, sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_dispatch_addresponse(dispatch, 0, &local, task, response,
+ NULL, &id, &dispentry, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(message, 0, sizeof(message));
+ message[0] = (id >> 8) & 0xff;
+ message[1] = id & 0xff;
+
+ region.base = message;
+ region.length = sizeof(message);
+ result = isc_app_onrun(dt_mctx, task, startit, &region);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_app_run();
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_int_equal(atomic_load_acquire(&responses), 2);
+
+ /*
+ * Shutdown nameserver.
+ */
+ isc_socket_cancel(sock, task, ISC_SOCKCANCEL_RECV);
+ isc_socket_detach(&sock);
+ isc_task_detach(&task);
+
+ /*
+ * Shutdown the dispatch.
+ */
+ dns_dispatch_detach(&dispatch);
+ dns_dispatchmgr_destroy(&dispatchmgr);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(dispatchset_create, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(dispatchset_get, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(dispatch_getnext, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/dnstap_test.c b/lib/dns/tests/dnstap_test.c
new file mode 100644
index 0000000..17addc6
--- /dev/null
+++ b/lib/dns/tests/dnstap_test.c
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/file.h>
+#include <isc/print.h>
+#include <isc/stdio.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/dnstap.h>
+#include <dns/view.h>
+
+#include "dnstest.h"
+
+#ifdef HAVE_DNSTAP
+
+#include <fstrm.h>
+
+#include <protobuf-c/protobuf-c.h>
+
+#define TAPFILE "testdata/dnstap/dnstap.file"
+#define TAPSOCK "testdata/dnstap/dnstap.sock"
+
+#define TAPSAVED "testdata/dnstap/dnstap.saved"
+#define TAPTEXT "testdata/dnstap/dnstap.text"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static void
+cleanup() {
+ (void)isc_file_remove(TAPFILE);
+ (void)isc_file_remove(TAPSOCK);
+}
+
+/* set up dnstap environment */
+static void
+create_test(void **state) {
+ isc_result_t result;
+ dns_dtenv_t *dtenv = NULL;
+ struct fstrm_iothr_options *fopt;
+
+ UNUSED(state);
+
+ cleanup();
+
+ fopt = fstrm_iothr_options_init();
+ assert_non_null(fopt);
+ fstrm_iothr_options_set_num_input_queues(fopt, 1);
+
+ result = dns_dt_create(dt_mctx, dns_dtmode_file, TAPFILE, &fopt, NULL,
+ &dtenv);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ if (dtenv != NULL) {
+ dns_dt_detach(&dtenv);
+ }
+ if (fopt != NULL) {
+ fstrm_iothr_options_destroy(&fopt);
+ }
+
+ assert_true(isc_file_exists(TAPFILE));
+
+ fopt = fstrm_iothr_options_init();
+ assert_non_null(fopt);
+ fstrm_iothr_options_set_num_input_queues(fopt, 1);
+
+ result = dns_dt_create(dt_mctx, dns_dtmode_unix, TAPSOCK, &fopt, NULL,
+ &dtenv);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ if (dtenv != NULL) {
+ dns_dt_detach(&dtenv);
+ }
+ if (fopt != NULL) {
+ fstrm_iothr_options_destroy(&fopt);
+ }
+
+ /* 'create' should succeed, but the file shouldn't exist yet */
+ assert_false(isc_file_exists(TAPSOCK));
+
+ fopt = fstrm_iothr_options_init();
+ assert_non_null(fopt);
+ fstrm_iothr_options_set_num_input_queues(fopt, 1);
+
+ result = dns_dt_create(dt_mctx, 33, TAPSOCK, &fopt, NULL, &dtenv);
+ assert_int_equal(result, ISC_R_FAILURE);
+ assert_null(dtenv);
+ if (dtenv != NULL) {
+ dns_dt_detach(&dtenv);
+ }
+ if (fopt != NULL) {
+ fstrm_iothr_options_destroy(&fopt);
+ }
+
+ cleanup();
+}
+
+/* send dnstap messages */
+static void
+send_test(void **state) {
+ isc_result_t result;
+ dns_dtenv_t *dtenv = NULL;
+ dns_dthandle_t *handle = NULL;
+ uint8_t *data;
+ size_t dsize;
+ unsigned char zone[DNS_NAME_MAXWIRE];
+ unsigned char qambuffer[4096], rambuffer[4096];
+ unsigned char qrmbuffer[4096], rrmbuffer[4096];
+ isc_buffer_t zb, qamsg, ramsg, qrmsg, rrmsg;
+ size_t qasize, qrsize, rasize, rrsize;
+ dns_fixedname_t zfname;
+ dns_name_t *zname;
+ dns_dtmsgtype_t dt;
+ dns_view_t *view = NULL;
+ dns_compress_t cctx;
+ isc_region_t zr;
+ isc_sockaddr_t qaddr;
+ isc_sockaddr_t raddr;
+ struct in_addr in;
+ isc_stdtime_t now;
+ isc_time_t p, f;
+ struct fstrm_iothr_options *fopt;
+
+ UNUSED(state);
+
+ cleanup();
+
+ result = dns_test_makeview("test", &view);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ fopt = fstrm_iothr_options_init();
+ assert_non_null(fopt);
+ fstrm_iothr_options_set_num_input_queues(fopt, 1);
+
+ result = dns_dt_create(dt_mctx, dns_dtmode_file, TAPFILE, &fopt, NULL,
+ &dtenv);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_dt_attach(dtenv, &view->dtenv);
+ view->dttypes = DNS_DTTYPE_ALL;
+
+ /*
+ * Set up some test data
+ */
+ zname = dns_fixedname_initname(&zfname);
+ isc_buffer_constinit(&zb, "example.com.", 12);
+ isc_buffer_add(&zb, 12);
+ result = dns_name_fromtext(zname, &zb, NULL, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(&zr, 0, sizeof(zr));
+ isc_buffer_init(&zb, zone, sizeof(zone));
+ result = dns_compress_init(&cctx, -1, dt_mctx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE);
+ result = dns_name_towire(zname, &cctx, &zb);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_compress_invalidate(&cctx);
+ isc_buffer_usedregion(&zb, &zr);
+
+ in.s_addr = inet_addr("10.53.0.1");
+ isc_sockaddr_fromin(&qaddr, &in, 2112);
+ in.s_addr = inet_addr("10.53.0.2");
+ isc_sockaddr_fromin(&raddr, &in, 2112);
+
+ isc_stdtime_get(&now);
+ isc_time_set(&p, now - 3600, 0); /* past */
+ isc_time_set(&f, now + 3600, 0); /* future */
+
+ result = dns_test_getdata("testdata/dnstap/query.auth", qambuffer,
+ sizeof(qambuffer), &qasize);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_buffer_init(&qamsg, qambuffer, qasize);
+ isc_buffer_add(&qamsg, qasize);
+
+ result = dns_test_getdata("testdata/dnstap/response.auth", rambuffer,
+ sizeof(rambuffer), &rasize);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_buffer_init(&ramsg, rambuffer, rasize);
+ isc_buffer_add(&ramsg, rasize);
+
+ result = dns_test_getdata("testdata/dnstap/query.recursive", qrmbuffer,
+ sizeof(qrmbuffer), &qrsize);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_buffer_init(&qrmsg, qrmbuffer, qrsize);
+ isc_buffer_add(&qrmsg, qrsize);
+
+ result = dns_test_getdata("testdata/dnstap/response.recursive",
+ rrmbuffer, sizeof(rrmbuffer), &rrsize);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_buffer_init(&rrmsg, rrmbuffer, rrsize);
+ isc_buffer_add(&rrmsg, rrsize);
+
+ for (dt = DNS_DTTYPE_SQ; dt <= DNS_DTTYPE_TR; dt <<= 1) {
+ isc_buffer_t *m;
+ isc_sockaddr_t *q = &qaddr, *r = &raddr;
+
+ switch (dt) {
+ case DNS_DTTYPE_AQ:
+ m = &qamsg;
+ break;
+ case DNS_DTTYPE_AR:
+ m = &ramsg;
+ break;
+ default:
+ m = &qrmsg;
+ if ((dt & DNS_DTTYPE_RESPONSE) != 0) {
+ m = &ramsg;
+ }
+ break;
+ }
+
+ dns_dt_send(view, dt, q, r, false, &zr, &p, &f, m);
+ dns_dt_send(view, dt, q, r, false, &zr, NULL, &f, m);
+ dns_dt_send(view, dt, q, r, false, &zr, &p, NULL, m);
+ dns_dt_send(view, dt, q, r, false, &zr, NULL, NULL, m);
+ dns_dt_send(view, dt, q, r, true, &zr, &p, &f, m);
+ dns_dt_send(view, dt, q, r, true, &zr, NULL, &f, m);
+ dns_dt_send(view, dt, q, r, true, &zr, &p, NULL, m);
+ dns_dt_send(view, dt, q, r, true, &zr, NULL, NULL, m);
+ }
+
+ dns_dt_detach(&view->dtenv);
+ dns_dt_detach(&dtenv);
+ dns_view_detach(&view);
+
+ result = dns_dt_open(TAPFILE, dns_dtmode_file, dt_mctx, &handle);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ while (dns_dt_getframe(handle, &data, &dsize) == ISC_R_SUCCESS) {
+ dns_dtdata_t *dtdata = NULL;
+ isc_region_t r;
+ static dns_dtmsgtype_t expected = DNS_DTTYPE_SQ;
+ static int n = 0;
+
+ r.base = data;
+ r.length = dsize;
+
+ result = dns_dt_parse(dt_mctx, &r, &dtdata);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ if (result != ISC_R_SUCCESS) {
+ n++;
+ continue;
+ }
+
+ assert_int_equal(dtdata->type, expected);
+ if (++n % 8 == 0) {
+ expected <<= 1;
+ }
+
+ dns_dtdata_free(&dtdata);
+ }
+
+ if (fopt != NULL) {
+ fstrm_iothr_options_destroy(&fopt);
+ }
+ if (handle != NULL) {
+ dns_dt_close(&handle);
+ }
+ cleanup();
+}
+
+/* dnstap message to text */
+static void
+totext_test(void **state) {
+ isc_result_t result;
+ dns_dthandle_t *handle = NULL;
+ uint8_t *data;
+ size_t dsize;
+ FILE *fp = NULL;
+
+ UNUSED(state);
+
+ result = dns_dt_open(TAPSAVED, dns_dtmode_file, dt_mctx, &handle);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_stdio_open(TAPTEXT, "r", &fp);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ while (dns_dt_getframe(handle, &data, &dsize) == ISC_R_SUCCESS) {
+ dns_dtdata_t *dtdata = NULL;
+ isc_buffer_t *b = NULL;
+ isc_region_t r;
+ char s[BUFSIZ], *p;
+
+ r.base = data;
+ r.length = dsize;
+
+ /* read the corresponding line of text */
+ p = fgets(s, sizeof(s), fp);
+ assert_ptr_equal(p, s);
+ if (p == NULL) {
+ break;
+ }
+
+ p = strchr(p, '\n');
+ if (p != NULL) {
+ *p = '\0';
+ }
+
+ /* parse dnstap frame */
+ result = dns_dt_parse(dt_mctx, &r, &dtdata);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ isc_buffer_allocate(dt_mctx, &b, 2048);
+ assert_non_null(b);
+ if (b == NULL) {
+ break;
+ }
+
+ /* convert to text and compare */
+ result = dns_dt_datatotext(dtdata, &b);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_string_equal((char *)isc_buffer_base(b), s);
+
+ dns_dtdata_free(&dtdata);
+ isc_buffer_free(&b);
+ }
+
+ if (handle != NULL) {
+ dns_dt_close(&handle);
+ }
+ cleanup();
+}
+#endif /* HAVE_DNSTAP */
+
+int
+main(void) {
+#if HAVE_DNSTAP
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(create_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(send_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(totext_test, _setup, _teardown),
+ };
+
+ /* make sure text conversion gets the right local time */
+ setenv("TZ", "PST8", 1);
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+#else /* if HAVE_DNSTAP */
+ print_message("1..0 # Skipped: dnstap not enabled\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+#endif /* HAVE_DNSTAP */
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* HAVE_CMOCKA */
diff --git a/lib/dns/tests/dnstest.c b/lib/dns/tests/dnstest.c
new file mode 100644
index 0000000..a8cd6b5
--- /dev/null
+++ b/lib/dns/tests/dnstest.c
@@ -0,0 +1,643 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#if HAVE_CMOCKA
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/app.h>
+#include <isc/buffer.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/hex.h>
+#include <isc/lex.h>
+#include <isc/managers.h>
+#include <isc/mem.h>
+#include <isc/os.h>
+#include <isc/print.h>
+#include <isc/socket.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/db.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/result.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+
+#include "dnstest.h"
+
+#define CHECK(r) \
+ do { \
+ result = (r); \
+ if (result != ISC_R_SUCCESS) { \
+ goto cleanup; \
+ } \
+ } while (0)
+
+isc_mem_t *dt_mctx = NULL;
+isc_log_t *lctx = NULL;
+isc_nm_t *netmgr = NULL;
+isc_taskmgr_t *taskmgr = NULL;
+isc_task_t *maintask = NULL;
+isc_timermgr_t *timermgr = NULL;
+isc_socketmgr_t *socketmgr = NULL;
+dns_zonemgr_t *zonemgr = NULL;
+bool app_running = false;
+int ncpus;
+bool debug_mem_record = true;
+
+static bool dst_active = false;
+static bool test_running = false;
+
+/*
+ * Logging categories: this needs to match the list in bin/named/log.c.
+ */
+static isc_logcategory_t categories[] = { { "", 0 },
+ { "client", 0 },
+ { "network", 0 },
+ { "update", 0 },
+ { "queries", 0 },
+ { "unmatched", 0 },
+ { "update-security", 0 },
+ { "query-errors", 0 },
+ { NULL, 0 } };
+
+static void
+cleanup_managers(void) {
+ if (maintask != NULL) {
+ isc_task_shutdown(maintask);
+ isc_task_destroy(&maintask);
+ }
+
+ isc_managers_destroy(netmgr == NULL ? NULL : &netmgr,
+ taskmgr == NULL ? NULL : &taskmgr);
+
+ if (socketmgr != NULL) {
+ isc_socketmgr_destroy(&socketmgr);
+ }
+ if (timermgr != NULL) {
+ isc_timermgr_destroy(&timermgr);
+ }
+ if (app_running) {
+ isc_app_finish();
+ }
+}
+
+static isc_result_t
+create_managers(void) {
+ isc_result_t result;
+ ncpus = isc_os_ncpus();
+
+ CHECK(isc_managers_create(dt_mctx, ncpus, 0, &netmgr, &taskmgr));
+ CHECK(isc_timermgr_create(dt_mctx, &timermgr));
+ CHECK(isc_socketmgr_create(dt_mctx, &socketmgr));
+ CHECK(isc_task_create_bound(taskmgr, 0, &maintask, 0));
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cleanup_managers();
+ return (result);
+}
+
+isc_result_t
+dns_test_begin(FILE *logfile, bool start_managers) {
+ isc_result_t result;
+
+ INSIST(!test_running);
+ test_running = true;
+
+ if (start_managers) {
+ CHECK(isc_app_start());
+ }
+ if (debug_mem_record) {
+ isc_mem_debugging |= ISC_MEM_DEBUGRECORD;
+ }
+
+ INSIST(dt_mctx == NULL);
+ isc_mem_create(&dt_mctx);
+
+ /* Don't check the memory leaks as they hide the assertions */
+ isc_mem_setdestroycheck(dt_mctx, false);
+
+ INSIST(!dst_active);
+ CHECK(dst_lib_init(dt_mctx, NULL));
+ dst_active = true;
+
+ if (logfile != NULL) {
+ isc_logdestination_t destination;
+ isc_logconfig_t *logconfig = NULL;
+
+ INSIST(lctx == NULL);
+ isc_log_create(dt_mctx, &lctx, &logconfig);
+ isc_log_registercategories(lctx, categories);
+ isc_log_setcontext(lctx);
+ dns_log_init(lctx);
+ dns_log_setcontext(lctx);
+
+ destination.file.stream = logfile;
+ destination.file.name = NULL;
+ destination.file.versions = ISC_LOG_ROLLNEVER;
+ destination.file.maximum_size = 0;
+ isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC,
+ ISC_LOG_DYNAMIC, &destination, 0);
+ CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL));
+ }
+
+ dns_result_register();
+
+ if (start_managers) {
+ CHECK(create_managers());
+ }
+
+ /*
+ * The caller might run from another directory, so tests
+ * that access test data files must first chdir to the proper
+ * location.
+ */
+ if (chdir(TESTS) == -1) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dns_test_end();
+ return (result);
+}
+
+void
+dns_test_end(void) {
+ cleanup_managers();
+
+ dst_lib_destroy();
+ dst_active = false;
+
+ if (lctx != NULL) {
+ isc_log_destroy(&lctx);
+ }
+
+ if (dt_mctx != NULL) {
+ isc_mem_destroy(&dt_mctx);
+ }
+
+ test_running = false;
+}
+
+/*
+ * Create a view.
+ */
+isc_result_t
+dns_test_makeview(const char *name, dns_view_t **viewp) {
+ isc_result_t result;
+ dns_view_t *view = NULL;
+
+ CHECK(dns_view_create(dt_mctx, dns_rdataclass_in, name, &view));
+ *viewp = view;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (view != NULL) {
+ dns_view_detach(&view);
+ }
+ return (result);
+}
+
+isc_result_t
+dns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view,
+ bool createview) {
+ dns_fixedname_t fixed_origin;
+ dns_zone_t *zone = NULL;
+ isc_result_t result;
+ dns_name_t *origin;
+
+ REQUIRE(view == NULL || !createview);
+
+ /*
+ * Create the zone structure.
+ */
+ result = dns_zone_create(&zone, dt_mctx);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Set zone type and origin.
+ */
+ dns_zone_settype(zone, dns_zone_primary);
+ origin = dns_fixedname_initname(&fixed_origin);
+ result = dns_name_fromstring(origin, name, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto detach_zone;
+ }
+ result = dns_zone_setorigin(zone, origin);
+ if (result != ISC_R_SUCCESS) {
+ goto detach_zone;
+ }
+
+ /*
+ * If requested, create a view.
+ */
+ if (createview) {
+ result = dns_test_makeview("view", &view);
+ if (result != ISC_R_SUCCESS) {
+ goto detach_zone;
+ }
+ }
+
+ /*
+ * If a view was passed as an argument or created above, attach the
+ * created zone to it. Otherwise, set the zone's class to IN.
+ */
+ if (view != NULL) {
+ dns_zone_setview(zone, view);
+ dns_zone_setclass(zone, view->rdclass);
+ dns_view_addzone(view, zone);
+ } else {
+ dns_zone_setclass(zone, dns_rdataclass_in);
+ }
+
+ *zonep = zone;
+
+ return (ISC_R_SUCCESS);
+
+detach_zone:
+ dns_zone_detach(&zone);
+
+ return (result);
+}
+
+isc_result_t
+dns_test_setupzonemgr(void) {
+ isc_result_t result;
+ REQUIRE(zonemgr == NULL);
+
+ result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr,
+ &zonemgr);
+ return (result);
+}
+
+isc_result_t
+dns_test_managezone(dns_zone_t *zone) {
+ isc_result_t result;
+ REQUIRE(zonemgr != NULL);
+
+ result = dns_zonemgr_setsize(zonemgr, 1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_zonemgr_managezone(zonemgr, zone);
+ return (result);
+}
+
+void
+dns_test_releasezone(dns_zone_t *zone) {
+ REQUIRE(zonemgr != NULL);
+ dns_zonemgr_releasezone(zonemgr, zone);
+}
+
+void
+dns_test_closezonemgr(void) {
+ REQUIRE(zonemgr != NULL);
+
+ dns_zonemgr_shutdown(zonemgr);
+ dns_zonemgr_detach(&zonemgr);
+}
+
+/*
+ * Sleep for 'usec' microseconds.
+ */
+void
+dns_test_nap(uint32_t usec) {
+ struct timespec ts;
+
+ ts.tv_sec = usec / 1000000;
+ ts.tv_nsec = (usec % 1000000) * 1000;
+ nanosleep(&ts, NULL);
+}
+
+isc_result_t
+dns_test_loaddb(dns_db_t **db, dns_dbtype_t dbtype, const char *origin,
+ const char *testfile) {
+ isc_result_t result;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+
+ name = dns_fixedname_initname(&fixed);
+
+ result = dns_name_fromstring(name, origin, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_create(dt_mctx, "rbt", name, dbtype, dns_rdataclass_in,
+ 0, NULL, db);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_load(*db, testfile, dns_masterformat_text, 0);
+ return (result);
+}
+
+static int
+fromhex(char c) {
+ if (c >= '0' && c <= '9') {
+ return (c - '0');
+ } else if (c >= 'a' && c <= 'f') {
+ return (c - 'a' + 10);
+ } else if (c >= 'A' && c <= 'F') {
+ return (c - 'A' + 10);
+ }
+
+ printf("bad input format: %02x\n", c);
+ exit(3);
+}
+
+/*
+ * Format contents of given memory region as a hex string, using the buffer
+ * of length 'buflen' pointed to by 'buf'. 'buflen' must be at least three
+ * times 'len'. Always returns 'buf'.
+ */
+char *
+dns_test_tohex(const unsigned char *data, size_t len, char *buf,
+ size_t buflen) {
+ isc_constregion_t source = { .base = data, .length = len };
+ isc_buffer_t target;
+ isc_result_t result;
+
+ memset(buf, 0, buflen);
+ isc_buffer_init(&target, buf, buflen);
+ result = isc_hex_totext((isc_region_t *)&source, 1, " ", &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (buf);
+}
+
+isc_result_t
+dns_test_getdata(const char *file, unsigned char *buf, size_t bufsiz,
+ size_t *sizep) {
+ isc_result_t result;
+ unsigned char *bp;
+ char *rp, *wp;
+ char s[BUFSIZ];
+ size_t len, i;
+ FILE *f = NULL;
+ int n;
+
+ result = isc_stdio_open(file, "r", &f);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ bp = buf;
+ while (fgets(s, sizeof(s), f) != NULL) {
+ rp = s;
+ wp = s;
+ len = 0;
+ while (*rp != '\0') {
+ if (*rp == '#') {
+ break;
+ }
+ if (*rp != ' ' && *rp != '\t' && *rp != '\r' &&
+ *rp != '\n')
+ {
+ *wp++ = *rp;
+ len++;
+ }
+ rp++;
+ }
+ if (len == 0U) {
+ continue;
+ }
+ if (len % 2 != 0U) {
+ CHECK(ISC_R_UNEXPECTEDEND);
+ }
+ if (len > bufsiz * 2) {
+ CHECK(ISC_R_NOSPACE);
+ }
+ rp = s;
+ for (i = 0; i < len; i += 2) {
+ n = fromhex(*rp++);
+ n *= 16;
+ n += fromhex(*rp++);
+ *bp++ = n;
+ }
+ }
+
+ *sizep = bp - buf;
+
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ isc_stdio_close(f);
+ return (result);
+}
+
+static void
+nullmsg(dns_rdatacallbacks_t *cb, const char *fmt, ...) {
+ UNUSED(cb);
+ UNUSED(fmt);
+}
+
+isc_result_t
+dns_test_rdatafromstring(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t rdtype, unsigned char *dst,
+ size_t dstlen, const char *src, bool warnings) {
+ dns_rdatacallbacks_t callbacks;
+ isc_buffer_t source, target;
+ isc_lex_t *lex = NULL;
+ isc_lexspecials_t specials = { 0 };
+ isc_result_t result;
+ size_t length;
+
+ REQUIRE(rdata != NULL);
+ REQUIRE(DNS_RDATA_INITIALIZED(rdata));
+ REQUIRE(dst != NULL);
+ REQUIRE(src != NULL);
+
+ /*
+ * Set up source to hold the input string.
+ */
+ length = strlen(src);
+ isc_buffer_constinit(&source, src, length);
+ isc_buffer_add(&source, length);
+
+ /*
+ * Create a lexer as one is required by dns_rdata_fromtext().
+ */
+ result = isc_lex_create(dt_mctx, 64, &lex);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Set characters which will be treated as valid multi-line RDATA
+ * delimiters while reading the source string. These should match
+ * specials from lib/dns/master.c.
+ */
+ specials[0] = 1;
+ specials['('] = 1;
+ specials[')'] = 1;
+ specials['"'] = 1;
+ isc_lex_setspecials(lex, specials);
+
+ /*
+ * Expect DNS masterfile comments.
+ */
+ isc_lex_setcomments(lex, ISC_LEXCOMMENT_DNSMASTERFILE);
+
+ /*
+ * Point lexer at source.
+ */
+ result = isc_lex_openbuffer(lex, &source);
+ if (result != ISC_R_SUCCESS) {
+ goto destroy_lexer;
+ }
+
+ /*
+ * Set up target for storing uncompressed wire form of provided RDATA.
+ */
+ isc_buffer_init(&target, dst, dstlen);
+
+ /*
+ * Set up callbacks so warnings and errors are not printed.
+ */
+ if (!warnings) {
+ dns_rdatacallbacks_init(&callbacks);
+ callbacks.warn = callbacks.error = nullmsg;
+ }
+
+ /*
+ * Parse input string, determining result.
+ */
+ result = dns_rdata_fromtext(rdata, rdclass, rdtype, lex, dns_rootname,
+ 0, dt_mctx, &target, &callbacks);
+
+destroy_lexer:
+ isc_lex_destroy(&lex);
+
+ return (result);
+}
+
+void
+dns_test_namefromstring(const char *namestr, dns_fixedname_t *fname) {
+ size_t length;
+ isc_buffer_t *b = NULL;
+ isc_result_t result;
+ dns_name_t *name;
+
+ length = strlen(namestr);
+
+ name = dns_fixedname_initname(fname);
+
+ isc_buffer_allocate(dt_mctx, &b, length);
+
+ isc_buffer_putmem(b, (const unsigned char *)namestr, length);
+ result = dns_name_fromtext(name, b, dns_rootname, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_free(&b);
+}
+
+isc_result_t
+dns_test_difffromchanges(dns_diff_t *diff, const zonechange_t *changes,
+ bool warnings) {
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned char rdata_buf[1024];
+ dns_difftuple_t *tuple = NULL;
+ isc_consttextregion_t region;
+ dns_rdatatype_t rdatatype;
+ dns_fixedname_t fixedname;
+ dns_rdata_t rdata;
+ dns_name_t *name;
+ size_t i;
+
+ REQUIRE(diff != NULL);
+ REQUIRE(changes != NULL);
+
+ dns_diff_init(dt_mctx, diff);
+
+ for (i = 0; changes[i].owner != NULL; i++) {
+ /*
+ * Parse owner name.
+ */
+ name = dns_fixedname_initname(&fixedname);
+ result = dns_name_fromstring(name, changes[i].owner, 0,
+ dt_mctx);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ /*
+ * Parse RDATA type.
+ */
+ region.base = changes[i].type;
+ region.length = strlen(changes[i].type);
+ result = dns_rdatatype_fromtext(&rdatatype,
+ (isc_textregion_t *)&region);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ /*
+ * Parse RDATA.
+ */
+ dns_rdata_init(&rdata);
+ result = dns_test_rdatafromstring(
+ &rdata, dns_rdataclass_in, rdatatype, rdata_buf,
+ sizeof(rdata_buf), changes[i].rdata, warnings);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ /*
+ * Create a diff tuple for the parsed change and append it to
+ * the diff.
+ */
+ result = dns_difftuple_create(dt_mctx, changes[i].op, name,
+ changes[i].ttl, &rdata, &tuple);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ dns_diff_append(diff, &tuple);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ dns_diff_clear(diff);
+ }
+
+ return (result);
+}
+#endif /* HAVE_CMOCKA */
diff --git a/lib/dns/tests/dnstest.h b/lib/dns/tests/dnstest.h
new file mode 100644
index 0000000..f19b518
--- /dev/null
+++ b/lib/dns/tests/dnstest.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/hash.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/diff.h>
+#include <dns/result.h>
+#include <dns/zone.h>
+
+typedef struct {
+ dns_diffop_t op;
+ const char *owner;
+ dns_ttl_t ttl;
+ const char *type;
+ const char *rdata;
+} zonechange_t;
+
+#define ZONECHANGE_SENTINEL \
+ { \
+ 0, NULL, 0, NULL, NULL \
+ }
+
+extern isc_mem_t *dt_mctx;
+extern isc_log_t *lctx;
+extern isc_taskmgr_t *taskmgr;
+extern isc_task_t *maintask;
+extern isc_timermgr_t *timermgr;
+extern isc_socketmgr_t *socketmgr;
+extern dns_zonemgr_t *zonemgr;
+extern bool app_running;
+extern int ncpus;
+extern bool debug_mem_record;
+
+isc_result_t
+dns_test_begin(FILE *logfile, bool create_managers);
+
+void
+dns_test_end(void);
+
+isc_result_t
+dns_test_makeview(const char *name, dns_view_t **viewp);
+
+/*%
+ * Create a zone with origin 'name', return a pointer to the zone object in
+ * 'zonep'.
+ *
+ * If 'view' is set, the returned zone will be assigned to the passed view.
+ * 'createview' must be set to false when 'view' is non-NULL.
+ *
+ * If 'view' is not set and 'createview' is true, a new view is also created
+ * and the returned zone is assigned to it. This imposes two requirements on
+ * the caller: 1) the returned zone has to be subsequently assigned to a zone
+ * manager, otherwise its cleanup will fail, 2) the created view has to be
+ * cleaned up by the caller.
+ *
+ * If 'view' is not set and 'createview' is false, the returned zone will not
+ * be assigned to any view.
+ */
+isc_result_t
+dns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view,
+ bool createview);
+
+isc_result_t
+dns_test_setupzonemgr(void);
+
+isc_result_t
+dns_test_managezone(dns_zone_t *zone);
+
+void
+dns_test_releasezone(dns_zone_t *zone);
+
+void
+dns_test_closezonemgr(void);
+
+void
+dns_test_nap(uint32_t usec);
+
+isc_result_t
+dns_test_loaddb(dns_db_t **db, dns_dbtype_t dbtype, const char *origin,
+ const char *testfile);
+
+isc_result_t
+dns_test_getdata(const char *file, unsigned char *buf, size_t bufsiz,
+ size_t *sizep);
+
+char *
+dns_test_tohex(const unsigned char *data, size_t len, char *buf, size_t buflen);
+
+/*%
+ * Try parsing text form RDATA in "src" (of class "rdclass" and type "rdtype")
+ * into a structure representing that RDATA at "rdata", storing the
+ * uncompressed wire form of that RDATA at "dst", which is "dstlen" bytes long.
+ * Set 'warnings' to true to print logged warnings from dns_rdata_fromtext().
+ */
+isc_result_t
+dns_test_rdatafromstring(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
+ dns_rdatatype_t rdtype, unsigned char *dst,
+ size_t dstlen, const char *src, bool warnings);
+
+void
+dns_test_namefromstring(const char *namestr, dns_fixedname_t *fname);
+
+/*%
+ * Given a pointer to an uninitialized dns_diff_t structure in 'diff', make it
+ * contain diff tuples representing zone database changes listed in 'changes'.
+ * Set 'warnings' to true to print logged warnings from dns_rdata_fromtext().
+ */
+isc_result_t
+dns_test_difffromchanges(dns_diff_t *diff, const zonechange_t *changes,
+ bool warnings);
diff --git a/lib/dns/tests/dst_test.c b/lib/dns/tests/dst_test.c
new file mode 100644
index 0000000..f5370cc
--- /dev/null
+++ b/lib/dns/tests/dst_test.c
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/file.h>
+#include <isc/hex.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dst/dst.h>
+#include <dst/result.h>
+
+#include "../dst_internal.h"
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+/* Read sig in file at path to buf. Check signature ineffability */
+static isc_result_t
+sig_fromfile(const char *path, isc_buffer_t *buf) {
+ isc_result_t result;
+ size_t rval, len;
+ FILE *fp = NULL;
+ unsigned char val;
+ char *p, *data;
+ off_t size;
+
+ result = isc_stdio_open(path, "rb", &fp);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_file_getsizefd(fileno(fp), &size);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ data = isc_mem_get(dt_mctx, (size + 1));
+ assert_non_null(data);
+
+ len = (size_t)size;
+ p = data;
+ while (len != 0U) {
+ result = isc_stdio_read(p, 1, len, fp, &rval);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ len -= rval;
+ p += rval;
+ }
+ isc_stdio_close(fp);
+
+ p = data;
+ len = size;
+ while (len > 0U) {
+ if ((*p == '\r') || (*p == '\n')) {
+ ++p;
+ --len;
+ continue;
+ } else if (len < 2U) {
+ goto err;
+ }
+ if (('0' <= *p) && (*p <= '9')) {
+ val = *p - '0';
+ } else if (('A' <= *p) && (*p <= 'F')) {
+ val = *p - 'A' + 10;
+ } else {
+ result = ISC_R_BADHEX;
+ goto err;
+ }
+ ++p;
+ val <<= 4;
+ --len;
+ if (('0' <= *p) && (*p <= '9')) {
+ val |= (*p - '0');
+ } else if (('A' <= *p) && (*p <= 'F')) {
+ val |= (*p - 'A' + 10);
+ } else {
+ result = ISC_R_BADHEX;
+ goto err;
+ }
+ ++p;
+ --len;
+ isc_buffer_putuint8(buf, val);
+ }
+
+ result = ISC_R_SUCCESS;
+
+err:
+ isc_mem_put(dt_mctx, data, size + 1);
+ return (result);
+}
+
+static void
+check_sig(const char *datapath, const char *sigpath, const char *keyname,
+ dns_keytag_t id, dns_secalg_t alg, int type, bool expect) {
+ isc_result_t result;
+ size_t rval, len;
+ FILE *fp;
+ dst_key_t *key = NULL;
+ unsigned char sig[512];
+ unsigned char *p;
+ unsigned char *data;
+ off_t size;
+ isc_buffer_t b;
+ isc_buffer_t databuf, sigbuf;
+ isc_region_t datareg, sigreg;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ dst_context_t *ctx = NULL;
+
+ /*
+ * Read data from file in a form usable by dst_verify.
+ */
+ result = isc_stdio_open(datapath, "rb", &fp);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_file_getsizefd(fileno(fp), &size);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ data = isc_mem_get(dt_mctx, (size + 1));
+ assert_non_null(data);
+
+ p = data;
+ len = (size_t)size;
+ do {
+ result = isc_stdio_read(p, 1, len, fp, &rval);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ len -= rval;
+ p += rval;
+ } while (len);
+ isc_stdio_close(fp);
+
+ /*
+ * Read key from file in a form usable by dst_verify.
+ */
+ name = dns_fixedname_initname(&fname);
+ isc_buffer_constinit(&b, keyname, strlen(keyname));
+ isc_buffer_add(&b, strlen(keyname));
+ result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dst_key_fromfile(name, id, alg, type, "testdata/dst", dt_mctx,
+ &key);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_init(&databuf, data, (unsigned int)size);
+ isc_buffer_add(&databuf, (unsigned int)size);
+ isc_buffer_usedregion(&databuf, &datareg);
+
+ memset(sig, 0, sizeof(sig));
+ isc_buffer_init(&sigbuf, sig, sizeof(sig));
+
+ /*
+ * Read precomputed signature from file in a form usable by dst_verify.
+ */
+ result = sig_fromfile(sigpath, &sigbuf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Verify that the key signed the data.
+ */
+ isc_buffer_remainingregion(&sigbuf, &sigreg);
+
+ result = dst_context_create(key, dt_mctx, DNS_LOGCATEGORY_GENERAL,
+ false, 0, &ctx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dst_context_adddata(ctx, &datareg);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dst_context_verify(ctx, &sigreg);
+
+ /*
+ * Compute the expected signature and emit it
+ * so the precomputed signature can be updated.
+ * This should only be done if the covered data
+ * is updated.
+ */
+ if (expect && result != ISC_R_SUCCESS) {
+ isc_result_t result2;
+
+ dst_context_destroy(&ctx);
+ result2 = dst_context_create(
+ key, dt_mctx, DNS_LOGCATEGORY_GENERAL, false, 0, &ctx);
+ assert_int_equal(result2, ISC_R_SUCCESS);
+
+ result2 = dst_context_adddata(ctx, &datareg);
+ assert_int_equal(result2, ISC_R_SUCCESS);
+
+ char sigbuf2[4096];
+ isc_buffer_t sigb;
+ isc_buffer_init(&sigb, sigbuf2, sizeof(sigbuf2));
+
+ result2 = dst_context_sign(ctx, &sigb);
+ assert_int_equal(result2, ISC_R_SUCCESS);
+
+ isc_region_t r;
+ isc_buffer_usedregion(&sigb, &r);
+
+ char hexbuf[4096] = { 0 };
+ isc_buffer_t hb;
+ isc_buffer_init(&hb, hexbuf, sizeof(hexbuf));
+
+ isc_hex_totext(&r, 0, "", &hb);
+
+ fprintf(stderr, "# %s:\n# %s\n", sigpath, hexbuf);
+ }
+
+ isc_mem_put(dt_mctx, data, size + 1);
+ dst_context_destroy(&ctx);
+ dst_key_free(&key);
+
+ assert_true((expect && (result == ISC_R_SUCCESS)) ||
+ (!expect && (result != ISC_R_SUCCESS)));
+
+ return;
+}
+
+static void
+sig_test(void **state) {
+ UNUSED(state);
+
+ struct {
+ const char *datapath;
+ const char *sigpath;
+ const char *keyname;
+ dns_keytag_t keyid;
+ dns_secalg_t alg;
+ bool expect;
+ } testcases[] = {
+ { "testdata/dst/test1.data", "testdata/dst/test1.ecdsa256sig",
+ "test.", 49130, DST_ALG_ECDSA256, true },
+ { "testdata/dst/test1.data", "testdata/dst/test1.rsasha256sig",
+ "test.", 11349, DST_ALG_RSASHA256, true },
+ { /* wrong sig */
+ "testdata/dst/test1.data", "testdata/dst/test1.ecdsa256sig",
+ "test.", 11349, DST_ALG_RSASHA256, false },
+ { /* wrong data */
+ "testdata/dst/test2.data", "testdata/dst/test1.ecdsa256sig",
+ "test.", 49130, DST_ALG_ECDSA256, false },
+ };
+ unsigned int i;
+
+ for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) {
+ if (!dst_algorithm_supported(testcases[i].alg)) {
+ continue;
+ }
+
+ check_sig(testcases[i].datapath, testcases[i].sigpath,
+ testcases[i].keyname, testcases[i].keyid,
+ testcases[i].alg, DST_TYPE_PRIVATE | DST_TYPE_PUBLIC,
+ testcases[i].expect);
+ }
+}
+
+#if !defined(USE_PKCS11)
+static void
+check_cmp(const char *key1_name, dns_keytag_t key1_id, const char *key2_name,
+ dns_keytag_t key2_id, dns_secalg_t alg, int type, bool expect) {
+ isc_result_t result;
+ dst_key_t *key1 = NULL;
+ dst_key_t *key2 = NULL;
+ isc_buffer_t b1;
+ isc_buffer_t b2;
+ dns_fixedname_t fname1;
+ dns_fixedname_t fname2;
+ dns_name_t *name1;
+ dns_name_t *name2;
+
+ /*
+ * Read key1 from the file.
+ */
+ name1 = dns_fixedname_initname(&fname1);
+ isc_buffer_constinit(&b1, key1_name, strlen(key1_name));
+ isc_buffer_add(&b1, strlen(key1_name));
+ result = dns_name_fromtext(name1, &b1, dns_rootname, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dst_key_fromfile(name1, key1_id, alg, type, "comparekeys",
+ dt_mctx, &key1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Read key2 from the file.
+ */
+ name2 = dns_fixedname_initname(&fname2);
+ isc_buffer_constinit(&b2, key2_name, strlen(key2_name));
+ isc_buffer_add(&b2, strlen(key2_name));
+ result = dns_name_fromtext(name2, &b2, dns_rootname, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dst_key_fromfile(name2, key2_id, alg, type, "comparekeys",
+ dt_mctx, &key2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Compare the keys (for public-only keys).
+ */
+ if ((type & DST_TYPE_PRIVATE) == 0) {
+ assert_true(dst_key_pubcompare(key1, key2, false) == expect);
+ }
+
+ /*
+ * Compare the keys (for both public-only keys and keypairs).
+ */
+ assert_true(dst_key_compare(key1, key2) == expect);
+
+ /*
+ * Free the keys
+ */
+ dst_key_free(&key2);
+ dst_key_free(&key1);
+
+ return;
+}
+
+static void
+cmp_test(void **state) {
+ UNUSED(state);
+
+ struct {
+ const char *key1_name;
+ dns_keytag_t key1_id;
+ const char *key2_name;
+ dns_keytag_t key2_id;
+ dns_secalg_t alg;
+ int type;
+ bool expect;
+ } testcases[] = {
+ /* RSA Keypair: self */
+ { "example.", 53461, "example.", 53461, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, true },
+
+ /* RSA Keypair: different key */
+ { "example.", 53461, "example2.", 37993, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false },
+
+ /* RSA Keypair: different PublicExponent (e) */
+ { "example.", 53461, "example-e.", 53973, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false },
+
+ /* RSA Keypair: different Modulus (n) */
+ { "example.", 53461, "example-n.", 37464, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false },
+
+ /* RSA Keypair: different PrivateExponent (d) */
+ { "example.", 53461, "example-d.", 53461, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false },
+
+ /* RSA Keypair: different Prime1 (p) */
+ { "example.", 53461, "example-p.", 53461, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false },
+
+ /* RSA Keypair: different Prime2 (q) */
+ { "example.", 53461, "example-q.", 53461, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false },
+
+ /* RSA Public Key: self */
+ { "example.", 53461, "example.", 53461, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC, true },
+
+ /* RSA Public Key: different key */
+ { "example.", 53461, "example2.", 37993, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC, false },
+
+ /* RSA Public Key: different PublicExponent (e) */
+ { "example.", 53461, "example-e.", 53973, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC, false },
+
+ /* RSA Public Key: different Modulus (n) */
+ { "example.", 53461, "example-n.", 37464, DST_ALG_RSASHA256,
+ DST_TYPE_PUBLIC, false },
+
+ /* ECDSA Keypair: self */
+ { "example.", 19786, "example.", 19786, DST_ALG_ECDSA256,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, true },
+
+ /* ECDSA Keypair: different key */
+ { "example.", 19786, "example2.", 16384, DST_ALG_ECDSA256,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false },
+
+ /* ECDSA Public Key: self */
+ { "example.", 19786, "example.", 19786, DST_ALG_ECDSA256,
+ DST_TYPE_PUBLIC, true },
+
+ /* ECDSA Public Key: different key */
+ { "example.", 19786, "example2.", 16384, DST_ALG_ECDSA256,
+ DST_TYPE_PUBLIC, false },
+
+ /* EdDSA Keypair: self */
+ { "example.", 63663, "example.", 63663, DST_ALG_ED25519,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, true },
+
+ /* EdDSA Keypair: different key */
+ { "example.", 63663, "example2.", 37529, DST_ALG_ED25519,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false },
+
+ /* EdDSA Public Key: self */
+ { "example.", 63663, "example.", 63663, DST_ALG_ED25519,
+ DST_TYPE_PUBLIC, true },
+
+ /* EdDSA Public Key: different key */
+ { "example.", 63663, "example2.", 37529, DST_ALG_ED25519,
+ DST_TYPE_PUBLIC, false },
+
+ /* DH Keypair: self */
+ { "example.", 65316, "example.", 65316, DST_ALG_DH,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_KEY, true },
+
+ /* DH Keypair: different key */
+ { "example.", 65316, "example2.", 19823, DST_ALG_DH,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_KEY, false },
+
+ /* DH Keypair: different key (with generator=5) */
+ { "example.", 65316, "example3.", 17187, DST_ALG_DH,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_KEY, false },
+
+ /* DH Keypair: different private key */
+ { "example.", 65316, "example-private.", 65316, DST_ALG_DH,
+ DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_KEY, false },
+
+ /* DH Public Key: self */
+ { "example.", 65316, "example.", 65316, DST_ALG_DH,
+ DST_TYPE_PUBLIC | DST_TYPE_KEY, true },
+
+ /* DH Public Key: different key */
+ { "example.", 65316, "example2.", 19823, DST_ALG_DH,
+ DST_TYPE_PUBLIC | DST_TYPE_KEY, false },
+
+ /* DH Public Key: different key (with generator=5) */
+ { "example.", 65316, "example3.", 17187, DST_ALG_DH,
+ DST_TYPE_PUBLIC | DST_TYPE_KEY, false },
+ };
+ unsigned int i;
+
+ for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) {
+ if (!dst_algorithm_supported(testcases[i].alg)) {
+ continue;
+ }
+
+ check_cmp(testcases[i].key1_name, testcases[i].key1_id,
+ testcases[i].key2_name, testcases[i].key2_id,
+ testcases[i].alg, testcases[i].type,
+ testcases[i].expect);
+ }
+}
+#endif /* #if !defined(USE_PKCS11) */
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(sig_test, _setup, _teardown),
+#if !defined(USE_PKCS11)
+ cmocka_unit_test_setup_teardown(cmp_test, _setup, _teardown),
+#endif /* #if !defined(USE_PKCS11) */
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/geoip_test.c b/lib/dns/tests/geoip_test.c
new file mode 100644
index 0000000..054213e
--- /dev/null
+++ b/lib/dns/tests/geoip_test.c
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/geoip.h>
+
+#include "dnstest.h"
+
+#if defined(HAVE_GEOIP2)
+#include <maxminddb.h>
+
+#include "../geoip2.c"
+
+/* Use GeoIP2 databases from the 'geoip2' system test */
+#define TEST_GEOIP_DATA "../../../bin/tests/system/geoip2/data"
+
+static dns_geoip_databases_t geoip;
+
+static MMDB_s geoip_country, geoip_city, geoip_as, geoip_isp, geoip_domain;
+
+static void
+load_geoip(const char *dir);
+static void
+close_geoip(void);
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Use databases from the geoip system test */
+ load_geoip(TEST_GEOIP_DATA);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ close_geoip();
+
+ dns_test_end();
+
+ return (0);
+}
+
+static MMDB_s *
+open_geoip2(const char *dir, const char *dbfile, MMDB_s *mmdb) {
+ char pathbuf[PATH_MAX];
+ int ret;
+
+ snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, dbfile);
+ ret = MMDB_open(pathbuf, MMDB_MODE_MMAP, mmdb);
+ if (ret == MMDB_SUCCESS) {
+ return (mmdb);
+ }
+
+ return (NULL);
+}
+
+static void
+load_geoip(const char *dir) {
+ geoip.country = open_geoip2(dir, "GeoIP2-Country.mmdb", &geoip_country);
+ geoip.city = open_geoip2(dir, "GeoIP2-City.mmdb", &geoip_city);
+ geoip.as = open_geoip2(dir, "GeoLite2-ASN.mmdb", &geoip_as);
+ geoip.isp = open_geoip2(dir, "GeoIP2-ISP.mmdb", &geoip_isp);
+ geoip.domain = open_geoip2(dir, "GeoIP2-Domain.mmdb", &geoip_domain);
+}
+
+static void
+close_geoip(void) {
+ MMDB_close(&geoip_country);
+ MMDB_close(&geoip_city);
+ MMDB_close(&geoip_as);
+ MMDB_close(&geoip_isp);
+ MMDB_close(&geoip_domain);
+}
+
+static bool
+/* Check if an MMDB entry of a given subtype exists for the given IP */
+entry_exists(dns_geoip_subtype_t subtype, const char *addr) {
+ struct in6_addr in6;
+ struct in_addr in4;
+ isc_netaddr_t na;
+ MMDB_s *db;
+
+ if (inet_pton(AF_INET6, addr, &in6) == 1) {
+ isc_netaddr_fromin6(&na, &in6);
+ } else if (inet_pton(AF_INET, addr, &in4) == 1) {
+ isc_netaddr_fromin(&na, &in4);
+ } else {
+ UNREACHABLE();
+ }
+
+ db = geoip2_database(&geoip, fix_subtype(&geoip, subtype));
+
+ return (db != NULL && get_entry_for(db, &na) != NULL);
+}
+
+/*
+ * Baseline test - check if get_entry_for() works as expected, i.e. that its
+ * return values are consistent with the contents of the test MMDBs found in
+ * bin/tests/system/geoip2/data/ (10.53.0.1 and fd92:7065:b8e:ffff::1 should be
+ * present in all databases, 192.0.2.128 should only be present in the country
+ * database, ::1 should be absent from all databases).
+ */
+static void
+baseline(void **state) {
+ dns_geoip_subtype_t subtype;
+
+ UNUSED(state);
+
+ subtype = dns_geoip_city_name;
+
+ assert_true(entry_exists(subtype, "10.53.0.1"));
+ assert_false(entry_exists(subtype, "192.0.2.128"));
+ assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1"));
+ assert_false(entry_exists(subtype, "::1"));
+
+ subtype = dns_geoip_country_name;
+
+ assert_true(entry_exists(subtype, "10.53.0.1"));
+ assert_true(entry_exists(subtype, "192.0.2.128"));
+ assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1"));
+ assert_false(entry_exists(subtype, "::1"));
+
+ subtype = dns_geoip_domain_name;
+
+ assert_true(entry_exists(subtype, "10.53.0.1"));
+ assert_false(entry_exists(subtype, "192.0.2.128"));
+ assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1"));
+ assert_false(entry_exists(subtype, "::1"));
+
+ subtype = dns_geoip_isp_name;
+
+ assert_true(entry_exists(subtype, "10.53.0.1"));
+ assert_false(entry_exists(subtype, "192.0.2.128"));
+ assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1"));
+ assert_false(entry_exists(subtype, "::1"));
+
+ subtype = dns_geoip_as_asnum;
+
+ assert_true(entry_exists(subtype, "10.53.0.1"));
+ assert_false(entry_exists(subtype, "192.0.2.128"));
+ assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1"));
+ assert_false(entry_exists(subtype, "::1"));
+}
+
+static bool
+do_lookup_string(const char *addr, dns_geoip_subtype_t subtype,
+ const char *string) {
+ dns_geoip_elem_t elt;
+ struct in_addr in4;
+ isc_netaddr_t na;
+ int n;
+
+ n = inet_pton(AF_INET, addr, &in4);
+ assert_int_equal(n, 1);
+ isc_netaddr_fromin(&na, &in4);
+
+ elt.subtype = subtype;
+ strlcpy(elt.as_string, string, sizeof(elt.as_string));
+
+ return (dns_geoip_match(&na, &geoip, &elt));
+}
+
+static bool
+do_lookup_string_v6(const char *addr, dns_geoip_subtype_t subtype,
+ const char *string) {
+ dns_geoip_elem_t elt;
+ struct in6_addr in6;
+ isc_netaddr_t na;
+ int n;
+
+ n = inet_pton(AF_INET6, addr, &in6);
+ assert_int_equal(n, 1);
+ isc_netaddr_fromin6(&na, &in6);
+
+ elt.subtype = subtype;
+ strlcpy(elt.as_string, string, sizeof(elt.as_string));
+
+ return (dns_geoip_match(&na, &geoip, &elt));
+}
+
+/* GeoIP country matching */
+static void
+country(void **state) {
+ bool match;
+
+ UNUSED(state);
+
+ if (geoip.country == NULL) {
+ skip();
+ }
+
+ match = do_lookup_string("10.53.0.1", dns_geoip_country_code, "AU");
+ assert_true(match);
+
+ match = do_lookup_string("10.53.0.1", dns_geoip_country_name,
+ "Australia");
+ assert_true(match);
+
+ match = do_lookup_string("192.0.2.128", dns_geoip_country_code, "O1");
+ assert_true(match);
+
+ match = do_lookup_string("192.0.2.128", dns_geoip_country_name,
+ "Other");
+ assert_true(match);
+}
+
+/* GeoIP country (ipv6) matching */
+static void
+country_v6(void **state) {
+ bool match;
+
+ UNUSED(state);
+
+ if (geoip.country == NULL) {
+ skip();
+ }
+
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ dns_geoip_country_code, "AU");
+ assert_true(match);
+
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ dns_geoip_country_name, "Australia");
+ assert_true(match);
+}
+
+/* GeoIP city (ipv4) matching */
+static void
+city(void **state) {
+ bool match;
+
+ UNUSED(state);
+
+ if (geoip.city == NULL) {
+ skip();
+ }
+
+ match = do_lookup_string("10.53.0.1", dns_geoip_city_continentcode,
+ "NA");
+ assert_true(match);
+
+ match = do_lookup_string("10.53.0.1", dns_geoip_city_countrycode, "US");
+ assert_true(match);
+
+ match = do_lookup_string("10.53.0.1", dns_geoip_city_countryname,
+ "United States");
+ assert_true(match);
+
+ match = do_lookup_string("10.53.0.1", dns_geoip_city_region, "CA");
+ assert_true(match);
+
+ match = do_lookup_string("10.53.0.1", dns_geoip_city_regionname,
+ "California");
+ assert_true(match);
+
+ match = do_lookup_string("10.53.0.1", dns_geoip_city_name,
+ "Redwood City");
+ assert_true(match);
+
+ match = do_lookup_string("10.53.0.1", dns_geoip_city_postalcode,
+ "94063");
+ assert_true(match);
+}
+
+/* GeoIP city (ipv6) matching */
+static void
+city_v6(void **state) {
+ bool match;
+
+ UNUSED(state);
+
+ if (geoip.city == NULL) {
+ skip();
+ }
+
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ dns_geoip_city_continentcode, "NA");
+ assert_true(match);
+
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ dns_geoip_city_countrycode, "US");
+ assert_true(match);
+
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ dns_geoip_city_countryname,
+ "United States");
+ assert_true(match);
+
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ dns_geoip_city_region, "CA");
+ assert_true(match);
+
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ dns_geoip_city_regionname, "California");
+ assert_true(match);
+
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ dns_geoip_city_name, "Redwood City");
+ assert_true(match);
+
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ dns_geoip_city_postalcode, "94063");
+ assert_true(match);
+}
+
+/* GeoIP asnum matching */
+static void
+asnum(void **state) {
+ bool match;
+
+ UNUSED(state);
+
+ if (geoip.as == NULL) {
+ skip();
+ }
+
+ match = do_lookup_string("10.53.0.3", dns_geoip_as_asnum, "AS100003");
+ assert_true(match);
+}
+
+/* GeoIP isp matching */
+static void
+isp(void **state) {
+ bool match;
+
+ UNUSED(state);
+
+ if (geoip.isp == NULL) {
+ skip();
+ }
+
+ match = do_lookup_string("10.53.0.1", dns_geoip_isp_name,
+ "One Systems, Inc.");
+ assert_true(match);
+}
+
+/* GeoIP org matching */
+static void
+org(void **state) {
+ bool match;
+
+ UNUSED(state);
+
+ if (geoip.as == NULL) {
+ skip();
+ }
+
+ match = do_lookup_string("10.53.0.2", dns_geoip_org_name,
+ "Two Technology Ltd.");
+ assert_true(match);
+}
+
+/* GeoIP domain matching */
+static void
+domain(void **state) {
+ bool match;
+
+ UNUSED(state);
+
+ if (geoip.domain == NULL) {
+ skip();
+ }
+
+ match = do_lookup_string("10.53.0.5", dns_geoip_domain_name, "five.es");
+ assert_true(match);
+}
+#endif /* HAVE_GEOIP2 */
+
+int
+main(void) {
+#if defined(HAVE_GEOIP2)
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(baseline), cmocka_unit_test(country),
+ cmocka_unit_test(country_v6), cmocka_unit_test(city),
+ cmocka_unit_test(city_v6), cmocka_unit_test(asnum),
+ cmocka_unit_test(isp), cmocka_unit_test(org),
+ cmocka_unit_test(domain),
+ };
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+#else /* if defined(HAVE_GEOIP2) */
+ print_message("1..0 # Skip GeoIP not enabled\n");
+#endif /* if defined(HAVE_GEOIP2) */
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* HAVE_CMOCKA */
diff --git a/lib/dns/tests/keytable_test.c b/lib/dns/tests/keytable_test.c
new file mode 100644
index 0000000..670b1b2
--- /dev/null
+++ b/lib/dns/tests/keytable_test.c
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/base64.h>
+#include <isc/buffer.h>
+#include <isc/md.h>
+#include <isc/util.h>
+
+#include <dns/fixedname.h>
+#include <dns/keytable.h>
+#include <dns/name.h>
+#include <dns/nta.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatastruct.h>
+#include <dns/rootns.h>
+#include <dns/view.h>
+
+#include <dst/dst.h>
+
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+dns_keytable_t *keytable = NULL;
+dns_ntatable_t *ntatable = NULL;
+
+static const char *keystr1 = "BQEAAAABok+vaUC9neRv8yeT/"
+ "FEGgN7svR8s7VBUVSBd8NsAiV8AlaAg "
+ "O5FHar3JQd95i/puZos6Vi6at9/"
+ "JBbN8qVmO2AuiXxVqfxMKxIcy+LEB "
+ "0Vw4NaSJ3N3uaVREso6aTSs98H/"
+ "25MjcwLOr7SFfXA7bGhZatLtYY/xu kp6Km5hMfkE=";
+
+static const char *keystr2 = "BQEAAAABwuHz9Cem0BJ0JQTO7C/a3McR6hMaufljs1dfG/"
+ "inaJpYv7vH "
+ "XTrAOm/MeKp+/x6eT4QLru0KoZkvZJnqTI8JyaFTw2OM/"
+ "ItBfh/hL2lm "
+ "Cft2O7n3MfeqYtvjPnY7dWghYW4sVfH7VVEGm958o9nfi7953"
+ "2Qeklxh x8pXWdeAaRU=";
+
+static dns_view_t *view = NULL;
+
+/*
+ * Test utilities. In general, these assume input parameters are valid
+ * (checking with assert_int_equal, thus aborting if not) and unlikely run time
+ * errors (such as memory allocation failure) won't happen. This helps keep
+ * the test code concise.
+ */
+
+/*
+ * Utility to convert C-string to dns_name_t. Return a pointer to
+ * static data, and so is not thread safe.
+ */
+static dns_name_t *
+str2name(const char *namestr) {
+ static dns_fixedname_t fname;
+ static dns_name_t *name;
+ static isc_buffer_t namebuf;
+ void *deconst_namestr;
+
+ name = dns_fixedname_initname(&fname);
+ DE_CONST(namestr, deconst_namestr); /* OK, since we don't modify it */
+ isc_buffer_init(&namebuf, deconst_namestr, strlen(deconst_namestr));
+ isc_buffer_add(&namebuf, strlen(namestr));
+ assert_int_equal(
+ dns_name_fromtext(name, &namebuf, dns_rootname, 0, NULL),
+ ISC_R_SUCCESS);
+
+ return (name);
+}
+
+static void
+create_keystruct(uint16_t flags, uint8_t proto, uint8_t alg, const char *keystr,
+ dns_rdata_dnskey_t *keystruct) {
+ unsigned char keydata[4096];
+ isc_buffer_t keydatabuf;
+ isc_region_t r;
+ const dns_rdataclass_t rdclass = dns_rdataclass_in;
+
+ keystruct->common.rdclass = rdclass;
+ keystruct->common.rdtype = dns_rdatatype_dnskey;
+ keystruct->mctx = dt_mctx;
+ ISC_LINK_INIT(&keystruct->common, link);
+ keystruct->flags = flags;
+ keystruct->protocol = proto;
+ keystruct->algorithm = alg;
+
+ isc_buffer_init(&keydatabuf, keydata, sizeof(keydata));
+ assert_int_equal(isc_base64_decodestring(keystr, &keydatabuf),
+ ISC_R_SUCCESS);
+ isc_buffer_usedregion(&keydatabuf, &r);
+ keystruct->datalen = r.length;
+ keystruct->data = isc_mem_allocate(dt_mctx, r.length);
+ memmove(keystruct->data, r.base, r.length);
+}
+
+static void
+create_dsstruct(dns_name_t *name, uint16_t flags, uint8_t proto, uint8_t alg,
+ const char *keystr, unsigned char *digest,
+ dns_rdata_ds_t *dsstruct) {
+ isc_result_t result;
+ unsigned char rrdata[4096];
+ isc_buffer_t rrdatabuf;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_dnskey_t dnskey;
+
+ /*
+ * Populate DNSKEY rdata structure.
+ */
+ create_keystruct(flags, proto, alg, keystr, &dnskey);
+
+ /*
+ * Convert to wire format.
+ */
+ isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata));
+ result = dns_rdata_fromstruct(&rdata, dnskey.common.rdclass,
+ dnskey.common.rdtype, &dnskey,
+ &rrdatabuf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Build DS rdata struct.
+ */
+ result = dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256, digest,
+ dsstruct);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_rdata_freestruct(&dnskey);
+}
+
+/* Common setup: create a keytable and ntatable to test with a few keys */
+static void
+create_tables() {
+ isc_result_t result;
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ dns_rdata_ds_t ds;
+ dns_fixedname_t fn;
+ dns_name_t *keyname = dns_fixedname_name(&fn);
+ isc_stdtime_t now;
+
+ result = dns_test_makeview("view", &view);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_int_equal(dns_keytable_create(dt_mctx, &keytable),
+ ISC_R_SUCCESS);
+ assert_int_equal(
+ dns_ntatable_create(view, taskmgr, timermgr, &ntatable),
+ ISC_R_SUCCESS);
+
+ /* Add a normal key */
+ dns_test_namefromstring("example.com", &fn);
+ create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds);
+ assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds),
+ ISC_R_SUCCESS);
+
+ /* Add an initializing managed key */
+ dns_test_namefromstring("managed.com", &fn);
+ create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds);
+ assert_int_equal(dns_keytable_add(keytable, true, true, keyname, &ds),
+ ISC_R_SUCCESS);
+
+ /* Add a null key */
+ assert_int_equal(dns_keytable_marksecure(keytable, str2name("null."
+ "example")),
+ ISC_R_SUCCESS);
+
+ /* Add a negative trust anchor, duration 1 hour */
+ isc_stdtime_get(&now);
+ assert_int_equal(dns_ntatable_add(ntatable,
+ str2name("insecure.example"), false,
+ now, 3600),
+ ISC_R_SUCCESS);
+}
+
+static void
+destroy_tables() {
+ if (ntatable != NULL) {
+ dns_ntatable_detach(&ntatable);
+ }
+ if (keytable != NULL) {
+ dns_keytable_detach(&keytable);
+ }
+
+ dns_view_detach(&view);
+}
+
+/* add keys to the keytable */
+static void
+add_test(void **state) {
+ dns_keynode_t *keynode = NULL;
+ dns_keynode_t *null_keynode = NULL;
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ dns_rdata_ds_t ds;
+ dns_fixedname_t fn;
+ dns_name_t *keyname = dns_fixedname_name(&fn);
+
+ UNUSED(state);
+
+ create_tables();
+
+ /*
+ * Getting the keynode for the example.com key should succeed.
+ */
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("example.com"), &keynode),
+ ISC_R_SUCCESS);
+
+ /*
+ * Try to add the same key. This should have no effect but
+ * report success.
+ */
+ dns_test_namefromstring("example.com", &fn);
+ create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds);
+ assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds),
+ ISC_R_SUCCESS);
+ dns_keytable_detachkeynode(keytable, &keynode);
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("example.com"), &keynode),
+ ISC_R_SUCCESS);
+
+ /* Add another key (different keydata) */
+ dns_keytable_detachkeynode(keytable, &keynode);
+ create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds);
+ assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds),
+ ISC_R_SUCCESS);
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("example.com"), &keynode),
+ ISC_R_SUCCESS);
+ dns_keytable_detachkeynode(keytable, &keynode);
+
+ /*
+ * Get the keynode for the managed.com key. Ensure the
+ * retrieved key is an initializing key, then mark it as trusted using
+ * dns_keynode_trust() and ensure the latter works as expected.
+ */
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("managed.com"), &keynode),
+ ISC_R_SUCCESS);
+ assert_int_equal(dns_keynode_initial(keynode), true);
+ dns_keynode_trust(keynode);
+ assert_int_equal(dns_keynode_initial(keynode), false);
+ dns_keytable_detachkeynode(keytable, &keynode);
+
+ /*
+ * Add a different managed key for managed.com, marking it as an
+ * initializing key. Since there is already a trusted key at the
+ * node, the node should *not* be marked as initializing.
+ */
+ dns_test_namefromstring("managed.com", &fn);
+ create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds);
+ assert_int_equal(dns_keytable_add(keytable, true, true, keyname, &ds),
+ ISC_R_SUCCESS);
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("managed.com"), &keynode),
+ ISC_R_SUCCESS);
+ assert_int_equal(dns_keynode_initial(keynode), false);
+ dns_keytable_detachkeynode(keytable, &keynode);
+
+ /*
+ * Add the same managed key again, but this time mark it as a
+ * non-initializing key. Ensure the previously added key is upgraded
+ * to a non-initializing key and make sure there are still two key
+ * nodes for managed.com, both containing non-initializing keys.
+ */
+ assert_int_equal(dns_keytable_add(keytable, true, false, keyname, &ds),
+ ISC_R_SUCCESS);
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("managed.com"), &keynode),
+ ISC_R_SUCCESS);
+ assert_int_equal(dns_keynode_initial(keynode), false);
+ dns_keytable_detachkeynode(keytable, &keynode);
+
+ /*
+ * Add a managed key at a new node, two.com, marking it as an
+ * initializing key.
+ */
+ dns_test_namefromstring("two.com", &fn);
+ create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds);
+ assert_int_equal(dns_keytable_add(keytable, true, true, keyname, &ds),
+ ISC_R_SUCCESS);
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("two.com"), &keynode),
+ ISC_R_SUCCESS);
+ assert_int_equal(dns_keynode_initial(keynode), true);
+ dns_keytable_detachkeynode(keytable, &keynode);
+
+ /*
+ * Add a different managed key for two.com, marking it as a
+ * non-initializing key. Since there is already an iniitalizing
+ * trust anchor for two.com and we haven't run dns_keynode_trust(),
+ * the initialization status should not change.
+ */
+ create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds);
+ assert_int_equal(dns_keytable_add(keytable, true, false, keyname, &ds),
+ ISC_R_SUCCESS);
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("two.com"), &keynode),
+ ISC_R_SUCCESS);
+ assert_int_equal(dns_keynode_initial(keynode), true);
+ dns_keytable_detachkeynode(keytable, &keynode);
+
+ /*
+ * Add a normal key to a name that has a null key. The null key node
+ * will be updated with the normal key.
+ */
+ assert_int_equal(dns_keytable_find(keytable, str2name("null.example"),
+ &null_keynode),
+ ISC_R_SUCCESS);
+ dns_test_namefromstring("null.example", &fn);
+ create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds);
+ assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds),
+ ISC_R_SUCCESS);
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("null.example"), &keynode),
+ ISC_R_SUCCESS);
+ assert_ptr_equal(keynode, null_keynode); /* should be the same node */
+ dns_keytable_detachkeynode(keytable, &null_keynode);
+
+ /*
+ * Try to add a null key to a name that already has a key. It's
+ * effectively no-op, so the same key node is still there.
+ * (Note: this and above checks confirm that if a name has a null key
+ * that's the only key for the name).
+ */
+ assert_int_equal(dns_keytable_marksecure(keytable, str2name("null."
+ "example")),
+ ISC_R_SUCCESS);
+ assert_int_equal(dns_keytable_find(keytable, str2name("null.example"),
+ &null_keynode),
+ ISC_R_SUCCESS);
+ assert_ptr_equal(keynode, null_keynode);
+ dns_keytable_detachkeynode(keytable, &null_keynode);
+
+ dns_keytable_detachkeynode(keytable, &keynode);
+ destroy_tables();
+}
+
+/* delete keys from the keytable */
+static void
+delete_test(void **state) {
+ UNUSED(state);
+
+ create_tables();
+
+ /* dns_keytable_delete requires exact match */
+ assert_int_equal(dns_keytable_delete(keytable, str2name("example.org")),
+ ISC_R_NOTFOUND);
+ assert_int_equal(dns_keytable_delete(keytable, str2name("s.example."
+ "com")),
+ ISC_R_NOTFOUND);
+ assert_int_equal(dns_keytable_delete(keytable, str2name("example.com")),
+ ISC_R_SUCCESS);
+
+ /* works also for nodes with a null key */
+ assert_int_equal(dns_keytable_delete(keytable, str2name("null."
+ "example")),
+ ISC_R_SUCCESS);
+
+ /* or a negative trust anchor */
+ assert_int_equal(dns_ntatable_delete(ntatable, str2name("insecure."
+ "example")),
+ ISC_R_SUCCESS);
+
+ destroy_tables();
+}
+
+/* delete key nodes from the keytable */
+static void
+deletekey_test(void **state) {
+ dns_rdata_dnskey_t dnskey;
+ dns_fixedname_t fn;
+ dns_name_t *keyname = dns_fixedname_name(&fn);
+
+ UNUSED(state);
+
+ create_tables();
+
+ /* key name doesn't match */
+ dns_test_namefromstring("example.org", &fn);
+ create_keystruct(257, 3, 5, keystr1, &dnskey);
+ assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey),
+ ISC_R_NOTFOUND);
+ dns_rdata_freestruct(&dnskey);
+
+ /* subdomain match is the same as no match */
+ dns_test_namefromstring("sub.example.org", &fn);
+ create_keystruct(257, 3, 5, keystr1, &dnskey);
+ assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey),
+ ISC_R_NOTFOUND);
+ dns_rdata_freestruct(&dnskey);
+
+ /* name matches but key doesn't match (resulting in PARTIALMATCH) */
+ dns_test_namefromstring("example.com", &fn);
+ create_keystruct(257, 3, 5, keystr2, &dnskey);
+ assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey),
+ DNS_R_PARTIALMATCH);
+ dns_rdata_freestruct(&dnskey);
+
+ /*
+ * exact match: should return SUCCESS on the first try, then
+ * PARTIALMATCH on the second (because the name existed but
+ * not a matching key).
+ */
+ create_keystruct(257, 3, 5, keystr1, &dnskey);
+ assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey),
+ ISC_R_SUCCESS);
+ assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey),
+ DNS_R_PARTIALMATCH);
+
+ /*
+ * after deleting the node, any deletekey or delete attempt should
+ * result in NOTFOUND.
+ */
+ assert_int_equal(dns_keytable_delete(keytable, keyname), ISC_R_SUCCESS);
+ assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey),
+ ISC_R_NOTFOUND);
+ dns_rdata_freestruct(&dnskey);
+
+ /*
+ * A null key node for a name is not deleted when searched by key;
+ * it must be deleted by dns_keytable_delete()
+ */
+ dns_test_namefromstring("null.example", &fn);
+ create_keystruct(257, 3, 5, keystr1, &dnskey);
+ assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey),
+ DNS_R_PARTIALMATCH);
+ assert_int_equal(dns_keytable_delete(keytable, keyname), ISC_R_SUCCESS);
+ dns_rdata_freestruct(&dnskey);
+
+ destroy_tables();
+}
+
+/* check find-variant operations */
+static void
+find_test(void **state) {
+ dns_keynode_t *keynode = NULL;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ UNUSED(state);
+
+ create_tables();
+
+ /*
+ * dns_keytable_find() requires exact name match. It matches node
+ * that has a null key, too.
+ */
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("example.org"), &keynode),
+ ISC_R_NOTFOUND);
+ assert_int_equal(dns_keytable_find(keytable,
+ str2name("sub.example.com"),
+ &keynode),
+ ISC_R_NOTFOUND);
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("example.com"), &keynode),
+ ISC_R_SUCCESS);
+ dns_keytable_detachkeynode(keytable, &keynode);
+ assert_int_equal(
+ dns_keytable_find(keytable, str2name("null.example"), &keynode),
+ ISC_R_SUCCESS);
+ dns_keytable_detachkeynode(keytable, &keynode);
+
+ /*
+ * dns_keytable_finddeepestmatch() allows partial match. Also match
+ * nodes with a null key.
+ */
+ name = dns_fixedname_initname(&fname);
+ assert_int_equal(dns_keytable_finddeepestmatch(
+ keytable, str2name("example.com"), name),
+ ISC_R_SUCCESS);
+ assert_true(dns_name_equal(name, str2name("example.com")));
+ assert_int_equal(dns_keytable_finddeepestmatch(
+ keytable, str2name("s.example.com"), name),
+ ISC_R_SUCCESS);
+ assert_true(dns_name_equal(name, str2name("example.com")));
+ assert_int_equal(dns_keytable_finddeepestmatch(
+ keytable, str2name("example.org"), name),
+ ISC_R_NOTFOUND);
+ assert_int_equal(dns_keytable_finddeepestmatch(
+ keytable, str2name("null.example"), name),
+ ISC_R_SUCCESS);
+ assert_true(dns_name_equal(name, str2name("null.example")));
+
+ destroy_tables();
+}
+
+/* check issecuredomain() */
+static void
+issecuredomain_test(void **state) {
+ bool issecure;
+ const char **n;
+ const char *names[] = { "example.com", "sub.example.com",
+ "null.example", "sub.null.example", NULL };
+
+ UNUSED(state);
+ create_tables();
+
+ /*
+ * Domains that are an exact or partial match of a key name are
+ * considered secure. It's the case even if the key is null
+ * (validation will then fail, but that's actually the intended effect
+ * of installing a null key).
+ */
+ for (n = names; *n != NULL; n++) {
+ assert_int_equal(dns_keytable_issecuredomain(keytable,
+ str2name(*n), NULL,
+ &issecure),
+ ISC_R_SUCCESS);
+ assert_true(issecure);
+ }
+
+ /*
+ * If the key table has no entry (not even a null one) for a domain or
+ * any of its ancestors, that domain is considered insecure.
+ */
+ assert_int_equal(dns_keytable_issecuredomain(keytable,
+ str2name("example.org"),
+ NULL, &issecure),
+ ISC_R_SUCCESS);
+ assert_false(issecure);
+
+ destroy_tables();
+}
+
+/* check dns_keytable_dump() */
+static void
+dump_test(void **state) {
+ FILE *f = fopen("/dev/null", "w");
+
+ UNUSED(state);
+
+ create_tables();
+
+ /*
+ * Right now, we only confirm the dump attempt doesn't cause disruption
+ * (so we don't check the dump content).
+ */
+ assert_int_equal(dns_keytable_dump(keytable, f), ISC_R_SUCCESS);
+ fclose(f);
+
+ destroy_tables();
+}
+
+/* check negative trust anchors */
+static void
+nta_test(void **state) {
+ isc_result_t result;
+ bool issecure, covered;
+ dns_fixedname_t fn;
+ dns_name_t *keyname = dns_fixedname_name(&fn);
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ dns_rdata_ds_t ds;
+ dns_view_t *myview = NULL;
+ isc_stdtime_t now;
+
+ UNUSED(state);
+
+ result = dns_test_makeview("view", &myview);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_create(taskmgr, 0, &myview->task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_view_initsecroots(myview, dt_mctx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_view_getsecroots(myview, &keytable);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_view_initntatable(myview, taskmgr, timermgr);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_view_getntatable(myview, &ntatable);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_test_namefromstring("example", &fn);
+ create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds);
+ result = dns_keytable_add(keytable, false, false, keyname, &ds),
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_stdtime_get(&now);
+ result = dns_ntatable_add(ntatable, str2name("insecure.example"), false,
+ now, 1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Should be secure */
+ result = dns_view_issecuredomain(myview,
+ str2name("test.secure.example"), now,
+ true, &covered, &issecure);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_false(covered);
+ assert_true(issecure);
+
+ /* Should not be secure */
+ result = dns_view_issecuredomain(myview,
+ str2name("test.insecure.example"), now,
+ true, &covered, &issecure);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(covered);
+ assert_false(issecure);
+
+ /* NTA covered */
+ covered = dns_view_ntacovers(myview, now, str2name("insecure.example"),
+ dns_rootname);
+ assert_true(covered);
+
+ /* Not NTA covered */
+ covered = dns_view_ntacovers(myview, now, str2name("secure.example"),
+ dns_rootname);
+ assert_false(covered);
+
+ /* As of now + 2, the NTA should be clear */
+ result = dns_view_issecuredomain(myview,
+ str2name("test.insecure.example"),
+ now + 2, true, &covered, &issecure);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_false(covered);
+ assert_true(issecure);
+
+ /* Now check deletion */
+ result = dns_view_issecuredomain(myview, str2name("test.new.example"),
+ now, true, &covered, &issecure);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_false(covered);
+ assert_true(issecure);
+
+ result = dns_ntatable_add(ntatable, str2name("new.example"), false, now,
+ 3600);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_view_issecuredomain(myview, str2name("test.new.example"),
+ now, true, &covered, &issecure);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(covered);
+ assert_false(issecure);
+
+ result = dns_ntatable_delete(ntatable, str2name("new.example"));
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_view_issecuredomain(myview, str2name("test.new.example"),
+ now, true, &covered, &issecure);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_false(covered);
+ assert_true(issecure);
+
+ /* Clean up */
+ dns_ntatable_detach(&ntatable);
+ dns_keytable_detach(&keytable);
+ dns_view_detach(&myview);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(add_test),
+ cmocka_unit_test(delete_test),
+ cmocka_unit_test(deletekey_test),
+ cmocka_unit_test(find_test),
+ cmocka_unit_test(issecuredomain_test),
+ cmocka_unit_test(dump_test),
+ cmocka_unit_test(nta_test),
+ };
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/master_test.c b/lib/dns/tests/master_test.c
new file mode 100644
index 0000000..c060c0d
--- /dev/null
+++ b/lib/dns/tests/master_test.c
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/cache.h>
+#include <dns/callbacks.h>
+#include <dns/db.h>
+#include <dns/master.h>
+#include <dns/masterdump.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static void
+nullmsg(dns_rdatacallbacks_t *cb, const char *fmt, ...) {
+ UNUSED(cb);
+ UNUSED(fmt);
+}
+
+#define BUFLEN 255
+#define BIGBUFLEN (70 * 1024)
+#define TEST_ORIGIN "test"
+
+static dns_masterrawheader_t header;
+static bool headerset;
+
+dns_name_t dns_origin;
+char origin[sizeof(TEST_ORIGIN)];
+unsigned char name_buf[BUFLEN];
+dns_rdatacallbacks_t callbacks;
+char *include_file = NULL;
+
+static void
+rawdata_callback(dns_zone_t *zone, dns_masterrawheader_t *header);
+
+static isc_result_t
+add_callback(void *arg, const dns_name_t *owner, dns_rdataset_t *dataset) {
+ char buf[BIGBUFLEN];
+ isc_buffer_t target;
+ isc_result_t result;
+
+ UNUSED(arg);
+
+ isc_buffer_init(&target, buf, BIGBUFLEN);
+ result = dns_rdataset_totext(dataset, owner, false, false, &target);
+ return (result);
+}
+
+static void
+rawdata_callback(dns_zone_t *zone, dns_masterrawheader_t *h) {
+ UNUSED(zone);
+ header = *h;
+ headerset = true;
+}
+
+static isc_result_t
+setup_master(void (*warn)(struct dns_rdatacallbacks *, const char *, ...),
+ void (*error)(struct dns_rdatacallbacks *, const char *, ...)) {
+ isc_result_t result;
+ int len;
+ isc_buffer_t source;
+ isc_buffer_t target;
+
+ strlcpy(origin, TEST_ORIGIN, sizeof(origin));
+ len = strlen(origin);
+ isc_buffer_init(&source, origin, len);
+ isc_buffer_add(&source, len);
+ isc_buffer_setactive(&source, len);
+ isc_buffer_init(&target, name_buf, BUFLEN);
+ dns_name_init(&dns_origin, NULL);
+ dns_master_initrawheader(&header);
+
+ result = dns_name_fromtext(&dns_origin, &source, dns_rootname, 0,
+ &target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdatacallbacks_init_stdio(&callbacks);
+ callbacks.add = add_callback;
+ callbacks.rawdata = rawdata_callback;
+ callbacks.zone = NULL;
+ if (warn != NULL) {
+ callbacks.warn = warn;
+ }
+ if (error != NULL) {
+ callbacks.error = error;
+ }
+ headerset = false;
+ return (result);
+}
+
+static isc_result_t
+test_master(const char *testfile, dns_masterformat_t format,
+ void (*warn)(struct dns_rdatacallbacks *, const char *, ...),
+ void (*error)(struct dns_rdatacallbacks *, const char *, ...)) {
+ isc_result_t result;
+
+ result = setup_master(warn, error);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_rdatacallbacks_init_stdio(&callbacks);
+ callbacks.add = add_callback;
+ callbacks.rawdata = rawdata_callback;
+ callbacks.zone = NULL;
+ if (warn != NULL) {
+ callbacks.warn = warn;
+ }
+ if (error != NULL) {
+ callbacks.error = error;
+ }
+
+ result = dns_master_loadfile(testfile, &dns_origin, &dns_origin,
+ dns_rdataclass_in, true, 0, &callbacks,
+ NULL, NULL, dt_mctx, format, 0);
+ return (result);
+}
+
+static void
+include_callback(const char *filename, void *arg) {
+ char **argp = (char **)arg;
+ *argp = isc_mem_strdup(dt_mctx, filename);
+}
+
+/*
+ * Successful load test:
+ * dns_master_loadfile() loads a valid master file and returns success
+ */
+static void
+load_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master1.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, ISC_R_SUCCESS);
+}
+
+/*
+ * Unexpected end of file test:
+ * dns_master_loadfile() returns DNS_R_UNEXPECTED when file ends too soon
+ */
+static void
+unexpected_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master2.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, ISC_R_UNEXPECTEDEND);
+}
+
+/*
+ * No owner test:
+ * dns_master_loadfile() accepts broken zones with no TTL for first record
+ * if it is an SOA
+ */
+static void
+noowner_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master3.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, DNS_R_NOOWNER);
+}
+
+/*
+ * No TTL test:
+ * dns_master_loadfile() returns DNS_R_NOOWNER when no owner name is
+ * specified
+ */
+static void
+nottl_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master4.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, ISC_R_SUCCESS);
+}
+
+/*
+ * Bad class test:
+ * dns_master_loadfile() returns DNS_R_BADCLASS when record class doesn't
+ * match zone class
+ */
+static void
+badclass_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master5.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, DNS_R_BADCLASS);
+}
+
+/*
+ * Too big rdata test:
+ * dns_master_loadfile() returns ISC_R_NOSPACE when record is too big
+ */
+static void
+toobig_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master15.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, ISC_R_NOSPACE);
+}
+
+/*
+ * Maximum rdata test:
+ * dns_master_loadfile() returns ISC_R_SUCCESS when record is maximum size
+ */
+static void
+maxrdata_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master16.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, ISC_R_SUCCESS);
+}
+
+/*
+ * DNSKEY test:
+ * dns_master_loadfile() understands DNSKEY with key material
+ */
+static void
+dnskey_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master6.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, ISC_R_SUCCESS);
+}
+
+/*
+ * DNSKEY with no key material test:
+ * dns_master_loadfile() understands DNSKEY with no key material
+ *
+ * RFC 4034 removed the ability to signal NOKEY, so empty key material should
+ * be rejected.
+ */
+static void
+dnsnokey_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master7.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, ISC_R_UNEXPECTEDEND);
+}
+
+/*
+ * Include test:
+ * dns_master_loadfile() understands $INCLUDE
+ */
+static void
+include_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master8.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, DNS_R_SEENINCLUDE);
+}
+
+/*
+ * Include file list test:
+ * dns_master_loadfile4() returns names of included file
+ */
+static void
+master_includelist_test(void **state) {
+ isc_result_t result;
+ char *filename = NULL;
+
+ UNUSED(state);
+
+ result = setup_master(nullmsg, nullmsg);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_master_loadfile(
+ "testdata/master/master8.data", &dns_origin, &dns_origin,
+ dns_rdataclass_in, 0, true, &callbacks, include_callback,
+ &filename, dt_mctx, dns_masterformat_text, 0);
+ assert_int_equal(result, DNS_R_SEENINCLUDE);
+ assert_non_null(filename);
+ if (filename != NULL) {
+ assert_string_equal(filename, "testdata/master/master6.data");
+ isc_mem_free(dt_mctx, filename);
+ }
+}
+
+/*
+ * Include failure test:
+ * dns_master_loadfile() understands $INCLUDE failures
+ */
+static void
+includefail_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master9.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, DNS_R_BADCLASS);
+}
+
+/*
+ * Non-empty blank lines test:
+ * dns_master_loadfile() handles non-empty blank lines
+ */
+static void
+blanklines_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master10.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, ISC_R_SUCCESS);
+}
+
+/*
+ * SOA leading zeroes test:
+ * dns_master_loadfile() allows leading zeroes in SOA
+ */
+
+static void
+leadingzero_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = test_master("testdata/master/master11.data",
+ dns_masterformat_text, nullmsg, nullmsg);
+ assert_int_equal(result, ISC_R_SUCCESS);
+}
+
+/* masterfile totext tests */
+static void
+totext_test(void **state) {
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+ dns_rdatalist_t rdatalist;
+ isc_buffer_t target;
+ unsigned char buf[BIGBUFLEN];
+
+ UNUSED(state);
+
+ /* First, test with an empty rdataset */
+ dns_rdatalist_init(&rdatalist);
+ rdatalist.rdclass = dns_rdataclass_in;
+ rdatalist.type = dns_rdatatype_none;
+ rdatalist.covers = dns_rdatatype_none;
+
+ dns_rdataset_init(&rdataset);
+ result = dns_rdatalist_tordataset(&rdatalist, &rdataset);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_init(&target, buf, BIGBUFLEN);
+ result = dns_master_rdatasettotext(dns_rootname, &rdataset,
+ &dns_master_style_debug, NULL,
+ &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_buffer_usedlength(&target), 0);
+
+ /*
+ * XXX: We will also need to add tests for dumping various
+ * rdata types, classes, etc, and comparing the results against
+ * known-good output.
+ */
+}
+
+/*
+ * Raw load test:
+ * dns_master_loadfile() loads a valid raw file and returns success
+ */
+static void
+loadraw_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ /* Raw format version 0 */
+ result = test_master("testdata/master/master12.data",
+ dns_masterformat_raw, nullmsg, nullmsg);
+ assert_string_equal(isc_result_totext(result), "success");
+ assert_true(headerset);
+ assert_int_equal(header.flags, 0);
+
+ /* Raw format version 1, no source serial */
+ result = test_master("testdata/master/master13.data",
+ dns_masterformat_raw, nullmsg, nullmsg);
+ assert_string_equal(isc_result_totext(result), "success");
+ assert_true(headerset);
+ assert_int_equal(header.flags, 0);
+
+ /* Raw format version 1, source serial == 2011120101 */
+ result = test_master("testdata/master/master14.data",
+ dns_masterformat_raw, nullmsg, nullmsg);
+ assert_string_equal(isc_result_totext(result), "success");
+ assert_true(headerset);
+ assert_true((header.flags & DNS_MASTERRAW_SOURCESERIALSET) != 0);
+ assert_int_equal(header.sourceserial, 2011120101);
+}
+
+/*
+ * Raw dump test:
+ * dns_master_dump*() functions dump valid raw files
+ */
+static void
+dumpraw_test(void **state) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ dns_dbversion_t *version = NULL;
+ char myorigin[sizeof(TEST_ORIGIN)];
+ dns_name_t dnsorigin;
+ isc_buffer_t source, target;
+ unsigned char namebuf[BUFLEN];
+ int len;
+
+ UNUSED(state);
+
+ strlcpy(myorigin, TEST_ORIGIN, sizeof(myorigin));
+ len = strlen(myorigin);
+ isc_buffer_init(&source, myorigin, len);
+ isc_buffer_add(&source, len);
+ isc_buffer_setactive(&source, len);
+ isc_buffer_init(&target, namebuf, BUFLEN);
+ dns_name_init(&dnsorigin, NULL);
+ result = dns_name_fromtext(&dnsorigin, &source, dns_rootname, 0,
+ &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_create(dt_mctx, "rbt", &dnsorigin, dns_dbtype_zone,
+ dns_rdataclass_in, 0, NULL, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_load(db, "testdata/master/master1.data",
+ dns_masterformat_text, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_db_currentversion(db, &version);
+
+ result = dns_master_dump(dt_mctx, db, version,
+ &dns_master_style_default, "test.dump",
+ dns_masterformat_raw, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = test_master("test.dump", dns_masterformat_raw, nullmsg,
+ nullmsg);
+ assert_string_equal(isc_result_totext(result), "success");
+ assert_true(headerset);
+ assert_int_equal(header.flags, 0);
+
+ dns_master_initrawheader(&header);
+ header.sourceserial = 12345;
+ header.flags |= DNS_MASTERRAW_SOURCESERIALSET;
+
+ unlink("test.dump");
+ result = dns_master_dump(dt_mctx, db, version,
+ &dns_master_style_default, "test.dump",
+ dns_masterformat_raw, &header);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = test_master("test.dump", dns_masterformat_raw, nullmsg,
+ nullmsg);
+ assert_string_equal(isc_result_totext(result), "success");
+ assert_true(headerset);
+ assert_true((header.flags & DNS_MASTERRAW_SOURCESERIALSET) != 0);
+ assert_int_equal(header.sourceserial, 12345);
+
+ unlink("test.dump");
+ dns_db_closeversion(db, &version, false);
+ dns_db_detach(&db);
+}
+
+static const char *warn_expect_value;
+static bool warn_expect_result;
+
+static void
+warn_expect(struct dns_rdatacallbacks *mycallbacks, const char *fmt, ...) {
+ char buf[4096];
+ va_list ap;
+
+ UNUSED(mycallbacks);
+
+ warn_expect_result = false;
+
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ if (warn_expect_value != NULL && strstr(buf, warn_expect_value) != NULL)
+ {
+ warn_expect_result = true;
+ }
+}
+
+/*
+ * Origin change test:
+ * dns_master_loadfile() rejects zones with inherited name following $ORIGIN
+ */
+static void
+neworigin_test(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ warn_expect_value = "record with inherited owner";
+ result = test_master("testdata/master/master17.data",
+ dns_masterformat_text, warn_expect, nullmsg);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(warn_expect_result);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(load_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(unexpected_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(noowner_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(nottl_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(badclass_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(dnskey_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(dnsnokey_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(include_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(master_includelist_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(includefail_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(blanklines_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(leadingzero_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(totext_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(loadraw_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(dumpraw_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(toobig_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(maxrdata_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(neworigin_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/mkraw.pl b/lib/dns/tests/mkraw.pl
new file mode 100644
index 0000000..5e0db75
--- /dev/null
+++ b/lib/dns/tests/mkraw.pl
@@ -0,0 +1,26 @@
+#!/usr/bin/perl -w
+
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+# Convert a hexdump to binary format.
+#
+# To convert binary data to the input format for this command,
+# use the following:
+#
+# perl -e 'while (read(STDIN, my $byte, 1)) {
+# print unpack("H2", $byte);
+# }
+# print "\n";' < file > file.in
+
+use strict;
+chomp(my $line = <STDIN>);
+print pack("H*", $line);
diff --git a/lib/dns/tests/name_test.c b/lib/dns/tests/name_test.c
new file mode 100644
index 0000000..e48c64e
--- /dev/null
+++ b/lib/dns/tests/name_test.c
@@ -0,0 +1,796 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/mem.h>
+#include <isc/os.h>
+#include <isc/print.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <dns/compress.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+
+#include "dnstest.h"
+
+/* Set to true (or use -v option) for verbose output */
+static bool verbose = false;
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+/* dns_name_fullcompare test */
+static void
+fullcompare_test(void **state) {
+ dns_fixedname_t fixed1;
+ dns_fixedname_t fixed2;
+ dns_name_t *name1;
+ dns_name_t *name2;
+ dns_namereln_t relation;
+ int i;
+ isc_result_t result;
+ struct {
+ const char *name1;
+ const char *name2;
+ dns_namereln_t relation;
+ int order;
+ unsigned int nlabels;
+ } data[] = {
+ /* relative */
+ { "", "", dns_namereln_equal, 0, 0 },
+ { "foo", "", dns_namereln_subdomain, 1, 0 },
+ { "", "foo", dns_namereln_contains, -1, 0 },
+ { "foo", "bar", dns_namereln_none, 4, 0 },
+ { "bar", "foo", dns_namereln_none, -4, 0 },
+ { "bar.foo", "foo", dns_namereln_subdomain, 1, 1 },
+ { "foo", "bar.foo", dns_namereln_contains, -1, 1 },
+ { "baz.bar.foo", "bar.foo", dns_namereln_subdomain, 1, 2 },
+ { "bar.foo", "baz.bar.foo", dns_namereln_contains, -1, 2 },
+ { "foo.example", "bar.example", dns_namereln_commonancestor, 4,
+ 1 },
+
+ /* absolute */
+ { ".", ".", dns_namereln_equal, 0, 1 },
+ { "foo.", "bar.", dns_namereln_commonancestor, 4, 1 },
+ { "bar.", "foo.", dns_namereln_commonancestor, -4, 1 },
+ { "foo.example.", "bar.example.", dns_namereln_commonancestor,
+ 4, 2 },
+ { "bar.foo.", "foo.", dns_namereln_subdomain, 1, 2 },
+ { "foo.", "bar.foo.", dns_namereln_contains, -1, 2 },
+ { "baz.bar.foo.", "bar.foo.", dns_namereln_subdomain, 1, 3 },
+ { "bar.foo.", "baz.bar.foo.", dns_namereln_contains, -1, 3 },
+ { NULL, NULL, dns_namereln_none, 0, 0 }
+ };
+
+ UNUSED(state);
+
+ name1 = dns_fixedname_initname(&fixed1);
+ name2 = dns_fixedname_initname(&fixed2);
+ for (i = 0; data[i].name1 != NULL; i++) {
+ int order = 3000;
+ unsigned int nlabels = 3000;
+
+ if (data[i].name1[0] == 0) {
+ dns_fixedname_init(&fixed1);
+ } else {
+ result = dns_name_fromstring2(name1, data[i].name1,
+ NULL, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+ if (data[i].name2[0] == 0) {
+ dns_fixedname_init(&fixed2);
+ } else {
+ result = dns_name_fromstring2(name2, data[i].name2,
+ NULL, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+ relation = dns_name_fullcompare(name1, name1, &order, &nlabels);
+ assert_int_equal(relation, dns_namereln_equal);
+ assert_int_equal(order, 0);
+ assert_int_equal(nlabels, name1->labels);
+
+ /* Some random initializer */
+ order = 3001;
+ nlabels = 3001;
+
+ relation = dns_name_fullcompare(name1, name2, &order, &nlabels);
+ assert_int_equal(relation, data[i].relation);
+ assert_int_equal(order, data[i].order);
+ assert_int_equal(nlabels, data[i].nlabels);
+ }
+}
+
+static void
+compress_test(dns_name_t *name1, dns_name_t *name2, dns_name_t *name3,
+ unsigned char *expected, unsigned int length,
+ dns_compress_t *cctx, dns_decompress_t *dctx) {
+ isc_buffer_t source;
+ isc_buffer_t target;
+ dns_name_t name;
+ unsigned char buf1[1024];
+ unsigned char buf2[1024];
+
+ isc_buffer_init(&source, buf1, sizeof(buf1));
+ isc_buffer_init(&target, buf2, sizeof(buf2));
+
+ assert_int_equal(dns_name_towire(name1, cctx, &source), ISC_R_SUCCESS);
+
+ assert_int_equal(dns_name_towire(name2, cctx, &source), ISC_R_SUCCESS);
+ assert_int_equal(dns_name_towire(name2, cctx, &source), ISC_R_SUCCESS);
+ assert_int_equal(dns_name_towire(name3, cctx, &source), ISC_R_SUCCESS);
+
+ isc_buffer_setactive(&source, source.used);
+
+ dns_name_init(&name, NULL);
+ RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, 0, &target) ==
+ ISC_R_SUCCESS);
+ RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, 0, &target) ==
+ ISC_R_SUCCESS);
+ RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, 0, &target) ==
+ ISC_R_SUCCESS);
+ RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, 0, &target) ==
+ ISC_R_SUCCESS);
+ dns_decompress_invalidate(dctx);
+
+ assert_int_equal(target.used, length);
+ assert_true(memcmp(target.base, expected, target.used) == 0);
+}
+
+/* name compression test */
+static void
+compression_test(void **state) {
+ unsigned int allowed;
+ dns_compress_t cctx;
+ dns_decompress_t dctx;
+ dns_name_t name1;
+ dns_name_t name2;
+ dns_name_t name3;
+ isc_region_t r;
+ unsigned char plain1[] = "\003yyy\003foo";
+ unsigned char plain2[] = "\003bar\003yyy\003foo";
+ unsigned char plain3[] = "\003xxx\003bar\003foo";
+ unsigned char plain[] = "\003yyy\003foo\0\003bar\003yyy\003foo\0\003"
+ "bar\003yyy\003foo\0\003xxx\003bar\003foo";
+
+ UNUSED(state);
+
+ dns_name_init(&name1, NULL);
+ r.base = plain1;
+ r.length = sizeof(plain1);
+ dns_name_fromregion(&name1, &r);
+
+ dns_name_init(&name2, NULL);
+ r.base = plain2;
+ r.length = sizeof(plain2);
+ dns_name_fromregion(&name2, &r);
+
+ dns_name_init(&name3, NULL);
+ r.base = plain3;
+ r.length = sizeof(plain3);
+ dns_name_fromregion(&name3, &r);
+
+ /* Test 1: NONE */
+ allowed = DNS_COMPRESS_NONE;
+ assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS);
+ dns_compress_setmethods(&cctx, allowed);
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT);
+ dns_decompress_setmethods(&dctx, allowed);
+
+ compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx,
+ &dctx);
+
+ dns_compress_rollback(&cctx, 0);
+ dns_compress_invalidate(&cctx);
+
+ /* Test2: GLOBAL14 */
+ allowed = DNS_COMPRESS_GLOBAL14;
+ assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS);
+ dns_compress_setmethods(&cctx, allowed);
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT);
+ dns_decompress_setmethods(&dctx, allowed);
+
+ compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx,
+ &dctx);
+
+ dns_compress_rollback(&cctx, 0);
+ dns_compress_invalidate(&cctx);
+
+ /* Test3: ALL */
+ allowed = DNS_COMPRESS_ALL;
+ assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS);
+ dns_compress_setmethods(&cctx, allowed);
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT);
+ dns_decompress_setmethods(&dctx, allowed);
+
+ compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx,
+ &dctx);
+
+ dns_compress_rollback(&cctx, 0);
+ dns_compress_invalidate(&cctx);
+
+ /* Test4: NONE disabled */
+ allowed = DNS_COMPRESS_NONE;
+ assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS);
+ dns_compress_setmethods(&cctx, allowed);
+ dns_compress_disable(&cctx);
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT);
+ dns_decompress_setmethods(&dctx, allowed);
+
+ compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx,
+ &dctx);
+
+ dns_compress_rollback(&cctx, 0);
+ dns_compress_invalidate(&cctx);
+
+ /* Test5: GLOBAL14 disabled */
+ allowed = DNS_COMPRESS_GLOBAL14;
+ assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS);
+ dns_compress_setmethods(&cctx, allowed);
+ dns_compress_disable(&cctx);
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT);
+ dns_decompress_setmethods(&dctx, allowed);
+
+ compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx,
+ &dctx);
+
+ dns_compress_rollback(&cctx, 0);
+ dns_compress_invalidate(&cctx);
+
+ /* Test6: ALL disabled */
+ allowed = DNS_COMPRESS_ALL;
+ assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS);
+ dns_compress_setmethods(&cctx, allowed);
+ dns_compress_disable(&cctx);
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT);
+ dns_decompress_setmethods(&dctx, allowed);
+
+ compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx,
+ &dctx);
+
+ dns_compress_rollback(&cctx, 0);
+ dns_compress_invalidate(&cctx);
+}
+
+/* is trust-anchor-telemetry test */
+static void
+istat_test(void **state) {
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ isc_result_t result;
+ size_t i;
+ struct {
+ const char *name;
+ bool istat;
+ } data[] = { { ".", false },
+ { "_ta-", false },
+ { "_ta-1234", true },
+ { "_TA-1234", true },
+ { "+TA-1234", false },
+ { "_fa-1234", false },
+ { "_td-1234", false },
+ { "_ta_1234", false },
+ { "_ta-g234", false },
+ { "_ta-1h34", false },
+ { "_ta-12i4", false },
+ { "_ta-123j", false },
+ { "_ta-1234-abcf", true },
+ { "_ta-1234-abcf-ED89", true },
+ { "_ta-12345-abcf-ED89", false },
+ { "_ta-.example", false },
+ { "_ta-1234.example", true },
+ { "_ta-1234-abcf.example", true },
+ { "_ta-1234-abcf-ED89.example", true },
+ { "_ta-12345-abcf-ED89.example", false },
+ { "_ta-1234-abcfe-ED89.example", false },
+ { "_ta-1234-abcf-EcD89.example", false } };
+
+ UNUSED(state);
+
+ name = dns_fixedname_initname(&fixed);
+
+ for (i = 0; i < (sizeof(data) / sizeof(data[0])); i++) {
+ result = dns_name_fromstring(name, data[i].name, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(dns_name_istat(name), data[i].istat);
+ }
+}
+
+/* dns_nane_init */
+static void
+init_test(void **state) {
+ dns_name_t name;
+ unsigned char offsets[1];
+
+ UNUSED(state);
+
+ dns_name_init(&name, offsets);
+
+ assert_null(name.ndata);
+ assert_int_equal(name.length, 0);
+ assert_int_equal(name.labels, 0);
+ assert_int_equal(name.attributes, 0);
+ assert_ptr_equal(name.offsets, offsets);
+ assert_null(name.buffer);
+}
+
+/* dns_nane_invalidate */
+static void
+invalidate_test(void **state) {
+ dns_name_t name;
+ unsigned char offsets[1];
+
+ UNUSED(state);
+
+ dns_name_init(&name, offsets);
+ dns_name_invalidate(&name);
+
+ assert_null(name.ndata);
+ assert_int_equal(name.length, 0);
+ assert_int_equal(name.labels, 0);
+ assert_int_equal(name.attributes, 0);
+ assert_null(name.offsets);
+ assert_null(name.buffer);
+}
+
+/* dns_nane_setbuffer/hasbuffer */
+static void
+buffer_test(void **state) {
+ dns_name_t name;
+ unsigned char buf[BUFSIZ];
+ isc_buffer_t b;
+
+ UNUSED(state);
+
+ isc_buffer_init(&b, buf, BUFSIZ);
+ dns_name_init(&name, NULL);
+ dns_name_setbuffer(&name, &b);
+ assert_ptr_equal(name.buffer, &b);
+ assert_true(dns_name_hasbuffer(&name));
+}
+
+/* dns_nane_isabsolute */
+static void
+isabsolute_test(void **state) {
+ struct {
+ const char *namestr;
+ bool expect;
+ } testcases[] = { { "x", false },
+ { "a.b.c.d.", true },
+ { "x.z", false } };
+ unsigned int i;
+
+ UNUSED(state);
+
+ for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) {
+ isc_result_t result;
+ dns_name_t name;
+ unsigned char data[BUFSIZ];
+ isc_buffer_t b, nb;
+ size_t len;
+
+ len = strlen(testcases[i].namestr);
+ isc_buffer_constinit(&b, testcases[i].namestr, len);
+ isc_buffer_add(&b, len);
+
+ dns_name_init(&name, NULL);
+ isc_buffer_init(&nb, data, BUFSIZ);
+ dns_name_setbuffer(&name, &nb);
+ result = dns_name_fromtext(&name, &b, NULL, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_int_equal(dns_name_isabsolute(&name),
+ testcases[i].expect);
+ }
+}
+
+/* dns_nane_hash */
+static void
+hash_test(void **state) {
+ struct {
+ const char *name1;
+ const char *name2;
+ bool expect;
+ bool expecti;
+ } testcases[] = {
+ { "a.b.c.d", "A.B.C.D", true, false },
+ { "a.b.c.d.", "A.B.C.D.", true, false },
+ { "a.b.c.d", "a.b.c.d", true, true },
+ { "A.B.C.D.", "A.B.C.D.", true, false },
+ { "x.y.z.w", "a.b.c.d", false, false },
+ { "x.y.z.w.", "a.b.c.d.", false, false },
+ };
+ unsigned int i;
+
+ UNUSED(state);
+
+ for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) {
+ isc_result_t result;
+ dns_fixedname_t f1, f2;
+ dns_name_t *n1, *n2;
+ unsigned int h1, h2;
+
+ n1 = dns_fixedname_initname(&f1);
+ n2 = dns_fixedname_initname(&f2);
+
+ result = dns_name_fromstring2(n1, testcases[i].name1, NULL, 0,
+ NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_name_fromstring2(n2, testcases[i].name2, NULL, 0,
+ NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Check case-insensitive hashing first */
+ h1 = dns_name_hash(n1, false);
+ h2 = dns_name_hash(n2, false);
+
+ if (verbose) {
+ print_message("# %s hashes to %u, "
+ "%s to %u, case insensitive\n",
+ testcases[i].name1, h1,
+ testcases[i].name2, h2);
+ }
+
+ assert_int_equal((h1 == h2), testcases[i].expect);
+
+ /* Now case-sensitive */
+ h1 = dns_name_hash(n1, false);
+ h2 = dns_name_hash(n2, false);
+
+ if (verbose) {
+ print_message("# %s hashes to %u, "
+ "%s to %u, case sensitive\n",
+ testcases[i].name1, h1,
+ testcases[i].name2, h2);
+ }
+
+ assert_int_equal((h1 == h2), testcases[i].expect);
+ }
+}
+
+/* dns_nane_issubdomain */
+static void
+issubdomain_test(void **state) {
+ struct {
+ const char *name1;
+ const char *name2;
+ bool expect;
+ } testcases[] = {
+ { "c.d", "a.b.c.d", false }, { "c.d.", "a.b.c.d.", false },
+ { "b.c.d", "c.d", true }, { "a.b.c.d.", "c.d.", true },
+ { "a.b.c", "a.b.c", true }, { "a.b.c.", "a.b.c.", true },
+ { "x.y.z", "a.b.c", false }
+ };
+ unsigned int i;
+
+ UNUSED(state);
+
+ for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) {
+ isc_result_t result;
+ dns_fixedname_t f1, f2;
+ dns_name_t *n1, *n2;
+
+ n1 = dns_fixedname_initname(&f1);
+ n2 = dns_fixedname_initname(&f2);
+
+ result = dns_name_fromstring2(n1, testcases[i].name1, NULL, 0,
+ NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_name_fromstring2(n2, testcases[i].name2, NULL, 0,
+ NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ if (verbose) {
+ print_message("# check: %s %s a subdomain of %s\n",
+ testcases[i].name1,
+ testcases[i].expect ? "is" : "is not",
+ testcases[i].name2);
+ }
+
+ assert_int_equal(dns_name_issubdomain(n1, n2),
+ testcases[i].expect);
+ }
+}
+
+/* dns_nane_countlabels */
+static void
+countlabels_test(void **state) {
+ struct {
+ const char *namestr;
+ unsigned int expect;
+ } testcases[] = {
+ { "c.d", 2 }, { "c.d.", 3 }, { "a.b.c.d.", 5 },
+ { "a.b.c.d", 4 }, { "a.b.c", 3 }, { ".", 1 },
+ };
+ unsigned int i;
+
+ UNUSED(state);
+
+ for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) {
+ isc_result_t result;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ name = dns_fixedname_initname(&fname);
+
+ result = dns_name_fromstring2(name, testcases[i].namestr, NULL,
+ 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ if (verbose) {
+ print_message("# %s: expect %u labels\n",
+ testcases[i].namestr,
+ testcases[i].expect);
+ }
+
+ assert_int_equal(dns_name_countlabels(name),
+ testcases[i].expect);
+ }
+}
+
+/* dns_nane_getlabel */
+static void
+getlabel_test(void **state) {
+ struct {
+ const char *name1;
+ unsigned int pos1;
+ const char *name2;
+ unsigned int pos2;
+ } testcases[] = {
+ { "c.d", 1, "a.b.c.d", 3 },
+ { "a.b.c.d", 3, "c.d", 1 },
+ { "a.b.c.", 3, "A.B.C.", 3 },
+ };
+ unsigned int i;
+
+ UNUSED(state);
+
+ for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) {
+ isc_result_t result;
+ dns_fixedname_t f1, f2;
+ dns_name_t *n1, *n2;
+ dns_label_t l1, l2;
+ unsigned int j;
+
+ n1 = dns_fixedname_initname(&f1);
+ n2 = dns_fixedname_initname(&f2);
+
+ result = dns_name_fromstring2(n1, testcases[i].name1, NULL, 0,
+ NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_name_fromstring2(n2, testcases[i].name2, NULL, 0,
+ NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_name_getlabel(n1, testcases[i].pos1, &l1);
+ dns_name_getlabel(n2, testcases[i].pos2, &l2);
+ assert_int_equal(l1.length, l2.length);
+
+ for (j = 0; j < l1.length; j++) {
+ assert_int_equal(l1.base[j], l2.base[j]);
+ }
+ }
+}
+
+/* dns_nane_getlabelsequence */
+static void
+getlabelsequence_test(void **state) {
+ struct {
+ const char *name1;
+ unsigned int pos1;
+ const char *name2;
+ unsigned int pos2;
+ unsigned int range;
+ } testcases[] = {
+ { "c.d", 1, "a.b.c.d", 3, 1 },
+ { "a.b.c.d.e", 2, "c.d", 0, 2 },
+ { "a.b.c", 0, "a.b.c", 0, 3 },
+ };
+ unsigned int i;
+
+ UNUSED(state);
+
+ for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) {
+ isc_result_t result;
+ dns_name_t t1, t2;
+ dns_fixedname_t f1, f2;
+ dns_name_t *n1, *n2;
+
+ /* target names */
+ dns_name_init(&t1, NULL);
+ dns_name_init(&t2, NULL);
+
+ /* source names */
+ n1 = dns_fixedname_initname(&f1);
+ n2 = dns_fixedname_initname(&f2);
+
+ result = dns_name_fromstring2(n1, testcases[i].name1, NULL, 0,
+ NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_name_fromstring2(n2, testcases[i].name2, NULL, 0,
+ NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_name_getlabelsequence(n1, testcases[i].pos1,
+ testcases[i].range, &t1);
+ dns_name_getlabelsequence(n2, testcases[i].pos2,
+ testcases[i].range, &t2);
+
+ assert_true(dns_name_equal(&t1, &t2));
+ }
+}
+
+#ifdef DNS_BENCHMARK_TESTS
+
+/*
+ * XXXMUKS: Don't delete this code. It is useful in benchmarking the
+ * name parser, but we don't require it as part of the unit test runs.
+ */
+
+/* Benchmark dns_name_fromwire() implementation */
+
+static void *
+fromwire_thread(void *arg) {
+ unsigned int maxval = 32000000;
+ uint8_t data[] = { 3, 'w', 'w', 'w', 7, 'e', 'x',
+ 'a', 'm', 'p', 'l', 'e', 7, 'i',
+ 'n', 'v', 'a', 'l', 'i', 'd', 0 };
+ unsigned char output_data[DNS_NAME_MAXWIRE];
+ isc_buffer_t source, target;
+ unsigned int i;
+ dns_decompress_t dctx;
+
+ UNUSED(arg);
+
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT);
+ dns_decompress_setmethods(&dctx, DNS_COMPRESS_NONE);
+
+ isc_buffer_init(&source, data, sizeof(data));
+ isc_buffer_add(&source, sizeof(data));
+ isc_buffer_init(&target, output_data, sizeof(output_data));
+
+ /* Parse 32 million names in each thread */
+ for (i = 0; i < maxval; i++) {
+ dns_name_t name;
+
+ isc_buffer_clear(&source);
+ isc_buffer_clear(&target);
+ isc_buffer_add(&source, sizeof(data));
+ isc_buffer_setactive(&source, sizeof(data));
+
+ dns_name_init(&name, NULL);
+ (void)dns_name_fromwire(&name, &source, &dctx, 0, &target);
+ }
+
+ return (NULL);
+}
+
+static void
+benchmark_test(void **state) {
+ isc_result_t result;
+ unsigned int i;
+ isc_time_t ts1, ts2;
+ double t;
+ unsigned int nthreads;
+ isc_thread_t threads[32];
+
+ UNUSED(state);
+
+ debug_mem_record = false;
+
+ result = isc_time_now(&ts1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ nthreads = ISC_MIN(isc_os_ncpus(), 32);
+ nthreads = ISC_MAX(nthreads, 1);
+ for (i = 0; i < nthreads; i++) {
+ isc_thread_create(fromwire_thread, NULL, &threads[i]);
+ }
+
+ for (i = 0; i < nthreads; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ result = isc_time_now(&ts2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ t = isc_time_microdiff(&ts2, &ts1);
+
+ printf("%u dns_name_fromwire() calls, %f seconds, %f calls/second\n",
+ nthreads * 32000000, t / 1000000.0,
+ (nthreads * 32000000) / (t / 1000000.0));
+}
+
+#endif /* DNS_BENCHMARK_TESTS */
+
+int
+main(int argc, char **argv) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(fullcompare_test),
+ cmocka_unit_test_setup_teardown(compression_test, _setup,
+ _teardown),
+ cmocka_unit_test(istat_test),
+ cmocka_unit_test(init_test),
+ cmocka_unit_test(invalidate_test),
+ cmocka_unit_test(buffer_test),
+ cmocka_unit_test(isabsolute_test),
+ cmocka_unit_test(hash_test),
+ cmocka_unit_test(issubdomain_test),
+ cmocka_unit_test(countlabels_test),
+ cmocka_unit_test(getlabel_test),
+ cmocka_unit_test(getlabelsequence_test),
+#ifdef DNS_BENCHMARK_TESTS
+ cmocka_unit_test_setup_teardown(benchmark_test, _setup,
+ _teardown),
+#endif /* DNS_BENCHMARK_TESTS */
+ };
+ int c;
+
+ while ((c = isc_commandline_parse(argc, argv, "v")) != -1) {
+ switch (c) {
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/nsec3_test.c b/lib/dns/tests/nsec3_test.c
new file mode 100644
index 0000000..69f4be5
--- /dev/null
+++ b/lib/dns/tests/nsec3_test.c
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/nsec3.h>
+
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static void
+iteration_test(const char *file, unsigned int expected) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ unsigned int iterations;
+
+ result = dns_test_loaddb(&db, dns_dbtype_zone, "test", file);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ iterations = dns_nsec3_maxiterations();
+
+ assert_int_equal(iterations, expected);
+
+ dns_db_detach(&db);
+}
+
+/*%
+ * Structure containing parameters for nsec3param_salttotext_test().
+ */
+typedef struct {
+ const char *nsec3param_text; /* NSEC3PARAM RDATA in text form */
+ const char *expected_salt; /* string expected in target buffer */
+} nsec3param_salttotext_test_params_t;
+
+/*%
+ * Check whether dns_nsec3param_salttotext() handles supplied text form
+ * NSEC3PARAM RDATA correctly: test whether the result of calling the former is
+ * as expected and whether it properly checks available buffer space.
+ *
+ * Assumes supplied text form NSEC3PARAM RDATA is valid as testing handling of
+ * invalid NSEC3PARAM RDATA is out of scope of this unit test.
+ */
+static void
+nsec3param_salttotext_test(const nsec3param_salttotext_test_params_t *params) {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_nsec3param_t nsec3param;
+ unsigned char buf[1024];
+ isc_result_t result;
+ char salt[64];
+ size_t length;
+
+ /*
+ * Prepare a dns_rdata_nsec3param_t structure for testing.
+ */
+ result = dns_test_rdatafromstring(
+ &rdata, dns_rdataclass_in, dns_rdatatype_nsec3param, buf,
+ sizeof(buf), params->nsec3param_text, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_rdata_tostruct(&rdata, &nsec3param, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Check typical use.
+ */
+ result = dns_nsec3param_salttotext(&nsec3param, salt, sizeof(salt));
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(salt, params->expected_salt);
+
+ /*
+ * Ensure available space in the buffer is checked before the salt is
+ * printed to it and that the amount of space checked for includes the
+ * terminating NULL byte.
+ */
+ length = strlen(params->expected_salt);
+ assert_true(length < sizeof(salt) - 1); /* prevent buffer overwrite */
+ assert_true(length > 0U); /* prevent length underflow */
+
+ result = dns_nsec3param_salttotext(&nsec3param, salt, length - 1);
+ assert_int_equal(result, ISC_R_NOSPACE);
+
+ result = dns_nsec3param_salttotext(&nsec3param, salt, length);
+ assert_int_equal(result, ISC_R_NOSPACE);
+
+ result = dns_nsec3param_salttotext(&nsec3param, salt, length + 1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+}
+
+/*
+ * check that appropriate max iterations is returned for different
+ * key size mixes
+ */
+static void
+max_iterations(void **state) {
+ UNUSED(state);
+
+ iteration_test("testdata/nsec3/1024.db", 150);
+ iteration_test("testdata/nsec3/2048.db", 150);
+ iteration_test("testdata/nsec3/4096.db", 150);
+ iteration_test("testdata/nsec3/min-1024.db", 150);
+ iteration_test("testdata/nsec3/min-2048.db", 150);
+}
+
+/* check dns_nsec3param_salttotext() */
+static void
+nsec3param_salttotext(void **state) {
+ size_t i;
+
+ const nsec3param_salttotext_test_params_t tests[] = {
+ /*
+ * Tests with non-empty salts.
+ */
+ { "0 0 10 0123456789abcdef", "0123456789ABCDEF" },
+ { "0 1 11 0123456789abcdef", "0123456789ABCDEF" },
+ { "1 0 12 42", "42" },
+ { "1 1 13 42", "42" },
+ /*
+ * Test with empty salt.
+ */
+ { "0 0 0 -", "-" },
+ };
+
+ UNUSED(state);
+
+ for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ nsec3param_salttotext_test(&tests[i]);
+ }
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(max_iterations, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(nsec3param_salttotext, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/nsec3param_test.c b/lib/dns/tests/nsec3param_test.c
new file mode 100644
index 0000000..6fd1fc4
--- /dev/null
+++ b/lib/dns/tests/nsec3param_test.c
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/hex.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/nsec3.h>
+#include <dns/result.h>
+
+#include "../zone_p.h"
+#include "dnstest.h"
+
+#define HASH 1
+#define FLAGS 0
+#define ITER 5
+#define SALTLEN 4
+#define SALT "FEDCBA98"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+/*%
+ * Structures containing parameters for nsec3param_salttotext_test().
+ */
+typedef struct {
+ dns_hash_t hash;
+ unsigned char flags;
+ dns_iterations_t iterations;
+ unsigned char salt_length;
+ const char *salt;
+} nsec3param_rdata_test_params_t;
+
+typedef struct {
+ nsec3param_rdata_test_params_t lookup;
+ nsec3param_rdata_test_params_t expect;
+ bool resalt;
+ isc_result_t expected_result;
+} nsec3param_change_test_params_t;
+
+static void
+decode_salt(const char *string, unsigned char *salt, size_t saltlen) {
+ isc_buffer_t buf;
+ isc_result_t result;
+
+ isc_buffer_init(&buf, salt, saltlen);
+ result = isc_hex_decodestring(string, &buf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+}
+
+static void
+copy_params(nsec3param_rdata_test_params_t from, dns_rdata_nsec3param_t *to,
+ unsigned char *saltbuf, size_t saltlen) {
+ to->hash = from.hash;
+ to->flags = from.flags;
+ to->iterations = from.iterations;
+ to->salt_length = from.salt_length;
+ if (from.salt == NULL) {
+ to->salt = NULL;
+ } else if (strcmp(from.salt, "-") == 0) {
+ DE_CONST("-", to->salt);
+ } else {
+ decode_salt(from.salt, saltbuf, saltlen);
+ to->salt = saltbuf;
+ }
+}
+
+static nsec3param_rdata_test_params_t
+rdata_fromparams(uint8_t hash, uint8_t flags, uint16_t iter, uint8_t saltlen,
+ const char *salt) {
+ nsec3param_rdata_test_params_t nsec3param;
+ nsec3param.hash = hash;
+ nsec3param.flags = flags;
+ nsec3param.iterations = iter;
+ nsec3param.salt_length = saltlen;
+ nsec3param.salt = salt;
+ return (nsec3param);
+}
+
+/*%
+ * Check whether zone_lookup_nsec3param() finds the correct NSEC3PARAM
+ * and sets the correct parameters to use in dns_zone_setnsec3param().
+ */
+static void
+nsec3param_change_test(const nsec3param_change_test_params_t *test) {
+ dns_zone_t *zone = NULL;
+ dns_rdata_nsec3param_t param, lookup, expect;
+ isc_result_t result;
+ unsigned char lookupsalt[255];
+ unsigned char expectsalt[255];
+ unsigned char saltbuf[255];
+
+ /*
+ * Prepare a zone along with its signing keys.
+ */
+ result = dns_test_makezone("nsec3", &zone, NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_zone_setfile(zone, "testdata/nsec3param/nsec3.db.signed",
+ dns_masterformat_text,
+ &dns_master_style_default);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_zone_load(zone, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Copy parameters.
+ */
+ copy_params(test->lookup, &lookup, lookupsalt, sizeof(lookupsalt));
+ copy_params(test->expect, &expect, expectsalt, sizeof(expectsalt));
+
+ /*
+ * Test dns__zone_lookup_nsec3param().
+ */
+ result = dns__zone_lookup_nsec3param(zone, &lookup, &param, saltbuf,
+ test->resalt);
+ assert_int_equal(result, test->expected_result);
+ assert_int_equal(param.hash, expect.hash);
+ assert_int_equal(param.flags, expect.flags);
+ assert_int_equal(param.iterations, expect.iterations);
+ assert_int_equal(param.salt_length, expect.salt_length);
+ assert_non_null(param.salt);
+ if (expect.salt != NULL) {
+ int ret = memcmp(param.salt, expect.salt, expect.salt_length);
+ assert_true(ret == 0);
+ } else {
+ /*
+ * We don't know what the new salt is, but we can compare it
+ * to the previous salt and test that it has changed.
+ */
+ unsigned char salt[SALTLEN];
+ int ret;
+ decode_salt(SALT, salt, SALTLEN);
+ ret = memcmp(param.salt, salt, SALTLEN);
+ assert_false(ret == 0);
+ }
+
+ /*
+ * Detach.
+ */
+ dns_zone_detach(&zone);
+}
+
+static void
+nsec3param_change(void **state) {
+ size_t i;
+
+ /*
+ * Define tests.
+ */
+ const nsec3param_change_test_params_t tests[] = {
+ /*
+ * 1. Change nothing (don't care about salt).
+ * This should return ISC_R_SUCCESS because we are already
+ * using these NSEC3 parameters.
+ */
+ { rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, NULL),
+ rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, SALT), false,
+ ISC_R_SUCCESS },
+ /*
+ * 2. Change nothing, but force a resalt.
+ * This should change the salt. Set 'expect.salt' to NULL to
+ * test a new salt has been generated.
+ */
+ { rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, NULL),
+ rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, NULL), true,
+ DNS_R_NSEC3RESALT },
+ /*
+ * 3. Change iterations.
+ * The NSEC3 paarameters are not found, and there is no
+ * need to resalt because an explicit salt has been set,
+ * and resalt is not enforced.
+ */
+ { rdata_fromparams(HASH, FLAGS, 10, SALTLEN, SALT),
+ rdata_fromparams(HASH, FLAGS, 10, SALTLEN, SALT), false,
+ ISC_R_NOTFOUND },
+ /*
+ * 4. Change iterations, don't care about the salt.
+ * We don't care about the salt. Since we need to change the
+ * NSEC3 parameters, we will also resalt.
+ */
+ { rdata_fromparams(HASH, FLAGS, 10, SALTLEN, NULL),
+ rdata_fromparams(HASH, FLAGS, 10, SALTLEN, NULL), false,
+ DNS_R_NSEC3RESALT },
+ /*
+ * 5. Change salt length.
+ * Changing salt length means we need to resalt.
+ */
+ { rdata_fromparams(HASH, FLAGS, ITER, 16, NULL),
+ rdata_fromparams(HASH, FLAGS, ITER, 16, NULL), false,
+ DNS_R_NSEC3RESALT },
+ /*
+ * 6. Set explicit salt.
+ * A different salt, so the NSEC3 parameters are not found.
+ * No need to resalt because an explicit salt is available.
+ */
+ { rdata_fromparams(HASH, FLAGS, ITER, 4, "12345678"),
+ rdata_fromparams(HASH, FLAGS, ITER, 4, "12345678"), false,
+ ISC_R_NOTFOUND },
+ /*
+ * 7. Same salt.
+ * Nothing changed, so expect ISC_R_SUCCESS as a result.
+ */
+ { rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, SALT),
+ rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, SALT), false,
+ ISC_R_SUCCESS },
+ /*
+ * 8. Same salt, and force resalt.
+ * Nothing changed, but a resalt is enforced.
+ */
+ { rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, SALT),
+ rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, NULL), true,
+ DNS_R_NSEC3RESALT },
+ /*
+ * 9. No salt.
+ * Change parameters to use no salt. These parameters are
+ * not found, and no new salt needs to be generated.
+ */
+ { rdata_fromparams(HASH, FLAGS, ITER, 0, NULL),
+ rdata_fromparams(HASH, FLAGS, ITER, 0, "-"), true,
+ ISC_R_NOTFOUND },
+ /*
+ * 10. No salt, explicit.
+ * Same as above, but set no salt explicitly.
+ */
+ { rdata_fromparams(HASH, FLAGS, ITER, 0, "-"),
+ rdata_fromparams(HASH, FLAGS, ITER, 0, "-"), true,
+ ISC_R_NOTFOUND },
+ };
+
+ UNUSED(state);
+
+ /*
+ * Run tests.
+ */
+ for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ nsec3param_change_test(&tests[i]);
+ }
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(nsec3param_change, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/peer_test.c b/lib/dns/tests/peer_test.c
new file mode 100644
index 0000000..69ca8bb
--- /dev/null
+++ b/lib/dns/tests/peer_test.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/util.h>
+
+#include <dns/peer.h>
+
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+/* Test DSCP set/get functions */
+static void
+dscp(void **state) {
+ isc_result_t result;
+ isc_netaddr_t netaddr;
+ struct in_addr ina;
+ dns_peer_t *peer = NULL;
+ isc_dscp_t dscp;
+
+ UNUSED(state);
+
+ /*
+ * Create peer structure for the loopback address.
+ */
+ ina.s_addr = INADDR_LOOPBACK;
+ isc_netaddr_fromin(&netaddr, &ina);
+ result = dns_peer_new(dt_mctx, &netaddr, &peer);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * All should be not set on creation.
+ * 'dscp' should remain unchanged.
+ */
+ dscp = 100;
+ result = dns_peer_getquerydscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ assert_int_equal(dscp, 100);
+
+ result = dns_peer_getnotifydscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ assert_int_equal(dscp, 100);
+
+ result = dns_peer_gettransferdscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ assert_int_equal(dscp, 100);
+
+ /*
+ * Test that setting query dscp does not affect the other
+ * dscp values. 'dscp' should remain unchanged until
+ * dns_peer_getquerydscp is called.
+ */
+ dscp = 100;
+ result = dns_peer_setquerydscp(peer, 1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_peer_getnotifydscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ assert_int_equal(dscp, 100);
+
+ result = dns_peer_gettransferdscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ assert_int_equal(dscp, 100);
+
+ result = dns_peer_getquerydscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(dscp, 1);
+
+ /*
+ * Test that setting notify dscp does not affect the other
+ * dscp values. 'dscp' should remain unchanged until
+ * dns_peer_getquerydscp is called then should change again
+ * on dns_peer_getnotifydscp.
+ */
+ dscp = 100;
+ result = dns_peer_setnotifydscp(peer, 2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_peer_gettransferdscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ assert_int_equal(dscp, 100);
+
+ result = dns_peer_getquerydscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(dscp, 1);
+
+ result = dns_peer_getnotifydscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(dscp, 2);
+
+ /*
+ * Test that setting notify dscp does not affect the other
+ * dscp values. Check that appropriate values are returned.
+ */
+ dscp = 100;
+ result = dns_peer_settransferdscp(peer, 3);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_peer_getquerydscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(dscp, 1);
+
+ result = dns_peer_getnotifydscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(dscp, 2);
+
+ result = dns_peer_gettransferdscp(peer, &dscp);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(dscp, 3);
+
+ dns_peer_detach(&peer);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(dscp, _setup, _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/private_test.c b/lib/dns/tests/private_test.c
new file mode 100644
index 0000000..92ee391
--- /dev/null
+++ b/lib/dns/tests/private_test.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/util.h>
+
+#include <dns/nsec3.h>
+#include <dns/private.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatatype.h>
+
+#include <dst/dst.h>
+
+#include "dnstest.h"
+
+static dns_rdatatype_t privatetype = 65534;
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+typedef struct {
+ unsigned char alg;
+ dns_keytag_t keyid;
+ bool remove;
+ bool complete;
+} signing_testcase_t;
+
+typedef struct {
+ unsigned char hash;
+ unsigned char flags;
+ unsigned int iterations;
+ unsigned long salt;
+ bool remove;
+ bool pending;
+ bool nonsec;
+} nsec3_testcase_t;
+
+static void
+make_signing(signing_testcase_t *testcase, dns_rdata_t *private,
+ unsigned char *buf, size_t len) {
+ dns_rdata_init(private);
+
+ buf[0] = testcase->alg;
+ buf[1] = (testcase->keyid & 0xff00) >> 8;
+ buf[2] = (testcase->keyid & 0xff);
+ buf[3] = testcase->remove;
+ buf[4] = testcase->complete;
+ private->data = buf;
+ private->length = len;
+ private->type = privatetype;
+ private->rdclass = dns_rdataclass_in;
+}
+
+static void
+make_nsec3(nsec3_testcase_t *testcase, dns_rdata_t *private,
+ unsigned char *pbuf) {
+ dns_rdata_nsec3param_t params;
+ dns_rdata_t nsec3param = DNS_RDATA_INIT;
+ unsigned char bufdata[BUFSIZ];
+ isc_buffer_t buf;
+ uint32_t salt;
+ unsigned char *sp;
+ int slen = 4;
+
+ /* for simplicity, we're using a maximum salt length of 4 */
+ salt = htonl(testcase->salt);
+ sp = (unsigned char *)&salt;
+ while (slen > 0 && *sp == '\0') {
+ slen--;
+ sp++;
+ }
+
+ params.common.rdclass = dns_rdataclass_in;
+ params.common.rdtype = dns_rdatatype_nsec3param;
+ params.hash = testcase->hash;
+ params.iterations = testcase->iterations;
+ params.salt = sp;
+ params.salt_length = slen;
+
+ params.flags = testcase->flags;
+ if (testcase->remove) {
+ params.flags |= DNS_NSEC3FLAG_REMOVE;
+ if (testcase->nonsec) {
+ params.flags |= DNS_NSEC3FLAG_NONSEC;
+ }
+ } else {
+ params.flags |= DNS_NSEC3FLAG_CREATE;
+ if (testcase->pending) {
+ params.flags |= DNS_NSEC3FLAG_INITIAL;
+ }
+ }
+
+ isc_buffer_init(&buf, bufdata, sizeof(bufdata));
+ dns_rdata_fromstruct(&nsec3param, dns_rdataclass_in,
+ dns_rdatatype_nsec3param, &params, &buf);
+
+ dns_rdata_init(private);
+
+ dns_nsec3param_toprivate(&nsec3param, private, privatetype, pbuf,
+ DNS_NSEC3PARAM_BUFFERSIZE + 1);
+}
+
+/* convert private signing records to text */
+static void
+private_signing_totext_test(void **state) {
+ dns_rdata_t private;
+ int i;
+
+ signing_testcase_t testcases[] = { { DST_ALG_RSASHA512, 12345, 0, 0 },
+ { DST_ALG_RSASHA256, 54321, 1, 0 },
+ { DST_ALG_NSEC3RSASHA1, 22222, 0,
+ 1 },
+ { DST_ALG_RSASHA1, 33333, 1, 1 } };
+ const char *results[] = { "Signing with key 12345/RSASHA512",
+ "Removing signatures for key 54321/RSASHA256",
+ "Done signing with key 22222/NSEC3RSASHA1",
+ ("Done removing signatures for key "
+ "33333/RSASHA1") };
+ int ncases = 4;
+
+ UNUSED(state);
+
+ for (i = 0; i < ncases; i++) {
+ unsigned char data[5];
+ char output[BUFSIZ];
+ isc_buffer_t buf;
+
+ isc_buffer_init(&buf, output, sizeof(output));
+
+ make_signing(&testcases[i], &private, data, sizeof(data));
+ dns_private_totext(&private, &buf);
+ assert_string_equal(output, results[i]);
+ }
+}
+
+/* convert private chain records to text */
+static void
+private_nsec3_totext_test(void **state) {
+ dns_rdata_t private;
+ int i;
+
+ nsec3_testcase_t testcases[] = {
+ { 1, 0, 1, 0xbeef, 0, 0, 0 },
+ { 1, 1, 10, 0xdadd, 0, 0, 0 },
+ { 1, 0, 20, 0xbead, 0, 1, 0 },
+ { 1, 0, 30, 0xdeaf, 1, 0, 0 },
+ { 1, 0, 100, 0xfeedabee, 1, 0, 1 },
+ };
+ const char *results[] = { "Creating NSEC3 chain 1 0 1 BEEF",
+ "Creating NSEC3 chain 1 1 10 DADD",
+ "Pending NSEC3 chain 1 0 20 BEAD",
+ ("Removing NSEC3 chain 1 0 30 DEAF / "
+ "creating NSEC chain"),
+ "Removing NSEC3 chain 1 0 100 FEEDABEE" };
+ int ncases = 5;
+
+ UNUSED(state);
+
+ for (i = 0; i < ncases; i++) {
+ unsigned char data[DNS_NSEC3PARAM_BUFFERSIZE + 1];
+ char output[BUFSIZ];
+ isc_buffer_t buf;
+
+ isc_buffer_init(&buf, output, sizeof(output));
+
+ make_nsec3(&testcases[i], &private, data);
+ dns_private_totext(&private, &buf);
+ assert_string_equal(output, results[i]);
+ }
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(private_signing_totext_test,
+ _setup, _teardown),
+ cmocka_unit_test_setup_teardown(private_nsec3_totext_test,
+ _setup, _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/rbt_serialize_test.c b/lib/dns/tests/rbt_serialize_test.c
new file mode 100644
index 0000000..df56981
--- /dev/null
+++ b/lib/dns/tests/rbt_serialize_test.c
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/app.h>
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/mem.h>
+#include <isc/os.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/socket.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/compress.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/rbt.h>
+#include <dns/result.h>
+
+#include <dst/dst.h>
+
+#include "dnstest.h"
+
+#ifndef MAP_FILE
+#define MAP_FILE 0
+#endif /* ifndef MAP_FILE */
+
+/* Set to true (or use -v option) for verbose output */
+static bool verbose = false;
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+typedef struct data_holder {
+ int len;
+ const char *data;
+} data_holder_t;
+
+typedef struct rbt_testdata {
+ const char *name;
+ size_t name_len;
+ data_holder_t data;
+} rbt_testdata_t;
+
+#define DATA_ITEM(name) \
+ { \
+ (name), sizeof(name) - 1, { sizeof(name), (name) } \
+ }
+
+rbt_testdata_t testdata[] = { DATA_ITEM("first.com."),
+ DATA_ITEM("one.net."),
+ DATA_ITEM("two.com."),
+ DATA_ITEM("three.org."),
+ DATA_ITEM("asdf.com."),
+ DATA_ITEM("ghjkl.com."),
+ DATA_ITEM("1.edu."),
+ DATA_ITEM("2.edu."),
+ DATA_ITEM("3.edu."),
+ DATA_ITEM("123.edu."),
+ DATA_ITEM("1236.com."),
+ DATA_ITEM("and_so_forth.com."),
+ DATA_ITEM("thisisalongname.com."),
+ DATA_ITEM("a.b."),
+ DATA_ITEM("test.net."),
+ DATA_ITEM("whoknows.org."),
+ DATA_ITEM("blargh.com."),
+ DATA_ITEM("www.joe.com."),
+ DATA_ITEM("test.com."),
+ DATA_ITEM("isc.org."),
+ DATA_ITEM("uiop.mil."),
+ DATA_ITEM("last.fm."),
+ { NULL, 0, { 0, NULL } } };
+
+static void
+delete_data(void *data, void *arg) {
+ UNUSED(arg);
+ UNUSED(data);
+}
+
+static isc_result_t
+write_data(FILE *file, unsigned char *datap, void *arg, uint64_t *crc) {
+ isc_result_t result;
+ size_t ret = 0;
+ data_holder_t *data;
+ data_holder_t temp;
+ off_t where;
+
+ UNUSED(arg);
+
+ REQUIRE(file != NULL);
+ REQUIRE(crc != NULL);
+ REQUIRE(datap != NULL);
+ data = (data_holder_t *)datap;
+ REQUIRE((data->len == 0 && data->data == NULL) ||
+ (data->len != 0 && data->data != NULL));
+
+ result = isc_stdio_tell(file, &where);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ temp = *data;
+ temp.data = (data->len == 0 ? NULL
+ : (char *)((uintptr_t)where +
+ sizeof(data_holder_t)));
+
+ isc_crc64_update(crc, (void *)&temp, sizeof(temp));
+ ret = fwrite(&temp, sizeof(data_holder_t), 1, file);
+ if (ret != 1) {
+ return (ISC_R_FAILURE);
+ }
+ if (data->len > 0) {
+ isc_crc64_update(crc, (const void *)data->data, data->len);
+ ret = fwrite(data->data, data->len, 1, file);
+ if (ret != 1) {
+ return (ISC_R_FAILURE);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fix_data(dns_rbtnode_t *p, void *base, size_t max, void *arg, uint64_t *crc) {
+ data_holder_t *data;
+ size_t size;
+
+ UNUSED(base);
+ UNUSED(max);
+ UNUSED(arg);
+
+ REQUIRE(crc != NULL);
+ REQUIRE(p != NULL);
+
+ data = p->data;
+
+ if (data == NULL || (data->len == 0 && data->data != NULL) ||
+ (data->len != 0 && data->data == NULL))
+ {
+ return (ISC_R_INVALIDFILE);
+ }
+
+ size = max - ((char *)p - (char *)base);
+
+ if (data->len > (int)size || data->data > (const char *)max) {
+ return (ISC_R_INVALIDFILE);
+ }
+
+ isc_crc64_update(crc, (void *)data, sizeof(*data));
+
+ data->data = NULL;
+ if (data->len != 0) {
+ data->data = (char *)data + sizeof(data_holder_t);
+ }
+
+ if (data->len > 0) {
+ isc_crc64_update(crc, (const void *)data->data, data->len);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Load test data into the RBT.
+ */
+static void
+add_test_data(isc_mem_t *mctx, dns_rbt_t *rbt) {
+ char buffer[1024];
+ isc_buffer_t b;
+ isc_result_t result;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ dns_compress_t cctx;
+ rbt_testdata_t *testdatap = testdata;
+
+ dns_compress_init(&cctx, -1, mctx);
+
+ while (testdatap->name != NULL && testdatap->data.data != NULL) {
+ memmove(buffer, testdatap->name, testdatap->name_len);
+
+ isc_buffer_init(&b, buffer, testdatap->name_len);
+ isc_buffer_add(&b, testdatap->name_len);
+ name = dns_fixedname_initname(&fname);
+ result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ testdatap++;
+ continue;
+ }
+
+ if (name != NULL) {
+ result = dns_rbt_addname(rbt, name, &testdatap->data);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+ testdatap++;
+ }
+
+ dns_compress_invalidate(&cctx);
+}
+
+/*
+ * Walk the tree and ensure that all the test nodes are present.
+ */
+static void
+check_test_data(dns_rbt_t *rbt) {
+ char buffer[1024];
+ char *arg;
+ dns_fixedname_t fname;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ isc_buffer_t b;
+ data_holder_t *data;
+ isc_result_t result;
+ dns_name_t *foundname;
+ rbt_testdata_t *testdatap = testdata;
+
+ foundname = dns_fixedname_initname(&fixed);
+
+ while (testdatap->name != NULL && testdatap->data.data != NULL) {
+ memmove(buffer, testdatap->name, testdatap->name_len + 1);
+ arg = buffer;
+
+ isc_buffer_init(&b, arg, testdatap->name_len);
+ isc_buffer_add(&b, testdatap->name_len);
+ name = dns_fixedname_initname(&fname);
+ result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ testdatap++;
+ continue;
+ }
+
+ data = NULL;
+ result = dns_rbt_findname(rbt, name, 0, foundname,
+ (void *)&data);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ testdatap++;
+ }
+}
+
+static void
+data_printer(FILE *out, void *datap) {
+ data_holder_t *data = (data_holder_t *)datap;
+
+ fprintf(out, "%d bytes, %s", data->len, data->data);
+}
+
+/* Test writing an rbt to file */
+static void
+serialize_test(void **state) {
+ dns_rbt_t *rbt = NULL;
+ isc_result_t result;
+ FILE *rbtfile = NULL;
+ dns_rbt_t *rbt_deserialized = NULL;
+ off_t offset;
+ int fd;
+ off_t filesize = 0;
+ char *base;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ result = dns_rbt_create(dt_mctx, delete_data, NULL, &rbt);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ add_test_data(dt_mctx, rbt);
+
+ if (verbose) {
+ dns_rbt_printtext(rbt, data_printer, stdout);
+ }
+
+ /*
+ * Serialize the tree.
+ */
+ rbtfile = fopen("./zone.bin", "w+b");
+ assert_non_null(rbtfile);
+ result = dns_rbt_serialize_tree(rbtfile, rbt, write_data, NULL,
+ &offset);
+ assert_true(result == ISC_R_SUCCESS);
+ dns_rbt_destroy(&rbt);
+
+ /*
+ * Deserialize the tree.
+ * Map in the whole file in one go
+ */
+ fd = open("zone.bin", O_RDWR);
+ assert_int_not_equal(fd, -1);
+ isc_file_getsizefd(fd, &filesize);
+ base = mmap(NULL, filesize, PROT_READ | PROT_WRITE,
+ MAP_FILE | MAP_PRIVATE, fd, 0);
+ assert_true(base != NULL && base != MAP_FAILED);
+ close(fd);
+
+ result = dns_rbt_deserialize_tree(base, filesize, 0, dt_mctx,
+ delete_data, NULL, fix_data, NULL,
+ NULL, &rbt_deserialized);
+
+ /* Test to make sure we have a valid tree */
+ assert_true(result == ISC_R_SUCCESS);
+ if (rbt_deserialized == NULL) {
+ fail_msg("deserialized rbt is null!"); /* Abort execution. */
+ }
+
+ check_test_data(rbt_deserialized);
+
+ if (verbose) {
+ dns_rbt_printtext(rbt_deserialized, data_printer, stdout);
+ }
+
+ dns_rbt_destroy(&rbt_deserialized);
+ munmap(base, filesize);
+ unlink("zone.bin");
+}
+
+/* Test reading a corrupt map file */
+static void
+deserialize_corrupt_test(void **state) {
+ dns_rbt_t *rbt = NULL;
+ isc_result_t result;
+ FILE *rbtfile = NULL;
+ off_t offset;
+ int fd;
+ off_t filesize = 0;
+ char *base, *p, *q;
+ int i;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ /* Set up map file */
+ result = dns_rbt_create(dt_mctx, delete_data, NULL, &rbt);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ add_test_data(dt_mctx, rbt);
+ rbtfile = fopen("./zone.bin", "w+b");
+ assert_non_null(rbtfile);
+ result = dns_rbt_serialize_tree(rbtfile, rbt, write_data, NULL,
+ &offset);
+ assert_true(result == ISC_R_SUCCESS);
+ dns_rbt_destroy(&rbt);
+
+ /* Read back with random fuzzing */
+ for (i = 0; i < 256; i++) {
+ dns_rbt_t *rbt_deserialized = NULL;
+
+ fd = open("zone.bin", O_RDWR);
+ assert_int_not_equal(fd, -1);
+ isc_file_getsizefd(fd, &filesize);
+ base = mmap(NULL, filesize, PROT_READ | PROT_WRITE,
+ MAP_FILE | MAP_PRIVATE, fd, 0);
+ assert_true(base != NULL && base != MAP_FAILED);
+ close(fd);
+
+ /* Randomly fuzz a portion of the memory */
+ /* cppcheck-suppress nullPointerArithmeticRedundantCheck */
+ p = base + (isc_random_uniform(filesize));
+ /* cppcheck-suppress nullPointerArithmeticRedundantCheck */
+ q = base + filesize;
+ q -= (isc_random_uniform(q - p));
+ while (p++ < q) {
+ *p = isc_random8();
+ }
+
+ result = dns_rbt_deserialize_tree(
+ base, filesize, 0, dt_mctx, delete_data, NULL, fix_data,
+ NULL, NULL, &rbt_deserialized);
+
+ /* Test to make sure we have a valid tree */
+ assert_true(result == ISC_R_SUCCESS ||
+ result == ISC_R_INVALIDFILE);
+ if (result != ISC_R_SUCCESS) {
+ assert_null(rbt_deserialized);
+ }
+
+ if (rbt_deserialized != NULL) {
+ dns_rbt_destroy(&rbt_deserialized);
+ }
+
+ munmap(base, filesize);
+ }
+
+ unlink("zone.bin");
+}
+
+/* Test the dns_rbt_serialize_align() function */
+static void
+serialize_align_test(void **state) {
+ UNUSED(state);
+
+ assert_true(dns_rbt_serialize_align(0) == 0);
+ assert_true(dns_rbt_serialize_align(1) == 8);
+ assert_true(dns_rbt_serialize_align(2) == 8);
+ assert_true(dns_rbt_serialize_align(3) == 8);
+ assert_true(dns_rbt_serialize_align(4) == 8);
+ assert_true(dns_rbt_serialize_align(5) == 8);
+ assert_true(dns_rbt_serialize_align(6) == 8);
+ assert_true(dns_rbt_serialize_align(7) == 8);
+ assert_true(dns_rbt_serialize_align(8) == 8);
+ assert_true(dns_rbt_serialize_align(9) == 16);
+ assert_true(dns_rbt_serialize_align(0xff) == 0x100);
+ assert_true(dns_rbt_serialize_align(0x301) == 0x308);
+}
+
+int
+main(int argc, char **argv) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(serialize_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(deserialize_corrupt_test,
+ _setup, _teardown),
+ cmocka_unit_test(serialize_align_test),
+ };
+ int c;
+
+ while ((c = isc_commandline_parse(argc, argv, "v")) != -1) {
+ switch (c) {
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/rbt_test.c b/lib/dns/tests/rbt_test.c
new file mode 100644
index 0000000..e73ac09
--- /dev/null
+++ b/lib/dns/tests/rbt_test.c
@@ -0,0 +1,1390 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/app.h>
+#include <isc/buffer.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/mem.h>
+#include <isc/os.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/socket.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/compress.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/rbt.h>
+#include <dns/result.h>
+
+#include <dst/dst.h>
+
+#include "dnstest.h"
+
+typedef struct {
+ dns_rbt_t *rbt;
+ dns_rbt_t *rbt_distances;
+} test_context_t;
+
+/* The initial structure of domain tree will be as follows:
+ *
+ * .
+ * |
+ * b
+ * / \
+ * a d.e.f
+ * / | \
+ * c | g.h
+ * | |
+ * w.y i
+ * / | \ \
+ * x | z k
+ * | |
+ * p j
+ * / \
+ * o q
+ */
+
+/* The full absolute names of the nodes in the tree (the tree also
+ * contains "." which is not included in this list).
+ */
+static const char *const domain_names[] = {
+ "c", "b", "a", "x.d.e.f",
+ "z.d.e.f", "g.h", "i.g.h", "o.w.y.d.e.f",
+ "j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f", "k.g.h"
+};
+
+static const size_t domain_names_count =
+ (sizeof(domain_names) / sizeof(domain_names[0]));
+
+/* These are set as the node data for the tree used in distances check
+ * (for the names in domain_names[] above).
+ */
+static const int node_distances[] = { 3, 1, 2, 2, 2, 3, 1, 2, 1, 1, 2, 2 };
+
+/*
+ * The domain order should be:
+ * ., a, b, c, d.e.f, x.d.e.f, w.y.d.e.f, o.w.y.d.e.f, p.w.y.d.e.f,
+ * q.w.y.d.e.f, z.d.e.f, j.z.d.e.f, g.h, i.g.h, k.g.h
+ * . (no data, can't be found)
+ * |
+ * b
+ * / \
+ * a d.e.f
+ * / | \
+ * c | g.h
+ * | |
+ * w.y i
+ * / | \ \
+ * x | z k
+ * | |
+ * p j
+ * / \
+ * o q
+ */
+
+static const char *const ordered_names[] = {
+ "a", "b", "c", "d.e.f", "x.d.e.f",
+ "w.y.d.e.f", "o.w.y.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f", "z.d.e.f",
+ "j.z.d.e.f", "g.h", "i.g.h", "k.g.h"
+};
+
+static const size_t ordered_names_count =
+ (sizeof(ordered_names) / sizeof(*ordered_names));
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+static void
+delete_data(void *data, void *arg) {
+ UNUSED(arg);
+
+ isc_mem_put(dt_mctx, data, sizeof(size_t));
+}
+
+static test_context_t *
+test_context_setup(void) {
+ test_context_t *ctx;
+ isc_result_t result;
+ size_t i;
+
+ ctx = isc_mem_get(dt_mctx, sizeof(*ctx));
+ assert_non_null(ctx);
+
+ ctx->rbt = NULL;
+ result = dns_rbt_create(dt_mctx, delete_data, NULL, &ctx->rbt);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ ctx->rbt_distances = NULL;
+ result = dns_rbt_create(dt_mctx, delete_data, NULL,
+ &ctx->rbt_distances);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ for (i = 0; i < domain_names_count; i++) {
+ size_t *n;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ dns_test_namefromstring(domain_names[i], &fname);
+
+ name = dns_fixedname_name(&fname);
+
+ n = isc_mem_get(dt_mctx, sizeof(size_t));
+ assert_non_null(n);
+ *n = i + 1;
+ result = dns_rbt_addname(ctx->rbt, name, n);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ n = isc_mem_get(dt_mctx, sizeof(size_t));
+ assert_non_null(n);
+ *n = node_distances[i];
+ result = dns_rbt_addname(ctx->rbt_distances, name, n);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ return (ctx);
+}
+
+static void
+test_context_teardown(test_context_t *ctx) {
+ dns_rbt_destroy(&ctx->rbt);
+ dns_rbt_destroy(&ctx->rbt_distances);
+
+ isc_mem_put(dt_mctx, ctx, sizeof(*ctx));
+}
+
+/*
+ * Walk the tree and ensure that all the test nodes are present.
+ */
+static void
+check_test_data(dns_rbt_t *rbt) {
+ dns_fixedname_t fixed;
+ isc_result_t result;
+ dns_name_t *foundname;
+ size_t i;
+
+ foundname = dns_fixedname_initname(&fixed);
+
+ for (i = 0; i < domain_names_count; i++) {
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ size_t *n;
+
+ dns_test_namefromstring(domain_names[i], &fname);
+
+ name = dns_fixedname_name(&fname);
+ n = NULL;
+ result = dns_rbt_findname(rbt, name, 0, foundname, (void *)&n);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(*n, i + 1);
+ }
+}
+
+/* Test the creation of an rbt */
+static void
+rbt_create(void **state) {
+ test_context_t *ctx;
+ bool tree_ok;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ ctx = test_context_setup();
+
+ check_test_data(ctx->rbt);
+
+ tree_ok = dns__rbt_checkproperties(ctx->rbt);
+ assert_true(tree_ok);
+
+ test_context_teardown(ctx);
+}
+
+/* Test dns_rbt_nodecount() on a tree */
+static void
+rbt_nodecount(void **state) {
+ test_context_t *ctx;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ ctx = test_context_setup();
+
+ assert_int_equal(15, dns_rbt_nodecount(ctx->rbt));
+
+ test_context_teardown(ctx);
+}
+
+/* Test dns_rbtnode_get_distance() on a tree */
+static void
+rbtnode_get_distance(void **state) {
+ isc_result_t result;
+ test_context_t *ctx;
+ const char *name_str = "a";
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ dns_rbtnode_t *node = NULL;
+ dns_rbtnodechain_t chain;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ ctx = test_context_setup();
+
+ dns_test_namefromstring(name_str, &fname);
+ name = dns_fixedname_name(&fname);
+
+ dns_rbtnodechain_init(&chain);
+
+ result = dns_rbt_findnode(ctx->rbt_distances, name, NULL, &node, &chain,
+ 0, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ while (node != NULL) {
+ const size_t *distance = (const size_t *)node->data;
+ if (distance != NULL) {
+ assert_int_equal(*distance,
+ dns__rbtnode_getdistance(node));
+ }
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ if (result == ISC_R_NOMORE) {
+ break;
+ }
+ dns_rbtnodechain_current(&chain, NULL, NULL, &node);
+ }
+
+ assert_int_equal(result, ISC_R_NOMORE);
+
+ dns_rbtnodechain_invalidate(&chain);
+
+ test_context_teardown(ctx);
+}
+
+/*
+ * Test tree balance, inserting names in random order.
+ *
+ * This test checks an important performance-related property of
+ * the red-black tree, which is important for us: the longest
+ * path from a sub-tree's root to a node is no more than
+ * 2log(n). This check verifies that the tree is balanced.
+ */
+static void
+rbt_check_distance_random(void **state) {
+ dns_rbt_t *mytree = NULL;
+ const unsigned int log_num_nodes = 16;
+ isc_result_t result;
+ bool tree_ok;
+ int i;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ result = dns_rbt_create(dt_mctx, delete_data, NULL, &mytree);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Names are inserted in random order. */
+
+ /* Make a large 65536 node top-level domain tree, i.e., the
+ * following code inserts names such as:
+ *
+ * savoucnsrkrqzpkqypbygwoiliawpbmz.
+ * wkadamcbbpjtundbxcmuayuycposvngx.
+ * wzbpznemtooxdpjecdxynsfztvnuyfao.
+ * yueojmhyffslpvfmgyfwioxegfhepnqq.
+ */
+ for (i = 0; i < (1 << log_num_nodes); i++) {
+ size_t *n;
+ char namebuf[34];
+
+ n = isc_mem_get(dt_mctx, sizeof(size_t));
+ assert_non_null(n);
+ *n = i + 1;
+
+ while (1) {
+ int j;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ for (j = 0; j < 32; j++) {
+ uint32_t v = isc_random_uniform(26);
+ namebuf[j] = 'a' + v;
+ }
+ namebuf[32] = '.';
+ namebuf[33] = 0;
+
+ dns_test_namefromstring(namebuf, &fname);
+ name = dns_fixedname_name(&fname);
+
+ result = dns_rbt_addname(mytree, name, n);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ }
+
+ /* 1 (root . node) + (1 << log_num_nodes) */
+ assert_int_equal(1U + (1U << log_num_nodes), dns_rbt_nodecount(mytree));
+
+ /* The distance from each node to its sub-tree root must be less
+ * than 2 * log(n).
+ */
+ assert_true((2U * log_num_nodes) >= dns__rbt_getheight(mytree));
+
+ /* Also check RB tree properties */
+ tree_ok = dns__rbt_checkproperties(mytree);
+ assert_true(tree_ok);
+
+ dns_rbt_destroy(&mytree);
+}
+
+/*
+ * Test tree balance, inserting names in sorted order.
+ *
+ * This test checks an important performance-related property of
+ * the red-black tree, which is important for us: the longest
+ * path from a sub-tree's root to a node is no more than
+ * 2log(n). This check verifies that the tree is balanced.
+ */
+static void
+rbt_check_distance_ordered(void **state) {
+ dns_rbt_t *mytree = NULL;
+ const unsigned int log_num_nodes = 16;
+ isc_result_t result;
+ bool tree_ok;
+ int i;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ result = dns_rbt_create(dt_mctx, delete_data, NULL, &mytree);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Names are inserted in sorted order. */
+
+ /* Make a large 65536 node top-level domain tree, i.e., the
+ * following code inserts names such as:
+ *
+ * name00000000.
+ * name00000001.
+ * name00000002.
+ * name00000003.
+ */
+ for (i = 0; i < (1 << log_num_nodes); i++) {
+ size_t *n;
+ char namebuf[14];
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ n = isc_mem_get(dt_mctx, sizeof(size_t));
+ assert_non_null(n);
+ *n = i + 1;
+
+ snprintf(namebuf, sizeof(namebuf), "name%08x.", i);
+ dns_test_namefromstring(namebuf, &fname);
+ name = dns_fixedname_name(&fname);
+
+ result = dns_rbt_addname(mytree, name, n);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ /* 1 (root . node) + (1 << log_num_nodes) */
+ assert_int_equal(1U + (1U << log_num_nodes), dns_rbt_nodecount(mytree));
+
+ /* The distance from each node to its sub-tree root must be less
+ * than 2 * log(n).
+ */
+ assert_true((2U * log_num_nodes) >= dns__rbt_getheight(mytree));
+
+ /* Also check RB tree properties */
+ tree_ok = dns__rbt_checkproperties(mytree);
+ assert_true(tree_ok);
+
+ dns_rbt_destroy(&mytree);
+}
+
+static isc_result_t
+insert_helper(dns_rbt_t *rbt, const char *namestr, dns_rbtnode_t **node) {
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ dns_test_namefromstring(namestr, &fname);
+ name = dns_fixedname_name(&fname);
+
+ return (dns_rbt_addnode(rbt, name, node));
+}
+
+static bool
+compare_labelsequences(dns_rbtnode_t *node, const char *labelstr) {
+ dns_name_t name;
+ isc_result_t result;
+ char *nodestr = NULL;
+ bool is_equal;
+
+ dns_name_init(&name, NULL);
+ dns_rbt_namefromnode(node, &name);
+
+ result = dns_name_tostring(&name, &nodestr, dt_mctx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ is_equal = strcmp(labelstr, nodestr) == 0 ? true : false;
+
+ isc_mem_free(dt_mctx, nodestr);
+
+ return (is_equal);
+}
+
+/* Test insertion into a tree */
+static void
+rbt_insert(void **state) {
+ isc_result_t result;
+ test_context_t *ctx;
+ dns_rbtnode_t *node;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ ctx = test_context_setup();
+
+ /* Check node count before beginning. */
+ assert_int_equal(15, dns_rbt_nodecount(ctx->rbt));
+
+ /* Try to insert a node that already exists. */
+ node = NULL;
+ result = insert_helper(ctx->rbt, "d.e.f", &node);
+ assert_int_equal(result, ISC_R_EXISTS);
+
+ /* Node count must not have changed. */
+ assert_int_equal(15, dns_rbt_nodecount(ctx->rbt));
+
+ /* Try to insert a node that doesn't exist. */
+ node = NULL;
+ result = insert_helper(ctx->rbt, "0", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(compare_labelsequences(node, "0"));
+
+ /* Node count must have increased. */
+ assert_int_equal(16, dns_rbt_nodecount(ctx->rbt));
+
+ /* Another. */
+ node = NULL;
+ result = insert_helper(ctx->rbt, "example.com", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_non_null(node);
+ assert_null(node->data);
+
+ /* Node count must have increased. */
+ assert_int_equal(17, dns_rbt_nodecount(ctx->rbt));
+
+ /* Re-adding it should return EXISTS */
+ node = NULL;
+ result = insert_helper(ctx->rbt, "example.com", &node);
+ assert_int_equal(result, ISC_R_EXISTS);
+
+ /* Node count must not have changed. */
+ assert_int_equal(17, dns_rbt_nodecount(ctx->rbt));
+
+ /* Fission the node d.e.f */
+ node = NULL;
+ result = insert_helper(ctx->rbt, "k.e.f", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(compare_labelsequences(node, "k"));
+
+ /* Node count must have incremented twice ("d.e.f" fissioned to
+ * "d" and "e.f", and the newly added "k").
+ */
+ assert_int_equal(19, dns_rbt_nodecount(ctx->rbt));
+
+ /* Fission the node "g.h" */
+ node = NULL;
+ result = insert_helper(ctx->rbt, "h", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(compare_labelsequences(node, "h"));
+
+ /* Node count must have incremented ("g.h" fissioned to "g" and
+ * "h").
+ */
+ assert_int_equal(20, dns_rbt_nodecount(ctx->rbt));
+
+ /* Add child domains */
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "m.p.w.y.d.e.f", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(compare_labelsequences(node, "m"));
+ assert_int_equal(21, dns_rbt_nodecount(ctx->rbt));
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "n.p.w.y.d.e.f", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(compare_labelsequences(node, "n"));
+ assert_int_equal(22, dns_rbt_nodecount(ctx->rbt));
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "l.a", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(compare_labelsequences(node, "l"));
+ assert_int_equal(23, dns_rbt_nodecount(ctx->rbt));
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "r.d.e.f", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ node = NULL;
+ result = insert_helper(ctx->rbt, "s.d.e.f", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(25, dns_rbt_nodecount(ctx->rbt));
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "h.w.y.d.e.f", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Add more nodes one by one to cover left and right rotation
+ * functions.
+ */
+ node = NULL;
+ result = insert_helper(ctx->rbt, "f", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "m", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "nm", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "om", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "k", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "l", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "fe", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "ge", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "i", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "ae", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "n", &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ test_context_teardown(ctx);
+}
+
+/*
+ * Test removal from a tree
+ *
+ * This testcase checks that after node removal, the binary-search tree is
+ * valid and all nodes that are supposed to exist are present in the
+ * correct order. It mainly tests DomainTree as a BST, and not particularly
+ * as a red-black tree. This test checks node deletion when upper nodes
+ * have data.
+ */
+static void
+rbt_remove(void **state) {
+ isc_result_t result;
+ size_t j;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ /*
+ * Delete single nodes and check if the rest of the nodes exist.
+ */
+ for (j = 0; j < ordered_names_count; j++) {
+ dns_rbt_t *mytree = NULL;
+ dns_rbtnode_t *node;
+ size_t i;
+ size_t *n;
+ bool tree_ok;
+ dns_rbtnodechain_t chain;
+ size_t start_node;
+
+ /* Create a tree. */
+ result = dns_rbt_create(dt_mctx, delete_data, NULL, &mytree);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Insert test data into the tree. */
+ for (i = 0; i < domain_names_count; i++) {
+ node = NULL;
+ result = insert_helper(mytree, domain_names[i], &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ /* Check that all names exist in order. */
+ for (i = 0; i < ordered_names_count; i++) {
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ dns_test_namefromstring(ordered_names[i], &fname);
+
+ name = dns_fixedname_name(&fname);
+ node = NULL;
+ result = dns_rbt_findnode(mytree, name, NULL, &node,
+ NULL, DNS_RBTFIND_EMPTYDATA,
+ NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Add node data */
+ assert_non_null(node);
+ assert_null(node->data);
+
+ n = isc_mem_get(dt_mctx, sizeof(size_t));
+ assert_non_null(n);
+ *n = i;
+
+ node->data = n;
+ }
+
+ /* Now, delete the j'th node from the tree. */
+ {
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ dns_test_namefromstring(ordered_names[j], &fname);
+
+ name = dns_fixedname_name(&fname);
+
+ result = dns_rbt_deletename(mytree, name, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ /* Check RB tree properties. */
+ tree_ok = dns__rbt_checkproperties(mytree);
+ assert_true(tree_ok);
+
+ dns_rbtnodechain_init(&chain);
+
+ /* Now, walk through nodes in order. */
+ if (j == 0) {
+ /*
+ * Node for ordered_names[0] was already deleted
+ * above. We start from node 1.
+ */
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ dns_test_namefromstring(ordered_names[0], &fname);
+ name = dns_fixedname_name(&fname);
+ node = NULL;
+ result = dns_rbt_findnode(mytree, name, NULL, &node,
+ NULL, 0, NULL, NULL);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+
+ dns_test_namefromstring(ordered_names[1], &fname);
+ name = dns_fixedname_name(&fname);
+ node = NULL;
+ result = dns_rbt_findnode(mytree, name, NULL, &node,
+ &chain, 0, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ start_node = 1;
+ } else {
+ /* Start from node 0. */
+ dns_fixedname_t fname;
+ dns_name_t *name;
+
+ dns_test_namefromstring(ordered_names[0], &fname);
+ name = dns_fixedname_name(&fname);
+ node = NULL;
+ result = dns_rbt_findnode(mytree, name, NULL, &node,
+ &chain, 0, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ start_node = 0;
+ }
+
+ /*
+ * node and chain have been set by the code above at
+ * this point.
+ */
+ for (i = start_node; i < ordered_names_count; i++) {
+ dns_fixedname_t fname_j, fname_i;
+ dns_name_t *name_j, *name_i;
+
+ dns_test_namefromstring(ordered_names[j], &fname_j);
+ name_j = dns_fixedname_name(&fname_j);
+ dns_test_namefromstring(ordered_names[i], &fname_i);
+ name_i = dns_fixedname_name(&fname_i);
+
+ if (dns_name_equal(name_i, name_j)) {
+ /*
+ * This may be true for the last node if
+ * we seek ahead in the loop using
+ * dns_rbtnodechain_next() below.
+ */
+ if (node == NULL) {
+ break;
+ }
+
+ /* All ordered nodes have data
+ * initially. If any node is empty, it
+ * means it was removed, but an empty
+ * node exists because it is a
+ * super-domain. Just skip it.
+ */
+ if (node->data == NULL) {
+ result = dns_rbtnodechain_next(
+ &chain, NULL, NULL);
+ if (result == ISC_R_NOMORE) {
+ node = NULL;
+ } else {
+ dns_rbtnodechain_current(
+ &chain, NULL, NULL,
+ &node);
+ }
+ }
+ continue;
+ }
+
+ assert_non_null(node);
+
+ n = (size_t *)node->data;
+ if (n != NULL) {
+ /* printf("n=%zu, i=%zu\n", *n, i); */
+ assert_int_equal(*n, i);
+ }
+
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ if (result == ISC_R_NOMORE) {
+ node = NULL;
+ } else {
+ dns_rbtnodechain_current(&chain, NULL, NULL,
+ &node);
+ }
+ }
+
+ /* We should have reached the end of the tree. */
+ assert_null(node);
+
+ dns_rbt_destroy(&mytree);
+ }
+}
+
+static void
+insert_nodes(dns_rbt_t *mytree, char **names, size_t *names_count,
+ uint32_t num_names) {
+ uint32_t i;
+ dns_rbtnode_t *node;
+
+ for (i = 0; i < num_names; i++) {
+ size_t *n;
+ char namebuf[34];
+
+ n = isc_mem_get(dt_mctx, sizeof(size_t));
+ assert_non_null(n);
+
+ *n = i; /* Unused value */
+
+ while (1) {
+ int j;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ isc_result_t result;
+
+ for (j = 0; j < 32; j++) {
+ uint32_t v = isc_random_uniform(26);
+ namebuf[j] = 'a' + v;
+ }
+ namebuf[32] = '.';
+ namebuf[33] = 0;
+
+ dns_test_namefromstring(namebuf, &fname);
+ name = dns_fixedname_name(&fname);
+
+ node = NULL;
+ result = dns_rbt_addnode(mytree, name, &node);
+ if (result == ISC_R_SUCCESS) {
+ node->data = n;
+ names[*names_count] = isc_mem_strdup(dt_mctx,
+ namebuf);
+ assert_non_null(names[*names_count]);
+ *names_count += 1;
+ break;
+ }
+ }
+ }
+}
+
+static void
+remove_nodes(dns_rbt_t *mytree, char **names, size_t *names_count,
+ uint32_t num_names) {
+ uint32_t i;
+
+ UNUSED(mytree);
+
+ for (i = 0; i < num_names; i++) {
+ uint32_t node;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ isc_result_t result;
+
+ node = isc_random_uniform(*names_count);
+
+ dns_test_namefromstring(names[node], &fname);
+ name = dns_fixedname_name(&fname);
+
+ result = dns_rbt_deletename(mytree, name, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_mem_free(dt_mctx, names[node]);
+ if (*names_count > 0) {
+ names[node] = names[*names_count - 1];
+ names[*names_count - 1] = NULL;
+ *names_count -= 1;
+ }
+ }
+}
+
+static void
+check_tree(dns_rbt_t *mytree, char **names, size_t names_count) {
+ bool tree_ok;
+
+ UNUSED(names);
+
+ assert_int_equal(names_count + 1, dns_rbt_nodecount(mytree));
+
+ /*
+ * The distance from each node to its sub-tree root must be less
+ * than 2 * log_2(1024).
+ */
+ assert_true((2 * 10) >= dns__rbt_getheight(mytree));
+
+ /* Also check RB tree properties */
+ tree_ok = dns__rbt_checkproperties(mytree);
+ assert_true(tree_ok);
+}
+
+/*
+ * Test insert and remove in a loop.
+ *
+ * What is the best way to test our red-black tree code? It is
+ * not a good method to test every case handled in the actual
+ * code itself. This is because our approach itself may be
+ * incorrect.
+ *
+ * We test our code at the interface level here by exercising the
+ * tree randomly multiple times, checking that red-black tree
+ * properties are valid, and all the nodes that are supposed to be
+ * in the tree exist and are in order.
+ *
+ * NOTE: These tests are run within a single tree level in the
+ * forest. The number of nodes in the tree level doesn't grow
+ * over 1024.
+ */
+static void
+rbt_insert_and_remove(void **state) {
+ isc_result_t result;
+ dns_rbt_t *mytree = NULL;
+ size_t *n;
+ char *names[1024];
+ size_t names_count;
+ int i;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ result = dns_rbt_create(dt_mctx, delete_data, NULL, &mytree);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ n = isc_mem_get(dt_mctx, sizeof(size_t));
+ assert_non_null(n);
+ result = dns_rbt_addname(mytree, dns_rootname, n);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(names, 0, sizeof(names));
+ names_count = 0;
+
+ /* Repeat the insert/remove test some 4096 times */
+ for (i = 0; i < 4096; i++) {
+ uint32_t num_names;
+
+ if (names_count < 1024) {
+ num_names = isc_random_uniform(1024 - names_count);
+ num_names++;
+ } else {
+ num_names = 0;
+ }
+
+ insert_nodes(mytree, names, &names_count, num_names);
+ check_tree(mytree, names, names_count);
+
+ if (names_count > 0) {
+ num_names = isc_random_uniform(names_count);
+ num_names++;
+ } else {
+ num_names = 0;
+ }
+
+ remove_nodes(mytree, names, &names_count, num_names);
+ check_tree(mytree, names, names_count);
+ }
+
+ /* Remove the rest of the nodes */
+ remove_nodes(mytree, names, &names_count, names_count);
+ check_tree(mytree, names, names_count);
+
+ for (i = 0; i < 1024; i++) {
+ if (names[i] != NULL) {
+ isc_mem_free(dt_mctx, names[i]);
+ }
+ }
+
+ result = dns_rbt_deletename(mytree, dns_rootname, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(dns_rbt_nodecount(mytree), 0);
+
+ dns_rbt_destroy(&mytree);
+}
+
+/* Test findname return values */
+static void
+rbt_findname(void **state) {
+ isc_result_t result;
+ test_context_t *ctx = NULL;
+ dns_fixedname_t fname, found;
+ dns_name_t *name = NULL, *foundname = NULL;
+ size_t *n = NULL;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ ctx = test_context_setup();
+
+ /* Try to find a name that exists. */
+ dns_test_namefromstring("d.e.f", &fname);
+ name = dns_fixedname_name(&fname);
+
+ foundname = dns_fixedname_initname(&found);
+
+ result = dns_rbt_findname(ctx->rbt, name, DNS_RBTFIND_EMPTYDATA,
+ foundname, (void *)&n);
+ assert_true(dns_name_equal(foundname, name));
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Now without EMPTYDATA */
+ result = dns_rbt_findname(ctx->rbt, name, 0, foundname, (void *)&n);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+
+ /* Now one that partially matches */
+ dns_test_namefromstring("d.e.f.g.h.i.j", &fname);
+ name = dns_fixedname_name(&fname);
+ result = dns_rbt_findname(ctx->rbt, name, DNS_RBTFIND_EMPTYDATA,
+ foundname, (void *)&n);
+ assert_int_equal(result, DNS_R_PARTIALMATCH);
+
+ /* Now one that doesn't match */
+ dns_test_namefromstring("1.2", &fname);
+ name = dns_fixedname_name(&fname);
+ result = dns_rbt_findname(ctx->rbt, name, DNS_RBTFIND_EMPTYDATA,
+ foundname, (void *)&n);
+ assert_int_equal(result, DNS_R_PARTIALMATCH);
+ assert_true(dns_name_equal(foundname, dns_rootname));
+
+ test_context_teardown(ctx);
+}
+
+/* Test addname return values */
+static void
+rbt_addname(void **state) {
+ isc_result_t result;
+ test_context_t *ctx = NULL;
+ dns_fixedname_t fname;
+ dns_name_t *name = NULL;
+ size_t *n;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ ctx = test_context_setup();
+
+ n = isc_mem_get(dt_mctx, sizeof(size_t));
+ assert_non_null(n);
+ *n = 1;
+
+ dns_test_namefromstring("d.e.f.g.h.i.j.k", &fname);
+ name = dns_fixedname_name(&fname);
+
+ /* Add a name that doesn't exist */
+ result = dns_rbt_addname(ctx->rbt, name, n);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Now add again, should get ISC_R_EXISTS */
+ n = isc_mem_get(dt_mctx, sizeof(size_t));
+ assert_non_null(n);
+ *n = 2;
+ result = dns_rbt_addname(ctx->rbt, name, n);
+ assert_int_equal(result, ISC_R_EXISTS);
+ isc_mem_put(dt_mctx, n, sizeof(size_t));
+
+ test_context_teardown(ctx);
+}
+
+/* Test deletename return values */
+static void
+rbt_deletename(void **state) {
+ isc_result_t result;
+ test_context_t *ctx = NULL;
+ dns_fixedname_t fname;
+ dns_name_t *name = NULL;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ ctx = test_context_setup();
+
+ /* Delete a name that doesn't exist */
+ dns_test_namefromstring("z.x.y.w", &fname);
+ name = dns_fixedname_name(&fname);
+ result = dns_rbt_deletename(ctx->rbt, name, false);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+
+ /* Now one that does */
+ dns_test_namefromstring("d.e.f", &fname);
+ name = dns_fixedname_name(&fname);
+ result = dns_rbt_deletename(ctx->rbt, name, false);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+
+ test_context_teardown(ctx);
+}
+
+/* Test nodechain */
+static void
+rbt_nodechain(void **state) {
+ isc_result_t result;
+ test_context_t *ctx;
+ dns_fixedname_t fname, found, expect;
+ dns_name_t *name, *foundname, *expected;
+ dns_rbtnode_t *node = NULL;
+ dns_rbtnodechain_t chain;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ ctx = test_context_setup();
+
+ dns_rbtnodechain_init(&chain);
+
+ dns_test_namefromstring("a", &fname);
+ name = dns_fixedname_name(&fname);
+
+ result = dns_rbt_findnode(ctx->rbt, name, NULL, &node, &chain, 0, NULL,
+ NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ foundname = dns_fixedname_initname(&found);
+
+ dns_test_namefromstring("a", &expect);
+ expected = dns_fixedname_name(&expect);
+ UNUSED(expected);
+
+ result = dns_rbtnodechain_first(&chain, ctx->rbt, foundname, NULL);
+ assert_int_equal(result, DNS_R_NEWORIGIN);
+ assert_int_equal(dns_name_countlabels(foundname), 0);
+
+ result = dns_rbtnodechain_prev(&chain, NULL, NULL);
+ assert_int_equal(result, ISC_R_NOMORE);
+
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_rbtnodechain_last(&chain, ctx->rbt, NULL, NULL);
+ assert_int_equal(result, DNS_R_NEWORIGIN);
+
+ result = dns_rbtnodechain_next(&chain, NULL, NULL);
+ assert_int_equal(result, ISC_R_NOMORE);
+
+ result = dns_rbtnodechain_last(&chain, ctx->rbt, NULL, NULL);
+ assert_int_equal(result, DNS_R_NEWORIGIN);
+
+ result = dns_rbtnodechain_prev(&chain, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_rbtnodechain_invalidate(&chain);
+
+ test_context_teardown(ctx);
+}
+
+/* Test addname return values */
+static void
+rbtnode_namelen(void **state) {
+ isc_result_t result;
+ test_context_t *ctx = NULL;
+ dns_rbtnode_t *node;
+ unsigned int len;
+
+ UNUSED(state);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+
+ ctx = test_context_setup();
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, ".", &node);
+ len = dns__rbtnode_namelen(node);
+ assert_int_equal(result, ISC_R_EXISTS);
+ assert_int_equal(len, 1);
+ node = NULL;
+
+ result = insert_helper(ctx->rbt, "a.b.c.d.e.f.g.h.i.j.k.l.m", &node);
+ len = dns__rbtnode_namelen(node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(len, 27);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "isc.org", &node);
+ len = dns__rbtnode_namelen(node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(len, 9);
+
+ node = NULL;
+ result = insert_helper(ctx->rbt, "example.com", &node);
+ len = dns__rbtnode_namelen(node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(len, 13);
+
+ test_context_teardown(ctx);
+}
+
+#if defined(DNS_BENCHMARK_TESTS) && !defined(__SANITIZE_THREAD__)
+
+/*
+ * XXXMUKS: Don't delete this code. It is useful in benchmarking the
+ * RBT, but we don't require it as part of the unit test runs.
+ */
+
+static dns_fixedname_t *fnames;
+static dns_name_t **names;
+static int *values;
+
+static void *
+find_thread(void *arg) {
+ dns_rbt_t *mytree;
+ isc_result_t result;
+ dns_rbtnode_t *node;
+ unsigned int j, i;
+ unsigned int start = 0;
+
+ mytree = (dns_rbt_t *)arg;
+ while (start == 0) {
+ start = random() % 4000000;
+ }
+
+ /* Query 32 million random names from it in each thread */
+ for (j = 0; j < 8; j++) {
+ for (i = start; i != start - 1; i = (i + 1) % 4000000) {
+ node = NULL;
+ result = dns_rbt_findnode(mytree, names[i], NULL, &node,
+ NULL, DNS_RBTFIND_EMPTYDATA,
+ NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_non_null(node);
+ assert_int_equal(values[i], (intptr_t)node->data);
+ }
+ }
+
+ return (NULL);
+}
+
+/* Benchmark RBT implementation */
+static void
+benchmark(void **state) {
+ isc_result_t result;
+ char namestr[sizeof("name18446744073709551616.example.org.")];
+ unsigned int r;
+ dns_rbt_t *mytree;
+ dns_rbtnode_t *node;
+ unsigned int i;
+ unsigned int maxvalue = 1000000;
+ isc_time_t ts1, ts2;
+ double t;
+ unsigned int nthreads;
+ isc_thread_t threads[32];
+
+ UNUSED(state);
+
+ srandom(time(NULL));
+
+ debug_mem_record = false;
+
+ fnames = (dns_fixedname_t *)malloc(4000000 * sizeof(dns_fixedname_t));
+ names = (dns_name_t **)malloc(4000000 * sizeof(dns_name_t *));
+ values = (int *)malloc(4000000 * sizeof(int));
+
+ for (i = 0; i < 4000000; i++) {
+ r = ((unsigned long)random()) % maxvalue;
+ snprintf(namestr, sizeof(namestr), "name%u.example.org.", r);
+ dns_test_namefromstring(namestr, &fnames[i]);
+ names[i] = dns_fixedname_name(&fnames[i]);
+ values[i] = r;
+ }
+
+ /* Create a tree. */
+ mytree = NULL;
+ result = dns_rbt_create(dt_mctx, NULL, NULL, &mytree);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Insert test data into the tree. */
+ for (i = 0; i < maxvalue; i++) {
+ snprintf(namestr, sizeof(namestr), "name%u.example.org.", i);
+ node = NULL;
+ result = insert_helper(mytree, namestr, &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ node->data = (void *)(intptr_t)i;
+ }
+
+ result = isc_time_now(&ts1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ nthreads = ISC_MIN(isc_os_ncpus(), 32);
+ nthreads = ISC_MAX(nthreads, 1);
+ for (i = 0; i < nthreads; i++) {
+ isc_thread_create(find_thread, mytree, &threads[i]);
+ }
+
+ for (i = 0; i < nthreads; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ result = isc_time_now(&ts2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ t = isc_time_microdiff(&ts2, &ts1);
+
+ printf("%u findnode calls, %f seconds, %f calls/second\n",
+ nthreads * 8 * 4000000, t / 1000000.0,
+ (nthreads * 8 * 4000000) / (t / 1000000.0));
+
+ free(values);
+ free(names);
+ free(fnames);
+
+ dns_rbt_destroy(&mytree);
+}
+#endif /* defined(DNS_BENCHMARK_TESTS) && !defined(__SANITIZE_THREAD__) */
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(rbt_create, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(rbt_nodecount, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(rbtnode_get_distance, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(rbt_check_distance_random,
+ _setup, _teardown),
+ cmocka_unit_test_setup_teardown(rbt_check_distance_ordered,
+ _setup, _teardown),
+ cmocka_unit_test_setup_teardown(rbt_insert, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(rbt_remove, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(rbt_insert_and_remove, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(rbt_findname, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(rbt_addname, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(rbt_deletename, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(rbt_nodechain, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(rbtnode_namelen, _setup,
+ _teardown),
+#if defined(DNS_BENCHMARK_TESTS) && !defined(__SANITIZE_THREAD__)
+ cmocka_unit_test_setup_teardown(benchmark, _setup, _teardown),
+#endif /* defined(DNS_BENCHMARK_TESTS) && !defined(__SANITIZE_THREAD__) */
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/rbtdb_test.c b/lib/dns/tests/rbtdb_test.c
new file mode 100644
index 0000000..ac7b776
--- /dev/null
+++ b/lib/dns/tests/rbtdb_test.c
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/print.h>
+#include <isc/util.h>
+
+#include <dns/rbt.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+
+#include "dnstest.h"
+
+/* Include the main file */
+
+#include "../rbtdb.c"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+const char *ownercase_vectors[12][2] = {
+ {
+ "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz",
+ "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz",
+ },
+ {
+ "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz",
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ",
+ },
+ {
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ",
+ "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz",
+ },
+ {
+ "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ",
+ "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz",
+ },
+ {
+ "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVxXyYzZ",
+ "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvxxyyzz",
+ },
+ {
+ "WwW.ExAmPlE.OrG",
+ "wWw.eXaMpLe.oRg",
+ },
+ {
+ "_SIP.tcp.example.org",
+ "_sip.TCP.example.org",
+ },
+ {
+ "bind-USERS.lists.example.org",
+ "bind-users.lists.example.org",
+ },
+ {
+ "a0123456789.example.org",
+ "A0123456789.example.org",
+ },
+ {
+ "\\000.example.org",
+ "\\000.example.org",
+ },
+ {
+ "wWw.\\000.isc.org",
+ "www.\\000.isc.org",
+ },
+ {
+ "\255.example.org",
+ "\255.example.ORG",
+ }
+};
+
+static bool
+ownercase_test_one(const char *str1, const char *str2) {
+ isc_result_t result;
+ rbtdb_nodelock_t node_locks[1];
+ dns_rbtdb_t rbtdb = { .node_locks = node_locks };
+ dns_rbtnode_t rbtnode = { .locknum = 0 };
+ rdatasetheader_t header = { 0 };
+ unsigned char *raw = (unsigned char *)(&header) + sizeof(header);
+ dns_rdataset_t rdataset = {
+ .magic = DNS_RDATASET_MAGIC,
+ .private1 = &rbtdb,
+ .private2 = &rbtnode,
+ .private3 = raw,
+ .methods = &rdataset_methods,
+ };
+
+ isc_buffer_t b;
+ dns_fixedname_t fname1, fname2;
+ dns_name_t *name1, *name2;
+
+ memset(node_locks, 0, sizeof(node_locks));
+ /* Minimal initialization of the mock objects */
+ NODE_INITLOCK(&rbtdb.node_locks[0].lock);
+
+ name1 = dns_fixedname_initname(&fname1);
+ isc_buffer_constinit(&b, str1, strlen(str1));
+ isc_buffer_add(&b, strlen(str1));
+ result = dns_name_fromtext(name1, &b, dns_rootname, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ name2 = dns_fixedname_initname(&fname2);
+ isc_buffer_constinit(&b, str2, strlen(str2));
+ isc_buffer_add(&b, strlen(str2));
+ result = dns_name_fromtext(name2, &b, dns_rootname, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Store the case from name1 */
+ dns_rdataset_setownercase(&rdataset, name1);
+
+ assert_true(CASESET(&header));
+
+ /* Retrieve the case to name2 */
+ dns_rdataset_getownercase(&rdataset, name2);
+
+ NODE_DESTROYLOCK(&rbtdb.node_locks[0].lock);
+
+ return (dns_name_caseequal(name1, name2));
+}
+
+static void
+ownercase_test(void **state) {
+ UNUSED(state);
+
+ for (size_t n = 0; n < ARRAY_SIZE(ownercase_vectors); n++) {
+ assert_true(ownercase_test_one(ownercase_vectors[n][0],
+ ownercase_vectors[n][1]));
+ }
+
+ assert_false(ownercase_test_one("W.example.org", "\\000.example.org"));
+
+ /* Ö and ö in ISO Latin 1 */
+ assert_false(ownercase_test_one("\\216", "\\246"));
+}
+
+static void
+setownercase_test(void **state) {
+ isc_result_t result;
+ rbtdb_nodelock_t node_locks[1];
+ dns_rbtdb_t rbtdb = { .node_locks = node_locks };
+ dns_rbtnode_t rbtnode = { .locknum = 0 };
+ rdatasetheader_t header = { 0 };
+ unsigned char *raw = (unsigned char *)(&header) + sizeof(header);
+ dns_rdataset_t rdataset = {
+ .magic = DNS_RDATASET_MAGIC,
+ .private1 = &rbtdb,
+ .private2 = &rbtnode,
+ .private3 = raw,
+ .methods = &rdataset_methods,
+ };
+ const char *str1 =
+ "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
+
+ isc_buffer_t b;
+ dns_fixedname_t fname1, fname2;
+ dns_name_t *name1, *name2;
+
+ UNUSED(state);
+
+ /* Minimal initialization of the mock objects */
+ memset(node_locks, 0, sizeof(node_locks));
+ NODE_INITLOCK(&rbtdb.node_locks[0].lock);
+
+ name1 = dns_fixedname_initname(&fname1);
+ isc_buffer_constinit(&b, str1, strlen(str1));
+ isc_buffer_add(&b, strlen(str1));
+ result = dns_name_fromtext(name1, &b, dns_rootname, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ name2 = dns_fixedname_initname(&fname2);
+ isc_buffer_constinit(&b, str1, strlen(str1));
+ isc_buffer_add(&b, strlen(str1));
+ result = dns_name_fromtext(name2, &b, dns_rootname, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_false(CASESET(&header));
+
+ /* Retrieve the case to name2 */
+ dns_rdataset_getownercase(&rdataset, name2);
+
+ NODE_DESTROYLOCK(&rbtdb.node_locks[0].lock);
+
+ assert_true(dns_name_caseequal(name1, name2));
+}
+
+/*
+ * No operation water() callback. We need it to cause overmem condition, but
+ * nothing has to be done in the callback.
+ */
+static void
+overmempurge_water(void *arg, int mark) {
+ UNUSED(arg);
+ UNUSED(mark);
+}
+
+/*
+ * Add to a cache DB 'db' an rdataset of type 'rtype' at a name
+ * <idx>.example.com. The rdataset would contain one data, and rdata_len is
+ * its length. 'rtype' is supposed to be some private type whose data can be
+ * arbitrary (and it doesn't matter in this test).
+ */
+static void
+overmempurge_addrdataset(dns_db_t *db, isc_stdtime_t now, int idx,
+ dns_rdatatype_t rtype, size_t rdata_len,
+ bool longname) {
+ isc_result_t result;
+ dns_rdata_t rdata;
+ dns_dbnode_t *node = NULL;
+ dns_rdatalist_t rdatalist;
+ dns_rdataset_t rdataset;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ unsigned char rdatabuf[65535]; /* large enough for any valid RDATA */
+
+ REQUIRE(rdata_len <= sizeof(rdatabuf));
+
+ if (longname) {
+ /*
+ * Build a longest possible name (in wire format) that would
+ * result in a new rbt node with the long name data.
+ */
+ snprintf(namebuf, sizeof(namebuf),
+ "%010d.%010dabcdef%010dabcdef%010dabcdef%010dabcde."
+ "%010dabcdef%010dabcdef%010dabcdef%010dabcde."
+ "%010dabcdef%010dabcdef%010dabcdef%010dabcde."
+ "%010dabcdef%010dabcdef%010dabcdef01.",
+ idx, idx, idx, idx, idx, idx, idx, idx, idx, idx, idx,
+ idx, idx, idx, idx, idx);
+ } else {
+ snprintf(namebuf, sizeof(namebuf), "%d.example.com.", idx);
+ }
+
+ dns_test_namefromstring(namebuf, &fname);
+ name = dns_fixedname_name(&fname);
+
+ result = dns_db_findnode(db, name, true, &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_non_null(node);
+
+ dns_rdata_init(&rdata);
+ rdata.length = rdata_len;
+ rdata.data = rdatabuf;
+ rdata.rdclass = dns_rdataclass_in;
+ rdata.type = rtype;
+
+ dns_rdatalist_init(&rdatalist);
+ rdatalist.rdclass = dns_rdataclass_in;
+ rdatalist.type = rtype;
+ rdatalist.ttl = 3600;
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
+
+ dns_rdataset_init(&rdataset);
+ result = dns_rdatalist_tordataset(&rdatalist, &rdataset);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_db_addrdataset(db, node, NULL, now, &rdataset, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_db_detachnode(db, &node);
+}
+
+static void
+overmempurge_bigrdata_test(void **state) {
+ size_t maxcache = 2097152U; /* 2MB - same as DNS_CACHE_MINSIZE */
+ size_t hiwater = maxcache - (maxcache >> 3); /* borrowed from cache.c */
+ size_t lowater = maxcache - (maxcache >> 2); /* ditto */
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ isc_mem_t *mctx2 = NULL;
+ isc_stdtime_t now;
+ size_t i;
+
+ UNUSED(state);
+
+ isc_stdtime_get(&now);
+
+ isc_mem_create(&mctx2);
+
+ result = dns_db_create(mctx2, "rbt", dns_rootname, dns_dbtype_cache,
+ dns_rdataclass_in, 0, NULL, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_mem_setwater(mctx2, overmempurge_water, NULL, hiwater, lowater);
+
+ /*
+ * Add cache entries with minimum size of data until 'overmem'
+ * condition is triggered.
+ * This should eventually happen, but we also limit the number of
+ * iteration to avoid an infinite loop in case something gets wrong.
+ */
+ for (i = 0; !isc_mem_isovermem(mctx2) && i < (maxcache / 10); i++) {
+ overmempurge_addrdataset(db, now, i, 50053, 0, false);
+ }
+ assert_true(isc_mem_isovermem(mctx2));
+
+ /*
+ * Then try to add the same number of entries, each has very large data.
+ * 'overmem purge' should keep the total cache size from not exceeding
+ * the 'hiwater' mark too much. So we should be able to assume the
+ * cache size doesn't reach the "max".
+ */
+ while (i-- > 0) {
+ overmempurge_addrdataset(db, now, i, 50054, 65535, false);
+ assert_true(isc_mem_inuse(mctx2) < maxcache);
+ }
+
+ dns_db_detach(&db);
+ isc_mem_destroy(&mctx2);
+}
+
+static void
+overmempurge_longname_test(void **state) {
+ size_t maxcache = 2097152U; /* 2MB - same as DNS_CACHE_MINSIZE */
+ size_t hiwater = maxcache - (maxcache >> 3); /* borrowed from cache.c */
+ size_t lowater = maxcache - (maxcache >> 2); /* ditto */
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ isc_mem_t *mctx2 = NULL;
+ isc_stdtime_t now;
+ size_t i;
+
+ UNUSED(state);
+
+ isc_stdtime_get(&now);
+ isc_mem_create(&mctx2);
+
+ result = dns_db_create(mctx2, "rbt", dns_rootname, dns_dbtype_cache,
+ dns_rdataclass_in, 0, NULL, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_mem_setwater(mctx2, overmempurge_water, NULL, hiwater, lowater);
+
+ /*
+ * Add cache entries with minimum size of data until 'overmem'
+ * condition is triggered.
+ * This should eventually happen, but we also limit the number of
+ * iteration to avoid an infinite loop in case something gets wrong.
+ */
+ for (i = 0; !isc_mem_isovermem(mctx2) && i < (maxcache / 10); i++) {
+ overmempurge_addrdataset(db, now, i, 50053, 0, false);
+ }
+ assert_true(isc_mem_isovermem(mctx2));
+
+ /*
+ * Then try to add the same number of entries, each has very large data.
+ * 'overmem purge' should keep the total cache size from not exceeding
+ * the 'hiwater' mark too much. So we should be able to assume the
+ * cache size doesn't reach the "max".
+ */
+ while (i-- > 0) {
+ overmempurge_addrdataset(db, now, i, 50054, 0, true);
+ assert_true(isc_mem_inuse(mctx2) < maxcache);
+ }
+
+ dns_db_detach(&db);
+ isc_mem_destroy(&mctx2);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(ownercase_test),
+ cmocka_unit_test(setownercase_test),
+ cmocka_unit_test(overmempurge_bigrdata_test),
+ cmocka_unit_test(overmempurge_longname_test),
+ };
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/rdata_test.c b/lib/dns/tests/rdata_test.c
new file mode 100644
index 0000000..9bcac99
--- /dev/null
+++ b/lib/dns/tests/rdata_test.c
@@ -0,0 +1,3229 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+
+#include <isc/cmocka.h>
+#include <isc/commandline.h>
+#include <isc/hex.h>
+#include <isc/lex.h>
+#include <isc/print.h>
+#include <isc/stdio.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/rdata.h>
+
+#include "dnstest.h"
+
+static bool debug = false;
+
+/*
+ * An array of these structures is passed to compare_ok().
+ */
+struct compare_ok {
+ const char *text1; /* text passed to fromtext_*() */
+ const char *text2; /* text passed to fromtext_*() */
+ int answer; /* -1, 0, 1 */
+ int lineno; /* source line defining this RDATA */
+};
+typedef struct compare_ok compare_ok_t;
+
+struct textvsunknown {
+ const char *text1;
+ const char *text2;
+};
+typedef struct textvsunknown textvsunknown_t;
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+/*
+ * An array of these structures is passed to check_text_ok().
+ */
+typedef struct text_ok {
+ const char *text_in; /* text passed to fromtext_*() */
+ const char *text_out; /* text expected from totext_*();
+ * NULL indicates text_in is invalid */
+ unsigned int loop;
+} text_ok_t;
+
+/*
+ * An array of these structures is passed to check_wire_ok().
+ */
+typedef struct wire_ok {
+ unsigned char data[512]; /* RDATA in wire format */
+ size_t len; /* octets of data to parse */
+ bool ok; /* is this RDATA valid? */
+ unsigned int loop;
+} wire_ok_t;
+
+#define COMPARE(r1, r2, answer) \
+ { \
+ r1, r2, answer, __LINE__ \
+ }
+#define COMPARE_SENTINEL() \
+ { \
+ NULL, NULL, 0, __LINE__ \
+ }
+
+#define TEXT_VALID_CHANGED(data_in, data_out) \
+ { \
+ data_in, data_out, 0 \
+ }
+#define TEXT_VALID(data) \
+ { \
+ data, data, 0 \
+ }
+#define TEXT_VALID_LOOP(loop, data) \
+ { \
+ data, data, loop \
+ }
+#define TEXT_VALID_LOOPCHG(loop, data_in, data_out) \
+ { \
+ data_in, data_out, loop \
+ }
+#define TEXT_INVALID(data) \
+ { \
+ data, NULL, 0 \
+ }
+#define TEXT_SENTINEL() TEXT_INVALID(NULL)
+
+#define VARGC(...) (sizeof((unsigned char[]){ __VA_ARGS__ }))
+#define WIRE_TEST(ok, loop, ...) \
+ { \
+ { __VA_ARGS__ }, VARGC(__VA_ARGS__), ok, loop \
+ }
+#define WIRE_VALID(...) WIRE_TEST(true, 0, __VA_ARGS__)
+#define WIRE_VALID_LOOP(loop, ...) WIRE_TEST(true, loop, __VA_ARGS__)
+/*
+ * WIRE_INVALID() test cases must always have at least one octet specified to
+ * distinguish them from WIRE_SENTINEL(). Use the 'empty_ok' parameter passed
+ * to check_wire_ok() for indicating whether empty RDATA is allowed for a given
+ * RR type or not.
+ */
+#define WIRE_INVALID(FIRST, ...) WIRE_TEST(false, 0, FIRST, __VA_ARGS__)
+#define WIRE_SENTINEL() WIRE_TEST(false, 0)
+
+/*
+ * Call dns_rdata_fromwire() for data in 'src', which is 'srclen' octets in
+ * size and represents RDATA of given 'type' and 'class'. Store the resulting
+ * uncompressed wire form in 'dst', which is 'dstlen' octets in size, and make
+ * 'rdata' refer to that uncompressed wire form.
+ */
+static isc_result_t
+wire_to_rdata(const unsigned char *src, size_t srclen, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, unsigned char *dst, size_t dstlen,
+ dns_rdata_t *rdata) {
+ isc_buffer_t source, target;
+ dns_decompress_t dctx;
+ isc_result_t result;
+
+ /*
+ * Set up len-octet buffer pointing at data.
+ */
+ isc_buffer_constinit(&source, src, srclen);
+ isc_buffer_add(&source, srclen);
+ isc_buffer_setactive(&source, srclen);
+
+ /*
+ * Initialize target buffer.
+ */
+ isc_buffer_init(&target, dst, dstlen);
+
+ /*
+ * Try converting input data into uncompressed wire form.
+ */
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY);
+ result = dns_rdata_fromwire(rdata, rdclass, type, &source, &dctx, 0,
+ &target);
+ dns_decompress_invalidate(&dctx);
+
+ return (result);
+}
+
+/*
+ * Call dns_rdata_towire() for rdata and write to result to dst.
+ */
+static isc_result_t
+rdata_towire(dns_rdata_t *rdata, unsigned char *dst, size_t dstlen,
+ size_t *length) {
+ isc_buffer_t target;
+ dns_compress_t cctx;
+ isc_result_t result;
+
+ /*
+ * Initialize target buffer.
+ */
+ isc_buffer_init(&target, dst, dstlen);
+
+ /*
+ * Try converting input data into uncompressed wire form.
+ */
+ dns_compress_init(&cctx, -1, dt_mctx);
+ result = dns_rdata_towire(rdata, &cctx, &target);
+ dns_compress_invalidate(&cctx);
+
+ *length = isc_buffer_usedlength(&target);
+
+ return (result);
+}
+
+static isc_result_t
+additionaldata_cb(void *arg, const dns_name_t *name, dns_rdatatype_t qtype) {
+ UNUSED(arg);
+ UNUSED(name);
+ UNUSED(qtype);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * call dns_rdata_additionaldata() for rdata.
+ */
+static isc_result_t
+rdata_additionadata(dns_rdata_t *rdata) {
+ return (dns_rdata_additionaldata(rdata, additionaldata_cb, NULL));
+}
+
+/*
+ * Call dns_rdata_checknames() with various owner names chosen to
+ * match well known forms.
+ *
+ * We are currently only checking that the calls do not trigger
+ * assertion failures.
+ *
+ * XXXMPA A future extension could be to record the expected
+ * result and the expected value of 'bad'.
+ */
+static void
+rdata_checknames(dns_rdata_t *rdata) {
+ dns_fixedname_t fixed, bfixed;
+ dns_name_t *name, *bad;
+ isc_result_t result;
+
+ name = dns_fixedname_initname(&fixed);
+ bad = dns_fixedname_initname(&bfixed);
+
+ (void)dns_rdata_checknames(rdata, dns_rootname, NULL);
+ (void)dns_rdata_checknames(rdata, dns_rootname, bad);
+
+ result = dns_name_fromstring(name, "example.net", 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ (void)dns_rdata_checknames(rdata, name, NULL);
+ (void)dns_rdata_checknames(rdata, name, bad);
+
+ result = dns_name_fromstring(name, "in-addr.arpa", 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ (void)dns_rdata_checknames(rdata, name, NULL);
+ (void)dns_rdata_checknames(rdata, name, bad);
+
+ result = dns_name_fromstring(name, "ip6.arpa", 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ (void)dns_rdata_checknames(rdata, name, NULL);
+ (void)dns_rdata_checknames(rdata, name, bad);
+}
+
+/*
+ * Test whether converting rdata to a type-specific struct and then back to
+ * rdata results in the same uncompressed wire form. This checks whether
+ * tostruct_*() and fromstruct_*() routines for given RR class and type behave
+ * consistently.
+ *
+ * This function is called for every correctly processed input RDATA, from both
+ * check_text_ok_single() and check_wire_ok_single().
+ */
+static void
+check_struct_conversions(dns_rdata_t *rdata, size_t structsize,
+ unsigned int loop) {
+ dns_rdataclass_t rdclass = rdata->rdclass;
+ dns_rdatatype_t type = rdata->type;
+ isc_result_t result;
+ isc_buffer_t target;
+ void *rdata_struct;
+ char buf[1024];
+ unsigned int count = 0;
+
+ rdata_struct = isc_mem_allocate(dt_mctx, structsize);
+ assert_non_null(rdata_struct);
+
+ /*
+ * Convert from uncompressed wire form into type-specific struct.
+ */
+ result = dns_rdata_tostruct(rdata, rdata_struct, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Convert from type-specific struct into uncompressed wire form.
+ */
+ isc_buffer_init(&target, buf, sizeof(buf));
+ result = dns_rdata_fromstruct(NULL, rdclass, type, rdata_struct,
+ &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Ensure results are consistent.
+ */
+ assert_int_equal(isc_buffer_usedlength(&target), rdata->length);
+
+ assert_memory_equal(buf, rdata->data, rdata->length);
+
+ /*
+ * Check that one can walk hip rendezvous servers and
+ * https/svcb parameters.
+ */
+ switch (type) {
+ case dns_rdatatype_hip: {
+ dns_rdata_hip_t *hip = rdata_struct;
+
+ for (result = dns_rdata_hip_first(hip); result == ISC_R_SUCCESS;
+ result = dns_rdata_hip_next(hip))
+ {
+ dns_name_t name;
+ dns_name_init(&name, NULL);
+ dns_rdata_hip_current(hip, &name);
+ assert_int_not_equal(dns_name_countlabels(&name), 0);
+ assert_true(dns_name_isabsolute(&name));
+ count++;
+ }
+ assert_int_equal(result, ISC_R_NOMORE);
+ assert_int_equal(count, loop);
+ break;
+ }
+ case dns_rdatatype_https: {
+ dns_rdata_in_https_t *https = rdata_struct;
+
+ for (result = dns_rdata_in_https_first(https);
+ result == ISC_R_SUCCESS;
+ result = dns_rdata_in_https_next(https))
+ {
+ isc_region_t region;
+ dns_rdata_in_https_current(https, &region);
+ assert_true(region.length >= 4);
+ count++;
+ }
+ assert_int_equal(result, ISC_R_NOMORE);
+ assert_int_equal(count, loop);
+ break;
+ }
+ case dns_rdatatype_svcb: {
+ dns_rdata_in_svcb_t *svcb = rdata_struct;
+
+ for (result = dns_rdata_in_svcb_first(svcb);
+ result == ISC_R_SUCCESS;
+ result = dns_rdata_in_svcb_next(svcb))
+ {
+ isc_region_t region;
+ dns_rdata_in_svcb_current(svcb, &region);
+ assert_true(region.length >= 4);
+ count++;
+ }
+ assert_int_equal(result, ISC_R_NOMORE);
+ assert_int_equal(count, loop);
+ break;
+ }
+ }
+
+ isc_mem_free(dt_mctx, rdata_struct);
+}
+
+/*
+ * Check whether converting supplied text form RDATA into uncompressed wire
+ * form succeeds (tests fromtext_*()). If so, try converting it back into text
+ * form and see if it results in the original text (tests totext_*()).
+ */
+static void
+check_text_ok_single(const text_ok_t *text_ok, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, size_t structsize) {
+ unsigned char buf_fromtext[1024], buf_fromwire[1024], buf_towire[1024];
+ dns_rdata_t rdata = DNS_RDATA_INIT, rdata2 = DNS_RDATA_INIT;
+ char buf_totext[1024] = { 0 };
+ isc_buffer_t target;
+ isc_result_t result;
+ size_t length = 0;
+
+ if (debug) {
+ fprintf(stdout, "#check_text_ok_single(%s)\n",
+ text_ok->text_in);
+ }
+ /*
+ * Try converting text form RDATA into uncompressed wire form.
+ */
+ result = dns_test_rdatafromstring(&rdata, rdclass, type, buf_fromtext,
+ sizeof(buf_fromtext),
+ text_ok->text_in, false);
+ /*
+ * Check whether result is as expected.
+ */
+ if (text_ok->text_out != NULL) {
+ if (debug && result != ISC_R_SUCCESS) {
+ fprintf(stdout, "# '%s'\n", text_ok->text_in);
+ fprintf(stdout, "# result=%s\n",
+ dns_result_totext(result));
+ }
+ assert_int_equal(result, ISC_R_SUCCESS);
+ } else {
+ if (debug && result == ISC_R_SUCCESS) {
+ fprintf(stdout, "#'%s'\n", text_ok->text_in);
+ }
+ assert_int_not_equal(result, ISC_R_SUCCESS);
+ }
+
+ /*
+ * If text form RDATA was not parsed correctly, performing any
+ * additional checks is pointless.
+ */
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ /*
+ * Try converting uncompressed wire form RDATA back into text form and
+ * check whether the resulting text is the same as the original one.
+ */
+ isc_buffer_init(&target, buf_totext, sizeof(buf_totext));
+ result = dns_rdata_totext(&rdata, NULL, &target);
+ if (result != ISC_R_SUCCESS && debug) {
+ size_t i;
+ fprintf(stdout, "# dns_rdata_totext -> %s",
+ dns_result_totext(result));
+ for (i = 0; i < rdata.length; i++) {
+ if ((i % 16) == 0) {
+ fprintf(stdout, "\n#");
+ }
+ fprintf(stdout, " %02x", rdata.data[i]);
+ }
+ fprintf(stdout, "\n");
+ }
+ assert_int_equal(result, ISC_R_SUCCESS);
+ /*
+ * Ensure buf_totext is properly NUL terminated as dns_rdata_totext()
+ * may attempt different output formats writing into the apparently
+ * unused part of the buffer.
+ */
+ isc_buffer_putuint8(&target, 0);
+ if (debug && strcmp(buf_totext, text_ok->text_out) != 0) {
+ fprintf(stdout, "# '%s' != '%s'\n", buf_totext,
+ text_ok->text_out);
+ }
+ assert_string_equal(buf_totext, text_ok->text_out);
+
+ if (debug) {
+ fprintf(stdout, "#dns_rdata_totext -> '%s'\n", buf_totext);
+ }
+
+ /*
+ * Ensure that fromtext_*() output is valid input for fromwire_*().
+ */
+ result = wire_to_rdata(rdata.data, rdata.length, rdclass, type,
+ buf_fromwire, sizeof(buf_fromwire), &rdata2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(rdata.length, rdata2.length);
+ assert_memory_equal(rdata.data, buf_fromwire, rdata.length);
+
+ /*
+ * Ensure that fromtext_*() output is valid input for towire_*().
+ */
+ result = rdata_towire(&rdata, buf_towire, sizeof(buf_towire), &length);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(rdata.length, length);
+ assert_memory_equal(rdata.data, buf_towire, length);
+
+ /*
+ * Test that additionaldata_*() succeeded.
+ */
+ result = rdata_additionadata(&rdata);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Exercise checknames_*().
+ */
+ rdata_checknames(&rdata);
+
+ /*
+ * Perform two-way conversion checks between uncompressed wire form and
+ * type-specific struct.
+ */
+ check_struct_conversions(&rdata, structsize, text_ok->loop);
+}
+
+/*
+ * Test whether converting rdata to text form and then parsing the result of
+ * that conversion again results in the same uncompressed wire form. This
+ * checks whether totext_*() output is parsable by fromtext_*() for given RR
+ * class and type.
+ *
+ * This function is called for every input RDATA which is successfully parsed
+ * by check_wire_ok_single() and whose type is not a meta-type.
+ */
+static void
+check_text_conversions(dns_rdata_t *rdata) {
+ char buf_totext[1024] = { 0 };
+ unsigned char buf_fromtext[1024];
+ isc_result_t result;
+ isc_buffer_t target;
+ dns_rdata_t rdata2 = DNS_RDATA_INIT;
+
+ /*
+ * Convert uncompressed wire form RDATA into text form. This
+ * conversion must succeed since input RDATA was successfully
+ * parsed by check_wire_ok_single().
+ */
+ isc_buffer_init(&target, buf_totext, sizeof(buf_totext));
+ result = dns_rdata_totext(rdata, NULL, &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ /*
+ * Ensure buf_totext is properly NUL terminated as dns_rdata_totext()
+ * may attempt different output formats writing into the apparently
+ * unused part of the buffer.
+ */
+ isc_buffer_putuint8(&target, 0);
+ if (debug) {
+ fprintf(stdout, "#'%s'\n", buf_totext);
+ }
+
+ /*
+ * Try parsing text form RDATA output by dns_rdata_totext() again.
+ */
+ result = dns_test_rdatafromstring(&rdata2, rdata->rdclass, rdata->type,
+ buf_fromtext, sizeof(buf_fromtext),
+ buf_totext, false);
+ if (debug && result != ISC_R_SUCCESS) {
+ fprintf(stdout, "# result = %s\n", dns_result_totext(result));
+ fprintf(stdout, "# '%s'\n", buf_fromtext);
+ }
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(rdata2.length, rdata->length);
+ assert_memory_equal(buf_fromtext, rdata->data, rdata->length);
+}
+
+/*
+ * Test whether converting rdata to multi-line text form and then parsing the
+ * result of that conversion again results in the same uncompressed wire form.
+ * This checks whether multi-line totext_*() output is parsable by fromtext_*()
+ * for given RR class and type.
+ *
+ * This function is called for every input RDATA which is successfully parsed
+ * by check_wire_ok_single() and whose type is not a meta-type.
+ */
+static void
+check_multiline_text_conversions(dns_rdata_t *rdata) {
+ char buf_totext[1024] = { 0 };
+ unsigned char buf_fromtext[1024];
+ isc_result_t result;
+ isc_buffer_t target;
+ dns_rdata_t rdata2 = DNS_RDATA_INIT;
+ unsigned int flags;
+
+ /*
+ * Convert uncompressed wire form RDATA into multi-line text form.
+ * This conversion must succeed since input RDATA was successfully
+ * parsed by check_wire_ok_single().
+ */
+ isc_buffer_init(&target, buf_totext, sizeof(buf_totext));
+ flags = dns_master_styleflags(&dns_master_style_default);
+ result = dns_rdata_tofmttext(rdata, dns_rootname, flags, 80 - 32, 4,
+ "\n", &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ /*
+ * Ensure buf_totext is properly NUL terminated as
+ * dns_rdata_tofmttext() may attempt different output formats
+ * writing into the apparently unused part of the buffer.
+ */
+ isc_buffer_putuint8(&target, 0);
+ if (debug) {
+ fprintf(stdout, "#'%s'\n", buf_totext);
+ }
+
+ /*
+ * Try parsing multi-line text form RDATA output by
+ * dns_rdata_tofmttext() again.
+ */
+ result = dns_test_rdatafromstring(&rdata2, rdata->rdclass, rdata->type,
+ buf_fromtext, sizeof(buf_fromtext),
+ buf_totext, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(rdata2.length, rdata->length);
+ assert_memory_equal(buf_fromtext, rdata->data, rdata->length);
+}
+
+/*
+ * Test whether supplied wire form RDATA is properly handled as being either
+ * valid or invalid for an RR of given rdclass and type.
+ */
+static void
+check_wire_ok_single(const wire_ok_t *wire_ok, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, size_t structsize) {
+ unsigned char buf[1024], buf_towire[1024];
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ size_t length = 0;
+
+ /*
+ * Try converting wire data into uncompressed wire form.
+ */
+ result = wire_to_rdata(wire_ok->data, wire_ok->len, rdclass, type, buf,
+ sizeof(buf), &rdata);
+ /*
+ * Check whether result is as expected.
+ */
+ if (wire_ok->ok) {
+ assert_int_equal(result, ISC_R_SUCCESS);
+ } else {
+ assert_int_not_equal(result, ISC_R_SUCCESS);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ /*
+ * If data was parsed correctly, perform two-way conversion checks
+ * between uncompressed wire form and type-specific struct.
+ *
+ * If the RR type is not a meta-type, additionally perform two-way
+ * conversion checks between:
+ *
+ * - uncompressed wire form and text form,
+ * - uncompressed wire form and multi-line text form.
+ */
+ check_struct_conversions(&rdata, structsize, wire_ok->loop);
+ if (!dns_rdatatype_ismeta(rdata.type)) {
+ check_text_conversions(&rdata);
+ check_multiline_text_conversions(&rdata);
+ }
+
+ /*
+ * Ensure that fromwire_*() output is valid input for towire_*().
+ */
+ result = rdata_towire(&rdata, buf_towire, sizeof(buf_towire), &length);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(rdata.length, length);
+ assert_memory_equal(rdata.data, buf_towire, length);
+
+ /*
+ * Test that additionaldata_*() succeeded.
+ */
+ result = rdata_additionadata(&rdata);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Exercise checknames_*().
+ */
+ rdata_checknames(&rdata);
+}
+
+/*
+ * Test fromtext_*() and totext_*() routines for given RR class and type for
+ * each text form RDATA in the supplied array. See the comment for
+ * check_text_ok_single() for an explanation of how exactly these routines are
+ * tested.
+ */
+static void
+check_text_ok(const text_ok_t *text_ok, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, size_t structsize) {
+ size_t i;
+
+ /*
+ * Check all entries in the supplied array.
+ */
+ for (i = 0; text_ok[i].text_in != NULL; i++) {
+ check_text_ok_single(&text_ok[i], rdclass, type, structsize);
+ }
+}
+
+/*
+ * For each wire form RDATA in the supplied array, check whether it is properly
+ * handled as being either valid or invalid for an RR of given rdclass and
+ * type, then check whether trying to process a zero-length wire data buffer
+ * yields the expected result. This checks whether the fromwire_*() routine
+ * for given RR class and type behaves as expected.
+ */
+static void
+check_wire_ok(const wire_ok_t *wire_ok, bool empty_ok, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type, size_t structsize) {
+ wire_ok_t empty_wire = WIRE_TEST(empty_ok, 0);
+ size_t i;
+
+ /*
+ * Check all entries in the supplied array.
+ */
+ for (i = 0; wire_ok[i].len != 0; i++) {
+ if (debug) {
+ fprintf(stderr, "calling check_wire_ok_single on %zu\n",
+ i);
+ }
+ check_wire_ok_single(&wire_ok[i], rdclass, type, structsize);
+ }
+
+ /*
+ * Check empty wire data.
+ */
+ check_wire_ok_single(&empty_wire, rdclass, type, structsize);
+}
+
+/*
+ * Check that two records compare as expected with dns_rdata_compare().
+ */
+static void
+check_compare_ok_single(const compare_ok_t *compare_ok,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type) {
+ dns_rdata_t rdata1 = DNS_RDATA_INIT, rdata2 = DNS_RDATA_INIT;
+ unsigned char buf1[1024], buf2[1024];
+ isc_result_t result;
+ int answer;
+
+ result = dns_test_rdatafromstring(&rdata1, rdclass, type, buf1,
+ sizeof(buf1), compare_ok->text1,
+ false);
+ if (result != ISC_R_SUCCESS) {
+ fail_msg("# line %d: '%s': expected success, got failure",
+ compare_ok->lineno, compare_ok->text1);
+ }
+
+ result = dns_test_rdatafromstring(&rdata2, rdclass, type, buf2,
+ sizeof(buf2), compare_ok->text2,
+ false);
+
+ if (result != ISC_R_SUCCESS) {
+ fail_msg("# line %d: '%s': expected success, got failure",
+ compare_ok->lineno, compare_ok->text2);
+ }
+
+ answer = dns_rdata_compare(&rdata1, &rdata2);
+ if (compare_ok->answer == 0 && answer != 0) {
+ fail_msg("# line %d: dns_rdata_compare('%s', '%s'): "
+ "expected equal, got %s",
+ compare_ok->lineno, compare_ok->text1,
+ compare_ok->text2,
+ (answer > 0) ? "greater than" : "less than");
+ }
+ if (compare_ok->answer < 0 && answer >= 0) {
+ fail_msg("# line %d: dns_rdata_compare('%s', '%s'): "
+ "expected less than, got %s",
+ compare_ok->lineno, compare_ok->text1,
+ compare_ok->text2,
+ (answer == 0) ? "equal" : "greater than");
+ }
+ if (compare_ok->answer > 0 && answer <= 0) {
+ fail_msg("line %d: dns_rdata_compare('%s', '%s'): "
+ "expected greater than, got %s",
+ compare_ok->lineno, compare_ok->text1,
+ compare_ok->text2,
+ (answer == 0) ? "equal" : "less than");
+ }
+}
+
+/*
+ * Check that all the records sets in compare_ok compare as expected
+ * with dns_rdata_compare().
+ */
+static void
+check_compare_ok(const compare_ok_t *compare_ok, dns_rdataclass_t rdclass,
+ dns_rdatatype_t type) {
+ size_t i;
+ /*
+ * Check all entries in the supplied array.
+ */
+ for (i = 0; compare_ok[i].text1 != NULL; i++) {
+ check_compare_ok_single(&compare_ok[i], rdclass, type);
+ }
+}
+
+/*
+ * Test whether supplied sets of text form and/or wire form RDATA are handled
+ * as expected.
+ *
+ * The empty_ok argument denotes whether an attempt to parse a zero-length wire
+ * data buffer should succeed or not (it is valid for some RR types). There is
+ * no point in performing a similar check for empty text form RDATA, because
+ * dns_rdata_fromtext() returns ISC_R_UNEXPECTEDEND before calling fromtext_*()
+ * for the given RR class and type.
+ */
+static void
+check_rdata(const text_ok_t *text_ok, const wire_ok_t *wire_ok,
+ const compare_ok_t *compare_ok, bool empty_ok,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type, size_t structsize) {
+ if (text_ok != NULL) {
+ check_text_ok(text_ok, rdclass, type, structsize);
+ }
+ if (wire_ok != NULL) {
+ check_wire_ok(wire_ok, empty_ok, rdclass, type, structsize);
+ }
+ if (compare_ok != NULL) {
+ check_compare_ok(compare_ok, rdclass, type);
+ }
+}
+
+/*
+ * Check presentation vs unknown format of the record.
+ */
+static void
+check_textvsunknown_single(const textvsunknown_t *textvsunknown,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type) {
+ dns_rdata_t rdata1 = DNS_RDATA_INIT, rdata2 = DNS_RDATA_INIT;
+ unsigned char buf1[1024], buf2[1024];
+ isc_result_t result;
+
+ result = dns_test_rdatafromstring(&rdata1, rdclass, type, buf1,
+ sizeof(buf1), textvsunknown->text1,
+ false);
+ if (debug && result != ISC_R_SUCCESS) {
+ fprintf(stdout, "# '%s'\n", textvsunknown->text1);
+ fprintf(stdout, "# result=%s\n", dns_result_totext(result));
+ }
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_test_rdatafromstring(&rdata2, rdclass, type, buf2,
+ sizeof(buf2), textvsunknown->text2,
+ false);
+ if (debug && result != ISC_R_SUCCESS) {
+ fprintf(stdout, "# '%s'\n", textvsunknown->text2);
+ fprintf(stdout, "# result=%s\n", dns_result_totext(result));
+ }
+ assert_int_equal(result, ISC_R_SUCCESS);
+ if (debug && rdata1.length != rdata2.length) {
+ fprintf(stdout, "# '%s'\n", textvsunknown->text1);
+ fprintf(stdout, "# rdata1.length (%u) != rdata2.length (%u)\n",
+ rdata1.length, rdata2.length);
+ }
+ assert_int_equal(rdata1.length, rdata2.length);
+ if (debug && memcmp(rdata1.data, rdata2.data, rdata1.length) != 0) {
+ unsigned int i;
+ fprintf(stdout, "# '%s'\n", textvsunknown->text1);
+ for (i = 0; i < rdata1.length; i++) {
+ if (rdata1.data[i] != rdata2.data[i]) {
+ fprintf(stderr, "# %u: %02x != %02x\n", i,
+ rdata1.data[i], rdata2.data[i]);
+ }
+ }
+ }
+ assert_memory_equal(rdata1.data, rdata2.data, rdata1.length);
+}
+
+static void
+check_textvsunknown(const textvsunknown_t *textvsunknown,
+ dns_rdataclass_t rdclass, dns_rdatatype_t type) {
+ size_t i;
+
+ /*
+ * Check all entries in the supplied array.
+ */
+ for (i = 0; textvsunknown[i].text1 != NULL; i++) {
+ check_textvsunknown_single(&textvsunknown[i], rdclass, type);
+ }
+}
+
+/*
+ * Common tests for RR types based on KEY that require key data:
+ *
+ * - CDNSKEY (RFC 7344)
+ * - DNSKEY (RFC 4034)
+ * - RKEY (draft-reid-dnsext-rkey-00)
+ */
+static void
+key_required(void **state, dns_rdatatype_t type, size_t size) {
+ wire_ok_t wire_ok[] = { /*
+ * RDATA must be at least 5 octets in size:
+ *
+ * - 2 octets for Flags,
+ * - 1 octet for Protocol,
+ * - 1 octet for Algorithm,
+ * - Public Key must not be empty.
+ *
+ * RFC 2535 section 3.1.2 allows the Public Key
+ * to be empty if bits 0-1 of Flags are both
+ * set, but that only applies to KEY records:
+ * for the RR types tested here, the Public Key
+ * must not be empty.
+ */
+ WIRE_INVALID(0x00),
+ WIRE_INVALID(0x00, 0x00),
+ WIRE_INVALID(0x00, 0x00, 0x00),
+ WIRE_INVALID(0xc0, 0x00, 0x00, 0x00),
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00),
+ WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00),
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(NULL, wire_ok, NULL, false, dns_rdataclass_in, type, size);
+}
+
+/* APL RDATA manipulations */
+static void
+apl(void **state) {
+ text_ok_t text_ok[] = {
+ /* empty list */
+ TEXT_VALID(""),
+ /* min,max prefix IPv4 */
+ TEXT_VALID("1:0.0.0.0/0"), TEXT_VALID("1:127.0.0.1/32"),
+ /* min,max prefix IPv6 */
+ TEXT_VALID("2:::/0"), TEXT_VALID("2:::1/128"),
+ /* negated */
+ TEXT_VALID("!1:0.0.0.0/0"), TEXT_VALID("!1:127.0.0.1/32"),
+ TEXT_VALID("!2:::/0"), TEXT_VALID("!2:::1/128"),
+ /* bits set after prefix length - not disallowed */
+ TEXT_VALID("1:127.0.0.0/0"), TEXT_VALID("2:8000::/0"),
+ /* multiple */
+ TEXT_VALID("1:0.0.0.0/0 1:127.0.0.1/32"),
+ TEXT_VALID("1:0.0.0.0/0 !1:127.0.0.1/32"),
+ /* family 0, prefix 0, positive */
+ TEXT_VALID("\\# 4 00000000"),
+ /* family 0, prefix 0, negative */
+ TEXT_VALID("\\# 4 00000080"),
+ /* prefix too long */
+ TEXT_INVALID("1:0.0.0.0/33"), TEXT_INVALID("2:::/129"),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL()
+ };
+ wire_ok_t wire_ok[] = { /* zero length */
+ WIRE_VALID(),
+ /* prefix too big IPv4 */
+ WIRE_INVALID(0x00, 0x01, 33U, 0x00),
+ /* prefix too big IPv6 */
+ WIRE_INVALID(0x00, 0x02, 129U, 0x00),
+ /* trailing zero octet in afdpart */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x01, 0x00),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, true, dns_rdataclass_in,
+ dns_rdatatype_apl, sizeof(dns_rdata_in_apl_t));
+}
+
+/*
+ * http://broadband-forum.org/ftp/pub/approved-specs/af-saa-0069.000.pdf
+ *
+ * ATMA RR’s have the following RDATA format:
+ *
+ * 1 1 1 1 1 1
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * | FORMAT | |
+ * +--+--+--+--+--+--+--+--+ |
+ * / ADDRESS /
+ * | |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ *
+ * The fields have the following meaning:
+ *
+ * * FORMAT: One octet that indicates the format of ADDRESS. The two
+ * possible values for FORMAT are value 0 indicating ATM End System Address
+ * (AESA) format and value 1 indicating E.164 format.
+ *
+ * * ADDRESS: Variable length string of octets containing the ATM address of
+ * the node to which this RR pertains.
+ *
+ * When the format value is 0, indicating that the address is in AESA format,
+ * the address is coded as described in ISO 8348/AD 2 using the preferred
+ * binary encoding of the ISO NSAP format. When the format value is 1,
+ * indicating that the address is in E.164 format, the Address/Number Digits
+ * appear in the order in which they would be entered on a numeric keypad.
+ * Digits are coded in IA5 characters with the leftmost bit of each digit set
+ * to 0. This ATM address appears in ATM End System Address Octets field (AESA
+ * format) or the Address/Number Digits field (E.164 format) of the Called
+ * party number information element [ATMUNI3.1]. Subaddress information is
+ * intentionally not included because E.164 subaddress information is used for
+ * routing.
+ *
+ * ATMA RRs cause no additional section processing.
+ */
+static void
+atma(void **state) {
+ text_ok_t text_ok[] = { TEXT_VALID("00"),
+ TEXT_VALID_CHANGED("0.0", "00"),
+ /*
+ * multiple consecutive periods
+ */
+ TEXT_INVALID("0..0"),
+ /*
+ * trailing period
+ */
+ TEXT_INVALID("00."),
+ /*
+ * leading period
+ */
+ TEXT_INVALID(".00"),
+ /*
+ * Not full octets.
+ */
+ TEXT_INVALID("000"),
+ /*
+ * E.164
+ */
+ TEXT_VALID("+61200000000"),
+ /*
+ * E.164 with periods
+ */
+ TEXT_VALID_CHANGED("+61.2.0000.0000", "+6120000"
+ "0000"),
+ /*
+ * E.164 with period at end
+ */
+ TEXT_INVALID("+61200000000."),
+ /*
+ * E.164 with multiple consecutive periods
+ */
+ TEXT_INVALID("+612..00000000"),
+ /*
+ * E.164 with period before the leading digit.
+ */
+ TEXT_INVALID("+.61200000000"),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL() };
+ wire_ok_t wire_ok[] = {
+ /*
+ * Too short.
+ */
+ WIRE_INVALID(0x00), WIRE_INVALID(0x01),
+ /*
+ * all digits
+ */
+ WIRE_VALID(0x01, '6', '1', '2', '0', '0', '0'),
+ /*
+ * non digit
+ */
+ WIRE_INVALID(0x01, '+', '6', '1', '2', '0', '0', '0'),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_atma, sizeof(dns_rdata_in_atma_t));
+}
+
+/* AMTRELAY RDATA manipulations */
+static void
+amtrelay(void **state) {
+ text_ok_t text_ok[] = {
+ TEXT_INVALID(""), TEXT_INVALID("0"), TEXT_INVALID("0 0"),
+ /* gateway type 0 */
+ TEXT_VALID("0 0 0"), TEXT_VALID("0 1 0"),
+ TEXT_INVALID("0 2 0"), /* discovery out of range */
+ TEXT_VALID("255 1 0"), /* max precedence */
+ TEXT_INVALID("256 1 0"), /* precedence out of range */
+
+ /* IPv4 gateway */
+ TEXT_INVALID("0 0 1"), /* no address */
+ TEXT_VALID("0 0 1 0.0.0.0"),
+ TEXT_INVALID("0 0 1 0.0.0.0 x"), /* extra */
+ TEXT_INVALID("0 0 1 0.0.0.0.0"), /* bad address */
+ TEXT_INVALID("0 0 1 ::"), /* bad address */
+ TEXT_INVALID("0 0 1 ."), /* bad address */
+
+ /* IPv6 gateway */
+ TEXT_INVALID("0 0 2"), /* no address */
+ TEXT_VALID("0 0 2 ::"), TEXT_INVALID("0 0 2 :: xx"), /* extra */
+ TEXT_INVALID("0 0 2 0.0.0.0"), /* bad address */
+ TEXT_INVALID("0 0 2 ."), /* bad address */
+
+ /* hostname gateway */
+ TEXT_INVALID("0 0 3"), /* no name */
+ /* IPv4 is a valid name */
+ TEXT_VALID_CHANGED("0 0 3 0.0.0.0", "0 0 3 0.0.0.0."),
+ /* IPv6 is a valid name */
+ TEXT_VALID_CHANGED("0 0 3 ::", "0 0 3 ::."),
+ TEXT_VALID_CHANGED("0 0 3 example", "0 0 3 example."),
+ TEXT_VALID("0 0 3 example."),
+ TEXT_INVALID("0 0 3 example. x"), /* extra */
+
+ /* unknown gateway */
+ TEXT_VALID("\\# 2 0004"), TEXT_VALID("\\# 2 0084"),
+ TEXT_VALID("\\# 2 007F"), TEXT_VALID("\\# 3 000400"),
+ TEXT_VALID("\\# 3 008400"), TEXT_VALID("\\# 3 00FF00"),
+
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL()
+ };
+ wire_ok_t wire_ok[] = {
+ WIRE_INVALID(0x00), WIRE_VALID(0x00, 0x00),
+ WIRE_VALID(0x00, 0x80), WIRE_INVALID(0x00, 0x00, 0x00),
+ WIRE_INVALID(0x00, 0x80, 0x00),
+
+ WIRE_INVALID(0x00, 0x01), WIRE_INVALID(0x00, 0x01, 0x00),
+ WIRE_INVALID(0x00, 0x01, 0x00, 0x00),
+ WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x00),
+ WIRE_VALID(0x00, 0x01, 0x00, 0x00, 0x00, 0x00),
+ WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00),
+
+ WIRE_INVALID(0x00, 0x02), WIRE_INVALID(0x00, 0x02, 0x00),
+ WIRE_VALID(0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14,
+ 0x15),
+ WIRE_INVALID(0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16),
+
+ WIRE_INVALID(0x00, 0x03), WIRE_VALID(0x00, 0x03, 0x00),
+ WIRE_INVALID(0x00, 0x03, 0x00, 0x00), /* extra */
+
+ WIRE_VALID(0x00, 0x04), WIRE_VALID(0x00, 0x04, 0x00),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_amtrelay, sizeof(dns_rdata_amtrelay_t));
+}
+
+static void
+cdnskey(void **state) {
+ key_required(state, dns_rdatatype_cdnskey, sizeof(dns_rdata_cdnskey_t));
+}
+
+/*
+ * CSYNC tests.
+ *
+ * RFC 7477:
+ *
+ * 2.1. The CSYNC Resource Record Format
+ *
+ * 2.1.1. The CSYNC Resource Record Wire Format
+ *
+ * The CSYNC RDATA consists of the following fields:
+ *
+ * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | SOA Serial |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Flags | Type Bit Map /
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * / Type Bit Map (continued) /
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * 2.1.1.1. The SOA Serial Field
+ *
+ * The SOA Serial field contains a copy of the 32-bit SOA serial number
+ * from the child zone. If the soaminimum flag is set, parental agents
+ * querying children's authoritative servers MUST NOT act on data from
+ * zones advertising an SOA serial number less than this value. See
+ * [RFC1982] for properly implementing "less than" logic. If the
+ * soaminimum flag is not set, parental agents MUST ignore the value in
+ * the SOA Serial field. Clients can set the field to any value if the
+ * soaminimum flag is unset, such as the number zero.
+ *
+ * (...)
+ *
+ * 2.1.1.2. The Flags Field
+ *
+ * The Flags field contains 16 bits of boolean flags that define
+ * operations that affect the processing of the CSYNC record. The flags
+ * defined in this document are as follows:
+ *
+ * 0x00 0x01: "immediate"
+ *
+ * 0x00 0x02: "soaminimum"
+ *
+ * The definitions for how the flags are to be used can be found in
+ * Section 3.
+ *
+ * The remaining flags are reserved for use by future specifications.
+ * Undefined flags MUST be set to 0 by CSYNC publishers. Parental
+ * agents MUST NOT process a CSYNC record if it contains a 1 value for a
+ * flag that is unknown to or unsupported by the parental agent.
+ *
+ * 2.1.1.2.1. The Type Bit Map Field
+ *
+ * The Type Bit Map field indicates the record types to be processed by
+ * the parental agent, according to the procedures in Section 3. The
+ * Type Bit Map field is encoded in the same way as the Type Bit Map
+ * field of the NSEC record, described in [RFC4034], Section 4.1.2. If
+ * a bit has been set that a parental agent implementation does not
+ * understand, the parental agent MUST NOT act upon the record.
+ * Specifically, a parental agent must not simply copy the data, and it
+ * must understand the semantics associated with a bit in the Type Bit
+ * Map field that has been set to 1.
+ */
+static void
+csync(void **state) {
+ text_ok_t text_ok[] = { TEXT_INVALID(""),
+ TEXT_INVALID("0"),
+ TEXT_VALID("0 0"),
+ TEXT_VALID("0 0 A"),
+ TEXT_VALID("0 0 NS"),
+ TEXT_VALID("0 0 AAAA"),
+ TEXT_VALID("0 0 A AAAA"),
+ TEXT_VALID("0 0 A NS AAAA"),
+ TEXT_INVALID("0 0 A NS AAAA BOGUS"),
+ TEXT_SENTINEL() };
+ wire_ok_t wire_ok[] = {
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00),
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00, 0x00),
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00),
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00),
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Serial + flags only.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Bad type map.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Bad type map.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Good type map.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x02),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_csync, sizeof(dns_rdata_csync_t));
+}
+
+static void
+dnskey(void **state) {
+ key_required(state, dns_rdatatype_dnskey, sizeof(dns_rdata_dnskey_t));
+}
+
+/*
+ * DOA tests.
+ *
+ * draft-durand-doa-over-dns-03:
+ *
+ * 3.2. DOA RDATA Wire Format
+ *
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 0: | |
+ * | DOA-ENTERPRISE |
+ * | |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 4: | |
+ * | DOA-TYPE |
+ * | |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 8: | DOA-LOCATION | DOA-MEDIA-TYPE /
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 10: / /
+ * / DOA-MEDIA-TYPE (continued) /
+ * / /
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * / /
+ * / DOA-DATA /
+ * / /
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ *
+ * DOA-ENTERPRISE: a 32-bit unsigned integer in network order.
+ *
+ * DOA-TYPE: a 32-bit unsigned integer in network order.
+ *
+ * DOA-LOCATION: an 8-bit unsigned integer.
+ *
+ * DOA-MEDIA-TYPE: A <character-string> (see [RFC1035]). The first
+ * octet of the <character-string> contains the number of characters to
+ * follow.
+ *
+ * DOA-DATA: A variable length blob of binary data. The length of the
+ * DOA-DATA is not contained within the wire format of the RR and has to
+ * be computed from the RDLENGTH of the entire RR once other fields have
+ * been taken into account.
+ *
+ * 3.3. DOA RDATA Presentation Format
+ *
+ * The DOA-ENTERPRISE field is presented as an unsigned 32-bit decimal
+ * integer with range 0 - 4,294,967,295.
+ *
+ * The DOA-TYPE field is presented as an unsigned 32-bit decimal integer
+ * with range 0 - 4,294,967,295.
+ *
+ * The DOA-LOCATION field is presented as an unsigned 8-bit decimal
+ * integer with range 0 - 255.
+ *
+ * The DOA-MEDIA-TYPE field is presented as a single <character-string>.
+ *
+ * The DOA-DATA is presented as Base64 encoded data [RFC4648] unless the
+ * DOA-DATA is empty in which case it is presented as a single dash
+ * character ("-", ASCII 45). White space is permitted within Base64
+ * data.
+ */
+static void
+doa(void **state) {
+ text_ok_t text_ok[] = {
+ /*
+ * Valid, non-empty DOA-DATA.
+ */
+ TEXT_VALID("0 0 1 \"text/plain\" Zm9v"),
+ /*
+ * Valid, non-empty DOA-DATA with whitespace in between.
+ */
+ TEXT_VALID_CHANGED("0 0 1 \"text/plain\" Zm 9v", "0 0 1 "
+ "\"text/"
+ "plain\" "
+ "Zm9v"),
+ /*
+ * Valid, unquoted DOA-MEDIA-TYPE, non-empty DOA-DATA.
+ */
+ TEXT_VALID_CHANGED("0 0 1 text/plain Zm9v", "0 0 1 "
+ "\"text/plain\" "
+ "Zm9v"),
+ /*
+ * Invalid, quoted non-empty DOA-DATA.
+ */
+ TEXT_INVALID("0 0 1 \"text/plain\" \"Zm9v\""),
+ /*
+ * Valid, empty DOA-DATA.
+ */
+ TEXT_VALID("0 0 1 \"text/plain\" -"),
+ /*
+ * Invalid, quoted empty DOA-DATA.
+ */
+ TEXT_INVALID("0 0 1 \"text/plain\" \"-\""),
+ /*
+ * Invalid, missing "-" in empty DOA-DATA.
+ */
+ TEXT_INVALID("0 0 1 \"text/plain\""),
+ /*
+ * Valid, undefined DOA-LOCATION.
+ */
+ TEXT_VALID("0 0 100 \"text/plain\" Zm9v"),
+ /*
+ * Invalid, DOA-LOCATION too big.
+ */
+ TEXT_INVALID("0 0 256 \"text/plain\" ZM9v"),
+ /*
+ * Valid, empty DOA-MEDIA-TYPE, non-empty DOA-DATA.
+ */
+ TEXT_VALID("0 0 2 \"\" aHR0cHM6Ly93d3cuaXNjLm9yZy8="),
+ /*
+ * Valid, empty DOA-MEDIA-TYPE, empty DOA-DATA.
+ */
+ TEXT_VALID("0 0 1 \"\" -"),
+ /*
+ * Valid, DOA-MEDIA-TYPE with a space.
+ */
+ TEXT_VALID("0 0 1 \"plain text\" Zm9v"),
+ /*
+ * Invalid, missing DOA-MEDIA-TYPE.
+ */
+ TEXT_INVALID("1234567890 1234567890 1"),
+ /*
+ * Valid, DOA-DATA over 255 octets.
+ */
+ TEXT_VALID("1234567890 1234567890 1 \"image/gif\" "
+ "R0lGODlhKAAZAOMCAGZmZgBmmf///zOZzMz//5nM/zNmmWbM"
+ "/5nMzMzMzACZ/////////////////////yH5BAEKAA8ALAAA"
+ "AAAoABkAAATH8IFJK5U2a4337F5ogRkpnoCJrly7PrCKyh8c"
+ "3HgAhzT35MDbbtO7/IJIHbGiOiaTxVTpSVWWLqNq1UVyapNS"
+ "1wd3OAxug0LhnCubcVhsxysQnOt4ATpvvzHlFzl1AwODhWeF"
+ "AgRpen5/UhheAYMFdUB4SFcpGEGGdQeCAqBBLTuSk30EeXd9"
+ "pEsAbKGxjHqDSE0Sp6ixN4N1BJmbc7lIhmsBich1awPAjkY1"
+ "SZR8bJWrz382SGqIBQQFQd4IsUTaX+ceuudPEQA7"),
+ /*
+ * Invalid, bad Base64 in DOA-DATA.
+ */
+ TEXT_INVALID("1234567890 1234567890 1 \"image/gif\" R0lGODl"),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL()
+ };
+ wire_ok_t wire_ok[] = {
+ /*
+ * Valid, empty DOA-MEDIA-TYPE, empty DOA-DATA.
+ */
+ WIRE_VALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x01,
+ 0x00),
+ /*
+ * Invalid, missing DOA-MEDIA-TYPE.
+ */
+ WIRE_INVALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78,
+ 0x01),
+ /*
+ * Invalid, malformed DOA-MEDIA-TYPE length.
+ */
+ WIRE_INVALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78,
+ 0x01, 0xff),
+ /*
+ * Valid, empty DOA-DATA.
+ */
+ WIRE_VALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x01,
+ 0x03, 0x66, 0x6f, 0x6f),
+ /*
+ * Valid, non-empty DOA-DATA.
+ */
+ WIRE_VALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x01,
+ 0x03, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72),
+ /*
+ * Valid, DOA-DATA over 255 octets.
+ */
+ WIRE_VALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x01,
+ 0x06, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x00, 0x66,
+ 0x99, 0xff, 0xff, 0xff, 0x33, 0x99, 0xcc, 0xcc, 0xff,
+ 0xff, 0x99, 0xcc, 0xff, 0x33, 0x66, 0x99, 0x66, 0xcc,
+ 0xff, 0x99, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x99,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x21, 0xf9,
+ 0x04, 0x01, 0x0a, 0x00, 0x0f, 0x00, 0x2c, 0x00, 0x00,
+ 0x00, 0x00, 0x28, 0x00, 0x19, 0x00, 0x00, 0x04, 0xc7,
+ 0xf0, 0x81, 0x49, 0x2b, 0x95, 0x36, 0x6b, 0x8d, 0xf7,
+ 0xec, 0x5e, 0x68, 0x81, 0x19, 0x29, 0x9e, 0x80, 0x89,
+ 0xae, 0x5c, 0xbb, 0x3e, 0xb0, 0x8a, 0xca, 0x1f, 0x1c,
+ 0xdc, 0x78, 0x00, 0x87, 0x34, 0xf7, 0xe4, 0xc0, 0xdb,
+ 0x6e, 0xd3, 0xbb, 0xfc, 0x82, 0x48, 0x1d, 0xb1, 0xa2,
+ 0x3a, 0x26, 0x93, 0xc5, 0x54, 0xe9, 0x49, 0x55, 0x96,
+ 0x2e, 0xa3, 0x6a, 0xd5, 0x45, 0x72, 0x6a, 0x93, 0x52,
+ 0xd7, 0x07, 0x77, 0x38, 0x0c, 0x6e, 0x83, 0x42, 0xe1,
+ 0x9c, 0x2b, 0x9b, 0x71, 0x58, 0x6c, 0xc7, 0x2b, 0x10,
+ 0x9c, 0xeb, 0x78, 0x01, 0x3a, 0x6f, 0xbf, 0x31, 0xe5,
+ 0x17, 0x39, 0x75, 0x03, 0x03, 0x83, 0x85, 0x67, 0x85,
+ 0x02, 0x04, 0x69, 0x7a, 0x7e, 0x7f, 0x52, 0x18, 0x5e,
+ 0x01, 0x83, 0x05, 0x75, 0x40, 0x78, 0x48, 0x57, 0x29,
+ 0x18, 0x41, 0x86, 0x75, 0x07, 0x82, 0x02, 0xa0, 0x41,
+ 0x2d, 0x3b, 0x92, 0x93, 0x7d, 0x04, 0x79, 0x77, 0x7d,
+ 0xa4, 0x4b, 0x00, 0x6c, 0xa1, 0xb1, 0x8c, 0x7a, 0x83,
+ 0x48, 0x4d, 0x12, 0xa7, 0xa8, 0xb1, 0x37, 0x83, 0x75,
+ 0x04, 0x99, 0x9b, 0x73, 0xb9, 0x48, 0x86, 0x6b, 0x01,
+ 0x89, 0xc8, 0x75, 0x6b, 0x03, 0xc0, 0x8e, 0x46, 0x35,
+ 0x49, 0x94, 0x7c, 0x6c, 0x95, 0xab, 0xcf, 0x7f, 0x36,
+ 0x48, 0x6a, 0x88, 0x05, 0x04, 0x05, 0x41, 0xde, 0x08,
+ 0xb1, 0x44, 0xda, 0x5f, 0xe7, 0x1e, 0xba, 0xe7, 0x4f,
+ 0x11, 0x00, 0x3b),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_doa, sizeof(dns_rdata_doa_t));
+}
+
+/*
+ * DS tests.
+ *
+ * RFC 4034:
+ *
+ * 5.1. DS RDATA Wire Format
+ *
+ * The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet
+ * Algorithm field, a 1 octet Digest Type field, and a Digest field.
+ *
+ * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Key Tag | Algorithm | Digest Type |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * / /
+ * / Digest /
+ * / /
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * 5.1.1. The Key Tag Field
+ *
+ * The Key Tag field lists the key tag of the DNSKEY RR referred to by
+ * the DS record, in network byte order.
+ *
+ * The Key Tag used by the DS RR is identical to the Key Tag used by
+ * RRSIG RRs. Appendix B describes how to compute a Key Tag.
+ *
+ * 5.1.2. The Algorithm Field
+ *
+ * The Algorithm field lists the algorithm number of the DNSKEY RR
+ * referred to by the DS record.
+ *
+ * The algorithm number used by the DS RR is identical to the algorithm
+ * number used by RRSIG and DNSKEY RRs. Appendix A.1 lists the
+ * algorithm number types.
+ *
+ * 5.1.3. The Digest Type Field
+ *
+ * The DS RR refers to a DNSKEY RR by including a digest of that DNSKEY
+ * RR. The Digest Type field identifies the algorithm used to construct
+ * the digest. Appendix A.2 lists the possible digest algorithm types.
+ *
+ * 5.1.4. The Digest Field
+ *
+ * The DS record refers to a DNSKEY RR by including a digest of that
+ * DNSKEY RR.
+ *
+ * The digest is calculated by concatenating the canonical form of the
+ * fully qualified owner name of the DNSKEY RR with the DNSKEY RDATA,
+ * and then applying the digest algorithm.
+ *
+ * digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
+ *
+ * "|" denotes concatenation
+ *
+ * DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
+ *
+ * The size of the digest may vary depending on the digest algorithm and
+ * DNSKEY RR size. As of the time of this writing, the only defined
+ * digest algorithm is SHA-1, which produces a 20 octet digest.
+ */
+static void
+ds(void **state) {
+ text_ok_t text_ok[] = {
+ /*
+ * Invalid, empty record.
+ */
+ TEXT_INVALID(""),
+ /*
+ * Invalid, no algorithm.
+ */
+ TEXT_INVALID("0"),
+ /*
+ * Invalid, no digest type.
+ */
+ TEXT_INVALID("0 0"),
+ /*
+ * Invalid, no digest.
+ */
+ TEXT_INVALID("0 0 0"),
+ /*
+ * Valid, 1-octet digest for a reserved digest type.
+ */
+ TEXT_VALID("0 0 0 00"),
+ /*
+ * Invalid, short SHA-1 digest.
+ */
+ TEXT_INVALID("0 0 1 00"),
+ TEXT_INVALID("0 0 1 4FDCE83016EDD29077621FE568F8DADDB5809B"),
+ /*
+ * Valid, 20-octet SHA-1 digest.
+ */
+ TEXT_VALID("0 0 1 4FDCE83016EDD29077621FE568F8DADDB5809B6A"),
+ /*
+ * Invalid, excessively long SHA-1 digest.
+ */
+ TEXT_INVALID("0 0 1 4FDCE83016EDD29077621FE568F8DADDB5809B"
+ "6A00"),
+ /*
+ * Invalid, short SHA-256 digest.
+ */
+ TEXT_INVALID("0 0 2 00"),
+ TEXT_INVALID("0 0 2 D001BD422FFDA9B745425B71DC17D007E69186"
+ "9BD59C5F237D9BF85434C313"),
+ /*
+ * Valid, 32-octet SHA-256 digest.
+ */
+ TEXT_VALID_CHANGED("0 0 2 "
+ "D001BD422FFDA9B745425B71DC17D007E691869B"
+ "D59C5F237D9BF85434C3133F",
+ "0 0 2 "
+ "D001BD422FFDA9B745425B71DC17D007E691869B"
+ "D59C5F237D9BF854 34C3133F"),
+ /*
+ * Invalid, excessively long SHA-256 digest.
+ */
+ TEXT_INVALID("0 0 2 D001BD422FFDA9B745425B71DC17D007E69186"
+ "9BD59C5F237D9BF85434C3133F00"),
+ /*
+ * Valid, GOST is no longer supported, hence no length checks.
+ */
+ TEXT_VALID("0 0 3 00"),
+ /*
+ * Invalid, short SHA-384 digest.
+ */
+ TEXT_INVALID("0 0 4 00"),
+ TEXT_INVALID("0 0 4 AC748D6C5AA652904A8763D64B7DFFFFA98152"
+ "BE12128D238BEBB4814B648F5A841E15CAA2DE348891"
+ "A37A699F65E5"),
+ /*
+ * Valid, 48-octet SHA-384 digest.
+ */
+ TEXT_VALID_CHANGED("0 0 4 "
+ "AC748D6C5AA652904A8763D64B7DFFFFA98152BE"
+ "12128D238BEBB4814B648F5A841E15CAA2DE348891A"
+ "37A"
+ "699F65E54D",
+ "0 0 4 "
+ "AC748D6C5AA652904A8763D64B7DFFFFA98152BE"
+ "12128D238BEBB481 "
+ "4B648F5A841E15CAA2DE348891A37A"
+ "699F65E54D"),
+ /*
+ * Invalid, excessively long SHA-384 digest.
+ */
+ TEXT_INVALID("0 0 4 AC748D6C5AA652904A8763D64B7DFFFFA98152"
+ "BE12128D238BEBB4814B648F5A841E15CAA2DE348891"
+ "A37A699F65E54D00"),
+ /*
+ * Valid, 1-octet digest for an unassigned digest type.
+ */
+ TEXT_VALID("0 0 5 00"),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL()
+ };
+ wire_ok_t wire_ok[] = {
+ /*
+ * Invalid, truncated key tag.
+ */
+ WIRE_INVALID(0x00),
+ /*
+ * Invalid, no algorithm.
+ */
+ WIRE_INVALID(0x00, 0x00),
+ /*
+ * Invalid, no digest type.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00),
+ /*
+ * Invalid, no digest.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00),
+ /*
+ * Valid, 1-octet digest for a reserved digest type.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Invalid, short SHA-1 digest.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x01, 0x00),
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x01, 0x4F, 0xDC, 0xE8, 0x30,
+ 0x16, 0xED, 0xD2, 0x90, 0x77, 0x62, 0x1F, 0xE5,
+ 0x68, 0xF8, 0xDA, 0xDD, 0xB5, 0x80, 0x9B),
+ /*
+ * Valid, 20-octet SHA-1 digest.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x01, 0x4F, 0xDC, 0xE8, 0x30, 0x16,
+ 0xED, 0xD2, 0x90, 0x77, 0x62, 0x1F, 0xE5, 0x68, 0xF8,
+ 0xDA, 0xDD, 0xB5, 0x80, 0x9B, 0x6A),
+ /*
+ * Invalid, excessively long SHA-1 digest.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x01, 0x4F, 0xDC, 0xE8, 0x30,
+ 0x16, 0xED, 0xD2, 0x90, 0x77, 0x62, 0x1F, 0xE5,
+ 0x68, 0xF8, 0xDA, 0xDD, 0xB5, 0x80, 0x9B, 0x6A,
+ 0x00),
+ /*
+ * Invalid, short SHA-256 digest.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x02, 0x00),
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x02, 0xD0, 0x01, 0xBD, 0x42,
+ 0x2F, 0xFD, 0xA9, 0xB7, 0x45, 0x42, 0x5B, 0x71,
+ 0xDC, 0x17, 0xD0, 0x07, 0xE6, 0x91, 0x86, 0x9B,
+ 0xD5, 0x9C, 0x5F, 0x23, 0x7D, 0x9B, 0xF8, 0x54,
+ 0x34, 0xC3, 0x13),
+ /*
+ * Valid, 32-octet SHA-256 digest.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x02, 0xD0, 0x01, 0xBD, 0x42, 0x2F,
+ 0xFD, 0xA9, 0xB7, 0x45, 0x42, 0x5B, 0x71, 0xDC, 0x17,
+ 0xD0, 0x07, 0xE6, 0x91, 0x86, 0x9B, 0xD5, 0x9C, 0x5F,
+ 0x23, 0x7D, 0x9B, 0xF8, 0x54, 0x34, 0xC3, 0x13,
+ 0x3F),
+ /*
+ * Invalid, excessively long SHA-256 digest.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x02, 0xD0, 0x01, 0xBD, 0x42,
+ 0x2F, 0xFD, 0xA9, 0xB7, 0x45, 0x42, 0x5B, 0x71,
+ 0xDC, 0x17, 0xD0, 0x07, 0xE6, 0x91, 0x86, 0x9B,
+ 0xD5, 0x9C, 0x5F, 0x23, 0x7D, 0x9B, 0xF8, 0x54,
+ 0x34, 0xC3, 0x13, 0x3F, 0x00),
+ /*
+ * Valid, GOST is no longer supported, hence no length checks.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x03, 0x00),
+ /*
+ * Invalid, short SHA-384 digest.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x04, 0x00),
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x04, 0xAC, 0x74, 0x8D, 0x6C,
+ 0x5A, 0xA6, 0x52, 0x90, 0x4A, 0x87, 0x63, 0xD6,
+ 0x4B, 0x7D, 0xFF, 0xFF, 0xA9, 0x81, 0x52, 0xBE,
+ 0x12, 0x12, 0x8D, 0x23, 0x8B, 0xEB, 0xB4, 0x81,
+ 0x4B, 0x64, 0x8F, 0x5A, 0x84, 0x1E, 0x15, 0xCA,
+ 0xA2, 0xDE, 0x34, 0x88, 0x91, 0xA3, 0x7A, 0x69,
+ 0x9F, 0x65, 0xE5),
+ /*
+ * Valid, 48-octet SHA-384 digest.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x04, 0xAC, 0x74, 0x8D, 0x6C, 0x5A,
+ 0xA6, 0x52, 0x90, 0x4A, 0x87, 0x63, 0xD6, 0x4B, 0x7D,
+ 0xFF, 0xFF, 0xA9, 0x81, 0x52, 0xBE, 0x12, 0x12, 0x8D,
+ 0x23, 0x8B, 0xEB, 0xB4, 0x81, 0x4B, 0x64, 0x8F, 0x5A,
+ 0x84, 0x1E, 0x15, 0xCA, 0xA2, 0xDE, 0x34, 0x88, 0x91,
+ 0xA3, 0x7A, 0x69, 0x9F, 0x65, 0xE5, 0x4D),
+ /*
+ * Invalid, excessively long SHA-384 digest.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x04, 0xAC, 0x74, 0x8D, 0x6C,
+ 0x5A, 0xA6, 0x52, 0x90, 0x4A, 0x87, 0x63, 0xD6,
+ 0x4B, 0x7D, 0xFF, 0xFF, 0xA9, 0x81, 0x52, 0xBE,
+ 0x12, 0x12, 0x8D, 0x23, 0x8B, 0xEB, 0xB4, 0x81,
+ 0x4B, 0x64, 0x8F, 0x5A, 0x84, 0x1E, 0x15, 0xCA,
+ 0xA2, 0xDE, 0x34, 0x88, 0x91, 0xA3, 0x7A, 0x69,
+ 0x9F, 0x65, 0xE5, 0x4D, 0x00),
+ WIRE_VALID(0x00, 0x00, 0x04, 0x00, 0x00),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_ds, sizeof(dns_rdata_ds_t));
+}
+
+/*
+ * EDNS Client Subnet tests.
+ *
+ * RFC 7871:
+ *
+ * 6. Option Format
+ *
+ * This protocol uses an EDNS0 [RFC6891] option to include client
+ * address information in DNS messages. The option is structured as
+ * follows:
+ *
+ * +0 (MSB) +1 (LSB)
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 0: | OPTION-CODE |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 2: | OPTION-LENGTH |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 4: | FAMILY |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 6: | SOURCE PREFIX-LENGTH | SCOPE PREFIX-LENGTH |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 8: | ADDRESS... /
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ *
+ * o (Defined in [RFC6891]) OPTION-CODE, 2 octets, for ECS is 8 (0x00
+ * 0x08).
+ *
+ * o (Defined in [RFC6891]) OPTION-LENGTH, 2 octets, contains the
+ * length of the payload (everything after OPTION-LENGTH) in octets.
+ *
+ * o FAMILY, 2 octets, indicates the family of the address contained in
+ * the option, using address family codes as assigned by IANA in
+ * Address Family Numbers [Address_Family_Numbers].
+ *
+ * The format of the address part depends on the value of FAMILY. This
+ * document only defines the format for FAMILY 1 (IPv4) and FAMILY 2
+ * (IPv6), which are as follows:
+ *
+ * o SOURCE PREFIX-LENGTH, an unsigned octet representing the leftmost
+ * number of significant bits of ADDRESS to be used for the lookup.
+ * In responses, it mirrors the same value as in the queries.
+ *
+ * o SCOPE PREFIX-LENGTH, an unsigned octet representing the leftmost
+ * number of significant bits of ADDRESS that the response covers.
+ * In queries, it MUST be set to 0.
+ *
+ * o ADDRESS, variable number of octets, contains either an IPv4 or
+ * IPv6 address, depending on FAMILY, which MUST be truncated to the
+ * number of bits indicated by the SOURCE PREFIX-LENGTH field,
+ * padding with 0 bits to pad to the end of the last octet needed.
+ *
+ * o A server receiving an ECS option that uses either too few or too
+ * many ADDRESS octets, or that has non-zero ADDRESS bits set beyond
+ * SOURCE PREFIX-LENGTH, SHOULD return FORMERR to reject the packet,
+ * as a signal to the software developer making the request to fix
+ * their implementation.
+ *
+ * All fields are in network byte order ("big-endian", per [RFC1700],
+ * Data Notation).
+ */
+static void
+edns_client_subnet(void **state) {
+ wire_ok_t wire_ok[] = {
+ /*
+ * Option code with no content.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 0x00),
+ /*
+ * Option code family 0, source 0, scope 0.
+ */
+ WIRE_VALID(0x00, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Option code family 1 (IPv4), source 0, scope 0.
+ */
+ WIRE_VALID(0x00, 0x08, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00),
+ /*
+ * Option code family 2 (IPv6) , source 0, scope 0.
+ */
+ WIRE_VALID(0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00),
+ /*
+ * Extra octet.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00,
+ 0x00),
+ /*
+ * Source too long for IPv4.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 8, 0x00, 0x01, 33, 0x00, 0x00,
+ 0x00, 0x00, 0x00),
+ /*
+ * Source too long for IPv6.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 20, 0x00, 0x02, 129, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Scope too long for IPv4.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 8, 0x00, 0x01, 0x00, 33, 0x00,
+ 0x00, 0x00, 0x00),
+ /*
+ * Scope too long for IPv6.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 20, 0x00, 0x02, 0x00, 129, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * When family=0, source and scope should be 0.
+ */
+ WIRE_VALID(0x00, 0x08, 0x00, 4, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * When family=0, source and scope should be 0.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 5, 0x00, 0x00, 0x01, 0x00, 0x00),
+ /*
+ * When family=0, source and scope should be 0.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 5, 0x00, 0x00, 0x00, 0x01, 0x00),
+ /*
+ * Length too short for source IPv4.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 7, 0x00, 0x01, 32, 0x00, 0x00,
+ 0x00, 0x00),
+ /*
+ * Length too short for source IPv6.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 19, 0x00, 0x02, 128, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(NULL, wire_ok, NULL, true, dns_rdataclass_in,
+ dns_rdatatype_opt, sizeof(dns_rdata_opt_t));
+}
+
+/*
+ * http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt
+ *
+ * The RDATA portion of both the NIMLOC and EID records contains
+ * uninterpreted binary data. The representation in the text master file
+ * is an even number of hex characters (0 to 9, a to f), case is not
+ * significant. For readability, whitespace may be included in the value
+ * field and should be ignored when reading a master file.
+ */
+static void
+eid(void **state) {
+ text_ok_t text_ok[] = { TEXT_VALID("AABBCC"),
+ TEXT_VALID_CHANGED("AA bb cc", "AABBCC"),
+ TEXT_INVALID("aab"),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL() };
+ wire_ok_t wire_ok[] = { WIRE_VALID(0x00), WIRE_VALID(0xAA, 0xBB, 0xCC),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL() };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_eid, sizeof(dns_rdata_in_eid_t));
+}
+
+/*
+ * test that an oversized HIP record will be rejected
+ */
+static void
+hip(void **state) {
+ text_ok_t text_ok[] = {
+ /* RFC 8005 examples. */
+ TEXT_VALID_LOOP(0, "2 200100107B1A74DF365639CC39F1D578 "
+ "AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cI"
+ "vM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbW"
+ "Iy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+b"
+ "SRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWx"
+ "Z48AWkskmdHaVDP4BcelrTI3rMXdXF5D"),
+ TEXT_VALID_LOOP(1, "2 200100107B1A74DF365639CC39F1D578 "
+ "AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cI"
+ "vM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbW"
+ "Iy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+b"
+ "SRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWx"
+ "Z48AWkskmdHaVDP4BcelrTI3rMXdXF5D "
+ "rvs1.example.com."),
+ TEXT_VALID_LOOP(2, "2 200100107B1A74DF365639CC39F1D578 "
+ "AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cI"
+ "vM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbW"
+ "Iy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+b"
+ "SRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWx"
+ "Z48AWkskmdHaVDP4BcelrTI3rMXdXF5D "
+ "rvs1.example.com. rvs2.example.com."),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL()
+ };
+ unsigned char hipwire[DNS_RDATA_MAXLENGTH] = { 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x04, 0x41,
+ 0x42, 0x43, 0x44, 0x00 };
+ unsigned char buf[1024 * 1024];
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ size_t i;
+
+ UNUSED(state);
+
+ /*
+ * Fill the rest of input buffer with compression pointers.
+ */
+ for (i = 12; i < sizeof(hipwire) - 2; i += 2) {
+ hipwire[i] = 0xc0;
+ hipwire[i + 1] = 0x06;
+ }
+
+ result = wire_to_rdata(hipwire, sizeof(hipwire), dns_rdataclass_in,
+ dns_rdatatype_hip, buf, sizeof(buf), &rdata);
+ assert_int_equal(result, DNS_R_FORMERR);
+ check_text_ok(text_ok, dns_rdataclass_in, dns_rdatatype_hip,
+ sizeof(dns_rdata_hip_t));
+}
+
+/*
+ * ISDN tests.
+ *
+ * RFC 1183:
+ *
+ * 3.2. The ISDN RR
+ *
+ * The ISDN RR is defined with mnemonic ISDN and type code 20 (decimal).
+ *
+ * An ISDN (Integrated Service Digital Network) number is simply a
+ * telephone number. The intent of the members of the CCITT is to
+ * upgrade all telephone and data network service to a common service.
+ *
+ * The numbering plan (E.163/E.164) is the same as the familiar
+ * international plan for POTS (an un-official acronym, meaning Plain
+ * Old Telephone Service). In E.166, CCITT says "An E.163/E.164
+ * telephony subscriber may become an ISDN subscriber without a number
+ * change."
+ *
+ * ISDN has the following format:
+ *
+ * <owner> <ttl> <class> ISDN <ISDN-address> <sa>
+ *
+ * The <ISDN-address> field is required; <sa> is optional.
+ *
+ * <ISDN-address> identifies the ISDN number of <owner> and DDI (Direct
+ * Dial In) if any, as defined by E.164 [8] and E.163 [7], the ISDN and
+ * PSTN (Public Switched Telephone Network) numbering plan. E.163
+ * defines the country codes, and E.164 the form of the addresses. Its
+ * format in master files is a <character-string> syntactically
+ * identical to that used in TXT and HINFO.
+ *
+ * <sa> specifies the subaddress (SA). The format of <sa> in master
+ * files is a <character-string> syntactically identical to that used in
+ * TXT and HINFO.
+ *
+ * The format of ISDN is class insensitive. ISDN RRs cause no
+ * additional section processing.
+ *
+ * The <ISDN-address> is a string of characters, normally decimal
+ * digits, beginning with the E.163 country code and ending with the DDI
+ * if any. Note that ISDN, in Q.931, permits any IA5 character in the
+ * general case.
+ *
+ * The <sa> is a string of hexadecimal digits. For digits 0-9, the
+ * concrete encoding in the Q.931 call setup information element is
+ * identical to BCD.
+ *
+ * For example:
+ *
+ * Relay.Prime.COM. IN ISDN 150862028003217
+ * sh.Prime.COM. IN ISDN 150862028003217 004
+ *
+ * (Note: "1" is the country code for the North American Integrated
+ * Numbering Area, i.e., the system of "area codes" familiar to people
+ * in those countries.)
+ *
+ * The RR data is the ASCII representation of the digits. It is encoded
+ * as one or two <character-string>s, i.e., count followed by
+ * characters.
+ */
+static void
+isdn(void **state) {
+ wire_ok_t wire_ok[] = { /*
+ * "".
+ */
+ WIRE_VALID(0x00),
+ /*
+ * "\001".
+ */
+ WIRE_VALID(0x01, 0x01),
+ /*
+ * "\001" "".
+ */
+ WIRE_VALID(0x01, 0x01, 0x00),
+ /*
+ * "\001" "\001".
+ */
+ WIRE_VALID(0x01, 0x01, 0x01, 0x01),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(NULL, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_isdn, sizeof(dns_rdata_isdn_t));
+}
+
+/*
+ * KEY tests.
+ */
+static void
+key(void **state) {
+ wire_ok_t wire_ok[] = { /*
+ * RDATA is comprised of:
+ *
+ * - 2 octets for Flags,
+ * - 1 octet for Protocol,
+ * - 1 octet for Algorithm,
+ * - variable number of octets for Public Key.
+ *
+ * RFC 2535 section 3.1.2 states that if bits
+ * 0-1 of Flags are both set, the RR stops after
+ * the algorithm octet and thus its length must
+ * be 4 octets. In any other case, though, the
+ * Public Key part must not be empty.
+ */
+ WIRE_INVALID(0x00),
+ WIRE_INVALID(0x00, 0x00),
+ WIRE_INVALID(0x00, 0x00, 0x00),
+ WIRE_VALID(0xc0, 0x00, 0x00, 0x00),
+ WIRE_INVALID(0xc0, 0x00, 0x00, 0x00, 0x00),
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00),
+ WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00),
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(NULL, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_key, sizeof(dns_rdata_key_t));
+}
+
+/*
+ * LOC tests.
+ */
+static void
+loc(void **state) {
+ text_ok_t text_ok[] = {
+ TEXT_VALID_CHANGED("0 N 0 E 0", "0 0 0.000 N 0 0 0.000 E 0.00m "
+ "1m 10000m 10m"),
+ TEXT_VALID_CHANGED("0 S 0 W 0", "0 0 0.000 N 0 0 0.000 E 0.00m "
+ "1m 10000m 10m"),
+ TEXT_VALID_CHANGED("0 0 N 0 0 E 0", "0 0 0.000 N 0 0 0.000 E "
+ "0.00m 1m 10000m 10m"),
+ TEXT_VALID_CHANGED("0 0 0 N 0 0 0 E 0",
+ "0 0 0.000 N 0 0 0.000 E 0.00m 1m 10000m "
+ "10m"),
+ TEXT_VALID_CHANGED("0 0 0 N 0 0 0 E 0",
+ "0 0 0.000 N 0 0 0.000 E 0.00m 1m 10000m "
+ "10m"),
+ TEXT_VALID_CHANGED("0 0 0. N 0 0 0. E 0",
+ "0 0 0.000 N 0 0 0.000 E 0.00m 1m 10000m "
+ "10m"),
+ TEXT_VALID_CHANGED("0 0 .0 N 0 0 .0 E 0",
+ "0 0 0.000 N 0 0 0.000 E 0.00m 1m 10000m "
+ "10m"),
+ TEXT_INVALID("0 North 0 East 0"),
+ TEXT_INVALID("0 South 0 West 0"),
+ TEXT_INVALID("0 0 . N 0 0 0. E 0"),
+ TEXT_INVALID("0 0 0. N 0 0 . E 0"),
+ TEXT_INVALID("0 0 0. N 0 0 0. E m"),
+ TEXT_INVALID("0 0 0. N 0 0 0. E 0 ."),
+ TEXT_INVALID("0 0 0. N 0 0 0. E 0 m"),
+ TEXT_INVALID("0 0 0. N 0 0 0. E 0 0 ."),
+ TEXT_INVALID("0 0 0. N 0 0 0. E 0 0 m"),
+ TEXT_INVALID("0 0 0. N 0 0 0. E 0 0 0 ."),
+ TEXT_INVALID("0 0 0. N 0 0 0. E 0 0 0 m"),
+ TEXT_VALID_CHANGED("90 N 180 E 0", "90 0 0.000 N 180 0 0.000 E "
+ "0.00m 1m 10000m 10m"),
+ TEXT_INVALID("90 1 N 180 E 0"),
+ TEXT_INVALID("90 0 1 N 180 E 0"),
+ TEXT_INVALID("90 N 180 1 E 0"),
+ TEXT_INVALID("90 N 180 0 1 E 0"),
+ TEXT_VALID_CHANGED("90 S 180 W 0", "90 0 0.000 S 180 0 0.000 W "
+ "0.00m 1m 10000m 10m"),
+ TEXT_INVALID("90 1 S 180 W 0"),
+ TEXT_INVALID("90 0 1 S 180 W 0"),
+ TEXT_INVALID("90 S 180 1 W 0"),
+ TEXT_INVALID("90 S 180 0 1 W 0"),
+ TEXT_INVALID("0 0 0.000 E 0 0 0.000 E -0.95m 1m 10000m 10m"),
+ TEXT_VALID("0 0 0.000 N 0 0 0.000 E -0.95m 1m 10000m 10m"),
+ TEXT_VALID("0 0 0.000 N 0 0 0.000 E -0.05m 1m 10000m 10m"),
+ TEXT_VALID("0 0 0.000 N 0 0 0.000 E -100000.00m 1m 10000m 10m"),
+ TEXT_VALID("0 0 0.000 N 0 0 0.000 E 42849672.95m 1m 10000m "
+ "10m"),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, 0, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_loc, sizeof(dns_rdata_loc_t));
+}
+
+/*
+ * http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt
+ *
+ * The RDATA portion of both the NIMLOC and EID records contains
+ * uninterpreted binary data. The representation in the text master file
+ * is an even number of hex characters (0 to 9, a to f), case is not
+ * significant. For readability, whitespace may be included in the value
+ * field and should be ignored when reading a master file.
+ */
+static void
+nimloc(void **state) {
+ text_ok_t text_ok[] = { TEXT_VALID("AABBCC"),
+ TEXT_VALID_CHANGED("AA bb cc", "AABBCC"),
+ TEXT_INVALID("aab"),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL() };
+ wire_ok_t wire_ok[] = { WIRE_VALID(0x00), WIRE_VALID(0xAA, 0xBB, 0xCC),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL() };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_nimloc, sizeof(dns_rdata_in_nimloc_t));
+}
+
+/*
+ * NSEC tests.
+ *
+ * RFC 4034:
+ *
+ * 4.1. NSEC RDATA Wire Format
+ *
+ * The RDATA of the NSEC RR is as shown below:
+ *
+ * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * / Next Domain Name /
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * / Type Bit Maps /
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * 4.1.1. The Next Domain Name Field
+ *
+ * The Next Domain field contains the next owner name (in the canonical
+ * ordering of the zone) that has authoritative data or contains a
+ * delegation point NS RRset; see Section 6.1 for an explanation of
+ * canonical ordering. The value of the Next Domain Name field in the
+ * last NSEC record in the zone is the name of the zone apex (the owner
+ * name of the zone's SOA RR). This indicates that the owner name of
+ * the NSEC RR is the last name in the canonical ordering of the zone.
+ *
+ * A sender MUST NOT use DNS name compression on the Next Domain Name
+ * field when transmitting an NSEC RR.
+ *
+ * Owner names of RRsets for which the given zone is not authoritative
+ * (such as glue records) MUST NOT be listed in the Next Domain Name
+ * unless at least one authoritative RRset exists at the same owner
+ * name.
+ *
+ * 4.1.2. The Type Bit Maps Field
+ *
+ * The Type Bit Maps field identifies the RRset types that exist at the
+ * NSEC RR's owner name.
+ *
+ * The RR type space is split into 256 window blocks, each representing
+ * the low-order 8 bits of the 16-bit RR type space. Each block that
+ * has at least one active RR type is encoded using a single octet
+ * window number (from 0 to 255), a single octet bitmap length (from 1
+ * to 32) indicating the number of octets used for the window block's
+ * bitmap, and up to 32 octets (256 bits) of bitmap.
+ *
+ * Blocks are present in the NSEC RR RDATA in increasing numerical
+ * order.
+ *
+ * Type Bit Maps Field = ( Window Block # | Bitmap Length | Bitmap )+
+ *
+ * where "|" denotes concatenation.
+ *
+ * Each bitmap encodes the low-order 8 bits of RR types within the
+ * window block, in network bit order. The first bit is bit 0. For
+ * window block 0, bit 1 corresponds to RR type 1 (A), bit 2 corresponds
+ * to RR type 2 (NS), and so forth. For window block 1, bit 1
+ * corresponds to RR type 257, and bit 2 to RR type 258. If a bit is
+ * set, it indicates that an RRset of that type is present for the NSEC
+ * RR's owner name. If a bit is clear, it indicates that no RRset of
+ * that type is present for the NSEC RR's owner name.
+ *
+ * Bits representing pseudo-types MUST be clear, as they do not appear
+ * in zone data. If encountered, they MUST be ignored upon being read.
+ */
+static void
+nsec(void **state) {
+ text_ok_t text_ok[] = { TEXT_INVALID(""), TEXT_INVALID("."),
+ TEXT_VALID(". RRSIG"), TEXT_SENTINEL() };
+ wire_ok_t wire_ok[] = { WIRE_INVALID(0x00), WIRE_INVALID(0x00, 0x00),
+ WIRE_INVALID(0x00, 0x00, 0x00),
+ WIRE_VALID(0x00, 0x00, 0x01, 0x02),
+ WIRE_SENTINEL() };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_nsec, sizeof(dns_rdata_nsec_t));
+}
+
+/*
+ * NSEC3 tests.
+ *
+ * RFC 5155.
+ */
+static void
+nsec3(void **state) {
+ text_ok_t text_ok[] = { TEXT_INVALID(""),
+ TEXT_INVALID("."),
+ TEXT_INVALID(". RRSIG"),
+ TEXT_INVALID("1 0 10 76931F"),
+ TEXT_INVALID("1 0 10 76931F "
+ "IMQ912BREQP1POLAH3RMONG&"
+ "UED541AS"),
+ TEXT_INVALID("1 0 10 76931F "
+ "IMQ912BREQP1POLAH3RMONGAUED541AS "
+ "A RRSIG BADTYPE"),
+ TEXT_VALID("1 0 10 76931F "
+ "AJHVGTICN6K0VDA53GCHFMT219SRRQLM A "
+ "RRSIG"),
+ TEXT_VALID("1 0 10 76931F "
+ "AJHVGTICN6K0VDA53GCHFMT219SRRQLM"),
+ TEXT_VALID("1 0 10 - "
+ "AJHVGTICN6K0VDA53GCHFMT219SRRQLM"),
+ TEXT_SENTINEL() };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, NULL, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_nsec3, sizeof(dns_rdata_nsec3_t));
+}
+
+/* NXT RDATA manipulations */
+static void
+nxt(void **state) {
+ compare_ok_t compare_ok[] = {
+ COMPARE("a. A SIG", "a. A SIG", 0),
+ /*
+ * Records that differ only in the case of the next
+ * name should be equal.
+ */
+ COMPARE("A. A SIG", "a. A SIG", 0),
+ /*
+ * Sorting on name field.
+ */
+ COMPARE("A. A SIG", "b. A SIG", -1),
+ COMPARE("b. A SIG", "A. A SIG", 1),
+ /* bit map differs */
+ COMPARE("b. A SIG", "b. A AAAA SIG", -1),
+ /* order of bit map does not matter */
+ COMPARE("b. A SIG AAAA", "b. A AAAA SIG", 0), COMPARE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(NULL, NULL, compare_ok, false, dns_rdataclass_in,
+ dns_rdatatype_nxt, sizeof(dns_rdata_nxt_t));
+}
+
+static void
+rkey(void **state) {
+ text_ok_t text_ok[] = { /*
+ * Valid, flags set to 0 and a key is present.
+ */
+ TEXT_VALID("0 0 0 aaaa"),
+ /*
+ * Invalid, non-zero flags.
+ */
+ TEXT_INVALID("1 0 0 aaaa"),
+ TEXT_INVALID("65535 0 0 aaaa"),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL()
+ };
+ wire_ok_t wire_ok[] = { /*
+ * Valid, flags set to 0 and a key is present.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Invalid, non-zero flags.
+ */
+ WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x00),
+ WIRE_INVALID(0xff, 0xff, 0x00, 0x00, 0x00),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+ key_required(state, dns_rdatatype_rkey, sizeof(dns_rdata_rkey_t));
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_rkey, sizeof(dns_rdata_rkey_t));
+}
+
+/* SSHFP RDATA manipulations */
+static void
+sshfp(void **state) {
+ text_ok_t text_ok[] = { TEXT_INVALID(""), /* too short */
+ TEXT_INVALID("0"), /* reserved, too short */
+ TEXT_VALID("0 0"), /* no finger print */
+ TEXT_VALID("0 0 AA"), /* reserved */
+ TEXT_INVALID("0 1 AA"), /* too short SHA 1
+ * digest */
+ TEXT_INVALID("0 2 AA"), /* too short SHA 256
+ * digest */
+ TEXT_VALID("0 3 AA"), /* unknown finger print
+ * type */
+ /* good length SHA 1 digest */
+ TEXT_VALID("1 1 "
+ "00112233445566778899AABBCCDDEEFF171"
+ "81920"),
+ /* good length SHA 256 digest */
+ TEXT_VALID("4 2 "
+ "A87F1B687AC0E57D2A081A2F282672334D9"
+ "0ED316D2B818CA9580EA3 84D92401"),
+ /*
+ * totext splits the fingerprint into chunks and
+ * emits uppercase hex.
+ */
+ TEXT_VALID_CHANGED("1 2 "
+ "00112233445566778899aabbccd"
+ "deeff "
+ "00112233445566778899AABBCCD"
+ "DEEFF",
+ "1 2 "
+ "00112233445566778899AABBCCD"
+ "DEEFF"
+ "00112233445566778899AABB "
+ "CCDDEEFF"),
+ TEXT_SENTINEL() };
+ wire_ok_t wire_ok[] = {
+ WIRE_INVALID(0x00), /* reserved too short */
+ WIRE_VALID(0x00, 0x00), /* reserved no finger print */
+ WIRE_VALID(0x00, 0x00, 0x00), /* reserved */
+
+ /* too short SHA 1 digests */
+ WIRE_INVALID(0x00, 0x01), WIRE_INVALID(0x00, 0x01, 0x00),
+ WIRE_INVALID(0x00, 0x01, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD,
+ 0xEE, 0xFF, 0x17, 0x18, 0x19),
+ /* good length SHA 1 digest */
+ WIRE_VALID(0x00, 0x01, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+ 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
+ 0x17, 0x18, 0x19, 0x20),
+ /* too long SHA 1 digest */
+ WIRE_INVALID(0x00, 0x01, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD,
+ 0xEE, 0xFF, 0x17, 0x18, 0x19, 0x20, 0x21),
+ /* too short SHA 256 digests */
+ WIRE_INVALID(0x00, 0x02), WIRE_INVALID(0x00, 0x02, 0x00),
+ WIRE_INVALID(0x00, 0x02, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD,
+ 0xEE, 0xFF, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22,
+ 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30,
+ 0x31),
+ /* good length SHA 256 digest */
+ WIRE_VALID(0x00, 0x02, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+ 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
+ 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
+ 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32),
+ /* too long SHA 256 digest */
+ WIRE_INVALID(0x00, 0x02, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD,
+ 0xEE, 0xFF, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22,
+ 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30,
+ 0x31, 0x32, 0x33),
+ /* unknown digest, * no fingerprint */
+ WIRE_VALID(0x00, 0x03), WIRE_VALID(0x00, 0x03, 0x00), /* unknown
+ * digest
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_sshfp, sizeof(dns_rdata_sshfp_t));
+}
+
+/*
+ * WKS tests.
+ *
+ * RFC 1035:
+ *
+ * 3.4.2. WKS RDATA format
+ *
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * | ADDRESS |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * | PROTOCOL | |
+ * +--+--+--+--+--+--+--+--+ |
+ * | |
+ * / <BIT MAP> /
+ * / /
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ *
+ * where:
+ *
+ * ADDRESS An 32 bit Internet address
+ *
+ * PROTOCOL An 8 bit IP protocol number
+ *
+ * <BIT MAP> A variable length bit map. The bit map must be a
+ * multiple of 8 bits long.
+ *
+ * The WKS record is used to describe the well known services supported by
+ * a particular protocol on a particular internet address. The PROTOCOL
+ * field specifies an IP protocol number, and the bit map has one bit per
+ * port of the specified protocol. The first bit corresponds to port 0,
+ * the second to port 1, etc. If the bit map does not include a bit for a
+ * protocol of interest, that bit is assumed zero. The appropriate values
+ * and mnemonics for ports and protocols are specified in [RFC-1010].
+ *
+ * For example, if PROTOCOL=TCP (6), the 26th bit corresponds to TCP port
+ * 25 (SMTP). If this bit is set, a SMTP server should be listening on TCP
+ * port 25; if zero, SMTP service is not supported on the specified
+ * address.
+ */
+static void
+wks(void **state) {
+ text_ok_t text_ok[] = { /*
+ * Valid, IPv4 address in dotted-quad form.
+ */
+ TEXT_VALID("127.0.0.1 6"),
+ /*
+ * Invalid, IPv4 address not in dotted-quad
+ * form.
+ */
+ TEXT_INVALID("127.1 6"),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL()
+ };
+ wire_ok_t wire_ok[] = { /*
+ * Too short.
+ */
+ WIRE_INVALID(0x00, 0x08, 0x00, 0x00),
+ /*
+ * Minimal TCP.
+ */
+ WIRE_VALID(0x00, 0x08, 0x00, 0x00, 6),
+ /*
+ * Minimal UDP.
+ */
+ WIRE_VALID(0x00, 0x08, 0x00, 0x00, 17),
+ /*
+ * Minimal other.
+ */
+ WIRE_VALID(0x00, 0x08, 0x00, 0x00, 1),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_wks, sizeof(dns_rdata_in_wks_t));
+}
+
+static void
+https_svcb(void **state) {
+ /*
+ * Known keys: mandatory, apln, no-default-alpn, port,
+ * ipv4hint, port, ipv6hint, dohpath.
+ */
+ text_ok_t text_ok[] = {
+ /* unknown key invalid */
+ TEXT_INVALID("1 . unknown="),
+ /* no domain */
+ TEXT_INVALID("0"),
+ /* minimal record */
+ TEXT_VALID_LOOP(0, "0 ."),
+ /* Alias form requires SvcFieldValue to be empty */
+ TEXT_INVALID("0 . alpn=\"h2\""),
+ /* no "key" prefix */
+ TEXT_INVALID("2 svc.example.net. 0=\"2222\""),
+ /* no key value */
+ TEXT_INVALID("2 svc.example.net. key"),
+ /* no key value */
+ TEXT_INVALID("2 svc.example.net. key=\"2222\""),
+ /* zero pad invalid */
+ TEXT_INVALID("2 svc.example.net. key07=\"2222\""),
+ TEXT_VALID_LOOP(1, "2 svc.example.net. key8=\"2222\""),
+ TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key8=2222",
+ "2 svc.example.net. key8=\"2222\""),
+ TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h2",
+ "2 svc.example.net. alpn=\"h2\""),
+ TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h3",
+ "2 svc.example.net. alpn=\"h3\""),
+ /* alpn has 2 sub field "h2" and "h3" */
+ TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h2,h3",
+ "2 svc.example.net. alpn=\"h2,h3\""),
+ /* apln has 2 sub fields "h1,h2" and "h3" (comma escaped) */
+ TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h1\\\\,h2,h3",
+ "2 svc.example.net. alpn=\"h1\\\\,h2,h3\""),
+ TEXT_VALID_LOOP(1, "2 svc.example.net. port=50"),
+ /* no-default-alpn, alpn is required */
+ TEXT_INVALID("2 svc.example.net. no-default-alpn"),
+ /* no-default-alpn with alpn present */
+ TEXT_VALID_LOOPCHG(
+ 2, "2 svc.example.net. no-default-alpn alpn=h2",
+ "2 svc.example.net. alpn=\"h2\" no-default-alpn"),
+ /* empty hint */
+ TEXT_INVALID("2 svc.example.net. ipv4hint="),
+ TEXT_VALID_LOOP(1, "2 svc.example.net. "
+ "ipv4hint=10.50.0.1,10.50.0.2"),
+ /* empty hint */
+ TEXT_INVALID("2 svc.example.net. ipv6hint="),
+ TEXT_VALID_LOOP(1, "2 svc.example.net. ipv6hint=::1,2002::1"),
+ TEXT_VALID_LOOP(1, "2 svc.example.net. ech=abcdefghijkl"),
+ /* bad base64 */
+ TEXT_INVALID("2 svc.example.net. ech=abcdefghijklm"),
+ TEXT_VALID_LOOP(1, "2 svc.example.net. key8=\"2222\""),
+ /* Out of key order on input (alpn == key1). */
+ TEXT_VALID_LOOPCHG(2,
+ "2 svc.example.net. key8=\"2222\" alpn=h2",
+ "2 svc.example.net. alpn=\"h2\" "
+ "key8=\"2222\""),
+ TEXT_VALID_LOOP(1, "2 svc.example.net. key65535=\"2222\""),
+ TEXT_INVALID("2 svc.example.net. key65536=\"2222\""),
+ TEXT_VALID_LOOP(1, "2 svc.example.net. key10"),
+ TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key11=",
+ "2 svc.example.net. key11"),
+ TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key12=\"\"",
+ "2 svc.example.net. key12"),
+ /* empty alpn-id sub fields */
+ TEXT_INVALID("2 svc.example.net. alpn"),
+ TEXT_INVALID("2 svc.example.net. alpn="),
+ TEXT_INVALID("2 svc.example.net. alpn=,h1"),
+ TEXT_INVALID("2 svc.example.net. alpn=h1,"),
+ TEXT_INVALID("2 svc.example.net. alpn=h1,,h2"),
+ /* mandatory */
+ TEXT_VALID_LOOP(2, "2 svc.example.net. mandatory=alpn "
+ "alpn=\"h2\""),
+ TEXT_VALID_LOOP(3, "2 svc.example.net. mandatory=alpn,port "
+ "alpn=\"h2\" port=443"),
+ TEXT_VALID_LOOPCHG(3,
+ "2 svc.example.net. mandatory=port,alpn "
+ "alpn=\"h2\" port=443",
+ "2 svc.example.net. mandatory=alpn,port "
+ "alpn=\"h2\" port=443"),
+ TEXT_INVALID("2 svc.example.net. mandatory=mandatory"),
+ TEXT_INVALID("2 svc.example.net. mandatory=port"),
+ TEXT_INVALID("2 svc.example.net. mandatory=,port port=433"),
+ TEXT_INVALID("2 svc.example.net. mandatory=port, port=433"),
+ TEXT_INVALID("2 svc.example.net. "
+ "mandatory=alpn,,port alpn=h2 port=433"),
+ /* mandatory w/ unknown key values */
+ TEXT_VALID_LOOP(2, "2 svc.example.net. mandatory=key8 key8"),
+ TEXT_VALID_LOOP(3, "2 svc.example.net. mandatory=key8,key9 "
+ "key8 key9"),
+ TEXT_VALID_LOOPCHG(
+ 3, "2 svc.example.net. mandatory=key9,key8 key8 key9",
+ "2 svc.example.net. mandatory=key8,key9 key8 key9"),
+ TEXT_INVALID("2 svc.example.net. "
+ "mandatory=key8,key8"),
+ TEXT_INVALID("2 svc.example.net. mandatory=,key8"),
+ TEXT_INVALID("2 svc.example.net. mandatory=key8,"),
+ TEXT_INVALID("2 svc.example.net. "
+ "mandatory=key8,,key8"),
+ /* Invalid test vectors */
+ TEXT_INVALID("1 foo.example.com. ( key123=abc key123=def )"),
+ TEXT_INVALID("1 foo.example.com. mandatory"),
+ TEXT_INVALID("1 foo.example.com. alpn"),
+ TEXT_INVALID("1 foo.example.com. port"),
+ TEXT_INVALID("1 foo.example.com. ipv4hint"),
+ TEXT_INVALID("1 foo.example.com. ipv6hint"),
+ TEXT_INVALID("1 foo.example.com. no-default-alpn=abc"),
+ TEXT_INVALID("1 foo.example.com. mandatory=key123"),
+ TEXT_INVALID("1 foo.example.com. mandatory=mandatory"),
+ TEXT_INVALID("1 foo.example.com. ( mandatory=key123,key123 "
+ "key123=abc)"),
+ /* dohpath tests */
+ TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/{?dns}",
+ "1 example.net. key7=\"/{?dns}\""),
+ TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/some/path{?dns}",
+ "1 example.net. key7=\"/some/path{?dns}\""),
+ TEXT_INVALID("1 example.com. dohpath=no-slash"),
+ TEXT_INVALID("1 example.com. dohpath=/{?notdns}"),
+ TEXT_INVALID("1 example.com. dohpath=/notvariable"),
+ TEXT_SENTINEL()
+
+ };
+ wire_ok_t wire_ok[] = {
+ /*
+ * Too short
+ */
+ WIRE_INVALID(0x00, 0x00),
+ /*
+ * Minimal length record.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00),
+ /*
+ * Alias with non-empty SvcFieldValue (key7="").
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00),
+ /*
+ * Bad key7= length (longer than rdata).
+ */
+ WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x07, 0x00, 0x01),
+ /*
+ * Port (0x03) too small (zero and one octets).
+ */
+ WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00),
+ WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00),
+ /* Valid port */
+ WIRE_VALID_LOOP(1, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x02,
+ 0x00, 0x00),
+ /*
+ * Port (0x03) too big (three octets).
+ */
+ WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
+ 0x00, 0x00),
+ /*
+ * Duplicate keys.
+ */
+ WIRE_INVALID(0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00),
+ /*
+ * Out of order keys.
+ */
+ WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00),
+ /*
+ * Empty of mandatory key list.
+ */
+ WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * "mandatory=mandatory" is invalid
+ */
+ WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0x00),
+ /*
+ * Out of order mandatory key list.
+ */
+ WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
+ 0x80, 0x00, 0x71, 0x00, 0x71, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00),
+ /*
+ * Alpn(0x00 0x01) (length 0x00 0x09) "h1,h2" + "h3"
+ */
+ WIRE_VALID_LOOP(0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x09,
+ 5, 'h', '1', ',', 'h', '2', 2, 'h', '3'),
+ /*
+ * Alpn(0x00 0x01) (length 0x00 0x09) "h1\h2" + "h3"
+ */
+ WIRE_VALID_LOOP(0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x09,
+ 5, 'h', '1', '\\', 'h', '2', 2, 'h', '3'),
+ /*
+ * no-default-alpn (0x00 0x02) without alpn, alpn is required.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00),
+ /*
+ * Alpn(0x00 0x01) with zero length elements is invalid
+ */
+ WIRE_INVALID(0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x00, 0x00),
+ WIRE_SENTINEL()
+ };
+ /* Test vectors from RFCXXXX */
+ textvsunknown_t textvsunknown[] = {
+ /* AliasForm */
+ { "0 foo.example.com", "\\# 19 ( 00 00 03 66 6f 6f 07 65 78 61 "
+ "6d 70 6c 65 03 63 6f 6d 00)" },
+ /* ServiceForm */
+ { "1 .", "\\# 3 ( 00 01 00)" },
+ /* Port example */
+ { "16 foo.example.com port=53",
+ "\\# 25 ( 00 10 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f "
+ "6d 00 00 03 00 02 00 35 )" },
+ /* Unregistered keys with unquoted value. */
+ { "1 foo.example.com key667=hello",
+ "\\# 28 ( 00 01 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f "
+ "6d 00 02 9b 00 05 68 65 6c 6c 6f )" },
+ /*
+ * Quoted decimal-escaped character.
+ * 1 foo.example.com key667="hello\210qoo"
+ */
+ { "1 foo.example.com key667=\"hello\\210qoo\"",
+ "\\# 32 ( 00 01 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f "
+ "6d 00 02 9b 00 09 68 65 6c 6c 6f d2 71 6f 6f )" },
+ /*
+ * IPv6 hints example, quoted.
+ * 1 foo.example.com ipv6hint="2001:db8::1,2001:db8::53:1"
+ */
+ { "1 foo.example.com ipv6hint=\"2001:db8::1,2001:db8::53:1\"",
+ "\\# 55 ( 00 01 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f "
+ "6d 00 00 06 00 20 20 01 0d b8 00 00 00 00 00 00 00 00 00 00 "
+ "00 01 20 01 0d b8 00 00 00 00 00 00 00 00 00 53 00 01 )" },
+ /* SvcParamValues and mandatory out of order. */
+ { "16 foo.example.org alpn=h2,h3-19 mandatory=ipv4hint,alpn "
+ "ipv4hint=192.0.2.1",
+ "\\# 48 ( 00 10 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 "
+ "67 00 00 00 00 04 00 01 00 04 00 01 00 09 02 68 32 05 68 33 "
+ "2d 31 39 00 04 00 04 c0 00 02 01 )" },
+ /*
+ * Quoted ALPN with escaped comma and backslash.
+ * 16 foo.example.org alpn="f\\\\oo\\,bar,h2"
+ */
+ { "16 foo.example.org alpn=\"f\\\\\\\\oo\\\\,bar,h2\"",
+ "\\# 35 ( 00 10 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 "
+ "67 00 00 01 00 0c 08 66 5c 6f 6f 2c 62 61 72 02 68 32 )" },
+ /*
+ * Unquoted ALPN with escaped comma and backslash.
+ * 16 foo.example.org alpn=f\\\092oo\092,bar,h2
+ */
+ { "16 foo.example.org alpn=f\\\\\\092oo\\092,bar,h2",
+ "\\# 35 ( 00 10 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 "
+ "67 00 00 01 00 0c 08 66 5c 6f 6f 2c 62 61 72 02 68 32 )" },
+ { NULL, NULL }
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_svcb, sizeof(dns_rdata_in_svcb_t));
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_https, sizeof(dns_rdata_in_https_t));
+
+ check_textvsunknown(textvsunknown, dns_rdataclass_in,
+ dns_rdatatype_svcb);
+ check_textvsunknown(textvsunknown, dns_rdataclass_in,
+ dns_rdatatype_https);
+}
+
+/*
+ * ZONEMD tests.
+ *
+ * Excerpted from RFC 8976:
+ *
+ * The ZONEMD RDATA wire format is encoded as follows:
+ *
+ * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Serial |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Scheme |Hash Algorithm | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+ * | Digest |
+ * / /
+ * / /
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * 2.2.1. The Serial Field
+ *
+ * The Serial field is a 32-bit unsigned integer in network byte order.
+ * It is the serial number from the zone's SOA record ([RFC1035],
+ * Section 3.3.13) for which the zone digest was generated.
+ *
+ * It is included here to clearly bind the ZONEMD RR to a particular
+ * version of the zone's content. Without the serial number, a stand-
+ * alone ZONEMD digest has no obvious association to any particular
+ * instance of a zone.
+ *
+ * 2.2.2. The Scheme Field
+ *
+ * The Scheme field is an 8-bit unsigned integer that identifies the
+ * methods by which data is collated and presented as input to the
+ * hashing function.
+ *
+ * Herein, SIMPLE, with Scheme value 1, is the only standardized Scheme
+ * defined for ZONEMD records and it MUST be supported by
+ * implementations. The "ZONEMD Schemes" registry is further described
+ * in Section 5.
+ *
+ * Scheme values 240-254 are allocated for Private Use.
+ *
+ * 2.2.3. The Hash Algorithm Field
+ *
+ * The Hash Algorithm field is an 8-bit unsigned integer that identifies
+ * the cryptographic hash algorithm used to construct the digest.
+ *
+ * Herein, SHA384 ([RFC6234]), with Hash Algorithm value 1, is the only
+ * standardized Hash Algorithm defined for ZONEMD records that MUST be
+ * supported by implementations. When SHA384 is used, the size of the
+ * Digest field is 48 octets. The result of the SHA384 digest algorithm
+ * MUST NOT be truncated, and the entire 48-octet digest is published in
+ * the ZONEMD record.
+ *
+ * SHA512 ([RFC6234]), with Hash Algorithm value 2, is also defined for
+ * ZONEMD records and SHOULD be supported by implementations. When
+ * SHA512 is used, the size of the Digest field is 64 octets. The
+ * result of the SHA512 digest algorithm MUST NOT be truncated, and the
+ * entire 64-octet digest is published in the ZONEMD record.
+ *
+ * Hash Algorithm values 240-254 are allocated for Private Use.
+ *
+ * The "ZONEMD Hash Algorithms" registry is further described in
+ * Section 5.
+ *
+ * 2.2.4. The Digest Field
+ *
+ * The Digest field is a variable-length sequence of octets containing
+ * the output of the hash algorithm. The length of the Digest field is
+ * determined by deducting the fixed size of the Serial, Scheme, and
+ * Hash Algorithm fields from the RDATA size in the ZONEMD RR header.
+ *
+ * The Digest field MUST NOT be shorter than 12 octets. Digests for the
+ * SHA384 and SHA512 hash algorithms specified herein are never
+ * truncated. Digests for future hash algorithms MAY be truncated but
+ * MUST NOT be truncated to a length that results in less than 96 bits
+ * (12 octets) of equivalent strength.
+ *
+ * Section 3 describes how to calculate the digest for a zone.
+ * Section 4 describes how to use the digest to verify the contents of a
+ * zone.
+ *
+ */
+
+static void
+zonemd(void **state) {
+ text_ok_t text_ok[] = {
+ TEXT_INVALID(""),
+ /* No digest scheme or digest type*/
+ TEXT_INVALID("0"),
+ /* No digest type */
+ TEXT_INVALID("0 0"),
+ /* No digest */
+ TEXT_INVALID("0 0 0"),
+ /* No digest */
+ TEXT_INVALID("99999999 0 0"),
+ /* No digest */
+ TEXT_INVALID("2019020700 0 0"),
+ /* Digest too short */
+ TEXT_INVALID("2019020700 1 1 DEADBEEF"),
+ /* Digest too short */
+ TEXT_INVALID("2019020700 1 2 DEADBEEF"),
+ /* Digest too short */
+ TEXT_INVALID("2019020700 1 3 DEADBEEFDEADBEEFDEADBE"),
+ /* Digest type unknown */
+ TEXT_VALID("2019020700 1 3 DEADBEEFDEADBEEFDEADBEEF"),
+ /* Digest type max */
+ TEXT_VALID("2019020700 1 255 DEADBEEFDEADBEEFDEADBEEF"),
+ /* Digest type too big */
+ TEXT_INVALID("2019020700 0 256 DEADBEEFDEADBEEFDEADBEEF"),
+ /* Scheme max */
+ TEXT_VALID("2019020700 255 3 DEADBEEFDEADBEEFDEADBEEF"),
+ /* Scheme too big */
+ TEXT_INVALID("2019020700 256 3 DEADBEEFDEADBEEFDEADBEEF"),
+ /* SHA384 */
+ TEXT_VALID("2019020700 1 1 "
+ "7162D2BB75C047A53DE98767C9192BEB"
+ "14DB01E7E2267135DAF0230A 19BA4A31"
+ "6AF6BF64AA5C7BAE24B2992850300509"),
+ /* SHA512 */
+ TEXT_VALID("2019020700 1 2 "
+ "08CFA1115C7B948C4163A901270395EA"
+ "226A930CD2CBCF2FA9A5E6EB 85F37C8A"
+ "4E114D884E66F176EAB121CB02DB7D65"
+ "2E0CC4827E7A3204 F166B47E5613FD27"),
+ /* SHA384 too short and with private scheme */
+ TEXT_INVALID("2021042801 0 1 "
+ "7162D2BB75C047A53DE98767C9192BEB"
+ "6AF6BF64AA5C7BAE24B2992850300509"),
+ /* SHA512 too short and with private scheme */
+ TEXT_INVALID("2021042802 5 2 "
+ "A897B40072ECAE9E4CA3F1F227DE8F5E"
+ "480CDEBB16DFC64C1C349A7B5F6C71AB"
+ "E8A88B76EF0BA1604EC25752E946BF98"),
+ TEXT_SENTINEL()
+ };
+ wire_ok_t wire_ok[] = {
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00),
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00, 0x00),
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00),
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00),
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Short.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+ /*
+ * Short 11-octet digest.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00),
+ /*
+ * Minimal, 12-octet hash for an undefined digest type.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00),
+ /*
+ * SHA-384 is defined, so we insist there be a digest of
+ * the expected length.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00),
+ /*
+ * 48-octet digest, valid for SHA-384.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xde, 0xad, 0xbe,
+ 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe,
+ 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe,
+ 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa,
+ 0xce),
+ /*
+ * 56-octet digest, too long for SHA-384.
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xde, 0xad,
+ 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad,
+ 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad,
+ 0xbe, 0xef, 0xfa, 0xce),
+ /*
+ * 56-octet digest, too short for SHA-512
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0xde, 0xad,
+ 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad,
+ 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad,
+ 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad),
+ /*
+ * 64-octet digest, just right for SHA-512
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0xde, 0xad, 0xbe,
+ 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe,
+ 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe,
+ 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe,
+ 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef),
+ /*
+ * 72-octet digest, too long for SHA-512
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0xde, 0xad,
+ 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad,
+ 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad,
+ 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce),
+ /*
+ * 56-octet digest, valid for an undefined digest type.
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xde, 0xad, 0xbe,
+ 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe,
+ 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe,
+ 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce),
+ /*
+ * Sentinel.
+ */
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_zonemd, sizeof(dns_rdata_zonemd_t));
+}
+
+static void
+atcname(void **state) {
+ unsigned int i;
+ UNUSED(state);
+#define UNR "# Unexpected result from dns_rdatatype_atcname for type %u\n"
+ for (i = 0; i < 0xffffU; i++) {
+ bool tf = dns_rdatatype_atcname((dns_rdatatype_t)i);
+ switch (i) {
+ case dns_rdatatype_nsec:
+ case dns_rdatatype_key:
+ case dns_rdatatype_rrsig:
+ if (!tf) {
+ print_message(UNR, i);
+ }
+ assert_true(tf);
+ break;
+ default:
+ if (tf) {
+ print_message(UNR, i);
+ }
+ assert_false(tf);
+ break;
+ }
+ }
+#undef UNR
+}
+
+static void
+atparent(void **state) {
+ unsigned int i;
+ UNUSED(state);
+#define UNR "# Unexpected result from dns_rdatatype_atparent for type %u\n"
+ for (i = 0; i < 0xffffU; i++) {
+ bool tf = dns_rdatatype_atparent((dns_rdatatype_t)i);
+ switch (i) {
+ case dns_rdatatype_ds:
+ if (!tf) {
+ print_message(UNR, i);
+ }
+ assert_true(tf);
+ break;
+ default:
+ if (tf) {
+ print_message(UNR, i);
+ }
+ assert_false(tf);
+ break;
+ }
+ }
+#undef UNR
+}
+
+static void
+iszonecutauth(void **state) {
+ unsigned int i;
+ UNUSED(state);
+#define UNR "# Unexpected result from dns_rdatatype_iszonecutauth for type %u\n"
+ for (i = 0; i < 0xffffU; i++) {
+ bool tf = dns_rdatatype_iszonecutauth((dns_rdatatype_t)i);
+ switch (i) {
+ case dns_rdatatype_ns:
+ case dns_rdatatype_ds:
+ case dns_rdatatype_nsec:
+ case dns_rdatatype_key:
+ case dns_rdatatype_rrsig:
+ if (!tf) {
+ print_message(UNR, i);
+ }
+ assert_true(tf);
+ break;
+ default:
+ if (tf) {
+ print_message(UNR, i);
+ }
+ assert_false(tf);
+ break;
+ }
+ }
+#undef UNR
+}
+
+int
+main(int argc, char **argv) {
+ const struct CMUnitTest tests[] = {
+ /* types */
+ cmocka_unit_test_setup_teardown(amtrelay, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(apl, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(atma, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(cdnskey, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(csync, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(dnskey, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(doa, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(ds, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(eid, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(hip, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(https_svcb, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(isdn, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(key, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(loc, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(nimloc, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(nsec, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(nsec3, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(nxt, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(rkey, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(sshfp, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(wks, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(zonemd, _setup, _teardown),
+ /* other tests */
+ cmocka_unit_test_setup_teardown(edns_client_subnet, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(atcname, NULL, NULL),
+ cmocka_unit_test_setup_teardown(atparent, NULL, NULL),
+ cmocka_unit_test_setup_teardown(iszonecutauth, NULL, NULL),
+ };
+ struct CMUnitTest selected[sizeof(tests) / sizeof(tests[0])];
+ size_t i;
+ int c;
+
+ memset(selected, 0, sizeof(selected));
+
+ while ((c = isc_commandline_parse(argc, argv, "dlt:")) != -1) {
+ switch (c) {
+ case 'd':
+ debug = true;
+ break;
+ case 'l':
+ for (i = 0; i < (sizeof(tests) / sizeof(tests[0])); i++)
+ {
+ if (tests[i].name != NULL) {
+ fprintf(stdout, "%s\n", tests[i].name);
+ }
+ }
+ return (0);
+ case 't':
+ if (!cmocka_add_test_byname(
+ tests, isc_commandline_argument, selected))
+ {
+ fprintf(stderr, "unknown test '%s'\n",
+ isc_commandline_argument);
+ exit(1);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (selected[0].name != NULL) {
+ return (cmocka_run_group_tests(selected, NULL, NULL));
+ } else {
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+ }
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/rdataset_test.c b/lib/dns/tests/rdataset_test.c
new file mode 100644
index 0000000..ebdd128
--- /dev/null
+++ b/lib/dns/tests/rdataset_test.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/util.h>
+
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+/* test trimming of rdataset TTLs */
+static void
+trimttl(void **state) {
+ dns_rdataset_t rdataset, sigrdataset;
+ dns_rdata_rrsig_t rrsig;
+ isc_stdtime_t ttltimenow, ttltimeexpire;
+
+ ttltimenow = 10000000;
+ ttltimeexpire = ttltimenow + 800;
+
+ UNUSED(state);
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_init(&sigrdataset);
+
+ rdataset.ttl = 900;
+ sigrdataset.ttl = 1000;
+ rrsig.timeexpire = ttltimeexpire;
+ rrsig.originalttl = 1000;
+
+ dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, true);
+ assert_int_equal(rdataset.ttl, 800);
+ assert_int_equal(sigrdataset.ttl, 800);
+
+ rdataset.ttl = 900;
+ sigrdataset.ttl = 1000;
+ rrsig.timeexpire = ttltimenow - 200;
+ rrsig.originalttl = 1000;
+
+ dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, true);
+ assert_int_equal(rdataset.ttl, 120);
+ assert_int_equal(sigrdataset.ttl, 120);
+
+ rdataset.ttl = 900;
+ sigrdataset.ttl = 1000;
+ rrsig.timeexpire = ttltimenow - 200;
+ rrsig.originalttl = 1000;
+
+ dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow,
+ false);
+ assert_int_equal(rdataset.ttl, 0);
+ assert_int_equal(sigrdataset.ttl, 0);
+
+ sigrdataset.ttl = 900;
+ rdataset.ttl = 1000;
+ rrsig.timeexpire = ttltimeexpire;
+ rrsig.originalttl = 1000;
+
+ dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, true);
+ assert_int_equal(rdataset.ttl, 800);
+ assert_int_equal(sigrdataset.ttl, 800);
+
+ sigrdataset.ttl = 900;
+ rdataset.ttl = 1000;
+ rrsig.timeexpire = ttltimenow - 200;
+ rrsig.originalttl = 1000;
+
+ dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, true);
+ assert_int_equal(rdataset.ttl, 120);
+ assert_int_equal(sigrdataset.ttl, 120);
+
+ sigrdataset.ttl = 900;
+ rdataset.ttl = 1000;
+ rrsig.timeexpire = ttltimenow - 200;
+ rrsig.originalttl = 1000;
+
+ dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow,
+ false);
+ assert_int_equal(rdataset.ttl, 0);
+ assert_int_equal(sigrdataset.ttl, 0);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(trimttl, _setup, _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/rdatasetstats_test.c b/lib/dns/tests/rdatasetstats_test.c
new file mode 100644
index 0000000..20b333d
--- /dev/null
+++ b/lib/dns/tests/rdatasetstats_test.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/print.h>
+#include <isc/util.h>
+
+#include <dns/stats.h>
+
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static void
+set_typestats(dns_stats_t *stats, dns_rdatatype_t type) {
+ dns_rdatastatstype_t which;
+ unsigned int attributes;
+
+ attributes = 0;
+ which = DNS_RDATASTATSTYPE_VALUE(type, attributes);
+ dns_rdatasetstats_increment(stats, which);
+
+ attributes = DNS_RDATASTATSTYPE_ATTR_NXRRSET;
+ which = DNS_RDATASTATSTYPE_VALUE(type, attributes);
+ dns_rdatasetstats_increment(stats, which);
+}
+
+static void
+set_nxdomainstats(dns_stats_t *stats) {
+ dns_rdatastatstype_t which;
+ unsigned int attributes;
+
+ attributes = DNS_RDATASTATSTYPE_ATTR_NXDOMAIN;
+ which = DNS_RDATASTATSTYPE_VALUE(0, attributes);
+ dns_rdatasetstats_increment(stats, which);
+}
+
+static void
+mark_stale(dns_stats_t *stats, dns_rdatatype_t type, int from, int to) {
+ dns_rdatastatstype_t which;
+ unsigned int attributes;
+
+ attributes = from;
+ which = DNS_RDATASTATSTYPE_VALUE(type, attributes);
+ dns_rdatasetstats_decrement(stats, which);
+
+ attributes |= to;
+ which = DNS_RDATASTATSTYPE_VALUE(type, attributes);
+ dns_rdatasetstats_increment(stats, which);
+
+ attributes = DNS_RDATASTATSTYPE_ATTR_NXRRSET | from;
+ which = DNS_RDATASTATSTYPE_VALUE(type, attributes);
+ dns_rdatasetstats_decrement(stats, which);
+
+ attributes |= to;
+ which = DNS_RDATASTATSTYPE_VALUE(type, attributes);
+ dns_rdatasetstats_increment(stats, which);
+}
+
+static void
+mark_nxdomain_stale(dns_stats_t *stats, int from, int to) {
+ dns_rdatastatstype_t which;
+ unsigned int attributes;
+
+ attributes = DNS_RDATASTATSTYPE_ATTR_NXDOMAIN | from;
+ which = DNS_RDATASTATSTYPE_VALUE(0, attributes);
+ dns_rdatasetstats_decrement(stats, which);
+
+ attributes |= to;
+ which = DNS_RDATASTATSTYPE_VALUE(0, attributes);
+ dns_rdatasetstats_increment(stats, which);
+}
+
+#define ATTRIBUTE_SET(y) ((attributes & (y)) != 0)
+static void
+verify_active_counters(dns_rdatastatstype_t which, uint64_t value, void *arg) {
+ unsigned int attributes;
+#if debug
+ unsigned int type;
+#endif /* if debug */
+
+ UNUSED(which);
+ UNUSED(arg);
+
+ attributes = DNS_RDATASTATSTYPE_ATTR(which);
+#if debug
+ type = DNS_RDATASTATSTYPE_BASE(which);
+
+ fprintf(stderr, "%s%s%s%s%s/%u, %u\n",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_OTHERTYPE) ? "O" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXRRSET) ? "!" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_ANCIENT) ? "~" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_STALE) ? "#" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) ? "X" : " ",
+ type, (unsigned)value);
+#endif /* if debug */
+ if ((attributes & DNS_RDATASTATSTYPE_ATTR_ANCIENT) == 0 &&
+ (attributes & DNS_RDATASTATSTYPE_ATTR_STALE) == 0)
+ {
+ assert_int_equal(value, 1);
+ } else {
+ assert_int_equal(value, 0);
+ }
+}
+
+static void
+verify_stale_counters(dns_rdatastatstype_t which, uint64_t value, void *arg) {
+ unsigned int attributes;
+#if debug
+ unsigned int type;
+#endif /* if debug */
+
+ UNUSED(which);
+ UNUSED(arg);
+
+ attributes = DNS_RDATASTATSTYPE_ATTR(which);
+#if debug
+ type = DNS_RDATASTATSTYPE_BASE(which);
+
+ fprintf(stderr, "%s%s%s%s%s/%u, %u\n",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_OTHERTYPE) ? "O" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXRRSET) ? "!" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_ANCIENT) ? "~" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_STALE) ? "#" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) ? "X" : " ",
+ type, (unsigned)value);
+#endif /* if debug */
+ if ((attributes & DNS_RDATASTATSTYPE_ATTR_STALE) != 0) {
+ assert_int_equal(value, 1);
+ } else {
+ assert_int_equal(value, 0);
+ }
+}
+
+static void
+verify_ancient_counters(dns_rdatastatstype_t which, uint64_t value, void *arg) {
+ unsigned int attributes;
+#if debug
+ unsigned int type;
+#endif /* if debug */
+
+ UNUSED(which);
+ UNUSED(arg);
+
+ attributes = DNS_RDATASTATSTYPE_ATTR(which);
+#if debug
+ type = DNS_RDATASTATSTYPE_BASE(which);
+
+ fprintf(stderr, "%s%s%s%s%s/%u, %u\n",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_OTHERTYPE) ? "O" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXRRSET) ? "!" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_ANCIENT) ? "~" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_STALE) ? "#" : " ",
+ ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) ? "X" : " ",
+ type, (unsigned)value);
+#endif /* if debug */
+ if ((attributes & DNS_RDATASTATSTYPE_ATTR_ANCIENT) != 0) {
+ assert_int_equal(value, 1);
+ } else {
+ assert_int_equal(value, 0);
+ }
+}
+/*
+ * Individual unit tests
+ */
+
+/*
+ * Test that rdatasetstats counters are properly set when moving from
+ * active -> stale -> ancient.
+ */
+static void
+rdatasetstats(void **state, bool servestale) {
+ unsigned int i;
+ unsigned int from = 0;
+ dns_stats_t *stats = NULL;
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_rdatasetstats_create(dt_mctx, &stats);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* First 255 types. */
+ for (i = 1; i <= 255; i++) {
+ set_typestats(stats, (dns_rdatatype_t)i);
+ }
+ /* Specials */
+ set_typestats(stats, (dns_rdatatype_t)1000);
+ set_nxdomainstats(stats);
+
+ /* Check that all active counters are set to appropriately. */
+ dns_rdatasetstats_dump(stats, verify_active_counters, NULL, 1);
+
+ if (servestale) {
+ /* Mark stale */
+ for (i = 1; i <= 255; i++) {
+ mark_stale(stats, (dns_rdatatype_t)i, 0,
+ DNS_RDATASTATSTYPE_ATTR_STALE);
+ }
+ mark_stale(stats, (dns_rdatatype_t)1000, 0,
+ DNS_RDATASTATSTYPE_ATTR_STALE);
+ mark_nxdomain_stale(stats, 0, DNS_RDATASTATSTYPE_ATTR_STALE);
+
+ /* Check that all counters are set to appropriately. */
+ dns_rdatasetstats_dump(stats, verify_stale_counters, NULL, 1);
+
+ /* Set correct staleness state */
+ from = DNS_RDATASTATSTYPE_ATTR_STALE;
+ }
+
+ /* Mark ancient */
+ for (i = 1; i <= 255; i++) {
+ mark_stale(stats, (dns_rdatatype_t)i, from,
+ DNS_RDATASTATSTYPE_ATTR_ANCIENT);
+ }
+ mark_stale(stats, (dns_rdatatype_t)1000, from,
+ DNS_RDATASTATSTYPE_ATTR_ANCIENT);
+ mark_nxdomain_stale(stats, from, DNS_RDATASTATSTYPE_ATTR_ANCIENT);
+
+ /*
+ * Check that all counters are set to appropriately.
+ */
+ dns_rdatasetstats_dump(stats, verify_ancient_counters, NULL, 1);
+
+ dns_stats_detach(&stats);
+}
+
+/*
+ * Test that rdatasetstats counters are properly set when moving from
+ * active -> stale -> ancient.
+ */
+static void
+test_rdatasetstats_active_stale_ancient(void **state) {
+ rdatasetstats(state, true);
+}
+
+/*
+ * Test that rdatasetstats counters are properly set when moving from
+ * active -> ancient.
+ */
+static void
+test_rdatasetstats_active_ancient(void **state) {
+ rdatasetstats(state, false);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_rdatasetstats_active_stale_ancient, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(
+ test_rdatasetstats_active_ancient, _setup, _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/resolver_test.c b/lib/dns/tests/resolver_test.c
new file mode 100644
index 0000000..ed89d01
--- /dev/null
+++ b/lib/dns/tests/resolver_test.c
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/app.h>
+#include <isc/buffer.h>
+#include <isc/print.h>
+#include <isc/socket.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/dispatch.h>
+#include <dns/name.h>
+#include <dns/resolver.h>
+#include <dns/view.h>
+
+#include "dnstest.h"
+
+static dns_dispatchmgr_t *dispatchmgr = NULL;
+static dns_dispatch_t *dispatch = NULL;
+static dns_view_t *view = NULL;
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+ isc_sockaddr_t local;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_dispatchmgr_create(dt_mctx, &dispatchmgr);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_test_makeview("view", &view);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_sockaddr_any(&local);
+ result = dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, &local,
+ 4096, 100, 100, 100, 500, 0, 0, &dispatch);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_dispatch_detach(&dispatch);
+ dns_view_detach(&view);
+ dns_dispatchmgr_destroy(&dispatchmgr);
+ dns_test_end();
+
+ return (0);
+}
+
+static void
+mkres(dns_resolver_t **resolverp) {
+ isc_result_t result;
+
+ result = dns_resolver_create(view, taskmgr, 1, 1, socketmgr, timermgr,
+ 0, dispatchmgr, dispatch, NULL, resolverp);
+ assert_int_equal(result, ISC_R_SUCCESS);
+}
+
+static void
+destroy_resolver(dns_resolver_t **resolverp) {
+ dns_resolver_shutdown(*resolverp);
+ dns_resolver_detach(resolverp);
+}
+
+/* dns_resolver_create */
+static void
+create_test(void **state) {
+ dns_resolver_t *resolver = NULL;
+
+ UNUSED(state);
+
+ mkres(&resolver);
+ destroy_resolver(&resolver);
+}
+
+/* dns_resolver_gettimeout */
+static void
+gettimeout_test(void **state) {
+ dns_resolver_t *resolver = NULL;
+ unsigned int timeout;
+
+ UNUSED(state);
+
+ mkres(&resolver);
+
+ timeout = dns_resolver_gettimeout(resolver);
+ assert_true(timeout > 0);
+
+ destroy_resolver(&resolver);
+}
+
+/* dns_resolver_settimeout */
+static void
+settimeout_test(void **state) {
+ dns_resolver_t *resolver = NULL;
+ unsigned int default_timeout, timeout;
+
+ UNUSED(state);
+
+ mkres(&resolver);
+
+ default_timeout = dns_resolver_gettimeout(resolver);
+ dns_resolver_settimeout(resolver, default_timeout + 1);
+ timeout = dns_resolver_gettimeout(resolver);
+ assert_true(timeout == default_timeout + 1);
+
+ destroy_resolver(&resolver);
+}
+
+/* dns_resolver_settimeout */
+static void
+settimeout_default_test(void **state) {
+ dns_resolver_t *resolver = NULL;
+ unsigned int default_timeout, timeout;
+
+ UNUSED(state);
+
+ mkres(&resolver);
+
+ default_timeout = dns_resolver_gettimeout(resolver);
+ dns_resolver_settimeout(resolver, default_timeout + 100);
+
+ timeout = dns_resolver_gettimeout(resolver);
+ assert_int_equal(timeout, default_timeout + 100);
+
+ dns_resolver_settimeout(resolver, 0);
+ timeout = dns_resolver_gettimeout(resolver);
+ assert_int_equal(timeout, default_timeout);
+
+ destroy_resolver(&resolver);
+}
+
+/* dns_resolver_settimeout below minimum */
+static void
+settimeout_belowmin_test(void **state) {
+ dns_resolver_t *resolver = NULL;
+ unsigned int default_timeout, timeout;
+
+ UNUSED(state);
+
+ mkres(&resolver);
+
+ default_timeout = dns_resolver_gettimeout(resolver);
+ dns_resolver_settimeout(resolver, 9000);
+
+ timeout = dns_resolver_gettimeout(resolver);
+ assert_int_equal(timeout, default_timeout);
+
+ destroy_resolver(&resolver);
+}
+
+/* dns_resolver_settimeout over maximum */
+static void
+settimeout_overmax_test(void **state) {
+ dns_resolver_t *resolver = NULL;
+ unsigned int timeout;
+
+ UNUSED(state);
+
+ mkres(&resolver);
+
+ dns_resolver_settimeout(resolver, 4000000);
+ timeout = dns_resolver_gettimeout(resolver);
+ assert_in_range(timeout, 0, 3999999);
+ destroy_resolver(&resolver);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(create_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(gettimeout_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(settimeout_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(settimeout_default_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(settimeout_belowmin_test,
+ _setup, _teardown),
+ cmocka_unit_test_setup_teardown(settimeout_overmax_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/result_test.c b/lib/dns/tests/result_test.c
new file mode 100644
index 0000000..3a05c71
--- /dev/null
+++ b/lib/dns/tests/result_test.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/lib.h>
+#include <dns/result.h>
+
+#include <dst/result.h>
+
+/*
+ * Check ids array is populated.
+ */
+static void
+ids(void **state) {
+ const char *str;
+ isc_result_t result;
+
+ UNUSED(state);
+
+ dns_result_register();
+ dst_result_register();
+
+ for (result = ISC_RESULTCLASS_DNS;
+ result < (ISC_RESULTCLASS_DNS + DNS_R_NRESULTS); result++)
+ {
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+ }
+
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+
+ for (result = ISC_RESULTCLASS_DST;
+ result < (ISC_RESULTCLASS_DST + DST_R_NRESULTS); result++)
+ {
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+ }
+
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+
+ for (result = ISC_RESULTCLASS_DNSRCODE;
+ result < (ISC_RESULTCLASS_DNSRCODE + DNS_R_NRCODERESULTS);
+ result++)
+ {
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+ }
+
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(ids),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/rsa_test.c b/lib/dns/tests/rsa_test.c
new file mode 100644
index 0000000..7d8897b
--- /dev/null
+++ b/lib/dns/tests/rsa_test.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/print.h>
+#include <isc/util.h>
+
+#include <pk11/site.h>
+
+#include "../dst_internal.h"
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static unsigned char d[10] = { 0xa, 0x10, 0xbb, 0, 0xfe,
+ 0x15, 0x1, 0x88, 0xcc, 0x7d };
+
+static unsigned char sigsha1[256] = {
+ 0x45, 0x55, 0xd6, 0xf8, 0x05, 0xd2, 0x2e, 0x79, 0x14, 0x2b, 0x1b, 0xd1,
+ 0x4b, 0xb7, 0xcd, 0xc0, 0xa2, 0xf3, 0x85, 0x32, 0x1f, 0xa3, 0xfd, 0x1f,
+ 0x30, 0xe0, 0xde, 0xb2, 0x6f, 0x3c, 0x8e, 0x2b, 0x82, 0x92, 0xcd, 0x1c,
+ 0x1b, 0xdf, 0xe6, 0xd5, 0x4d, 0x93, 0xe6, 0xaa, 0x40, 0x28, 0x1b, 0x7b,
+ 0x2e, 0x40, 0x4d, 0xb5, 0x4d, 0x43, 0xe8, 0xfc, 0x93, 0x86, 0x68, 0xe3,
+ 0xbf, 0x73, 0x9a, 0x1e, 0x6b, 0x5d, 0x52, 0xb8, 0x98, 0x1c, 0x94, 0xe1,
+ 0x85, 0x8b, 0xee, 0xb1, 0x4f, 0x22, 0x71, 0xcb, 0xfd, 0xb2, 0xa8, 0x88,
+ 0x64, 0xb4, 0xb1, 0x4a, 0xa1, 0x7a, 0xce, 0x52, 0x83, 0xd8, 0xf2, 0x9e,
+ 0x67, 0x4c, 0xc3, 0x37, 0x74, 0xfe, 0xe0, 0x25, 0x2a, 0xfd, 0xa3, 0x09,
+ 0xff, 0x8a, 0x92, 0x0d, 0xa9, 0xb3, 0x90, 0x23, 0xbe, 0x6a, 0x2c, 0x9e,
+ 0x5c, 0x6d, 0xb4, 0xa7, 0xd7, 0x97, 0xdd, 0xc6, 0xb8, 0xae, 0xd4, 0x88,
+ 0x64, 0x63, 0x1e, 0x85, 0x20, 0x09, 0xea, 0xc4, 0x0b, 0xca, 0xbf, 0x83,
+ 0x5c, 0x89, 0xae, 0x64, 0x15, 0x76, 0x06, 0x51, 0xb6, 0xa1, 0x99, 0xb2,
+ 0x3c, 0x50, 0x99, 0x86, 0x7d, 0xc7, 0xca, 0x4e, 0x1d, 0x2c, 0x17, 0xbb,
+ 0x6c, 0x7a, 0xc9, 0x3f, 0x5e, 0x28, 0x57, 0x2c, 0xda, 0x01, 0x1d, 0xe8,
+ 0x01, 0xf8, 0xf6, 0x37, 0xe1, 0x34, 0x56, 0xae, 0x6e, 0xb1, 0xd4, 0xa2,
+ 0xc4, 0x02, 0xc1, 0xca, 0x96, 0xb0, 0x06, 0x72, 0x2a, 0x27, 0xaa, 0xc8,
+ 0xd5, 0x50, 0x81, 0x49, 0x46, 0x33, 0xf8, 0xf7, 0x6b, 0xf4, 0x9c, 0x30,
+ 0x90, 0x50, 0xf6, 0x16, 0x76, 0x9d, 0xc6, 0x73, 0xb5, 0xbc, 0x8a, 0xb6,
+ 0x1d, 0x98, 0xcb, 0xce, 0x36, 0x6f, 0x60, 0xec, 0x96, 0x49, 0x08, 0x85,
+ 0x5b, 0xc1, 0x8e, 0xb0, 0xea, 0x9e, 0x1f, 0xd6, 0x27, 0x7f, 0xb6, 0xe0,
+ 0x04, 0x12, 0xd2, 0x81
+};
+
+static unsigned char sigsha256[256] = {
+ 0x83, 0x53, 0x15, 0xfc, 0xca, 0xdb, 0xf6, 0x0d, 0x53, 0x24, 0x5b, 0x5a,
+ 0x8e, 0xd0, 0xbe, 0x5e, 0xbc, 0xe8, 0x9e, 0x92, 0x3c, 0xfa, 0x93, 0x03,
+ 0xce, 0x2f, 0xc7, 0x6d, 0xd0, 0xbb, 0x9d, 0x06, 0x83, 0xc6, 0xd3, 0xc0,
+ 0xc1, 0x57, 0x9c, 0x82, 0x17, 0x7f, 0xb5, 0xf8, 0x31, 0x18, 0xda, 0x46,
+ 0x05, 0x2c, 0xf8, 0xea, 0xaa, 0xcd, 0x99, 0x18, 0xff, 0x23, 0x5e, 0xef,
+ 0xf0, 0x87, 0x47, 0x6e, 0x91, 0xfd, 0x19, 0x0b, 0x39, 0x19, 0x6a, 0xc8,
+ 0xdf, 0x71, 0x66, 0x8e, 0xa9, 0xa0, 0x79, 0x5c, 0x2c, 0x52, 0x00, 0x61,
+ 0x17, 0x86, 0x66, 0x03, 0x52, 0xad, 0xec, 0x06, 0x53, 0xd9, 0x6d, 0xe3,
+ 0xe3, 0xea, 0x28, 0x15, 0xb3, 0x75, 0xf4, 0x61, 0x7d, 0xed, 0x69, 0x2c,
+ 0x24, 0xf3, 0x21, 0xb1, 0x8a, 0xea, 0x60, 0xa2, 0x9e, 0x6a, 0xa6, 0x53,
+ 0x12, 0xf6, 0x5c, 0xef, 0xd7, 0x49, 0x4a, 0x02, 0xe7, 0xf8, 0x64, 0x89,
+ 0x13, 0xac, 0xd5, 0x1e, 0x58, 0xff, 0xa1, 0x63, 0xdd, 0xa0, 0x1f, 0x44,
+ 0x99, 0x6a, 0x59, 0x7f, 0x35, 0xbd, 0xf1, 0xf3, 0x7a, 0x28, 0x44, 0xe3,
+ 0x4c, 0x68, 0xb1, 0xb3, 0x97, 0x3c, 0x46, 0xe3, 0xc2, 0x12, 0x9e, 0x68,
+ 0x0b, 0xa6, 0x6c, 0x8f, 0x58, 0x48, 0x44, 0xa4, 0xf7, 0xa7, 0xc2, 0x91,
+ 0x8f, 0xbf, 0x00, 0xd0, 0x01, 0x35, 0xd4, 0x86, 0x6e, 0x1f, 0xea, 0x42,
+ 0x60, 0xb1, 0x84, 0x27, 0xf4, 0x99, 0x36, 0x06, 0x98, 0x12, 0x83, 0x32,
+ 0x9f, 0xcd, 0x50, 0x5a, 0x5e, 0xb8, 0x8e, 0xfe, 0x8d, 0x8d, 0x33, 0x2d,
+ 0x45, 0xe1, 0xc9, 0xdf, 0x2a, 0xd8, 0x38, 0x1d, 0x95, 0xd4, 0x42, 0xee,
+ 0x93, 0x5b, 0x0f, 0x1e, 0x07, 0x06, 0x3a, 0x92, 0xf1, 0x59, 0x1d, 0x6e,
+ 0x1c, 0x31, 0xf3, 0xce, 0xa9, 0x1f, 0xad, 0x4d, 0x76, 0x4d, 0x24, 0x98,
+ 0xe2, 0x0e, 0x8c, 0x35
+};
+
+static unsigned char sigsha512[512] = {
+ 0x4e, 0x2f, 0x63, 0x42, 0xc5, 0xf3, 0x05, 0x4a, 0xa6, 0x3a, 0x93, 0xa0,
+ 0xd9, 0x33, 0xa0, 0xd1, 0x46, 0x33, 0x42, 0xe8, 0x74, 0xeb, 0x3b, 0x10,
+ 0x82, 0xd7, 0xcf, 0x39, 0x23, 0xb3, 0xe9, 0x23, 0x53, 0x87, 0x8c, 0xee,
+ 0x78, 0xcb, 0xb3, 0xd9, 0xd2, 0x6d, 0x1a, 0x7c, 0x01, 0x4f, 0xed, 0x8d,
+ 0xf2, 0x72, 0xe4, 0x6a, 0x00, 0x8a, 0x60, 0xa6, 0xd5, 0x9c, 0x43, 0x6c,
+ 0xef, 0x38, 0x0c, 0x74, 0x82, 0x5d, 0x22, 0xaa, 0x87, 0x81, 0x90, 0x9c,
+ 0x64, 0x07, 0x9b, 0x13, 0x51, 0xe0, 0xa5, 0xc2, 0x83, 0x78, 0x2b, 0x9b,
+ 0xb3, 0x8a, 0x9d, 0x36, 0x33, 0xbd, 0x0d, 0x53, 0x84, 0xae, 0xe8, 0x13,
+ 0x36, 0xf6, 0xdf, 0x96, 0xe9, 0xda, 0xc3, 0xd7, 0xa9, 0x2f, 0xf3, 0x5e,
+ 0x5f, 0x1f, 0x7f, 0x38, 0x7e, 0x8d, 0xbe, 0x90, 0x5e, 0x13, 0xb2, 0x20,
+ 0xbb, 0x9d, 0xfe, 0xe1, 0x52, 0xce, 0xe6, 0x80, 0xa7, 0x95, 0x24, 0x59,
+ 0xe3, 0xac, 0x24, 0xc4, 0xfa, 0x1c, 0x44, 0x34, 0x29, 0x8d, 0xb1, 0xd0,
+ 0xd9, 0x4c, 0xff, 0xc4, 0xdb, 0xca, 0xc4, 0x3f, 0x38, 0xf9, 0xe4, 0xaf,
+ 0x75, 0x0a, 0x67, 0x4d, 0xa0, 0x2b, 0xb0, 0x83, 0xce, 0x53, 0xc4, 0xb9,
+ 0x2e, 0x61, 0xb6, 0x64, 0xe5, 0xb5, 0xe5, 0xac, 0x9d, 0x51, 0xec, 0x58,
+ 0x42, 0x90, 0x78, 0xf6, 0x46, 0x96, 0xef, 0xb6, 0x97, 0xb7, 0x54, 0x28,
+ 0x1a, 0x4c, 0x29, 0xf4, 0x7a, 0x33, 0xc6, 0x07, 0xfd, 0xec, 0x97, 0x36,
+ 0x1d, 0x42, 0x88, 0x94, 0x27, 0xc2, 0xa3, 0xe1, 0xd4, 0x87, 0xa1, 0x8a,
+ 0x2b, 0xff, 0x47, 0x60, 0xfe, 0x1f, 0xaf, 0xc2, 0xeb, 0x17, 0xdd, 0x56,
+ 0xc5, 0x94, 0x5c, 0xcb, 0x23, 0xe5, 0x49, 0x4d, 0x99, 0x06, 0x02, 0x5a,
+ 0xfc, 0xfc, 0xdc, 0xee, 0x49, 0xbc, 0x47, 0x60, 0xff, 0x6a, 0x63, 0x8b,
+ 0xe1, 0x2e, 0xa3, 0xa7
+};
+
+/* RSA verify */
+static void
+isc_rsa_verify_test(void **state) {
+ isc_result_t ret;
+ dns_fixedname_t fname;
+ isc_buffer_t buf;
+ dns_name_t *name;
+ dst_key_t *key = NULL;
+ dst_context_t *ctx = NULL;
+ isc_region_t r;
+
+ UNUSED(state);
+
+ name = dns_fixedname_initname(&fname);
+ isc_buffer_constinit(&buf, "rsa.", 4);
+ isc_buffer_add(&buf, 4);
+ ret = dns_name_fromtext(name, &buf, NULL, 0, NULL);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ ret = dst_key_fromfile(name, 29238, DST_ALG_RSASHA256, DST_TYPE_PUBLIC,
+ "./", dt_mctx, &key);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ /* RSASHA1 - May not be supported by the OS */
+ if (dst_algorithm_supported(DST_ALG_RSASHA1)) {
+ key->key_alg = DST_ALG_RSASHA1;
+
+ ret = dst_context_create(key, dt_mctx, DNS_LOGCATEGORY_DNSSEC,
+ false, 0, &ctx);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ r.base = d;
+ r.length = 10;
+ ret = dst_context_adddata(ctx, &r);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ r.base = sigsha1;
+ r.length = 256;
+ ret = dst_context_verify(ctx, &r);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ dst_context_destroy(&ctx);
+ }
+
+ /* RSASHA256 */
+
+ key->key_alg = DST_ALG_RSASHA256;
+
+ ret = dst_context_create(key, dt_mctx, DNS_LOGCATEGORY_DNSSEC, false, 0,
+ &ctx);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ r.base = d;
+ r.length = 10;
+ ret = dst_context_adddata(ctx, &r);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ r.base = sigsha256;
+ r.length = 256;
+ ret = dst_context_verify(ctx, &r);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ dst_context_destroy(&ctx);
+
+ /* RSASHA512 */
+
+ key->key_alg = DST_ALG_RSASHA512;
+
+ ret = dst_context_create(key, dt_mctx, DNS_LOGCATEGORY_DNSSEC, false, 0,
+ &ctx);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ r.base = d;
+ r.length = 10;
+ ret = dst_context_adddata(ctx, &r);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ r.base = sigsha512;
+ r.length = 256;
+ ret = dst_context_verify(ctx, &r);
+ assert_int_equal(ret, ISC_R_SUCCESS);
+
+ dst_context_destroy(&ctx);
+
+ dst_key_free(&key);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(isc_rsa_verify_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* HAVE_CMOCKA */
diff --git a/lib/dns/tests/sigs_test.c b/lib/dns/tests/sigs_test.c
new file mode 100644
index 0000000..8e438cb
--- /dev/null
+++ b/lib/dns/tests/sigs_test.c
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/list.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/stdtime.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/diff.h>
+#include <dns/dnssec.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/result.h>
+#include <dns/types.h>
+#include <dns/zone.h>
+
+#include <dst/dst.h>
+
+#include "../zone_p.h"
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+/*%
+ * Structure characterizing a single diff tuple in the dns_diff_t structure
+ * prepared by dns__zone_updatesigs().
+ */
+typedef struct {
+ dns_diffop_t op;
+ const char *owner;
+ dns_ttl_t ttl;
+ const char *type;
+} zonediff_t;
+
+#define ZONEDIFF_SENTINEL \
+ { \
+ 0, NULL, 0, NULL \
+ }
+
+/*%
+ * Structure defining a dns__zone_updatesigs() test.
+ */
+typedef struct {
+ const char *description; /* test description */
+ const zonechange_t *changes; /* array of "raw" zone changes */
+ const zonediff_t *zonediff; /* array of "processed" zone changes
+ * */
+} updatesigs_test_params_t;
+
+/*%
+ * Check whether the 'found' tuple matches the 'expected' tuple. 'found' is
+ * the 'index'th tuple output by dns__zone_updatesigs() in test 'test'.
+ */
+static void
+compare_tuples(const zonediff_t *expected, dns_difftuple_t *found,
+ size_t index) {
+ char found_covers[DNS_RDATATYPE_FORMATSIZE] = {};
+ char found_type[DNS_RDATATYPE_FORMATSIZE] = {};
+ char found_name[DNS_NAME_FORMATSIZE];
+ isc_consttextregion_t typeregion;
+ dns_fixedname_t expected_fname;
+ dns_rdatatype_t expected_type;
+ dns_name_t *expected_name;
+ dns_rdata_rrsig_t rrsig;
+ isc_buffer_t typebuf;
+ isc_result_t result;
+
+ REQUIRE(expected != NULL);
+ REQUIRE(found != NULL);
+ REQUIRE(index > 0);
+
+ /*
+ * Check operation.
+ */
+ assert_int_equal(expected->op, found->op);
+
+ /*
+ * Check owner name.
+ */
+ expected_name = dns_fixedname_initname(&expected_fname);
+ result = dns_name_fromstring(expected_name, expected->owner, 0,
+ dt_mctx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_name_format(&found->name, found_name, sizeof(found_name));
+ assert_true(dns_name_equal(expected_name, &found->name));
+
+ /*
+ * Check TTL.
+ */
+ assert_int_equal(expected->ttl, found->ttl);
+
+ /*
+ * Parse expected RR type.
+ */
+ typeregion.base = expected->type;
+ typeregion.length = strlen(expected->type);
+ result = dns_rdatatype_fromtext(&expected_type,
+ (isc_textregion_t *)&typeregion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Format found RR type for reporting purposes.
+ */
+ isc_buffer_init(&typebuf, found_type, sizeof(found_type));
+ result = dns_rdatatype_totext(found->rdata.type, &typebuf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Check RR type.
+ */
+ switch (expected->op) {
+ case DNS_DIFFOP_ADDRESIGN:
+ case DNS_DIFFOP_DELRESIGN:
+ /*
+ * Found tuple must be of type RRSIG.
+ */
+ assert_int_equal(found->rdata.type, dns_rdatatype_rrsig);
+ if (found->rdata.type != dns_rdatatype_rrsig) {
+ break;
+ }
+ /*
+ * The signature must cover an RRset of type 'expected->type'.
+ */
+ result = dns_rdata_tostruct(&found->rdata, &rrsig, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_buffer_init(&typebuf, found_covers, sizeof(found_covers));
+ result = dns_rdatatype_totext(rrsig.covered, &typebuf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(expected_type, rrsig.covered);
+ break;
+ default:
+ /*
+ * Found tuple must be of type 'expected->type'.
+ */
+ assert_int_equal(expected_type, found->rdata.type);
+ break;
+ }
+}
+
+/*%
+ * Perform a single dns__zone_updatesigs() test defined in 'test'. All other
+ * arguments are expected to remain constant between subsequent invocations of
+ * this function.
+ */
+static void
+updatesigs_test(const updatesigs_test_params_t *test, dns_zone_t *zone,
+ dns_db_t *db, dst_key_t *zone_keys[], unsigned int nkeys,
+ isc_stdtime_t now) {
+ size_t tuples_expected, tuples_found, index;
+ dns_dbversion_t *version = NULL;
+ dns_diff_t raw_diff, zone_diff;
+ const zonediff_t *expected;
+ dns_difftuple_t *found;
+ isc_result_t result;
+
+ dns__zonediff_t zonediff = {
+ .diff = &zone_diff,
+ .offline = false,
+ };
+
+ REQUIRE(test != NULL);
+ REQUIRE(test->description != NULL);
+ REQUIRE(test->changes != NULL);
+ REQUIRE(zone != NULL);
+ REQUIRE(db != NULL);
+ REQUIRE(zone_keys != NULL);
+
+ /*
+ * Create a new version of the zone's database.
+ */
+ result = dns_db_newversion(db, &version);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Create a diff representing the supplied changes.
+ */
+ result = dns_test_difffromchanges(&raw_diff, test->changes, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Apply the "raw" diff to the new version of the zone's database as
+ * this is what dns__zone_updatesigs() expects to happen before it is
+ * called.
+ */
+ dns_diff_apply(&raw_diff, db, version);
+
+ /*
+ * Initialize the structure dns__zone_updatesigs() will modify.
+ */
+ dns_diff_init(dt_mctx, &zone_diff);
+
+ /*
+ * Check whether dns__zone_updatesigs() behaves as expected.
+ */
+ result = dns__zone_updatesigs(&raw_diff, db, version, zone_keys, nkeys,
+ zone, now - 3600, now + 3600, 0, now,
+ true, false, &zonediff);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(ISC_LIST_EMPTY(raw_diff.tuples));
+ assert_false(ISC_LIST_EMPTY(zone_diff.tuples));
+
+ /*
+ * Ensure that the number of tuples in the zone diff is as expected.
+ */
+
+ tuples_expected = 0;
+ for (expected = test->zonediff; expected->owner != NULL; expected++) {
+ tuples_expected++;
+ }
+
+ tuples_found = 0;
+ for (found = ISC_LIST_HEAD(zone_diff.tuples); found != NULL;
+ found = ISC_LIST_NEXT(found, link))
+ {
+ tuples_found++;
+ }
+
+ assert_int_equal(tuples_expected, tuples_found);
+
+ /*
+ * Ensure that every tuple in the zone diff matches expectations.
+ */
+ expected = test->zonediff;
+ index = 1;
+ for (found = ISC_LIST_HEAD(zone_diff.tuples); found != NULL;
+ found = ISC_LIST_NEXT(found, link))
+ {
+ compare_tuples(expected, found, index);
+ expected++;
+ index++;
+ }
+
+ /*
+ * Apply changes to zone database contents and clean up.
+ */
+ dns_db_closeversion(db, &version, true);
+ dns_diff_clear(&zone_diff);
+ dns_diff_clear(&raw_diff);
+}
+
+/* dns__zone_updatesigs() tests */
+static void
+updatesigs_next_test(void **state) {
+ dst_key_t *zone_keys[DNS_MAXZONEKEYS];
+ dns_zone_t *zone = NULL;
+ dns_db_t *db = NULL;
+ isc_result_t result;
+ unsigned int nkeys;
+ isc_stdtime_t now;
+ size_t i;
+
+ UNUSED(state);
+
+ /*
+ * Prepare a zone along with its signing keys.
+ */
+
+ result = dns_test_makezone("example", &zone, NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_test_loaddb(&db, dns_dbtype_zone, "example",
+ "testdata/master/master18.data");
+ assert_int_equal(result, DNS_R_SEENINCLUDE);
+
+ result = dns_zone_setkeydirectory(zone, "testkeys");
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_stdtime_get(&now);
+ result = dns__zone_findkeys(zone, db, NULL, now, dt_mctx,
+ DNS_MAXZONEKEYS, zone_keys, &nkeys);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(nkeys, 2);
+
+ /*
+ * Define the tests to be run. Note that changes to zone database
+ * contents introduced by each test are preserved between tests.
+ */
+
+ const zonechange_t changes_add[] = {
+ { DNS_DIFFOP_ADD, "foo.example", 300, "TXT", "foo" },
+ { DNS_DIFFOP_ADD, "bar.example", 600, "TXT", "bar" },
+ ZONECHANGE_SENTINEL,
+ };
+ const zonediff_t zonediff_add[] = {
+ { DNS_DIFFOP_ADDRESIGN, "foo.example", 300, "TXT" },
+ { DNS_DIFFOP_ADD, "foo.example", 300, "TXT" },
+ { DNS_DIFFOP_ADDRESIGN, "bar.example", 600, "TXT" },
+ { DNS_DIFFOP_ADD, "bar.example", 600, "TXT" },
+ ZONEDIFF_SENTINEL,
+ };
+ const updatesigs_test_params_t test_add = {
+ .description = "add new RRsets",
+ .changes = changes_add,
+ .zonediff = zonediff_add,
+ };
+
+ const zonechange_t changes_append[] = {
+ { DNS_DIFFOP_ADD, "foo.example", 300, "TXT", "foo1" },
+ { DNS_DIFFOP_ADD, "foo.example", 300, "TXT", "foo2" },
+ ZONECHANGE_SENTINEL,
+ };
+ const zonediff_t zonediff_append[] = {
+ { DNS_DIFFOP_DELRESIGN, "foo.example", 300, "TXT" },
+ { DNS_DIFFOP_ADDRESIGN, "foo.example", 300, "TXT" },
+ { DNS_DIFFOP_ADD, "foo.example", 300, "TXT" },
+ { DNS_DIFFOP_ADD, "foo.example", 300, "TXT" },
+ ZONEDIFF_SENTINEL,
+ };
+ const updatesigs_test_params_t test_append = {
+ .description = "append multiple RRs to an existing RRset",
+ .changes = changes_append,
+ .zonediff = zonediff_append,
+ };
+
+ const zonechange_t changes_replace[] = {
+ { DNS_DIFFOP_DEL, "bar.example", 600, "TXT", "bar" },
+ { DNS_DIFFOP_ADD, "bar.example", 600, "TXT", "rab" },
+ ZONECHANGE_SENTINEL,
+ };
+ const zonediff_t zonediff_replace[] = {
+ { DNS_DIFFOP_DELRESIGN, "bar.example", 600, "TXT" },
+ { DNS_DIFFOP_ADDRESIGN, "bar.example", 600, "TXT" },
+ { DNS_DIFFOP_DEL, "bar.example", 600, "TXT" },
+ { DNS_DIFFOP_ADD, "bar.example", 600, "TXT" },
+ ZONEDIFF_SENTINEL,
+ };
+ const updatesigs_test_params_t test_replace = {
+ .description = "replace an existing RRset",
+ .changes = changes_replace,
+ .zonediff = zonediff_replace,
+ };
+
+ const zonechange_t changes_delete[] = {
+ { DNS_DIFFOP_DEL, "bar.example", 600, "TXT", "rab" },
+ ZONECHANGE_SENTINEL,
+ };
+ const zonediff_t zonediff_delete[] = {
+ { DNS_DIFFOP_DELRESIGN, "bar.example", 600, "TXT" },
+ { DNS_DIFFOP_DEL, "bar.example", 600, "TXT" },
+ ZONEDIFF_SENTINEL,
+ };
+ const updatesigs_test_params_t test_delete = {
+ .description = "delete an existing RRset",
+ .changes = changes_delete,
+ .zonediff = zonediff_delete,
+ };
+
+ const zonechange_t changes_mixed[] = {
+ { DNS_DIFFOP_ADD, "baz.example", 900, "TXT", "baz1" },
+ { DNS_DIFFOP_ADD, "baz.example", 900, "A", "127.0.0.1" },
+ { DNS_DIFFOP_ADD, "baz.example", 900, "TXT", "baz2" },
+ { DNS_DIFFOP_ADD, "baz.example", 900, "AAAA", "::1" },
+ ZONECHANGE_SENTINEL,
+ };
+ const zonediff_t zonediff_mixed[] = {
+ { DNS_DIFFOP_ADDRESIGN, "baz.example", 900, "TXT" },
+ { DNS_DIFFOP_ADD, "baz.example", 900, "TXT" },
+ { DNS_DIFFOP_ADD, "baz.example", 900, "TXT" },
+ { DNS_DIFFOP_ADDRESIGN, "baz.example", 900, "A" },
+ { DNS_DIFFOP_ADD, "baz.example", 900, "A" },
+ { DNS_DIFFOP_ADDRESIGN, "baz.example", 900, "AAAA" },
+ { DNS_DIFFOP_ADD, "baz.example", 900, "AAAA" },
+ ZONEDIFF_SENTINEL,
+ };
+ const updatesigs_test_params_t test_mixed = {
+ .description = "add different RRsets with common owner name",
+ .changes = changes_mixed,
+ .zonediff = zonediff_mixed,
+ };
+
+ const updatesigs_test_params_t *tests[] = {
+ &test_add, &test_append, &test_replace,
+ &test_delete, &test_mixed,
+ };
+
+ /*
+ * Run tests.
+ */
+ for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ updatesigs_test(tests[i], zone, db, zone_keys, nkeys, now);
+ }
+
+ /*
+ * Clean up.
+ */
+ for (i = 0; i < nkeys; i++) {
+ dst_key_free(&zone_keys[i]);
+ }
+ dns_db_detach(&db);
+ dns_zone_detach(&zone);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(updatesigs_next_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/testdata/db/data.db b/lib/dns/tests/testdata/db/data.db
new file mode 100644
index 0000000..67a4fba
--- /dev/null
+++ b/lib/dns/tests/testdata/db/data.db
@@ -0,0 +1,22 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+a in ns ns.vix.com.
+a in ns ns2.vix.com.
+a in ns ns3.vix.com.
+b in a 1.2.3.4
diff --git a/lib/dns/tests/testdata/dbiterator/zone1.data b/lib/dns/tests/testdata/dbiterator/zone1.data
new file mode 100644
index 0000000..c380d39
--- /dev/null
+++ b/lib/dns/tests/testdata/dbiterator/zone1.data
@@ -0,0 +1,30 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 600
+@ in soa localhost. postmaster.localhost. (
+ 2011080901 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 600 ) ;minimum
+ in ns ns
+ in ns ns2
+ns in a 10.0.0.1
+ns2 in a 10.0.0.2
+
+a in txt "test"
+b in txt "test"
+c in txt "test"
+d.e.f in txt "test"
+e in txt "test"
+f.g.h in txt "test"
+f.g.i in txt "test"
+f.g.j in txt "test"
+k in txt "test"
diff --git a/lib/dns/tests/testdata/dbiterator/zone2.data b/lib/dns/tests/testdata/dbiterator/zone2.data
new file mode 100644
index 0000000..7265c27
--- /dev/null
+++ b/lib/dns/tests/testdata/dbiterator/zone2.data
@@ -0,0 +1,319 @@
+; File written on Mon Aug 15 16:51:56 2011
+; dnssec_signzone version 9.7.3rc1
+test. 600 IN SOA localhost. postmaster.localhost. (
+ 2011080901 ; serial
+ 3600 ; refresh (1 hour)
+ 1800 ; retry (30 minutes)
+ 604800 ; expire (1 week)
+ 600 ; minimum (10 minutes)
+ )
+ 600 RRSIG SOA 7 1 600 20110914225156 (
+ 20110815225156 39833 test.
+ IoQPcpx+Y2btVBBdM2H/9ppRMjphB1thwrdh
+ midhKH+MXDAauUIENucugi3zLsc1o2ke8LnQ
+ v3lCLd/bb5MD1otuS8vOw1GWEFhXOUBZU6wS
+ QwEIcG4BiSlz7/GvOlRa2znkOmZ3c8bD/J3Y
+ XUWDI3BEDPgrZqfxEvoMyPEWjO8= )
+ 600 NS ns.test.
+ 600 NS ns2.test.
+ 600 RRSIG NS 7 1 600 20110914225156 (
+ 20110815225156 39833 test.
+ OgEimhmFIAqlH0hyQy3pTsveBHKyqs9WfO1S
+ uDPRj3DFgFEAjoY473T8GxG2C+jTVL/UMVcb
+ BTZ8wIAiUHhqKLcmr0q/1X+kNUs7tNi+6oMn
+ /jxaOuRL6c8Kf2gl2t4g6JTwQqLQhUHTfQP+
+ bEfKUr75VsVfxCQZIHlZ3/AlxZM= )
+ 600 DNSKEY 256 3 7 (
+ AwEAAc0FzrE7jUiaKIGZpIaFE8E989topAJN
+ dWIQUQ7BSKabmpBP2M+SXHwIiQ/yC25iqudO
+ IxjRcK7nHB1VoP84xU2oMj6eeSqQHf/bYaji
+ Y8IfR7lgrzoDWzq+0rtnKMJc/JM8SMkcoBAS
+ llvxarDJTZheZjlrCvhpRJC+FAkBsx81
+ ) ; key id = 39833
+ 600 DNSKEY 257 3 7 (
+ AwEAAc55LPDhBLqfDUpjYYbBt+N63CiZtKrD
+ UDGeFAerbw0MWIUi3PgMr7yGVrj8e5Qjp9UN
+ zBUax6NdhlYVtFA8CwMTXGBjxgyqUoWpce08
+ lswxfE70BpgUA6w5efs0/mYtX9/A76etCaSI
+ oNH2vfa47BCdCPDfC1uTgyeuNuDvhszHaSiD
+ 8OY7tLa/voecUlq38sdqi2raf2DvgOm7rdFa
+ reXOS/WIj7zd4XYrV1JGthxOMVlQ7zdv9rVd
+ UNUIF2d4hwCZJQr0ejhmvB3m/DuNmNOPYmnv
+ KTmLSE+IJ6baqYvKOVxwV+SaCnuJEjv+3Yrx
+ 8WQYD/iS9WBhC9FUit0dy+0=
+ ) ; key id = 57183
+ 600 RRSIG DNSKEY 7 1 600 20110914225156 (
+ 20110815225156 39833 test.
+ xPV+bSGUlbxA5MKBeeRbwUDh3Qc+dm77+OHQ
+ BHIr1L8/kRP5o5J7MqPA37kea6nhyltYf9xM
+ RsxyiaBGUUeLyWg/q6hTtkNgAHifOPAhiDz8
+ AJDSTdSsq9RVtjdobAD0jyzz9sWnB+TPSOmj
+ Nlyd7VtPVEuSYljgawwfBBO3Kho= )
+ 600 RRSIG DNSKEY 7 1 600 20110914225156 (
+ 20110815225156 57183 test.
+ S3jkC7AvyFc4ShfHt6AWgS4zpx9DzWHBK9gV
+ 2H23OJzy8H1At/CjKxWVHLJ/io+ygryVnt/I
+ 47Jyhh9i43TnXj8il475YsweGnXGZSorrcXA
+ 3IsD2lOuRYnp3yetxe2ZrMGNDqqImE6X4x1a
+ UJI0cbE2UMZfUt8Rm5USiGzwAEgFD1OXxvMD
+ UT3flyp+Ote9FConK8gewV4wlJuBFemWT7BZ
+ lUYnoqfuAeEn2+1pIBS0iA0LNFjNBaEgtcjo
+ QeweN32yKoApau47Dl/Klw7KFT8+PLZ0QPbt
+ XAkJU7q94Q5aucDuHCSCTCc+2vZxdEnXKvRY
+ rfLuG8r/V5Kn+1iYrQ== )
+ 0 NSEC3PARAM 1 0 10 -
+ 0 RRSIG NSEC3PARAM 7 1 0 20110914225156 (
+ 20110815225156 39833 test.
+ kghSSeP8AZiQ/zmxgxAyG0itoUMo5adG5pxD
+ p8T3ZmbxEUSyG5acxBFkmeY39wVU0Cda8tWc
+ HHrMbB5e2GN8z6xJ0A4rVyXfKSYJSz+iKWfk
+ 7sOFRjd8OLYE3di6PwIpk6ORUiRPMFLDQCH0
+ Q27hLsSoKyd50orKKI+ncjz7WzU= )
+a.test. 600 IN TXT "test"
+ 600 RRSIG TXT 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ UEVOlnL6CDRNCfk/Xge2oaGYCV1+ewwi5zJ0
+ CX4DdwiNEkItL4HgBe8xXfxgFC3qySdsSYPE
+ 1krdFyIkAclMCwHECd1UwZbGlMTEUGrE1KOB
+ 8vQY+OhIV9TAhqNwnjbu7s2ZdNUv3wiUPcfk
+ hCJ4rzP6yeV2inLwZulXnhxb6Pk= )
+b.test. 600 IN TXT "test"
+ 600 RRSIG TXT 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ HcyQlO9io6Rc5e4vVqlRmK5PacOaFQJmdERG
+ 5Aobpgm1FuCLC7F+IMZ0d1XvBWnsw9iDzV43
+ UKzTGqUSmDiSBzs4QzHlacGickIW8EOV4xyJ
+ +mcJ0FZh4YNbkt6CiX+8SF6IxfCMhRMjpSsK
+ rWqJMG3LXkI6W9stShzsYAFBOzQ= )
+e.test. 600 IN TXT "test"
+ 600 RRSIG TXT 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ jUn5FGRTL9OcFU7tvfkUnSwY8jA+8JynE0hi
+ ZJbYXDU5CiWGmR2B3yPHxUCewRqouyVCV8bc
+ xZsSuBxvcdYKryYDbjsmB83GlSEuxE9J7XZs
+ 8SxUP8PobLVqzXgEZS/XRU2G+R915ZDP9/iL
+ z9oYwc9TkeyXbp8J/ZsH88tG980= )
+c.test. 600 IN TXT "test"
+ 600 RRSIG TXT 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ cRxAj45oFDDCd8xQXxD1F0Qq8XeBWAj8EYS3
+ 7nFXAgAy8sTczFvYCNGj79o7BALJwM4vc/wx
+ 6rjsiO/sHgfTMEBDq6lH9Wql72uhwavI2SrL
+ /h/wBP5q4BXlQ4xp6cLhhdifOWhNTvLP+Fe5
+ U6yjvqneiKspze9SiFbcmRDiJds= )
+d.e.f.test. 600 IN TXT "test"
+ 600 RRSIG TXT 7 4 600 20110914225156 (
+ 20110815225156 39833 test.
+ ENjCzr/P9rJmj5OJLzYwWtHtBg2Uz+qJDucz
+ I97Pq9F819/c5sxNfT4hgICCw6ZfT4ffbzye
+ fFJ0JVrh2cYOzu68ozlgek/Uml1UW0pDQVdI
+ s4zEgp4XK9wXUxtWChSqp5YXMdeHegZFu32i
+ IMNTbJDudwYSwhr2FyG92ZRi8Y8= )
+f.g.h.test. 600 IN TXT "test"
+ 600 RRSIG TXT 7 4 600 20110914225156 (
+ 20110815225156 39833 test.
+ HT7iocFsfDjeX6j9RJdE3xfVGkIxhajFHgM/
+ T/mJj/al4HKV6Ajia8DhpdfDrgM2m7r+Pgcn
+ FSIstfebQsuFCnHX/gIalDND/grHKsetQnMP
+ Y7O4QLsRnTV53fdlqQ4eT+jBW6fzJdGySVN+
+ bg6kNJZS8DebjmlKtZz7tXjkP+4= )
+f.g.i.test. 600 IN TXT "test"
+ 600 RRSIG TXT 7 4 600 20110914225156 (
+ 20110815225156 39833 test.
+ kHJJeNSL1rz4QRYqOzhGMQl1yIdio7l8Lg8H
+ f0TsvFLa6BudVtwKUm+Kz2QiDn7/Lew8w0KX
+ vVHxX/Vwl3Ixk54YgMKLNogz2TEvnh/VGiS7
+ 8r0oSUrg0CFd+xDfxnLeRqX5NNfMuSJap5WH
+ Aw7IVeRjXDwJFYnytMEnTrhHHHg= )
+f.g.j.test. 600 IN TXT "test"
+ 600 RRSIG TXT 7 4 600 20110914225156 (
+ 20110815225156 39833 test.
+ lIEHEhDFhOWK8W/F2xWELU2p/X77S2KTivm9
+ sY4k3RPsLNHE7p+lF8p72Lcb79rtltnoVYtE
+ pTIiaUcmgGwfaI4cwfXbeuEgnuTiLg7Xrefx
+ 3GT86Q+8gfgbMXUmRA/eouWZhCOaYJN99gYz
+ urzDMiRLYmILHmLlnvo82SgXeuk= )
+k.test. 600 IN TXT "test"
+ 600 RRSIG TXT 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ wC3zgYWsuLga8Vu3QFu/Ci8SzRbA5bvjSmDj
+ NzcpjU5cvJBxtgzatCr02AaUC94bI0JzNrEB
+ nFyWCYw55lyy+bAHU1u05UcQmz0n5yxkvmHX
+ i8ZjMyQkAvNKodJHaFQqUKKIDuSHD2EziKqg
+ eNn55YRS11ihkODehUVNl7TnYeA= )
+ns.test. 600 IN A 10.0.0.1
+ 600 RRSIG A 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ VyK/WlQ6ikXdjF/arGzyAyYhOc8IYNBp4QLW
+ gtYjvbjIcV5+9JINWmUs61VjJ14nES1sI0xb
+ 9vQJuiPXTM1awUAnvOKLhaX6fbJaEiR1w6Cf
+ RT5QKBMxNBKVStqdabHcigY4DUuc1PQk1vCw
+ yMUJt3nHNVMZk+XAycNHzBeYjik= )
+ns2.test. 600 IN A 10.0.0.2
+ 600 RRSIG A 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ CX6UlZL+5NQJViKfbe/E3uIJk/wjUzoiHBhY
+ B6gS8nxZzlRPdTTXyMZoRa4etTZEbrRjnyXk
+ 1rP47faCUwbh//XqukN9f7FZ4Y39NpPS2XpX
+ 0Lx6M93Jz46lbzmseMFs2YmNMzzhN4uhRvl/
+ 8gPtYsn9KMXnAlFfa4XrE5LNVyY= )
+1F3JQ6EANHNHOCMUPQTVNM339VDTR51C.test. 600 IN NSEC3 1 0 10 - 7QKPELF33JOK9BVJ7CKE99AHG40B0SH7 A RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ w7aS12lxLNh+G1B/2kEq1BO6IzYvyC8n/MGV
+ 0jvFnapNXGZMPrPxGeO2wkw1JXepuXCv98be
+ M4SjQywaH+VP6ZMTIfjxRxtcCM+aLAFhiz0l
+ /MILEkjemmxjAfvV7emRVMwCGcoGI7qC3Xxq
+ q5g8EzJiYyTCOnI5LKRggn97wGg= )
+7QKPELF33JOK9BVJ7CKE99AHG40B0SH7.test. 600 IN NSEC3 1 0 10 - 94Q15K1V1VE5F87EI37T2B9A39EEC368 TXT RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ J4ObL3p4eN0jWh06M+rX2SSPANQoKfnosElB
+ KcKE7fLqEjKK7N6Yh6KUlbEP25tfeZ7W6GBJ
+ b7q6Nh0Ax8fYdc/6JVvmxcwWcx5Lw1TfITGB
+ ttFntJlbp1A8lwP3pn8Ksql1X2ogh78AsgTb
+ X5kmXVukC1oEzt98EAa/V/an8QA= )
+CS8M3UVG0UJDR6USBES4U9SNUGQI2RJE.test. 600 IN NSEC3 1 0 10 - ETEQB5V431INUIIE547FKSOF7O4DJ62J A RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ Vyd/2b0S15fACJ8TiPXKtScV9A/ZztVumZAm
+ o2S6jaVJKWik+8orDW+WiJ4/PEl26PK2m1uv
+ HD2beuUCHj9EnYkN/dzL3Bsc302qr9xqsh0q
+ VFS2moznoNG415ZV3vgYR7L9DAp43ZeFuw6I
+ 7sr21hLYLUeo31xBsJg7RlOL+4s= )
+ETEQB5V431INUIIE547FKSOF7O4DJ62J.test. 600 IN NSEC3 1 0 10 - F8G1MB0JUEU3FBI11CAVFIPGEA3POOIM
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ oOHs1eb3JYeOMOnzE2PS6NIXBNzSoTYPIxo/
+ P0d/ihsLKra3yNJNPTlu4kf+FZoNYAGtMK/D
+ 6dZWFvtdswDdi2C5WSgsanuHqXq5Lr3A1nCe
+ cQI5PO4RrLymB+MtYg15CNKcnc0WmJO8deSR
+ WzNOarC+Iz1Xj3FkKDS4FFr+02Q= )
+94Q15K1V1VE5F87EI37T2B9A39EEC368.test. 600 IN NSEC3 1 0 10 - CS8M3UVG0UJDR6USBES4U9SNUGQI2RJE
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ K0PvN7YtHQ63x/x2yXXa2S9GBGuTNJywDZ8M
+ wyMSwytCb9mn4hnKD5mJHaXGTw3YX7usbnEO
+ ce6hiJdN/VhMfbRMOvUpgyblOj4kXiYVZY1a
+ SyycfugK/Hu1j4az7lIhhnnx58GChA6mg8Vx
+ 3Uz6cNDDCSTBTl09NyeUUrKWsHQ= )
+FBH6B0LHT9PPQB1P98D228HA1H52L8PO.test. 600 IN NSEC3 1 0 10 - JGU2L7C3LKLHAKC5RHUOORTI2DCKK3KL
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ giXRE+4ZeIzDrhx1XkFSpIKGFd3UGzlrLZnO
+ Ur9nMUfwvU5A3fitEkdayo3ZDH7MQGpSotaH
+ ReiFXx3Z6Hm2NIN/RHYZQr9e0vbMYSjkANdu
+ HWBA1SrSq5SHyuy970mPd4jfTHiABCo6fJGB
+ ykGClZGou0WSaB+Ak19fMbeQ2Wo= )
+JGU2L7C3LKLHAKC5RHUOORTI2DCKK3KL.test. 600 IN NSEC3 1 0 10 - KFMJ88CKMKUQQJE59IKFBOLLLD4DF55H TXT RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ BHTDUgZdWNLgz3xHYMqvlWK/IJ0xrXESoREc
+ 6D3sO9bcLTMYPO9t80itOlipwp4AmaVOBXPt
+ cKSdgsUXDEtHqNSxtGbNr5xQ+Aqsep0GX71V
+ HkcIuiNdTUw83dkajCHMkmQCbEjp9mbdiTmS
+ haNW2EsscldfaS1aq5tYUhCT3l4= )
+L993U6VC0DUV5QJ8TRPD2IQLM8FJ7AT9.test. 600 IN NSEC3 1 0 10 - LSMRLLNBQGGK8J6V40KLM2LG5TE4FS0P
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ vE7K0Nrju4qLFDYkIyMY5bIMT0wu8MJdxL6u
+ 7WVA4HepccKQcUnvVoBAcrA9+MUeteyrad8Y
+ SJvQIt7sz5t7FViWSq5IMPVPujWtW5J30LhJ
+ mOLd1KmnFWoVthJ1oFNzBM80A60seKNnEw1M
+ lV6Y+v0gNYIQensUb9w6SVMTpxE= )
+F8G1MB0JUEU3FBI11CAVFIPGEA3POOIM.test. 600 IN NSEC3 1 0 10 - FA1T7MKUUV9SD4VDBJQ3GRFK1IDTCKL7
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ DkL9ONc0vpsKdG20ol8XPAaVfLb7kf1wnKbR
+ rQUB1trGSHm/Igo06of43zm9J+56htFJg1xD
+ I2de0sCUBQYyHVBBDiBAd1g+ZvcpUlLP0w8M
+ NxMviMiG/WQAdGXHwYfUimwMWD7gNGl1m05H
+ HwYmzGs+d1bClDNBrFhdfdL2+iA= )
+LSMRLLNBQGGK8J6V40KLM2LG5TE4FS0P.test. 600 IN NSEC3 1 0 10 - LUAN2Q3I2OCVSD41MP08HNA9JP22D38K
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ ZgiWuMqodQuhwuAF6CIiJTsdRahi+poOiZAM
+ WXNP0wXfdptcG2uhbdDwy+0crhe3tuybhwcb
+ CuiaQUh0XNPhgF+qmXpGobaqBhCEvCF4K9qY
+ OCIoMfsI1pIBVbMw0+YXVarFZ8+mfNU/+6n6
+ yy2+1nCg3k4XR2Dpv4CeDBfcAuM= )
+NAL1UIEBM38NKMN6RQOKE8T781IA7UKI.test. 600 IN NSEC3 1 0 10 - OUSGP0LO9FGAROHDULQVSTI3OLQIBB39 TXT RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ x8JiXPI+EXHz8ZO/VW0/+9wWsBNqeSMxXZIV
+ ibOnogSg7Wi7Yq1xftKC2+xEevNxSZnBibEy
+ Sgro5xKTf0n7pD9hHVBLoYmOOnbXY3QNQ2EQ
+ y3LdPT355WmwVddVOOxNpNRp2zQyqg7BhVA3
+ wxY7tyVQd4x1+95ATUQBnFditdE= )
+KFMJ88CKMKUQQJE59IKFBOLLLD4DF55H.test. 600 IN NSEC3 1 0 10 - L993U6VC0DUV5QJ8TRPD2IQLM8FJ7AT9 TXT RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ KQPaN2Ecebifbl4Bz5Yo0x2DgGmZiVhpSydm
+ oy/5NtMjt7G472JrKlqByap+VxW0bpzo3IER
+ 3P8Dsv7pfBD4/Cl5sFqwZL7wYy7RB4dQLVCi
+ Pepc/Mr3gR2XmL91fpGttMj5jGscnVQJCyFa
+ obzhsVaVImUQZFDPb0UQUHwIhOA= )
+LUAN2Q3I2OCVSD41MP08HNA9JP22D38K.test. 600 IN NSEC3 1 0 10 - NAL1UIEBM38NKMN6RQOKE8T781IA7UKI TXT RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ NJ+X3d0qh2+fbSnG0iQPxAeDIOzX5NTmY9fS
+ x7IO/DDcgUhPvl1YYdz5J999cec1zzOKp10J
+ YbsIAzg0w/Y4D4CBUw3IkcOrUFOODb6eJQGb
+ rVFRqmp3BUP4qOAWUZvx4oQ0KG4K/h/KJMbU
+ Vcdl7PF7G5O5hMyR9UWg4zal7Sk= )
+OUSGP0LO9FGAROHDULQVSTI3OLQIBB39.test. 600 IN NSEC3 1 0 10 - PQQ28M3U2MM08GGFV3JKR76G2H9IUJPC TXT RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ A/qxYrSE/smBGbST8j8eGPCrRnwvVa25kDha
+ IuA3nv0vzXhFvlruc9f0HRGwsq6A2pw3I5W+
+ xo2/JxsNyFOotdwaDDEBzqPkJmrzupxQS4Hm
+ rHSLnRnNw4QzvzNjAGWMYAoe3OeHC47wmAtI
+ qE91EHZTlPP28CUXOMo+7sCaOa8= )
+U0UVS2SUP89P2TM3PJO4TC1GPJ2O6519.test. 600 IN NSEC3 1 0 10 - VA2VG5BEMCKQP6MS5NHHGL18031BIA7M NS SOA RRSIG DNSKEY NSEC3PARAM
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ rahhkfiF+Rk6oqbWTdu9qcwhmj5hbDuIFdiJ
+ GmaG+cFSv5Mjp+txNVCvBK9Hq/VpW0ypen/3
+ JC0sVAugSX+HAKAgyaMKmgWCvoQZ6ZSJUh7o
+ LRPcT+oxVXQAqjovxpaV8k6sYo44tpljPdOD
+ UluWAP5SrmJKjzCxs27KGRx8MK4= )
+VA2VG5BEMCKQP6MS5NHHGL18031BIA7M.test. 600 IN NSEC3 1 0 10 - VAKOQ2TPD7S25NFBJT73J3C4OGU10RJ5 TXT RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ XcBeZ8lo9Qo8z56+1FdGDjh6ZHCfO+MQ/wnY
+ TEUo/aWLkPTyq39nLhe0qVBJxmDpM+KQFuG9
+ cjQT5fvrlrY+lv6dedB64EBMYy4kKbIv7N5+
+ r6+sfWlvtKsfXxysLSk2+jLEm5NuLFrOdNas
+ WLVsq741D3YcWt4kM1HCyk3DNF8= )
+FA1T7MKUUV9SD4VDBJQ3GRFK1IDTCKL7.test. 600 IN NSEC3 1 0 10 - FBH6B0LHT9PPQB1P98D228HA1H52L8PO TXT RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ jB/vLrvx4sQQD7J3ZacAAyhcFmIPh7LH3ljw
+ IAIaeLb10oX5q1/nQKYdfq976TMy5sWpBcmd
+ i91WLxd+T/gOSumyP8bC3g+SUoyZ9wxY6A6a
+ MMx1rn0QA9IKrxMqojs9M3urJ8QAeIS+KyAn
+ rbyyJuG+EVm0prqlPZtzUi28WCI= )
+PQQ28M3U2MM08GGFV3JKR76G2H9IUJPC.test. 600 IN NSEC3 1 0 10 - U0UVS2SUP89P2TM3PJO4TC1GPJ2O6519
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ asCOU9OkVWMvUU2IUpwMgdYf0faA04zPbaFf
+ qywYsv3NH01Lky6G3a0WUPAbBm7TAYx/ln8a
+ 559vlpp/gpXEl9CcLrjO6wy5i0ryp8gVHtKJ
+ rQlEc/uw4SY+S5t7FuZc2rNRdAbxVMYuwrvm
+ HBsKDPblre3e06ZZFEmnGFzCgmg= )
+VAKOQ2TPD7S25NFBJT73J3C4OGU10RJ5.test. 600 IN NSEC3 1 0 10 - VNCCJH8JPOLGLAGVMV3FKS09M7RRDU47 TXT RRSIG
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ Pt4tKB1p/jsyLYab9LSt5MF1KTRT18nRTOox
+ q0IACkXkKx7W5xv6nSYXIB+nQzNp1Y1hhoXn
+ 9IFi0liPnIAOp73w4vybhfIdTFiEmHPHT6O9
+ VIx5cSriqBI6Qda8GtfeIb96P8SojbUk5BDI
+ g18iYjviGhQYRgpU3tg1qd7pbcc= )
+VNCCJH8JPOLGLAGVMV3FKS09M7RRDU47.test. 600 IN NSEC3 1 0 10 - 1F3JQ6EANHNHOCMUPQTVNM339VDTR51C
+ 600 RRSIG NSEC3 7 2 600 20110914225156 (
+ 20110815225156 39833 test.
+ ZMZPHawhkuzSV7C7zkgghH/jpw9CQVR1JUXq
+ pAeY2iIIWwNhfuskJaLgtu/5SuKnJtrv6D4N
+ g+lfEkBReia5xO/SCcHv8/hXEPH8vZ4xe1C9
+ 6GVB6ip2hKw2g5HpyF7X18WgwZ0cqPWVg+Q+
+ xRLpXH+53391Wt5rG7qJswn5RLE= )
diff --git a/lib/dns/tests/testdata/diff/zone1.data b/lib/dns/tests/testdata/diff/zone1.data
new file mode 100644
index 0000000..8ddf669
--- /dev/null
+++ b/lib/dns/tests/testdata/diff/zone1.data
@@ -0,0 +1,13 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+@ 0 SOA . . 0 0 0 0 0
+@ 0 NS @
+@ 0 A 1.2.3.4
+remove 0 A 5.6.7.8
diff --git a/lib/dns/tests/testdata/diff/zone2.data b/lib/dns/tests/testdata/diff/zone2.data
new file mode 100644
index 0000000..363af42
--- /dev/null
+++ b/lib/dns/tests/testdata/diff/zone2.data
@@ -0,0 +1,14 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+@ 0 SOA . . 0 0 0 0 0
+@ 0 NS @
+@ 0 A 1.2.3.4
+remove 0 A 5.6.7.8
+added 0 A 5.6.7.8
diff --git a/lib/dns/tests/testdata/diff/zone3.data b/lib/dns/tests/testdata/diff/zone3.data
new file mode 100644
index 0000000..ae3a60e
--- /dev/null
+++ b/lib/dns/tests/testdata/diff/zone3.data
@@ -0,0 +1,12 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+@ 0 SOA . . 0 0 0 0 0
+@ 0 NS @
+@ 0 A 1.2.3.4
diff --git a/lib/dns/tests/testdata/dnstap/dnstap.saved b/lib/dns/tests/testdata/dnstap/dnstap.saved
new file mode 100644
index 0000000..c657f41
--- /dev/null
+++ b/lib/dns/tests/testdata/dnstap/dnstap.saved
Binary files differ
diff --git a/lib/dns/tests/testdata/dnstap/dnstap.text b/lib/dns/tests/testdata/dnstap/dnstap.text
new file mode 100644
index 0000000..71977e4
--- /dev/null
+++ b/lib/dns/tests/testdata/dnstap/dnstap.text
@@ -0,0 +1,96 @@
+03-Feb-2017 15:47:16.000 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 SR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 SR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 SR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 SR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 SR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 SR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 SR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 SR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 CR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 CR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 CR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 CR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 CR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 CR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 CR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 CR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 AR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 AR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 AR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 AR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 AR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 AR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 AR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 AR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 RR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 RR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 RR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 RR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 RR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 RR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 RR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 RR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 FR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 FR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 FR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 FR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 FR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 FR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 FR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 FR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 15:47:16.000 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 TR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 TR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 TR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 TR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 TR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 17:47:16.000 TR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 TR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
+03-Feb-2017 16:47:16.830 TR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A
diff --git a/lib/dns/tests/testdata/dnstap/query.auth b/lib/dns/tests/testdata/dnstap/query.auth
new file mode 100644
index 0000000..a14f850
--- /dev/null
+++ b/lib/dns/tests/testdata/dnstap/query.auth
@@ -0,0 +1,4 @@
+# authoritative query, www.isc.org/A
+8d 24 00 20 00 01 00 00 00 00 00 01 03 77 77 77
+03 69 73 63 03 6f 72 67 00 00 01 00 01 00 00 29
+10 00 00 00 00 00 00 00
diff --git a/lib/dns/tests/testdata/dnstap/query.recursive b/lib/dns/tests/testdata/dnstap/query.recursive
new file mode 100644
index 0000000..8ee705f
--- /dev/null
+++ b/lib/dns/tests/testdata/dnstap/query.recursive
@@ -0,0 +1,4 @@
+# recursive query for www.isc.org/A
+bf 08 01 20 00 01 00 00 00 00 00 01 03 77 77 77
+03 69 73 63 03 6f 72 67 00 00 01 00 01 00 00 29
+10 00 00 00 00 00 00 00
diff --git a/lib/dns/tests/testdata/dnstap/response.auth b/lib/dns/tests/testdata/dnstap/response.auth
new file mode 100644
index 0000000..4d0ea81
--- /dev/null
+++ b/lib/dns/tests/testdata/dnstap/response.auth
@@ -0,0 +1,19 @@
+# authoritative response, www.isc.org/A
+8d 24 84 00 00 01 00 01 00 04 00 07 03 77 77 77
+03 69 73 63 03 6f 72 67 00 00 01 00 01 c0 0c 00
+01 00 01 00 00 00 3c 00 04 95 14 40 45 c0 10 00
+02 00 01 00 00 1c 20 00 0d 03 61 6d 73 06 73 6e
+73 2d 70 62 c0 10 c0 10 00 02 00 01 00 00 1c 20
+00 07 04 73 66 62 61 c0 3d c0 10 00 02 00 01 00
+00 1c 20 00 19 02 6e 73 03 69 73 63 0b 61 66 69
+6c 69 61 73 2d 6e 73 74 04 69 6e 66 6f 00 c0 10
+00 02 00 01 00 00 1c 20 00 06 03 6f 72 64 c0 3d
+c0 39 00 01 00 01 00 00 1c 20 00 04 c7 06 01 1e
+c0 39 00 1c 00 01 00 00 1c 20 00 10 20 01 05 00
+00 60 00 00 00 00 00 00 00 00 00 30 c0 8a 00 01
+00 01 00 00 1c 20 00 04 c7 06 00 1e c0 8a 00 1c
+00 01 00 00 1c 20 00 10 20 01 05 00 00 71 00 00
+00 00 00 00 00 00 00 30 c0 52 00 01 00 01 00 00
+1c 20 00 04 95 14 40 03 c0 52 00 1c 00 01 00 00
+1c 20 00 10 20 01 04 f8 00 00 00 02 00 00 00 00
+00 00 00 19 00 00 29 10 00 00 00 00 00 00 00
diff --git a/lib/dns/tests/testdata/dnstap/response.recursive b/lib/dns/tests/testdata/dnstap/response.recursive
new file mode 100644
index 0000000..6e3a3cf
--- /dev/null
+++ b/lib/dns/tests/testdata/dnstap/response.recursive
@@ -0,0 +1,19 @@
+# recursive response, www.isc.org/A
+bf 08 81 a0 00 01 00 01 00 04 00 07 03 77 77 77
+03 69 73 63 03 6f 72 67 00 00 01 00 01 c0 0c 00
+01 00 01 00 00 00 15 00 04 95 14 40 45 c0 10 00
+02 00 01 00 00 1b a6 00 0e 04 73 66 62 61 06 73
+6e 73 2d 70 62 c0 10 c0 10 00 02 00 01 00 00 1b
+a6 00 06 03 6f 72 64 c0 3e c0 10 00 02 00 01 00
+00 1b a6 00 19 02 6e 73 03 69 73 63 0b 61 66 69
+6c 69 61 73 2d 6e 73 74 04 69 6e 66 6f 00 c0 10
+00 02 00 01 00 00 1b a6 00 06 03 61 6d 73 c0 3e
+c0 8a 00 01 00 01 00 00 b1 d5 00 04 c7 06 01 1e
+c0 8a 00 1c 00 01 00 00 b1 d5 00 10 20 01 05 00
+00 60 00 00 00 00 00 00 00 00 00 30 c0 53 00 01
+00 01 00 00 b1 d5 00 04 c7 06 00 1e c0 53 00 1c
+00 01 00 00 b1 d5 00 10 20 01 05 00 00 71 00 00
+00 00 00 00 00 00 00 30 c0 39 00 01 00 01 00 00
+b1 d5 00 04 95 14 40 03 c0 39 00 1c 00 01 00 00
+b1 d5 00 10 20 01 04 f8 00 00 00 02 00 00 00 00
+00 00 00 19 00 00 29 10 00 00 00 00 00 00 00
diff --git a/lib/dns/tests/testdata/dst/Ktest.+008+11349.key b/lib/dns/tests/testdata/dst/Ktest.+008+11349.key
new file mode 100644
index 0000000..a1bd768
--- /dev/null
+++ b/lib/dns/tests/testdata/dst/Ktest.+008+11349.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 11349, for test.
+; Created: 20181025090713 (Thu Oct 25 11:07:13 2018)
+; Publish: 20181025090713 (Thu Oct 25 11:07:13 2018)
+; Activate: 20181025090713 (Thu Oct 25 11:07:13 2018)
+test. IN DNSKEY 256 3 8 AwEAAdqPwPScyURzeCUzEadKNYgQW50LPDV/ir9nWIbiSn2yMkymxiby BQH+Hk1neE9qa9X4XaEnKf5YZx7o14rRikmOb2lomtOkI9ovh1K/SvLO Zd1E3e61F29g1eCq52mMY3xAdEcBNqEq+6mgEwGmwl83+mAh5anxXNHa 2rcfdG+L
diff --git a/lib/dns/tests/testdata/dst/Ktest.+008+11349.private b/lib/dns/tests/testdata/dst/Ktest.+008+11349.private
new file mode 100644
index 0000000..5dfef79
--- /dev/null
+++ b/lib/dns/tests/testdata/dst/Ktest.+008+11349.private
@@ -0,0 +1,13 @@
+Private-key-format: v1.3
+Algorithm: 8 (RSASHA256)
+Modulus: 2o/A9JzJRHN4JTMRp0o1iBBbnQs8NX+Kv2dYhuJKfbIyTKbGJvIFAf4eTWd4T2pr1fhdoScp/lhnHujXitGKSY5vaWia06Qj2i+HUr9K8s5l3UTd7rUXb2DV4KrnaYxjfEB0RwE2oSr7qaATAabCXzf6YCHlqfFc0dratx90b4s=
+PublicExponent: AQAB
+PrivateExponent: a4qmX/YxlmvWpz8spYr/MhcSbQCVPKGoLKv2RFBeZODknRDGmW0mh6d5U47hBPqRWvRdZak2oX7wJqZdQGIAT25bC09rLNMctfxXKtzwSaXFjXZGHGv+bDHcqIltvIYmRbb0pK/LinFaLZqfpVe0WOfKuT9BT03BlwSZV8GKgZE=
+Prime1: 8oZLQoVpIqsiQw7bX5pTm/O0gEUnEzNOVEoLGsfIl68Lz/1CBm9ypTp8QOB0B9IpnH8vOS+NJM1az1d0RhqKow==
+Prime2: 5rSbE6duWIb90uICkAUJn4OztHX0fkd9GKNYdsHVReFBH2poXGojVGkW6i/IaYl4NEXXr5Z89dWtR+RNH2Z9+Q==
+Exponent1: 2IcuCmYyR9Gi9Vv+YIzYuRQMw7j5+hqEhJzW7UIRxdtzIG9s03INWZet9/5tmc35eM/Uyam6ynDN8vCRz0VDIQ==
+Exponent2: vKcdVKIKWrvwXXzRaaGk79rLnZsDFiwxQG96TIpOczkyfpUNx9xHDaRtx4zRTnPKZrxiFkRx5LkZXHt1EWNHSQ==
+Coefficient: pb9dFRZA2IRXDCGCM1ikp+QCs72wNn3hgURZLRLmtcBbQcYhP/dcp80SpInviwJPNRcKrfxninqygEARzfHtqQ==
+Created: 20181025090713
+Publish: 20181025090713
+Activate: 20181025090713
diff --git a/lib/dns/tests/testdata/dst/Ktest.+013+49130.key b/lib/dns/tests/testdata/dst/Ktest.+013+49130.key
new file mode 100644
index 0000000..e3ff931
--- /dev/null
+++ b/lib/dns/tests/testdata/dst/Ktest.+013+49130.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 49130, for test.
+; Created: 20181025090718 (Thu Oct 25 11:07:18 2018)
+; Publish: 20181025090718 (Thu Oct 25 11:07:18 2018)
+; Activate: 20181025090718 (Thu Oct 25 11:07:18 2018)
+test. IN DNSKEY 256 3 13 uP04fwB/DuBBqdjPLseIoFT7vgtP8Lr/be1NhRBvibwQ+Hr+3GQhIKIK XbamgOUxXJ9JDjWFAT2KXw0V3sAN9w==
diff --git a/lib/dns/tests/testdata/dst/Ktest.+013+49130.private b/lib/dns/tests/testdata/dst/Ktest.+013+49130.private
new file mode 100644
index 0000000..754d9f9
--- /dev/null
+++ b/lib/dns/tests/testdata/dst/Ktest.+013+49130.private
@@ -0,0 +1,6 @@
+Private-key-format: v1.3
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: feGDRABRCbcsCqssKK5B5518y95smrv/cJnz2pa/UVA=
+Created: 20181025090718
+Publish: 20181025090718
+Activate: 20181025090718
diff --git a/lib/dns/tests/testdata/dst/test1.data b/lib/dns/tests/testdata/dst/test1.data
new file mode 100644
index 0000000..cf84e9f
--- /dev/null
+++ b/lib/dns/tests/testdata/dst/test1.data
@@ -0,0 +1,3077 @@
+Network Working Group P. Mockapetris
+Request for Comments: 1035 ISI
+ November 1987
+Obsoletes: RFCs 882, 883, 973
+
+ DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION
+
+
+1. STATUS OF THIS MEMO
+
+This RFC describes the details of the domain system and protocol, and
+assumes that the reader is familiar with the concepts discussed in a
+companion RFC, "Domain Names - Concepts and Facilities" [RFC-1034].
+
+The domain system is a mixture of functions and data types which are an
+official protocol and functions and data types which are still
+experimental. Since the domain system is intentionally extensible, new
+data types and experimental behavior should always be expected in parts
+of the system beyond the official protocol. The official protocol parts
+include standard queries, responses and the Internet class RR data
+formats (e.g., host addresses). Since the previous RFC set, several
+definitions have changed, so some previous definitions are obsolete.
+
+Experimental or obsolete features are clearly marked in these RFCs, and
+such information should be used with caution.
+
+The reader is especially cautioned not to depend on the values which
+appear in examples to be current or complete, since their purpose is
+primarily pedagogical. Distribution of this memo is unlimited.
+
+ Table of Contents
+
+ 1. STATUS OF THIS MEMO 1
+ 2. INTRODUCTION 3
+ 2.1. Overview 3
+ 2.2. Common configurations 4
+ 2.3. Conventions 7
+ 2.3.1. Preferred name syntax 7
+ 2.3.2. Data Transmission Order 8
+ 2.3.3. Character Case 9
+ 2.3.4. Size limits 10
+ 3. DOMAIN NAME SPACE AND RR DEFINITIONS 10
+ 3.1. Name space definitions 10
+ 3.2. RR definitions 11
+ 3.2.1. Format 11
+ 3.2.2. TYPE values 12
+ 3.2.3. QTYPE values 12
+ 3.2.4. CLASS values 13
+
+
+
+Mockapetris [Page 1]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ 3.2.5. QCLASS values 13
+ 3.3. Standard RRs 13
+ 3.3.1. CNAME RDATA format 14
+ 3.3.2. HINFO RDATA format 14
+ 3.3.3. MB RDATA format (EXPERIMENTAL) 14
+ 3.3.4. MD RDATA format (Obsolete) 15
+ 3.3.5. MF RDATA format (Obsolete) 15
+ 3.3.6. MG RDATA format (EXPERIMENTAL) 16
+ 3.3.7. MINFO RDATA format (EXPERIMENTAL) 16
+ 3.3.8. MR RDATA format (EXPERIMENTAL) 17
+ 3.3.9. MX RDATA format 17
+ 3.3.10. NULL RDATA format (EXPERIMENTAL) 17
+ 3.3.11. NS RDATA format 18
+ 3.3.12. PTR RDATA format 18
+ 3.3.13. SOA RDATA format 19
+ 3.3.14. TXT RDATA format 20
+ 3.4. ARPA Internet specific RRs 20
+ 3.4.1. A RDATA format 20
+ 3.4.2. WKS RDATA format 21
+ 3.5. IN-ADDR.ARPA domain 22
+ 3.6. Defining new types, classes, and special namespaces 24
+ 4. MESSAGES 25
+ 4.1. Format 25
+ 4.1.1. Header section format 26
+ 4.1.2. Question section format 28
+ 4.1.3. Resource record format 29
+ 4.1.4. Message compression 30
+ 4.2. Transport 32
+ 4.2.1. UDP usage 32
+ 4.2.2. TCP usage 32
+ 5. MASTER FILES 33
+ 5.1. Format 33
+ 5.2. Use of master files to define zones 35
+ 5.3. Master file example 36
+ 6. NAME SERVER IMPLEMENTATION 37
+ 6.1. Architecture 37
+ 6.1.1. Control 37
+ 6.1.2. Database 37
+ 6.1.3. Time 39
+ 6.2. Standard query processing 39
+ 6.3. Zone refresh and reload processing 39
+ 6.4. Inverse queries (Optional) 40
+ 6.4.1. The contents of inverse queries and responses 40
+ 6.4.2. Inverse query and response example 41
+ 6.4.3. Inverse query processing 42
+
+
+
+
+
+
+Mockapetris [Page 2]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ 6.5. Completion queries and responses 42
+ 7. RESOLVER IMPLEMENTATION 43
+ 7.1. Transforming a user request into a query 43
+ 7.2. Sending the queries 44
+ 7.3. Processing responses 46
+ 7.4. Using the cache 47
+ 8. MAIL SUPPORT 47
+ 8.1. Mail exchange binding 48
+ 8.2. Mailbox binding (Experimental) 48
+ 9. REFERENCES and BIBLIOGRAPHY 50
+ Index 54
+
+2. INTRODUCTION
+
+2.1. Overview
+
+The goal of domain names is to provide a mechanism for naming resources
+in such a way that the names are usable in different hosts, networks,
+protocol families, internets, and administrative organizations.
+
+From the user's point of view, domain names are useful as arguments to a
+local agent, called a resolver, which retrieves information associated
+with the domain name. Thus a user might ask for the host address or
+mail information associated with a particular domain name. To enable
+the user to request a particular type of information, an appropriate
+query type is passed to the resolver with the domain name. To the user,
+the domain tree is a single information space; the resolver is
+responsible for hiding the distribution of data among name servers from
+the user.
+
+From the resolver's point of view, the database that makes up the domain
+space is distributed among various name servers. Different parts of the
+domain space are stored in different name servers, although a particular
+data item will be stored redundantly in two or more name servers. The
+resolver starts with knowledge of at least one name server. When the
+resolver processes a user query it asks a known name server for the
+information; in return, the resolver either receives the desired
+information or a referral to another name server. Using these
+referrals, resolvers learn the identities and contents of other name
+servers. Resolvers are responsible for dealing with the distribution of
+the domain space and dealing with the effects of name server failure by
+consulting redundant databases in other servers.
+
+Name servers manage two kinds of data. The first kind of data held in
+sets called zones; each zone is the complete database for a particular
+"pruned" subtree of the domain space. This data is called
+authoritative. A name server periodically checks to make sure that its
+zones are up to date, and if not, obtains a new copy of updated zones
+
+
+
+Mockapetris [Page 3]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+from master files stored locally or in another name server. The second
+kind of data is cached data which was acquired by a local resolver.
+This data may be incomplete, but improves the performance of the
+retrieval process when non-local data is repeatedly accessed. Cached
+data is eventually discarded by a timeout mechanism.
+
+This functional structure isolates the problems of user interface,
+failure recovery, and distribution in the resolvers and isolates the
+database update and refresh problems in the name servers.
+
+2.2. Common configurations
+
+A host can participate in the domain name system in a number of ways,
+depending on whether the host runs programs that retrieve information
+from the domain system, name servers that answer queries from other
+hosts, or various combinations of both functions. The simplest, and
+perhaps most typical, configuration is shown below:
+
+ Local Host | Foreign
+ |
+ +---------+ +----------+ | +--------+
+ | | user queries | |queries | | |
+ | User |-------------->| |---------|->|Foreign |
+ | Program | | Resolver | | | Name |
+ | |<--------------| |<--------|--| Server |
+ | | user responses| |responses| | |
+ +---------+ +----------+ | +--------+
+ | A |
+ cache additions | | references |
+ V | |
+ +----------+ |
+ | cache | |
+ +----------+ |
+
+User programs interact with the domain name space through resolvers; the
+format of user queries and user responses is specific to the host and
+its operating system. User queries will typically be operating system
+calls, and the resolver and its cache will be part of the host operating
+system. Less capable hosts may choose to implement the resolver as a
+subroutine to be linked in with every program that needs its services.
+Resolvers answer user queries with information they acquire via queries
+to foreign name servers and the local cache.
+
+Note that the resolver may have to make several queries to several
+different foreign name servers to answer a particular user query, and
+hence the resolution of a user query may involve several network
+accesses and an arbitrary amount of time. The queries to foreign name
+servers and the corresponding responses have a standard format described
+
+
+
+Mockapetris [Page 4]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+in this memo, and may be datagrams.
+
+Depending on its capabilities, a name server could be a stand alone
+program on a dedicated machine or a process or processes on a large
+timeshared host. A simple configuration might be:
+
+ Local Host | Foreign
+ |
+ +---------+ |
+ / /| |
+ +---------+ | +----------+ | +--------+
+ | | | | |responses| | |
+ | | | | Name |---------|->|Foreign |
+ | Master |-------------->| Server | | |Resolver|
+ | files | | | |<--------|--| |
+ | |/ | | queries | +--------+
+ +---------+ +----------+ |
+
+Here a primary name server acquires information about one or more zones
+by reading master files from its local file system, and answers queries
+about those zones that arrive from foreign resolvers.
+
+The DNS requires that all zones be redundantly supported by more than
+one name server. Designated secondary servers can acquire zones and
+check for updates from the primary server using the zone transfer
+protocol of the DNS. This configuration is shown below:
+
+ Local Host | Foreign
+ |
+ +---------+ |
+ / /| |
+ +---------+ | +----------+ | +--------+
+ | | | | |responses| | |
+ | | | | Name |---------|->|Foreign |
+ | Master |-------------->| Server | | |Resolver|
+ | files | | | |<--------|--| |
+ | |/ | | queries | +--------+
+ +---------+ +----------+ |
+ A |maintenance | +--------+
+ | +------------|->| |
+ | queries | |Foreign |
+ | | | Name |
+ +------------------|--| Server |
+ maintenance responses | +--------+
+
+In this configuration, the name server periodically establishes a
+virtual circuit to a foreign name server to acquire a copy of a zone or
+to check that an existing copy has not changed. The messages sent for
+
+
+
+Mockapetris [Page 5]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+these maintenance activities follow the same form as queries and
+responses, but the message sequences are somewhat different.
+
+The information flow in a host that supports all aspects of the domain
+name system is shown below:
+
+ Local Host | Foreign
+ |
+ +---------+ +----------+ | +--------+
+ | | user queries | |queries | | |
+ | User |-------------->| |---------|->|Foreign |
+ | Program | | Resolver | | | Name |
+ | |<--------------| |<--------|--| Server |
+ | | user responses| |responses| | |
+ +---------+ +----------+ | +--------+
+ | A |
+ cache additions | | references |
+ V | |
+ +----------+ |
+ | Shared | |
+ | database | |
+ +----------+ |
+ A | |
+ +---------+ refreshes | | references |
+ / /| | V |
+ +---------+ | +----------+ | +--------+
+ | | | | |responses| | |
+ | | | | Name |---------|->|Foreign |
+ | Master |-------------->| Server | | |Resolver|
+ | files | | | |<--------|--| |
+ | |/ | | queries | +--------+
+ +---------+ +----------+ |
+ A |maintenance | +--------+
+ | +------------|->| |
+ | queries | |Foreign |
+ | | | Name |
+ +------------------|--| Server |
+ maintenance responses | +--------+
+
+The shared database holds domain space data for the local name server
+and resolver. The contents of the shared database will typically be a
+mixture of authoritative data maintained by the periodic refresh
+operations of the name server and cached data from previous resolver
+requests. The structure of the domain data and the necessity for
+synchronization between name servers and resolvers imply the general
+characteristics of this database, but the actual format is up to the
+local implementor.
+
+
+
+
+Mockapetris [Page 6]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+Information flow can also be tailored so that a group of hosts act
+together to optimize activities. Sometimes this is done to offload less
+capable hosts so that they do not have to implement a full resolver.
+This can be appropriate for PCs or hosts which want to minimize the
+amount of new network code which is required. This scheme can also
+allow a group of hosts can share a small number of caches rather than
+maintaining a large number of separate caches, on the premise that the
+centralized caches will have a higher hit ratio. In either case,
+resolvers are replaced with stub resolvers which act as front ends to
+resolvers located in a recursive server in one or more name servers
+known to perform that service:
+
+ Local Hosts | Foreign
+ |
+ +---------+ |
+ | | responses |
+ | Stub |<--------------------+ |
+ | Resolver| | |
+ | |----------------+ | |
+ +---------+ recursive | | |
+ queries | | |
+ V | |
+ +---------+ recursive +----------+ | +--------+
+ | | queries | |queries | | |
+ | Stub |-------------->| Recursive|---------|->|Foreign |
+ | Resolver| | Server | | | Name |
+ | |<--------------| |<--------|--| Server |
+ +---------+ responses | |responses| | |
+ +----------+ | +--------+
+ | Central | |
+ | cache | |
+ +----------+ |
+
+In any case, note that domain components are always replicated for
+reliability whenever possible.
+
+2.3. Conventions
+
+The domain system has several conventions dealing with low-level, but
+fundamental, issues. While the implementor is free to violate these
+conventions WITHIN HIS OWN SYSTEM, he must observe these conventions in
+ALL behavior observed from other hosts.
+
+2.3.1. Preferred name syntax
+
+The DNS specifications attempt to be as general as possible in the rules
+for constructing domain names. The idea is that the name of any
+existing object can be expressed as a domain name with minimal changes.
+
+
+
+Mockapetris [Page 7]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+However, when assigning a domain name for an object, the prudent user
+will select a name which satisfies both the rules of the domain system
+and any existing rules for the object, whether these rules are published
+or implied by existing programs.
+
+For example, when naming a mail domain, the user should satisfy both the
+rules of this memo and those in RFC-822. When creating a new host name,
+the old rules for HOSTS.TXT should be followed. This avoids problems
+when old software is converted to use domain names.
+
+The following syntax will result in fewer problems with many
+
+applications that use domain names (e.g., mail, TELNET).
+
+<domain> ::= <subdomain> | " "
+
+<subdomain> ::= <label> | <subdomain> "." <label>
+
+<label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
+
+<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
+
+<let-dig-hyp> ::= <let-dig> | "-"
+
+<let-dig> ::= <letter> | <digit>
+
+<letter> ::= any one of the 52 alphabetic characters A through Z in
+upper case and a through z in lower case
+
+<digit> ::= any one of the ten digits 0 through 9
+
+Note that while upper and lower case letters are allowed in domain
+names, no significance is attached to the case. That is, two names with
+the same spelling but different case are to be treated as if identical.
+
+The labels must follow the rules for ARPANET host names. They must
+start with a letter, end with a letter or digit, and have as interior
+characters only letters, digits, and hyphen. There are also some
+restrictions on the length. Labels must be 63 characters or less.
+
+For example, the following strings identify hosts in the Internet:
+
+A.ISI.EDU XX.LCS.MIT.EDU SRI-NIC.ARPA
+
+2.3.2. Data Transmission Order
+
+The order of transmission of the header and data described in this
+document is resolved to the octet level. Whenever a diagram shows a
+
+
+
+Mockapetris [Page 8]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+group of octets, the order of transmission of those octets is the normal
+order in which they are read in English. For example, in the following
+diagram, the octets are transmitted in the order they are numbered.
+
+ 0 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 1 | 2 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 3 | 4 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 5 | 6 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+Whenever an octet represents a numeric quantity, the left most bit in
+the diagram is the high order or most significant bit. That is, the bit
+labeled 0 is the most significant bit. For example, the following
+diagram represents the value 170 (decimal).
+
+ 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+-+
+ |1 0 1 0 1 0 1 0|
+ +-+-+-+-+-+-+-+-+
+
+Similarly, whenever a multi-octet field represents a numeric quantity
+the left most bit of the whole field is the most significant bit. When
+a multi-octet quantity is transmitted the most significant octet is
+transmitted first.
+
+2.3.3. Character Case
+
+For all parts of the DNS that are part of the official protocol, all
+comparisons between character strings (e.g., labels, domain names, etc.)
+are done in a case-insensitive manner. At present, this rule is in
+force throughout the domain system without exception. However, future
+additions beyond current usage may need to use the full binary octet
+capabilities in names, so attempts to store domain names in 7-bit ASCII
+or use of special bytes to terminate labels, etc., should be avoided.
+
+When data enters the domain system, its original case should be
+preserved whenever possible. In certain circumstances this cannot be
+done. For example, if two RRs are stored in a database, one at x.y and
+one at X.Y, they are actually stored at the same place in the database,
+and hence only one casing would be preserved. The basic rule is that
+case can be discarded only when data is used to define structure in a
+database, and two names are identical when compared in a case
+insensitive manner.
+
+
+
+
+Mockapetris [Page 9]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+Loss of case sensitive data must be minimized. Thus while data for x.y
+and X.Y may both be stored under a single location x.y or X.Y, data for
+a.x and B.X would never be stored under A.x, A.X, b.x, or b.X. In
+general, this preserves the case of the first label of a domain name,
+but forces standardization of interior node labels.
+
+Systems administrators who enter data into the domain database should
+take care to represent the data they supply to the domain system in a
+case-consistent manner if their system is case-sensitive. The data
+distribution system in the domain system will ensure that consistent
+representations are preserved.
+
+2.3.4. Size limits
+
+Various objects and parameters in the DNS have size limits. They are
+listed below. Some could be easily changed, others are more
+fundamental.
+
+labels 63 octets or less
+
+names 255 octets or less
+
+TTL positive values of a signed 32 bit number.
+
+UDP messages 512 octets or less
+
+3. DOMAIN NAME SPACE AND RR DEFINITIONS
+
+3.1. Name space definitions
+
+Domain names in messages are expressed in terms of a sequence of labels.
+Each label is represented as a one octet length field followed by that
+number of octets. Since every domain name ends with the null label of
+the root, a domain name is terminated by a length byte of zero. The
+high order two bits of every length octet must be zero, and the
+remaining six bits of the length field limit the label to 63 octets or
+less.
+
+To simplify implementations, the total length of a domain name (i.e.,
+label octets and label length octets) is restricted to 255 octets or
+less.
+
+Although labels can contain any 8 bit values in octets that make up a
+label, it is strongly recommended that labels follow the preferred
+syntax described elsewhere in this memo, which is compatible with
+existing host naming conventions. Name servers and resolvers must
+compare labels in a case-insensitive manner (i.e., A=a), assuming ASCII
+with zero parity. Non-alphabetic codes must match exactly.
+
+
+
+Mockapetris [Page 10]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.2. RR definitions
+
+3.2.1. Format
+
+All RRs have the same top level format shown below:
+
+ 1 1 1 1 1 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | |
+ / /
+ / NAME /
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | TYPE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | CLASS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | TTL |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | RDLENGTH |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
+ / RDATA /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+
+where:
+
+NAME an owner name, i.e., the name of the node to which this
+ resource record pertains.
+
+TYPE two octets containing one of the RR TYPE codes.
+
+CLASS two octets containing one of the RR CLASS codes.
+
+TTL a 32 bit signed integer that specifies the time interval
+ that the resource record may be cached before the source
+ of the information should again be consulted. Zero
+ values are interpreted to mean that the RR can only be
+ used for the transaction in progress, and should not be
+ cached. For example, SOA records are always distributed
+ with a zero TTL to prohibit caching. Zero values can
+ also be used for extremely volatile data.
+
+RDLENGTH an unsigned 16 bit integer that specifies the length in
+ octets of the RDATA field.
+
+
+
+Mockapetris [Page 11]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+RDATA a variable length string of octets that describes the
+ resource. The format of this information varies
+ according to the TYPE and CLASS of the resource record.
+
+3.2.2. TYPE values
+
+TYPE fields are used in resource records. Note that these types are a
+subset of QTYPEs.
+
+TYPE value and meaning
+
+A 1 a host address
+
+NS 2 an authoritative name server
+
+MD 3 a mail destination (Obsolete - use MX)
+
+MF 4 a mail forwarder (Obsolete - use MX)
+
+CNAME 5 the canonical name for an alias
+
+SOA 6 marks the start of a zone of authority
+
+MB 7 a mailbox domain name (EXPERIMENTAL)
+
+MG 8 a mail group member (EXPERIMENTAL)
+
+MR 9 a mail rename domain name (EXPERIMENTAL)
+
+NULL 10 a null RR (EXPERIMENTAL)
+
+WKS 11 a well known service description
+
+PTR 12 a domain name pointer
+
+HINFO 13 host information
+
+MINFO 14 mailbox or mail list information
+
+MX 15 mail exchange
+
+TXT 16 text strings
+
+3.2.3. QTYPE values
+
+QTYPE fields appear in the question part of a query. QTYPES are a
+superset of TYPEs, hence all TYPEs are valid QTYPEs. In addition, the
+following QTYPEs are defined:
+
+
+
+Mockapetris [Page 12]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+AXFR 252 A request for a transfer of an entire zone
+
+MAILB 253 A request for mailbox-related records (MB, MG or MR)
+
+MAILA 254 A request for mail agent RRs (Obsolete - see MX)
+
+* 255 A request for all records
+
+3.2.4. CLASS values
+
+CLASS fields appear in resource records. The following CLASS mnemonics
+and values are defined:
+
+IN 1 the Internet
+
+CS 2 the CSNET class (Obsolete - used only for examples in
+ some obsolete RFCs)
+
+CH 3 the CHAOS class
+
+HS 4 Hesiod [Dyer 87]
+
+3.2.5. QCLASS values
+
+QCLASS fields appear in the question section of a query. QCLASS values
+are a superset of CLASS values; every CLASS is a valid QCLASS. In
+addition to CLASS values, the following QCLASSes are defined:
+
+* 255 any class
+
+3.3. Standard RRs
+
+The following RR definitions are expected to occur, at least
+potentially, in all classes. In particular, NS, SOA, CNAME, and PTR
+will be used in all classes, and have the same format in all classes.
+Because their RDATA format is known, all domain names in the RDATA
+section of these RRs may be compressed.
+
+<domain-name> is a domain name represented as a series of labels, and
+terminated by a label with zero length. <character-string> is a single
+length octet followed by that number of characters. <character-string>
+is treated as binary information, and can be up to 256 characters in
+length (including the length octet).
+
+
+
+
+
+
+
+
+Mockapetris [Page 13]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.3.1. CNAME RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / CNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+CNAME A <domain-name> which specifies the canonical or primary
+ name for the owner. The owner name is an alias.
+
+CNAME RRs cause no additional section processing, but name servers may
+choose to restart the query at the canonical name in certain cases. See
+the description of name server logic in [RFC-1034] for details.
+
+3.3.2. HINFO RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / CPU /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / OS /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+CPU A <character-string> which specifies the CPU type.
+
+OS A <character-string> which specifies the operating
+ system type.
+
+Standard values for CPU and OS can be found in [RFC-1010].
+
+HINFO records are used to acquire general information about a host. The
+main use is for protocols such as FTP that can use special procedures
+when talking between machines or operating systems of the same type.
+
+3.3.3. MB RDATA format (EXPERIMENTAL)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / MADNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+MADNAME A <domain-name> which specifies a host which has the
+ specified mailbox.
+
+
+
+Mockapetris [Page 14]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+MB records cause additional section processing which looks up an A type
+RRs corresponding to MADNAME.
+
+3.3.4. MD RDATA format (Obsolete)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / MADNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+MADNAME A <domain-name> which specifies a host which has a mail
+ agent for the domain which should be able to deliver
+ mail for the domain.
+
+MD records cause additional section processing which looks up an A type
+record corresponding to MADNAME.
+
+MD is obsolete. See the definition of MX and [RFC-974] for details of
+the new scheme. The recommended policy for dealing with MD RRs found in
+a master file is to reject them, or to convert them to MX RRs with a
+preference of 0.
+
+3.3.5. MF RDATA format (Obsolete)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / MADNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+MADNAME A <domain-name> which specifies a host which has a mail
+ agent for the domain which will accept mail for
+ forwarding to the domain.
+
+MF records cause additional section processing which looks up an A type
+record corresponding to MADNAME.
+
+MF is obsolete. See the definition of MX and [RFC-974] for details ofw
+the new scheme. The recommended policy for dealing with MD RRs found in
+a master file is to reject them, or to convert them to MX RRs with a
+preference of 10.
+
+
+
+
+
+
+
+Mockapetris [Page 15]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.3.6. MG RDATA format (EXPERIMENTAL)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / MGMNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+MGMNAME A <domain-name> which specifies a mailbox which is a
+ member of the mail group specified by the domain name.
+
+MG records cause no additional section processing.
+
+3.3.7. MINFO RDATA format (EXPERIMENTAL)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / RMAILBX /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / EMAILBX /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+RMAILBX A <domain-name> which specifies a mailbox which is
+ responsible for the mailing list or mailbox. If this
+ domain name names the root, the owner of the MINFO RR is
+ responsible for itself. Note that many existing mailing
+ lists use a mailbox X-request for the RMAILBX field of
+ mailing list X, e.g., Msgroup-request for Msgroup. This
+ field provides a more general mechanism.
+
+
+EMAILBX A <domain-name> which specifies a mailbox which is to
+ receive error messages related to the mailing list or
+ mailbox specified by the owner of the MINFO RR (similar
+ to the ERRORS-TO: field which has been proposed). If
+ this domain name names the root, errors should be
+ returned to the sender of the message.
+
+MINFO records cause no additional section processing. Although these
+records can be associated with a simple mailbox, they are usually used
+with a mailing list.
+
+
+
+
+
+
+
+
+Mockapetris [Page 16]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.3.8. MR RDATA format (EXPERIMENTAL)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / NEWNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+NEWNAME A <domain-name> which specifies a mailbox which is the
+ proper rename of the specified mailbox.
+
+MR records cause no additional section processing. The main use for MR
+is as a forwarding entry for a user who has moved to a different
+mailbox.
+
+3.3.9. MX RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | PREFERENCE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / EXCHANGE /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+PREFERENCE A 16 bit integer which specifies the preference given to
+ this RR among others at the same owner. Lower values
+ are preferred.
+
+EXCHANGE A <domain-name> which specifies a host willing to act as
+ a mail exchange for the owner name.
+
+MX records cause type A additional section processing for the host
+specified by EXCHANGE. The use of MX RRs is explained in detail in
+[RFC-974].
+
+3.3.10. NULL RDATA format (EXPERIMENTAL)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / <anything> /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+Anything at all may be in the RDATA field so long as it is 65535 octets
+or less.
+
+
+
+
+Mockapetris [Page 17]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+NULL records cause no additional section processing. NULL RRs are not
+allowed in master files. NULLs are used as placeholders in some
+experimental extensions of the DNS.
+
+3.3.11. NS RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / NSDNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+NSDNAME A <domain-name> which specifies a host which should be
+ authoritative for the specified class and domain.
+
+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.
+
+The NS RR states that the named host should be expected to have a zone
+starting at owner name of the specified class. Note that the class may
+not indicate the protocol family which should be used to communicate
+with the host, although it is typically a strong hint. For example,
+hosts which are name servers for either Internet (IN) or Hesiod (HS)
+class information are normally queried using IN class protocols.
+
+3.3.12. PTR RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / PTRDNAME /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+PTRDNAME A <domain-name> which points to some location in the
+ domain name space.
+
+PTR records cause no additional section processing. These RRs are used
+in special domains to point to some other location in the domain space.
+These records are simple data, and don't imply any special processing
+similar to that performed by CNAME, which identifies aliases. See the
+description of the IN-ADDR.ARPA domain for an example.
+
+
+
+
+
+
+
+
+Mockapetris [Page 18]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.3.13. SOA RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / MNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / RNAME /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | SERIAL |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | REFRESH |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | RETRY |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | EXPIRE |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | MINIMUM |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+MNAME The <domain-name> of the name server that was the
+ original or primary source of data for this zone.
+
+RNAME A <domain-name> which specifies the mailbox of the
+ person responsible for this zone.
+
+SERIAL The unsigned 32 bit version number of the original copy
+ of the zone. Zone transfers preserve this value. This
+ value wraps and should be compared using sequence space
+ arithmetic.
+
+REFRESH A 32 bit time interval before the zone should be
+ refreshed.
+
+RETRY A 32 bit time interval that should elapse before a
+ failed refresh should be retried.
+
+EXPIRE A 32 bit time value that specifies the upper limit on
+ the time interval that can elapse before the zone is no
+ longer authoritative.
+
+
+
+
+
+Mockapetris [Page 19]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+MINIMUM The unsigned 32 bit minimum TTL field that should be
+ exported with any RR from this zone.
+
+SOA records cause no additional section processing.
+
+All times are in units of seconds.
+
+Most of these fields are pertinent only for name server maintenance
+operations. However, MINIMUM is used in all query operations that
+retrieve RRs from a zone. Whenever a RR is sent in a response to a
+query, the TTL field is set to the maximum of the TTL field from the RR
+and the MINIMUM field in the appropriate SOA. Thus MINIMUM is a lower
+bound on the TTL field for all RRs in a zone. Note that this use of
+MINIMUM should occur when the RRs are copied into the response and not
+when the zone is loaded from a master file or via a zone transfer. The
+reason for this provison is to allow future dynamic update facilities to
+change the SOA RR with known semantics.
+
+
+3.3.14. TXT RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / TXT-DATA /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+TXT-DATA One or more <character-string>s.
+
+TXT RRs are used to hold descriptive text. The semantics of the text
+depends on the domain where it is found.
+
+3.4. Internet specific RRs
+
+3.4.1. A RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ADDRESS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+ADDRESS A 32 bit Internet address.
+
+Hosts that have multiple Internet addresses will have multiple A
+records.
+
+
+
+
+
+Mockapetris [Page 20]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+A records cause no additional section processing. The RDATA section of
+an A line in a master file is an Internet address expressed as four
+decimal numbers separated by dots without any embedded spaces (e.g.,
+"10.2.0.52" or "192.0.5.6").
+
+3.4.2. WKS RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ADDRESS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | PROTOCOL | |
+ +--+--+--+--+--+--+--+--+ |
+ | |
+ / <BIT MAP> /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+ADDRESS An 32 bit Internet address
+
+PROTOCOL An 8 bit IP protocol number
+
+<BIT MAP> A variable length bit map. The bit map must be a
+ multiple of 8 bits long.
+
+The WKS record is used to describe the well known services supported by
+a particular protocol on a particular internet address. The PROTOCOL
+field specifies an IP protocol number, and the bit map has one bit per
+port of the specified protocol. The first bit corresponds to port 0,
+the second to port 1, etc. If the bit map does not include a bit for a
+protocol of interest, that bit is assumed zero. The appropriate values
+and mnemonics for ports and protocols are specified in [RFC-1010].
+
+For example, if PROTOCOL=TCP (6), the 26th bit corresponds to TCP port
+25 (SMTP). If this bit is set, a SMTP server should be listening on TCP
+port 25; if zero, SMTP service is not supported on the specified
+address.
+
+The purpose of WKS RRs is to provide availability information for
+servers for TCP and UDP. If a server supports both TCP and UDP, or has
+multiple Internet addresses, then multiple WKS RRs are used.
+
+WKS RRs cause no additional section processing.
+
+In master files, both ports and protocols are expressed using mnemonics
+or decimal numbers.
+
+
+
+
+Mockapetris [Page 21]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.5. IN-ADDR.ARPA domain
+
+The Internet uses a special domain to support gateway location and
+Internet address to host mapping. Other classes may employ a similar
+strategy in other domains. The intent of this domain is to provide a
+guaranteed method to perform host address to host name mapping, and to
+facilitate queries to locate all gateways on a particular network in the
+Internet.
+
+Note that both of these services are similar to functions that could be
+performed by inverse queries; the difference is that this part of the
+domain name space is structured according to address, and hence can
+guarantee that the appropriate data can be located without an exhaustive
+search of the domain space.
+
+The domain begins at IN-ADDR.ARPA and has a substructure which follows
+the Internet addressing structure.
+
+Domain names in the IN-ADDR.ARPA domain are defined to have up to four
+labels in addition to the IN-ADDR.ARPA suffix. Each label represents
+one octet of an Internet address, and is expressed as a character string
+for a decimal value in the range 0-255 (with leading zeros omitted
+except in the case of a zero octet which is represented by a single
+zero).
+
+Host addresses are represented by domain names that have all four labels
+specified. Thus data for Internet address 10.2.0.52 is located at
+domain name 52.0.2.10.IN-ADDR.ARPA. The reversal, though awkward to
+read, allows zones to be delegated which are exactly one network of
+address space. For example, 10.IN-ADDR.ARPA can be a zone containing
+data for the ARPANET, while 26.IN-ADDR.ARPA can be a separate zone for
+MILNET. Address nodes are used to hold pointers to primary host names
+in the normal domain space.
+
+Network numbers correspond to some non-terminal nodes at various depths
+in the IN-ADDR.ARPA domain, since Internet network numbers are either 1,
+2, or 3 octets. Network nodes are used to hold pointers to the primary
+host names of gateways attached to that network. Since a gateway is, by
+definition, on more than one network, it will typically have two or more
+network nodes which point at it. Gateways will also have host level
+pointers at their fully qualified addresses.
+
+Both the gateway pointers at network nodes and the normal host pointers
+at full address nodes use the PTR RR to point back to the primary domain
+names of the corresponding hosts.
+
+For example, the IN-ADDR.ARPA domain will contain information about the
+ISI gateway between net 10 and 26, an MIT gateway from net 10 to MIT's
+
+
+
+Mockapetris [Page 22]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+net 18, and hosts A.ISI.EDU and MULTICS.MIT.EDU. Assuming that ISI
+gateway has addresses 10.2.0.22 and 26.0.0.103, and a name MILNET-
+GW.ISI.EDU, and the MIT gateway has addresses 10.0.0.77 and 18.10.0.4
+and a name GW.LCS.MIT.EDU, the domain database would contain:
+
+ 10.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
+ 10.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
+ 18.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
+ 26.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
+ 22.0.2.10.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
+ 103.0.0.26.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
+ 77.0.0.10.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
+ 4.0.10.18.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
+ 103.0.3.26.IN-ADDR.ARPA. PTR A.ISI.EDU.
+ 6.0.0.10.IN-ADDR.ARPA. PTR MULTICS.MIT.EDU.
+
+Thus a program which wanted to locate gateways on net 10 would originate
+a query of the form QTYPE=PTR, QCLASS=IN, QNAME=10.IN-ADDR.ARPA. It
+would receive two RRs in response:
+
+ 10.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
+ 10.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
+
+The program could then originate QTYPE=A, QCLASS=IN queries for MILNET-
+GW.ISI.EDU. and GW.LCS.MIT.EDU. to discover the Internet addresses of
+these gateways.
+
+A resolver which wanted to find the host name corresponding to Internet
+host address 10.0.0.6 would pursue a query of the form QTYPE=PTR,
+QCLASS=IN, QNAME=6.0.0.10.IN-ADDR.ARPA, and would receive:
+
+ 6.0.0.10.IN-ADDR.ARPA. PTR MULTICS.MIT.EDU.
+
+Several cautions apply to the use of these services:
+ - Since the IN-ADDR.ARPA special domain and the normal domain
+ for a particular host or gateway will be in different zones,
+ the possibility exists that that the data may be inconsistent.
+
+ - Gateways will often have two names in separate domains, only
+ one of which can be primary.
+
+ - Systems that use the domain database to initialize their
+ routing tables must start with enough gateway information to
+ guarantee that they can access the appropriate name server.
+
+ - The gateway data only reflects the existence of a gateway in a
+ manner equivalent to the current HOSTS.TXT file. It doesn't
+ replace the dynamic availability information from GGP or EGP.
+
+
+
+Mockapetris [Page 23]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.6. Defining new types, classes, and special namespaces
+
+The previously defined types and classes are the ones in use as of the
+date of this memo. New definitions should be expected. This section
+makes some recommendations to designers considering additions to the
+existing facilities. The mailing list NAMEDROPPERS@SRI-NIC.ARPA is the
+forum where general discussion of design issues takes place.
+
+In general, a new type is appropriate when new information is to be
+added to the database about an existing object, or we need new data
+formats for some totally new object. Designers should attempt to define
+types and their RDATA formats that are generally applicable to all
+classes, and which avoid duplication of information. New classes are
+appropriate when the DNS is to be used for a new protocol, etc which
+requires new class-specific data formats, or when a copy of the existing
+name space is desired, but a separate management domain is necessary.
+
+New types and classes need mnemonics for master files; the format of the
+master files requires that the mnemonics for type and class be disjoint.
+
+TYPE and CLASS values must be a proper subset of QTYPEs and QCLASSes
+respectively.
+
+The present system uses multiple RRs to represent multiple values of a
+type rather than storing multiple values in the RDATA section of a
+single RR. This is less efficient for most applications, but does keep
+RRs shorter. The multiple RRs assumption is incorporated in some
+experimental work on dynamic update methods.
+
+The present system attempts to minimize the duplication of data in the
+database in order to insure consistency. Thus, in order to find the
+address of the host for a mail exchange, you map the mail domain name to
+a host name, then the host name to addresses, rather than a direct
+mapping to host address. This approach is preferred because it avoids
+the opportunity for inconsistency.
+
+In defining a new type of data, multiple RR types should not be used to
+create an ordering between entries or express different formats for
+equivalent bindings, instead this information should be carried in the
+body of the RR and a single type used. This policy avoids problems with
+caching multiple types and defining QTYPEs to match multiple types.
+
+For example, the original form of mail exchange binding used two RR
+types one to represent a "closer" exchange (MD) and one to represent a
+"less close" exchange (MF). The difficulty is that the presence of one
+RR type in a cache doesn't convey any information about the other
+because the query which acquired the cached information might have used
+a QTYPE of MF, MD, or MAILA (which matched both). The redesigned
+
+
+
+Mockapetris [Page 24]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+service used a single type (MX) with a "preference" value in the RDATA
+section which can order different RRs. However, if any MX RRs are found
+in the cache, then all should be there.
+
+4. MESSAGES
+
+4.1. Format
+
+All communications inside of the domain protocol are carried in a single
+format called a message. The top level format of message is divided
+into 5 sections (some of which are empty in certain cases) shown below:
+
+ +---------------------+
+ | Header |
+ +---------------------+
+ | Question | the question for the name server
+ +---------------------+
+ | Answer | RRs answering the question
+ +---------------------+
+ | Authority | RRs pointing toward an authority
+ +---------------------+
+ | Additional | RRs holding additional information
+ +---------------------+
+
+The header section is always present. The header includes fields that
+specify which of the remaining sections are present, and also specify
+whether the message is a query or a response, a standard query or some
+other opcode, etc.
+
+The names of the sections after the header are derived from their use in
+standard queries. The question section contains fields that describe a
+question to a name server. These fields are a query type (QTYPE), a
+query class (QCLASS), and a query domain name (QNAME). The last three
+sections have the same format: a possibly empty list of concatenated
+resource records (RRs). The answer section contains RRs that answer the
+question; the authority section contains RRs that point toward an
+authoritative name server; the additional records section contains RRs
+which relate to the query, but are not strictly answers for the
+question.
+
+
+
+
+
+
+
+
+
+
+
+
+Mockapetris [Page 25]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+4.1.1. Header section format
+
+The header contains the following fields:
+
+ 1 1 1 1 1 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ID |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | QDCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ANCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | NSCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ARCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+ID A 16 bit identifier assigned by the program that
+ generates any kind of query. This identifier is copied
+ the corresponding reply and can be used by the requester
+ to match up replies to outstanding queries.
+
+QR A one bit field that specifies whether this message is a
+ query (0), or a response (1).
+
+OPCODE A four bit field that specifies kind of query in this
+ message. This value is set by the originator of a query
+ and copied into the response. The values are:
+
+ 0 a standard query (QUERY)
+
+ 1 an inverse query (IQUERY)
+
+ 2 a server status request (STATUS)
+
+ 3-15 reserved for future use
+
+AA Authoritative Answer - this bit is valid in responses,
+ and specifies that the responding name server is an
+ authority for the domain name in question section.
+
+ Note that the contents of the answer section may have
+ multiple owner names because of aliases. The AA bit
+
+
+
+Mockapetris [Page 26]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ corresponds to the name which matches the query name, or
+ the first owner name in the answer section.
+
+TC TrunCation - specifies that this message was truncated
+ due to length greater than that permitted on the
+ transmission channel.
+
+RD Recursion Desired - this bit may be set in a query and
+ is copied into the response. If RD is set, it directs
+ the name server to pursue the query recursively.
+ Recursive query support is optional.
+
+RA Recursion Available - this be is set or cleared in a
+ response, and denotes whether recursive query support is
+ available in the name server.
+
+Z Reserved for future use. Must be zero in all queries
+ and responses.
+
+RCODE Response code - this 4 bit field is set as part of
+ responses. The values have the following
+ interpretation:
+
+ 0 No error condition
+
+ 1 Format error - The name server was
+ unable to interpret the query.
+
+ 2 Server failure - The name server was
+ unable to process this query due to a
+ problem with the name server.
+
+ 3 Name Error - Meaningful only for
+ responses from an authoritative name
+ server, this code signifies that the
+ domain name referenced in the query does
+ not exist.
+
+ 4 Not Implemented - The name server does
+ not support the requested kind of query.
+
+ 5 Refused - The name server refuses to
+ perform the specified operation for
+ policy reasons. For example, a name
+ server may not wish to provide the
+ information to the particular requester,
+ or a name server may not wish to perform
+ a particular operation (e.g., zone
+
+
+
+Mockapetris [Page 27]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ transfer) for particular data.
+
+ 6-15 Reserved for future use.
+
+QDCOUNT an unsigned 16 bit integer specifying the number of
+ entries in the question section.
+
+ANCOUNT an unsigned 16 bit integer specifying the number of
+ resource records in the answer section.
+
+NSCOUNT an unsigned 16 bit integer specifying the number of name
+ server resource records in the authority records
+ section.
+
+ARCOUNT an unsigned 16 bit integer specifying the number of
+ resource records in the additional records section.
+
+4.1.2. Question section format
+
+The question section is used to carry the "question" in most queries,
+i.e., the parameters that define what is being asked. The section
+contains QDCOUNT (usually 1) entries, each of the following format:
+
+ 1 1 1 1 1 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | |
+ / QNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | QTYPE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | QCLASS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+QNAME a domain name represented as a sequence of labels, where
+ each label consists of a length octet followed by that
+ number of octets. The domain name terminates with the
+ zero length octet for the null label of the root. Note
+ that this field may be an odd number of octets; no
+ padding is used.
+
+QTYPE a two octet code which specifies the type of the query.
+ The values for this field include all codes valid for a
+ TYPE field, together with some more general codes which
+ can match more than one type of RR.
+
+
+
+Mockapetris [Page 28]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+QCLASS a two octet code that specifies the class of the query.
+ For example, the QCLASS field is IN for the Internet.
+
+4.1.3. Resource record format
+
+The answer, authority, and additional sections all share the same
+format: a variable number of resource records, where the number of
+records is specified in the corresponding count field in the header.
+Each resource record has the following format:
+ 1 1 1 1 1 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | |
+ / /
+ / NAME /
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | TYPE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | CLASS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | TTL |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | RDLENGTH |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
+ / RDATA /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+NAME a domain name to which this resource record pertains.
+
+TYPE two octets containing one of the RR type codes. This
+ field specifies the meaning of the data in the RDATA
+ field.
+
+CLASS two octets which specify the class of the data in the
+ RDATA field.
+
+TTL a 32 bit unsigned integer that specifies the time
+ interval (in seconds) that the resource record may be
+ cached before it should be discarded. Zero values are
+ interpreted to mean that the RR can only be used for the
+ transaction in progress, and should not be cached.
+
+
+
+
+
+Mockapetris [Page 29]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+RDLENGTH an unsigned 16 bit integer that specifies the length in
+ octets of the RDATA field.
+
+RDATA a variable length string of octets that describes the
+ resource. The format of this information varies
+ according to the TYPE and CLASS of the resource record.
+ For example, the if the TYPE is A and the CLASS is IN,
+ the RDATA field is a 4 octet ARPA Internet address.
+
+4.1.4. Message compression
+
+In order to reduce the size of messages, the domain system utilizes a
+compression scheme which eliminates the repetition of domain names in a
+message. 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 occurrence
+of the same name.
+
+The pointer takes the form of a two octet sequence:
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | 1 1| OFFSET |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+The first two bits are ones. This allows a pointer to be distinguished
+from a label, since the label must begin with two zero bits because
+labels are restricted to 63 octets or less. (The 10 and 01 combinations
+are reserved for future use.) The OFFSET field specifies an offset from
+the start of the message (i.e., the first octet of the ID field in the
+domain header). A zero offset specifies the first byte of the ID field,
+etc.
+
+The compression scheme allows a domain name in a message to be
+represented as either:
+
+ - a sequence of labels ending in a zero octet
+
+ - a pointer
+
+ - a sequence of labels ending with a pointer
+
+Pointers can only be used for occurrences of a domain name where the
+format is not class specific. If this were not the case, a name server
+or resolver would be required to know the format of all RRs it handled.
+As yet, there are no such cases, but they may occur in future RDATA
+formats.
+
+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
+
+
+
+Mockapetris [Page 30]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+used, the length of the compressed name is used in the length
+calculation, rather than the length of the expanded name.
+
+Programs are free to avoid using pointers in messages they generate,
+although this will reduce datagram capacity, and may cause truncation.
+However all programs are required to understand arriving messages that
+contain pointers.
+
+For example, a datagram might need to use the domain names F.ISI.ARPA,
+FOO.F.ISI.ARPA, ARPA, and the root. Ignoring the other fields of the
+message, these domain names might be represented as:
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 20 | 1 | F |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 22 | 3 | I |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 24 | S | I |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 26 | 4 | A |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 28 | R | P |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 30 | A | 0 |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 40 | 3 | F |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 42 | O | O |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 44 | 1 1| 20 |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 64 | 1 1| 26 |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 92 | 0 | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+The domain name for F.ISI.ARPA is shown at offset 20. The domain name
+FOO.F.ISI.ARPA is shown at offset 40; this definition uses a pointer to
+concatenate a label for FOO to the previously defined F.ISI.ARPA. The
+domain name ARPA is defined at offset 64 using a pointer to the ARPA
+component of the name F.ISI.ARPA at 20; note that this pointer relies on
+ARPA being the last label in the string at 20. The root domain name is
+
+
+
+Mockapetris [Page 31]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+defined by a single octet of zeros at 92; the root domain name has no
+labels.
+
+4.2. Transport
+
+The DNS assumes that messages will be transmitted as datagrams or in a
+byte stream carried by a virtual circuit. While virtual circuits can be
+used for any DNS activity, datagrams are preferred for queries due to
+their lower overhead and better performance. Zone refresh activities
+must use virtual circuits because of the need for reliable transfer.
+
+The Internet supports name server access using TCP [RFC-793] on server
+port 53 (decimal) as well as datagram access using UDP [RFC-768] on UDP
+port 53 (decimal).
+
+4.2.1. UDP usage
+
+Messages sent using UDP user server port 53 (decimal).
+
+Messages carried by UDP are restricted to 512 bytes (not counting the IP
+or UDP headers). Longer messages are truncated and the TC bit is set in
+the header.
+
+UDP is not acceptable for zone transfers, but is the recommended method
+for standard queries in the Internet. Queries sent using UDP may be
+lost, and hence a retransmission strategy is required. Queries or their
+responses may be reordered by the network, or by processing in name
+servers, so resolvers should not depend on them being returned in order.
+
+The optimal UDP retransmission policy will vary with performance of the
+Internet and the needs of the client, but the following are recommended:
+
+ - The client should try other servers and server addresses
+ before repeating a query to a specific address of a server.
+
+ - The retransmission interval should be based on prior
+ statistics if possible. Too aggressive retransmission can
+ easily slow responses for the community at large. Depending
+ on how well connected the client is to its expected servers,
+ the minimum retransmission interval should be 2-5 seconds.
+
+More suggestions on server selection and retransmission policy can be
+found in the resolver section of this memo.
+
+4.2.2. TCP usage
+
+Messages sent over TCP connections use server port 53 (decimal). The
+message is prefixed with a two byte length field which gives the message
+
+
+
+Mockapetris [Page 32]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+length, excluding the two byte length field. This length field allows
+the low-level processing to assemble a complete message before beginning
+to parse it.
+
+Several connection management policies are recommended:
+
+ - The server should not block other activities waiting for TCP
+ data.
+
+ - The server should support multiple connections.
+
+ - The server should assume that the client will initiate
+ connection closing, and should delay closing its end of the
+ connection until all outstanding client requests have been
+ satisfied.
+
+ - If the server needs to close a dormant connection to reclaim
+ resources, it should wait until the connection has been idle
+ for a period on the order of two minutes. In particular, the
+ server should allow the SOA and AXFR request sequence (which
+ begins a refresh operation) to be made on a single connection.
+ Since the server would be unable to answer queries anyway, a
+ unilateral close or reset may be used instead of a graceful
+ close.
+
+5. MASTER FILES
+
+Master files are text files that contain RRs in text form. Since the
+contents of a zone can be expressed in the form of a list of RRs a
+master file is most often used to define a zone, though it can be used
+to list a cache's contents. Hence, this section first discusses the
+format of RRs in a master file, and then the special considerations when
+a master file is used to create a zone in some name server.
+
+5.1. Format
+
+The format of these files is a sequence of entries. Entries are
+predominantly line-oriented, though parentheses can be used to continue
+a list of items across a line boundary, and text literals can contain
+CRLF within the text. Any combination of tabs and spaces act as a
+delimiter between the separate items that make up an entry. The end of
+any line in the master file can end with a comment. The comment starts
+with a ";" (semicolon).
+
+The following entries are defined:
+
+ <blank>[<comment>]
+
+
+
+
+Mockapetris [Page 33]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ $ORIGIN <domain-name> [<comment>]
+
+ $INCLUDE <file-name> [<domain-name>] [<comment>]
+
+ <domain-name><rr> [<comment>]
+
+ <blank><rr> [<comment>]
+
+Blank lines, with or without comments, are allowed anywhere in the file.
+
+Two control entries are defined: $ORIGIN and $INCLUDE. $ORIGIN is
+followed by a domain name, and resets the current origin for relative
+domain names to the stated name. $INCLUDE inserts the named file into
+the current file, and may optionally specify a domain name that sets the
+relative domain name origin for the included file. $INCLUDE may also
+have a comment. Note that a $INCLUDE entry never changes the relative
+origin of the parent file, regardless of changes to the relative origin
+made within the included file.
+
+The last two forms represent RRs. If an entry for an RR begins with a
+blank, then the RR is assumed to be owned by the last stated owner. If
+an RR entry begins with a <domain-name>, then the owner name is reset.
+
+<rr> contents take one of the following forms:
+
+ [<TTL>] [<class>] <type> <RDATA>
+
+ [<class>] [<TTL>] <type> <RDATA>
+
+The RR begins with optional TTL and class fields, followed by a type and
+RDATA field appropriate to the type and class. Class and type use the
+standard mnemonics, TTL is a decimal integer. Omitted class and TTL
+values are default to the last explicitly stated values. Since type and
+class mnemonics are disjoint, the parse is unique. (Note that this
+order is different from the order used in examples and the order used in
+the actual RRs; the given order allows easier parsing and defaulting.)
+
+<domain-name>s make up a large share of the data in the master file.
+The labels in the domain name are expressed as character strings and
+separated by dots. Quoting conventions allow arbitrary characters to be
+stored in domain names. Domain names that end in a dot are called
+absolute, and are taken as complete. Domain names which do not end in a
+dot are called relative; the actual domain name is the concatenation of
+the relative part with an origin specified in a $ORIGIN, $INCLUDE, or as
+an argument to the master file loading routine. A relative name is an
+error when no origin is available.
+
+
+
+
+
+Mockapetris [Page 34]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+<character-string> is expressed in one or two ways: as a contiguous set
+of characters without interior spaces, or as a string beginning with a "
+and ending with a ". Inside a " delimited string any character can
+occur, except for a " itself, which must be quoted using \ (back slash).
+
+Because these files are text files several special encodings are
+necessary to allow arbitrary data to be loaded. In particular:
+
+ of the root.
+
+@ A free standing @ is used to denote the current origin.
+
+\X where X is any character other than a digit (0-9), is
+ used to quote that character so that its special meaning
+ does not apply. For example, "\." can be used to place
+ a dot character in a label.
+
+\DDD where each D is a digit is the octet corresponding to
+ the decimal number described by DDD. The resulting
+ octet is assumed to be text and is not checked for
+ special meaning.
+
+( ) Parentheses are used to group data that crosses a line
+ boundary. In effect, line terminations are not
+ recognized within parentheses.
+
+; Semicolon is used to start a comment; the remainder of
+ the line is ignored.
+
+5.2. Use of master files to define zones
+
+When a master file is used to load a zone, the operation should be
+suppressed if any errors are encountered in the master file. The
+rationale for this is that a single error can have widespread
+consequences. For example, suppose that the RRs defining a delegation
+have syntax errors; then the server will return authoritative name
+errors for all names in the subzone (except in the case where the
+subzone is also present on the server).
+
+Several other validity checks that should be performed in addition to
+insuring that the file is syntactically correct:
+
+ 1. All RRs in the file should have the same class.
+
+ 2. Exactly one SOA RR should be present at the top of the zone.
+
+ 3. If delegations are present and glue information is required,
+ it should be present.
+
+
+
+Mockapetris [Page 35]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ 4. Information present outside of the authoritative nodes in the
+ zone should be glue information, rather than the result of an
+ origin or similar error.
+
+5.3. Master file example
+
+The following is an example file which might be used to define the
+ISI.EDU zone.and is loaded with an origin of ISI.EDU:
+
+@ IN SOA VENERA Action\.domains (
+ 20 ; SERIAL
+ 7200 ; REFRESH
+ 600 ; RETRY
+ 3600000; EXPIRE
+ 60) ; MINIMUM
+
+ NS A.ISI.EDU.
+ NS VENERA
+ NS VAXA
+ MX 10 VENERA
+ MX 20 VAXA
+
+A A 26.3.0.103
+
+VENERA A 10.1.0.52
+ A 128.9.0.32
+
+VAXA A 10.2.0.27
+ A 128.9.0.33
+
+
+$INCLUDE <SUBSYS>ISI-MAILBOXES.TXT
+
+Where the file <SUBSYS>ISI-MAILBOXES.TXT is:
+
+ MOE MB A.ISI.EDU.
+ LARRY MB A.ISI.EDU.
+ CURLEY MB A.ISI.EDU.
+ STOOGES MG MOE
+ MG LARRY
+ MG CURLEY
+
+Note the use of the \ character in the SOA RR to specify the responsible
+person mailbox "Action.domains@E.ISI.EDU".
+
+
+
+
+
+
+
+Mockapetris [Page 36]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+6. NAME SERVER IMPLEMENTATION
+
+6.1. Architecture
+
+The optimal structure for the name server will depend on the host
+operating system and whether the name server is integrated with resolver
+operations, either by supporting recursive service, or by sharing its
+database with a resolver. This section discusses implementation
+considerations for a name server which shares a database with a
+resolver, but most of these concerns are present in any name server.
+
+6.1.1. Control
+
+A name server must employ multiple concurrent activities, whether they
+are implemented as separate tasks in the host's OS or multiplexing
+inside a single name server program. It is simply not acceptable for a
+name server to block the service of UDP requests while it waits for TCP
+data for refreshing or query activities. Similarly, a name server
+should not attempt to provide recursive service without processing such
+requests in parallel, though it may choose to serialize requests from a
+single client, or to regard identical requests from the same client as
+duplicates. A name server should not substantially delay requests while
+it reloads a zone from master files or while it incorporates a newly
+refreshed zone into its database.
+
+6.1.2. Database
+
+While name server implementations are free to use any internal data
+structures they choose, the suggested structure consists of three major
+parts:
+
+ - A "catalog" data structure which lists the zones available to
+ this server, and a "pointer" to the zone data structure. The
+ main purpose of this structure is to find the nearest ancestor
+ zone, if any, for arriving standard queries.
+
+ - Separate data structures for each of the zones held by the
+ name server.
+
+ - A data structure for cached data. (or perhaps separate caches
+ for different classes)
+
+All of these data structures can be implemented an identical tree
+structure format, with different data chained off the nodes in different
+parts: in the catalog the data is pointers to zones, while in the zone
+and cache data structures, the data will be RRs. In designing the tree
+framework the designer should recognize that query processing will need
+to traverse the tree using case-insensitive label comparisons; and that
+
+
+
+Mockapetris [Page 37]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+in real data, a few nodes have a very high branching factor (100-1000 or
+more), but the vast majority have a very low branching factor (0-1).
+
+One way to solve the case problem is to store the labels for each node
+in two pieces: a standardized-case representation of the label where all
+ASCII characters are in a single case, together with a bit mask that
+denotes which characters are actually of a different case. The
+branching factor diversity can be handled using a simple linked list for
+a node until the branching factor exceeds some threshold, and
+transitioning to a hash structure after the threshold is exceeded. In
+any case, hash structures used to store tree sections must insure that
+hash functions and procedures preserve the casing conventions of the
+DNS.
+
+The use of separate structures for the different parts of the database
+is motivated by several factors:
+
+ - The catalog structure can be an almost static structure that
+ need change only when the system administrator changes the
+ zones supported by the server. This structure can also be
+ used to store parameters used to control refreshing
+ activities.
+
+ - The individual data structures for zones allow a zone to be
+ replaced simply by changing a pointer in the catalog. Zone
+ refresh operations can build a new structure and, when
+ complete, splice it into the database via a simple pointer
+ replacement. It is very important that when a zone is
+ refreshed, queries should not use old and new data
+ simultaneously.
+
+ - With the proper search procedures, authoritative data in zones
+ will always "hide", and hence take precedence over, cached
+ data.
+
+ - Errors in zone definitions that cause overlapping zones, etc.,
+ may cause erroneous responses to queries, but problem
+ determination is simplified, and the contents of one "bad"
+ zone can't corrupt another.
+
+ - Since the cache is most frequently updated, it is most
+ vulnerable to corruption during system restarts. It can also
+ become full of expired RR data. In either case, it can easily
+ be discarded without disturbing zone data.
+
+A major aspect of database design is selecting a structure which allows
+the name server to deal with crashes of the name server's host. State
+information which a name server should save across system crashes
+
+
+
+Mockapetris [Page 38]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+includes the catalog structure (including the state of refreshing for
+each zone) and the zone data itself.
+
+6.1.3. Time
+
+Both the TTL data for RRs and the timing data for refreshing activities
+depends on 32 bit timers in units of seconds. Inside the database,
+refresh timers and TTLs for cached data conceptually "count down", while
+data in the zone stays with constant TTLs.
+
+A recommended implementation strategy is to store time in two ways: as
+a relative increment and as an absolute time. One way to do this is to
+use positive 32 bit numbers for one type and negative numbers for the
+other. The RRs in zones use relative times; the refresh timers and
+cache data use absolute times. Absolute numbers are taken with respect
+to some known origin and converted to relative values when placed in the
+response to a query. When an absolute TTL is negative after conversion
+to relative, then the data is expired and should be ignored.
+
+6.2. Standard query processing
+
+The major algorithm for standard query processing is presented in
+[RFC-1034].
+
+When processing queries with QCLASS=*, or some other QCLASS which
+matches multiple classes, the response should never be authoritative
+unless the server can guarantee that the response covers all classes.
+
+When composing a response, RRs which are to be inserted in the
+additional section, but duplicate RRs in the answer or authority
+sections, may be omitted from the additional section.
+
+When a response is so long that truncation is required, the truncation
+should start at the end of the response and work forward in the
+datagram. Thus if there is any data for the authority section, the
+answer section is guaranteed to be unique.
+
+The MINIMUM value in the SOA should be used to set a floor on the TTL of
+data distributed from a zone. This floor function should be done when
+the data is copied into a response. This will allow future dynamic
+update protocols to change the SOA MINIMUM field without ambiguous
+semantics.
+
+6.3. Zone refresh and reload processing
+
+In spite of a server's best efforts, it may be unable to load zone data
+from a master file due to syntax errors, etc., or be unable to refresh a
+zone within the its expiration parameter. In this case, the name server
+
+
+
+Mockapetris [Page 39]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+should answer queries as if it were not supposed to possess the zone.
+
+If a master is sending a zone out via AXFR, and a new version is created
+during the transfer, the master should continue to send the old version
+if possible. In any case, it should never send part of one version and
+part of another. If completion is not possible, the master should reset
+the connection on which the zone transfer is taking place.
+
+6.4. Inverse queries (Optional)
+
+Inverse queries are an optional part of the DNS. Name servers are not
+required to support any form of inverse queries. If a name server
+receives an inverse query that it does not support, it returns an error
+response with the "Not Implemented" error set in the header. While
+inverse query support is optional, all name servers must be at least
+able to return the error response.
+
+6.4.1. The contents of inverse queries and responses Inverse
+queries reverse the mappings performed by standard query operations;
+while a standard query maps a domain name to a resource, an inverse
+query maps a resource to a domain name. For example, a standard query
+might bind a domain name to a host address; the corresponding inverse
+query binds the host address to a domain name.
+
+Inverse queries take the form of a single RR in the answer section of
+the message, with an empty question section. The owner name of the
+query RR and its TTL are not significant. The response carries
+questions in the question section which identify all names possessing
+the query RR WHICH THE NAME SERVER KNOWS. Since no name server knows
+about all of the domain name space, the response can never be assumed to
+be complete. Thus inverse queries are primarily useful for database
+management and debugging activities. Inverse queries are NOT an
+acceptable method of mapping host addresses to host names; use the IN-
+ADDR.ARPA domain instead.
+
+Where possible, name servers should provide case-insensitive comparisons
+for inverse queries. Thus an inverse query asking for an MX RR of
+"Venera.isi.edu" should get the same response as a query for
+"VENERA.ISI.EDU"; an inverse query for HINFO RR "IBM-PC UNIX" should
+produce the same result as an inverse query for "IBM-pc unix". However,
+this cannot be guaranteed because name servers may possess RRs that
+contain character strings but the name server does not know that the
+data is character.
+
+When a name server processes an inverse query, it either returns:
+
+ 1. zero, one, or multiple domain names for the specified
+ resource as QNAMEs in the question section
+
+
+
+Mockapetris [Page 40]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ 2. an error code indicating that the name server doesn't support
+ inverse mapping of the specified resource type.
+
+When the response to an inverse query contains one or more QNAMEs, the
+owner name and TTL of the RR in the answer section which defines the
+inverse query is modified to exactly match an RR found at the first
+QNAME.
+
+RRs returned in the inverse queries cannot be cached using the same
+mechanism as is used for the replies to standard queries. One reason
+for this is that a name might have multiple RRs of the same type, and
+only one would appear. For example, an inverse query for a single
+address of a multiply homed host might create the impression that only
+one address existed.
+
+6.4.2. Inverse query and response example The overall structure
+of an inverse query for retrieving the domain name that corresponds to
+Internet address 10.1.0.52 is shown below:
+
+ +-----------------------------------------+
+ Header | OPCODE=IQUERY, ID=997 |
+ +-----------------------------------------+
+ Question | <empty> |
+ +-----------------------------------------+
+ Answer | <anyname> A IN 10.1.0.52 |
+ +-----------------------------------------+
+ Authority | <empty> |
+ +-----------------------------------------+
+ Additional | <empty> |
+ +-----------------------------------------+
+
+This query asks for a question whose answer is the Internet style
+address 10.1.0.52. Since the owner name is not known, any domain name
+can be used as a placeholder (and is ignored). A single octet of zero,
+signifying the root, is usually used because it minimizes the length of
+the message. The TTL of the RR is not significant. The response to
+this query might be:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Mockapetris [Page 41]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ +-----------------------------------------+
+ Header | OPCODE=RESPONSE, ID=997 |
+ +-----------------------------------------+
+ Question |QTYPE=A, QCLASS=IN, QNAME=VENERA.ISI.EDU |
+ +-----------------------------------------+
+ Answer | VENERA.ISI.EDU A IN 10.1.0.52 |
+ +-----------------------------------------+
+ Authority | <empty> |
+ +-----------------------------------------+
+ Additional | <empty> |
+ +-----------------------------------------+
+
+Note that the QTYPE in a response to an inverse query is the same as the
+TYPE field in the answer section of the inverse query. Responses to
+inverse queries may contain multiple questions when the inverse is not
+unique. If the question section in the response is not empty, then the
+RR in the answer section is modified to correspond to be an exact copy
+of an RR at the first QNAME.
+
+6.4.3. Inverse query processing
+
+Name servers that support inverse queries can support these operations
+through exhaustive searches of their databases, but this becomes
+impractical as the size of the database increases. An alternative
+approach is to invert the database according to the search key.
+
+For name servers that support multiple zones and a large amount of data,
+the recommended approach is separate inversions for each zone. When a
+particular zone is changed during a refresh, only its inversions need to
+be redone.
+
+Support for transfer of this type of inversion may be included in future
+versions of the domain system, but is not supported in this version.
+
+6.5. Completion queries and responses
+
+The optional completion services described in RFC-882 and RFC-883 have
+been deleted. Redesigned services may become available in the future.
+
+
+
+
+
+
+
+
+
+
+
+
+
+Mockapetris [Page 42]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+7. RESOLVER IMPLEMENTATION
+
+The top levels of the recommended resolver algorithm are discussed in
+[RFC-1034]. This section discusses implementation details assuming the
+database structure suggested in the name server implementation section
+of this memo.
+
+7.1. Transforming a user request into a query
+
+The first step a resolver takes is to transform the client's request,
+stated in a format suitable to the local OS, into a search specification
+for RRs at a specific name which match a specific QTYPE and QCLASS.
+Where possible, the QTYPE and QCLASS should correspond to a single type
+and a single class, because this makes the use of cached data much
+simpler. The reason for this is that the presence of data of one type
+in a cache doesn't confirm the existence or non-existence of data of
+other types, hence the only way to be sure is to consult an
+authoritative source. If QCLASS=* is used, then authoritative answers
+won't be available.
+
+Since a resolver must be able to multiplex multiple requests if it is to
+perform its function efficiently, each pending request is usually
+represented in some block of state information. This state block will
+typically contain:
+
+ - A timestamp indicating the time the request began.
+ The timestamp is used to decide whether RRs in the database
+ can be used or are out of date. This timestamp uses the
+ absolute time format previously discussed for RR storage in
+ zones and caches. Note that when an RRs TTL indicates a
+ relative time, the RR must be timely, since it is part of a
+ zone. When the RR has an absolute time, it is part of a
+ cache, and the TTL of the RR is compared against the timestamp
+ for the start of the request.
+
+ Note that using the timestamp is superior to using a current
+ time, since it allows RRs with TTLs of zero to be entered in
+ the cache in the usual manner, but still used by the current
+ request, even after intervals of many seconds due to system
+ load, query retransmission timeouts, etc.
+
+ - Some sort of parameters to limit the amount of work which will
+ be performed for this request.
+
+ The amount of work which a resolver will do in response to a
+ client request must be limited to guard against errors in the
+ database, such as circular CNAME references, and operational
+ problems, such as network partition which prevents the
+
+
+
+Mockapetris [Page 43]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ resolver from accessing the name servers it needs. While
+ local limits on the number of times a resolver will retransmit
+ a particular query to a particular name server address are
+ essential, the resolver should have a global per-request
+ counter to limit work on a single request. The counter should
+ be set to some initial value and decremented whenever the
+ resolver performs any action (retransmission timeout,
+ retransmission, etc.) If the counter passes zero, the request
+ is terminated with a temporary error.
+
+ Note that if the resolver structure allows one request to
+ start others in parallel, such as when the need to access a
+ name server for one request causes a parallel resolve for the
+ name server's addresses, the spawned request should be started
+ with a lower counter. This prevents circular references in
+ the database from starting a chain reaction of resolver
+ activity.
+
+ - The SLIST data structure discussed in [RFC-1034].
+
+ This structure keeps track of the state of a request if it
+ must wait for answers from foreign name servers.
+
+7.2. Sending the queries
+
+As described in [RFC-1034], the basic task of the resolver is to
+formulate a query which will answer the client's request and direct that
+query to name servers which can provide the information. The resolver
+will usually only have very strong hints about which servers to ask, in
+the form of NS RRs, and may have to revise the query, in response to
+CNAMEs, or revise the set of name servers the resolver is asking, in
+response to delegation responses which point the resolver to name
+servers closer to the desired information. In addition to the
+information requested by the client, the resolver may have to call upon
+its own services to determine the address of name servers it wishes to
+contact.
+
+In any case, the model used in this memo assumes that the resolver is
+multiplexing attention between multiple requests, some from the client,
+and some internally generated. Each request is represented by some
+state information, and the desired behavior is that the resolver
+transmit queries to name servers in a way that maximizes the probability
+that the request is answered, minimizes the time that the request takes,
+and avoids excessive transmissions. The key algorithm uses the state
+information of the request to select the next name server address to
+query, and also computes a timeout which will cause the next action
+should a response not arrive. The next action will usually be a
+transmission to some other server, but may be a temporary error to the
+
+
+
+Mockapetris [Page 44]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+client.
+
+The resolver always starts with a list of server names to query (SLIST).
+This list will be all NS RRs which correspond to the nearest ancestor
+zone that the resolver knows about. To avoid startup problems, the
+resolver should have a set of default servers which it will ask should
+it have no current NS RRs which are appropriate. The resolver then adds
+to SLIST all of the known addresses for the name servers, and may start
+parallel requests to acquire the addresses of the servers when the
+resolver has the name, but no addresses, for the name servers.
+
+To complete initialization of SLIST, the resolver attaches whatever
+history information it has to the each address in SLIST. This will
+usually consist of some sort of weighted averages for the response time
+of the address, and the batting average of the address (i.e., how often
+the address responded at all to the request). Note that this
+information should be kept on a per address basis, rather than on a per
+name server basis, because the response time and batting average of a
+particular server may vary considerably from address to address. Note
+also that this information is actually specific to a resolver address /
+server address pair, so a resolver with multiple addresses may wish to
+keep separate histories for each of its addresses. Part of this step
+must deal with addresses which have no such history; in this case an
+expected round trip time of 5-10 seconds should be the worst case, with
+lower estimates for the same local network, etc.
+
+Note that whenever a delegation is followed, the resolver algorithm
+reinitializes SLIST.
+
+The information establishes a partial ranking of the available name
+server addresses. Each time an address is chosen and the state should
+be altered to prevent its selection again until all other addresses have
+been tried. The timeout for each transmission should be 50-100% greater
+than the average predicted value to allow for variance in response.
+
+Some fine points:
+
+ - The resolver may encounter a situation where no addresses are
+ available for any of the name servers named in SLIST, and
+ where the servers in the list are precisely those which would
+ normally be used to look up their own addresses. This
+ situation typically occurs when the glue address RRs have a
+ smaller TTL than the NS RRs marking delegation, or when the
+ resolver caches the result of a NS search. The resolver
+ should detect this condition and restart the search at the
+ next ancestor zone, or alternatively at the root.
+
+
+
+
+
+Mockapetris [Page 45]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ - If a resolver gets a server error or other bizarre response
+ from a name server, it should remove it from SLIST, and may
+ wish to schedule an immediate transmission to the next
+ candidate server address.
+
+7.3. Processing responses
+
+The first step in processing arriving response datagrams is to parse the
+response. This procedure should include:
+
+ - Check the header for reasonableness. Discard datagrams which
+ are queries when responses are expected.
+
+ - Parse the sections of the message, and insure that all RRs are
+ correctly formatted.
+
+ - As an optional step, check the TTLs of arriving data looking
+ for RRs with excessively long TTLs. If a RR has an
+ excessively long TTL, say greater than 1 week, either discard
+ the whole response, or limit all TTLs in the response to 1
+ week.
+
+The next step is to match the response to a current resolver request.
+The recommended strategy is to do a preliminary matching using the ID
+field in the domain header, and then to verify that the question section
+corresponds to the information currently desired. This requires that
+the transmission algorithm devote several bits of the domain ID field to
+a request identifier of some sort. This step has several fine points:
+
+ - Some name servers send their responses from different
+ addresses than the one used to receive the query. That is, a
+ resolver cannot rely that a response will come from the same
+ address which it sent the corresponding query to. This name
+ server bug is typically encountered in UNIX systems.
+
+ - If the resolver retransmits a particular request to a name
+ server it should be able to use a response from any of the
+ transmissions. However, if it is using the response to sample
+ the round trip time to access the name server, it must be able
+ to determine which transmission matches the response (and keep
+ transmission times for each outgoing message), or only
+ calculate round trip times based on initial transmissions.
+
+ - A name server will occasionally not have a current copy of a
+ zone which it should have according to some NS RRs. The
+ resolver should simply remove the name server from the current
+ SLIST, and continue.
+
+
+
+
+Mockapetris [Page 46]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+7.4. Using the cache
+
+In general, we expect a resolver to cache all data which it receives in
+responses since it may be useful in answering future client requests.
+However, there are several types of data which should not be cached:
+
+ - When several RRs of the same type are available for a
+ particular owner name, the resolver should either cache them
+ all or none at all. When a response is truncated, and a
+ resolver doesn't know whether it has a complete set, it should
+ not cache a possibly partial set of RRs.
+
+ - Cached data should never be used in preference to
+ authoritative data, so if caching would cause this to happen
+ the data should not be cached.
+
+ - The results of an inverse query should not be cached.
+
+ - The results of standard queries where the QNAME contains "*"
+ labels if the data might be used to construct wildcards. The
+ reason is that the cache does not necessarily contain existing
+ RRs or zone boundary information which is necessary to
+ restrict the application of the wildcard RRs.
+
+ - RR data in responses of dubious reliability. When a resolver
+ receives unsolicited responses or RR data other than that
+ requested, it should discard it without caching it. The basic
+ implication is that all sanity checks on a packet should be
+ performed before any of it is cached.
+
+In a similar vein, when a resolver has a set of RRs for some name in a
+response, and wants to cache the RRs, it should check its cache for
+already existing RRs. Depending on the circumstances, either the data
+in the response or the cache is preferred, but the two should never be
+combined. If the data in the response is from authoritative data in the
+answer section, it is always preferred.
+
+8. MAIL SUPPORT
+
+The domain system defines a standard for mapping mailboxes into domain
+names, and two methods for using the mailbox information to derive mail
+routing information. The first method is called mail exchange binding
+and the other method is mailbox binding. The mailbox encoding standard
+and mail exchange binding are part of the DNS official protocol, and are
+the recommended method for mail routing in the Internet. Mailbox
+binding is an experimental feature which is still under development and
+subject to change.
+
+
+
+
+Mockapetris [Page 47]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+The mailbox encoding standard assumes a mailbox name of the form
+"<local-part>@<mail-domain>". While the syntax allowed in each of these
+sections varies substantially between the various mail internets, the
+preferred syntax for the ARPA Internet is given in [RFC-822].
+
+The DNS encodes the <local-part> as a single label, and encodes the
+<mail-domain> as a domain name. The single label from the <local-part>
+is prefaced to the domain name from <mail-domain> to form the domain
+name corresponding to the mailbox. Thus the mailbox HOSTMASTER@SRI-
+NIC.ARPA is mapped into the domain name HOSTMASTER.SRI-NIC.ARPA. If the
+<local-part> contains dots or other special characters, its
+representation in a master file will require the use of backslash
+quoting to ensure that the domain name is properly encoded. For
+example, the mailbox Action.domains@ISI.EDU would be represented as
+Action\.domains.ISI.EDU.
+
+8.1. Mail exchange binding
+
+Mail exchange binding uses the <mail-domain> part of a mailbox
+specification to determine where mail should be sent. The <local-part>
+is not even consulted. [RFC-974] specifies this method in detail, and
+should be consulted before attempting to use mail exchange support.
+
+One of the advantages of this method is that it decouples mail
+destination naming from the hosts used to support mail service, at the
+cost of another layer of indirection in the lookup function. However,
+the addition layer should eliminate the need for complicated "%", "!",
+etc encodings in <local-part>.
+
+The essence of the method is that the <mail-domain> is used as a domain
+name to locate type MX RRs which list hosts willing to accept mail for
+<mail-domain>, together with preference values which rank the hosts
+according to an order specified by the administrators for <mail-domain>.
+
+In this memo, the <mail-domain> ISI.EDU is used in examples, together
+with the hosts VENERA.ISI.EDU and VAXA.ISI.EDU as mail exchanges for
+ISI.EDU. If a mailer had a message for Mockapetris@ISI.EDU, it would
+route it by looking up MX RRs for ISI.EDU. The MX RRs at ISI.EDU name
+VENERA.ISI.EDU and VAXA.ISI.EDU, and type A queries can find the host
+addresses.
+
+8.2. Mailbox binding (Experimental)
+
+In mailbox binding, the mailer uses the entire mail destination
+specification to construct a domain name. The encoded domain name for
+the mailbox is used as the QNAME field in a QTYPE=MAILB query.
+
+Several outcomes are possible for this query:
+
+
+
+Mockapetris [Page 48]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ 1. The query can return a name error indicating that the mailbox
+ does not exist as a domain name.
+
+ In the long term, this would indicate that the specified
+ mailbox doesn't exist. However, until the use of mailbox
+ binding is universal, this error condition should be
+ interpreted to mean that the organization identified by the
+ global part does not support mailbox binding. The
+ appropriate procedure is to revert to exchange binding at
+ this point.
+
+ 2. The query can return a Mail Rename (MR) RR.
+
+ The MR RR carries new mailbox specification in its RDATA
+ field. The mailer should replace the old mailbox with the
+ new one and retry the operation.
+
+ 3. The query can return a MB RR.
+
+ The MB RR carries a domain name for a host in its RDATA
+ field. The mailer should deliver the message to that host
+ via whatever protocol is applicable, e.g., b,SMTP.
+
+ 4. The query can return one or more Mail Group (MG) RRs.
+
+ This condition means that the mailbox was actually a mailing
+ list or mail group, rather than a single mailbox. Each MG RR
+ has a RDATA field that identifies a mailbox that is a member
+ of the group. The mailer should deliver a copy of the
+ message to each member.
+
+ 5. The query can return a MB RR as well as one or more MG RRs.
+
+ This condition means the the mailbox was actually a mailing
+ list. The mailer can either deliver the message to the host
+ specified by the MB RR, which will in turn do the delivery to
+ all members, or the mailer can use the MG RRs to do the
+ expansion itself.
+
+In any of these cases, the response may include a Mail Information
+(MINFO) RR. This RR is usually associated with a mail group, but is
+legal with a MB. The MINFO RR identifies two mailboxes. One of these
+identifies a responsible person for the original mailbox name. This
+mailbox should be used for requests to be added to a mail group, etc.
+The second mailbox name in the MINFO RR identifies a mailbox that should
+receive error messages for mail failures. This is particularly
+appropriate for mailing lists when errors in member names should be
+reported to a person other than the one who sends a message to the list.
+
+
+
+Mockapetris [Page 49]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+New fields may be added to this RR in the future.
+
+
+9. REFERENCES and BIBLIOGRAPHY
+
+[Dyer 87] S. Dyer, F. Hsu, "Hesiod", Project Athena
+ Technical Plan - Name Service, April 1987, version 1.9.
+
+ Describes the fundamentals of the Hesiod name service.
+
+[IEN-116] J. Postel, "Internet Name Server", IEN-116,
+ USC/Information Sciences Institute, August 1979.
+
+ A name service obsoleted by the Domain Name System, but
+ still in use.
+
+[Quarterman 86] J. Quarterman, and J. Hoskins, "Notable Computer Networks",
+ Communications of the ACM, October 1986, volume 29, number
+ 10.
+
+[RFC-742] K. Harrenstien, "NAME/FINGER", RFC-742, Network
+ Information Center, SRI International, December 1977.
+
+[RFC-768] J. Postel, "User Datagram Protocol", RFC-768,
+ USC/Information Sciences Institute, August 1980.
+
+[RFC-793] J. Postel, "Transmission Control Protocol", RFC-793,
+ USC/Information Sciences Institute, September 1981.
+
+[RFC-799] D. Mills, "Internet Name Domains", RFC-799, COMSAT,
+ September 1981.
+
+ Suggests introduction of a hierarchy in place of a flat
+ name space for the Internet.
+
+[RFC-805] J. Postel, "Computer Mail Meeting Notes", RFC-805,
+ USC/Information Sciences Institute, February 1982.
+
+[RFC-810] E. Feinler, K. Harrenstien, Z. Su, and V. White, "DOD
+ Internet Host Table Specification", RFC-810, Network
+ Information Center, SRI International, March 1982.
+
+ Obsolete. See RFC-952.
+
+[RFC-811] K. Harrenstien, V. White, and E. Feinler, "Hostnames
+ Server", RFC-811, Network Information Center, SRI
+ International, March 1982.
+
+
+
+
+Mockapetris [Page 50]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ Obsolete. See RFC-953.
+
+[RFC-812] K. Harrenstien, and V. White, "NICNAME/WHOIS", RFC-812,
+ Network Information Center, SRI International, March
+ 1982.
+
+[RFC-819] Z. Su, and J. Postel, "The Domain Naming Convention for
+ Internet User Applications", RFC-819, Network
+ Information Center, SRI International, August 1982.
+
+ Early thoughts on the design of the domain system.
+ Current implementation is completely different.
+
+[RFC-821] J. Postel, "Simple Mail Transfer Protocol", RFC-821,
+ USC/Information Sciences Institute, August 1980.
+
+[RFC-830] Z. Su, "A Distributed System for Internet Name Service",
+ RFC-830, Network Information Center, SRI International,
+ October 1982.
+
+ Early thoughts on the design of the domain system.
+ Current implementation is completely different.
+
+[RFC-882] P. Mockapetris, "Domain names - Concepts and
+ Facilities," RFC-882, USC/Information Sciences
+ Institute, November 1983.
+
+ Superseded by this memo.
+
+[RFC-883] P. Mockapetris, "Domain names - Implementation and
+ Specification," RFC-883, USC/Information Sciences
+ Institute, November 1983.
+
+ Superseded by this memo.
+
+[RFC-920] J. Postel and J. Reynolds, "Domain Requirements",
+ RFC-920, USC/Information Sciences Institute,
+ October 1984.
+
+ Explains the naming scheme for top level domains.
+
+[RFC-952] K. Harrenstien, M. Stahl, E. Feinler, "DoD Internet Host
+ Table Specification", RFC-952, SRI, October 1985.
+
+ Specifies the format of HOSTS.TXT, the host/address
+ table replaced by the DNS.
+
+
+
+
+
+Mockapetris [Page 51]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+[RFC-953] K. Harrenstien, M. Stahl, E. Feinler, "HOSTNAME Server",
+ RFC-953, SRI, October 1985.
+
+ This RFC contains the official specification of the
+ hostname server protocol, which is obsoleted by the DNS.
+ This TCP based protocol accesses information stored in
+ the RFC-952 format, and is used to obtain copies of the
+ host table.
+
+[RFC-973] P. Mockapetris, "Domain System Changes and
+ Observations", RFC-973, USC/Information Sciences
+ Institute, January 1986.
+
+ Describes changes to RFC-882 and RFC-883 and reasons for
+ them.
+
+[RFC-974] C. Partridge, "Mail routing and the domain system",
+ RFC-974, CSNET CIC BBN Labs, January 1986.
+
+ Describes the transition from HOSTS.TXT based mail
+ addressing to the more powerful MX system used with the
+ domain system.
+
+[RFC-1001] NetBIOS Working Group, "Protocol standard for a NetBIOS
+ service on a TCP/UDP transport: Concepts and Methods",
+ RFC-1001, March 1987.
+
+ This RFC and RFC-1002 are a preliminary design for
+ NETBIOS on top of TCP/IP which proposes to base NetBIOS
+ name service on top of the DNS.
+
+[RFC-1002] NetBIOS Working Group, "Protocol standard for a NetBIOS
+ service on a TCP/UDP transport: Detailed
+ Specifications", RFC-1002, March 1987.
+
+[RFC-1010] J. Reynolds, and J. Postel, "Assigned Numbers", RFC-1010,
+ USC/Information Sciences Institute, May 1987.
+
+ Contains socket numbers and mnemonics for host names,
+ operating systems, etc.
+
+[RFC-1031] W. Lazear, "MILNET Name Domain Transition", RFC-1031,
+ November 1987.
+
+ Describes a plan for converting the MILNET to the DNS.
+
+[RFC-1032] M. Stahl, "Establishing a Domain - Guidelines for
+ Administrators", RFC-1032, November 1987.
+
+
+
+Mockapetris [Page 52]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ Describes the registration policies used by the NIC to
+ administer the top level domains and delegate subzones.
+
+[RFC-1033] M. Lottor, "Domain Administrators Operations Guide",
+ RFC-1033, November 1987.
+
+ A cookbook for domain administrators.
+
+[Solomon 82] M. Solomon, L. Landweber, and D. Neuhengen, "The CSNET
+ Name Server", Computer Networks, vol 6, nr 3, July 1982.
+
+ Describes a name service for CSNET which is independent
+ from the DNS and DNS use in the CSNET.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Mockapetris [Page 53]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+Index
+
+ * 13
+
+ ; 33, 35
+
+ <character-string> 35
+ <domain-name> 34
+
+ @ 35
+
+ \ 35
+
+ A 12
+
+ Byte order 8
+
+ CH 13
+ Character case 9
+ CLASS 11
+ CNAME 12
+ Completion 42
+ CS 13
+
+ Hesiod 13
+ HINFO 12
+ HS 13
+
+ IN 13
+ IN-ADDR.ARPA domain 22
+ Inverse queries 40
+
+ Mailbox names 47
+ MB 12
+ MD 12
+ MF 12
+ MG 12
+ MINFO 12
+ MINIMUM 20
+ MR 12
+ MX 12
+
+ NS 12
+ NULL 12
+
+ Port numbers 32
+ Primary server 5
+ PTR 12, 18
+
+
+
+Mockapetris [Page 54]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ QCLASS 13
+ QTYPE 12
+
+ RDATA 12
+ RDLENGTH 11
+
+ Secondary server 5
+ SOA 12
+ Stub resolvers 7
+
+ TCP 32
+ TXT 12
+ TYPE 11
+
+ UDP 32
+
+ WKS 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Mockapetris [Page 55]
+
diff --git a/lib/dns/tests/testdata/dst/test1.ecdsa256sig b/lib/dns/tests/testdata/dst/test1.ecdsa256sig
new file mode 100644
index 0000000..dcae9d1
--- /dev/null
+++ b/lib/dns/tests/testdata/dst/test1.ecdsa256sig
@@ -0,0 +1 @@
+72E0998732EECAE2BEA12A278DFDEE14DB09A43C1E646A08BB0A6EEB90C5B75F9B359BEC1580313BFA8012C1DC15D34D1B227C71AD23161E2757AEB162AE3D99
diff --git a/lib/dns/tests/testdata/dst/test1.rsasha256sig b/lib/dns/tests/testdata/dst/test1.rsasha256sig
new file mode 100644
index 0000000..36e0b09
--- /dev/null
+++ b/lib/dns/tests/testdata/dst/test1.rsasha256sig
@@ -0,0 +1 @@
+C5CC8AB9FB5C0B4F03650456C993A868EB674ACBF2A867E023DC00F17D240CEDCADB8714981B7B48CF6CF86722632610FF312063B5E6D20EF441B89F02BC6813A35F9C6F045D017DB75C8724DBAA0C55A0D4EA850339944C75890B4DD0382AFA3E30E1CAA7B190C1B1FB17B5DD2279C0DF1049911E64198B3376070A34F38F4B
diff --git a/lib/dns/tests/testdata/dst/test2.data b/lib/dns/tests/testdata/dst/test2.data
new file mode 100644
index 0000000..a323bb3
--- /dev/null
+++ b/lib/dns/tests/testdata/dst/test2.data
@@ -0,0 +1,3077 @@
+Network Working Group P. Mockapetris
+Request for Comments: 1035 ISI
+ November 1987
+Obsoletes: RFCs 882, 883, 973
+
+ DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION
+
+
+1. STATUS OF THIS MEMO
+
+This RFC describes the details of the domain system and protocol, and
+assumes that the reader is familiar with the concepts discussed in a
+companion RFC, "Domain Names - Concepts and Facilities" [RFC-4301].
+
+The domain system is a mixture of functions and data types which are an
+official protocol and functions and data types which are still
+experimental. Since the domain system is intentionally extensible, new
+data types and experimental behavior should always be expected in parts
+of the system beyond the official protocol. The official protocol parts
+include standard queries, responses and the Internet class RR data
+formats (e.g., host addresses). Since the previous RFC set, several
+definitions have changed, so some previous definitions are obsolete.
+
+Experimental or obsolete features are clearly marked in these RFCs, and
+such information should be used with caution.
+
+The reader is especially cautioned not to depend on the values which
+appear in examples to be current or complete, since their purpose is
+primarily pedagogical. Distribution of this memo is unlimited.
+
+ Table of Contents
+
+ 1. STATUS OF THIS MEMO 1
+ 2. INTRODUCTION 3
+ 2.1. Overview 3
+ 2.2. Common configurations 4
+ 2.3. Conventions 7
+ 2.3.1. Preferred name syntax 7
+ 2.3.2. Data Transmission Order 8
+ 2.3.3. Character Case 9
+ 2.3.4. Size limits 10
+ 3. DOMAIN NAME SPACE AND RR DEFINITIONS 10
+ 3.1. Name space definitions 10
+ 3.2. RR definitions 11
+ 3.2.1. Format 11
+ 3.2.2. TYPE values 12
+ 3.2.3. QTYPE values 12
+ 3.2.4. CLASS values 13
+
+
+
+Mockapetris [Page 1]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ 3.2.5. QCLASS values 13
+ 3.3. Standard RRs 13
+ 3.3.1. CNAME RDATA format 14
+ 3.3.2. HINFO RDATA format 14
+ 3.3.3. MB RDATA format (EXPERIMENTAL) 14
+ 3.3.4. MD RDATA format (Obsolete) 15
+ 3.3.5. MF RDATA format (Obsolete) 15
+ 3.3.6. MG RDATA format (EXPERIMENTAL) 16
+ 3.3.7. MINFO RDATA format (EXPERIMENTAL) 16
+ 3.3.8. MR RDATA format (EXPERIMENTAL) 17
+ 3.3.9. MX RDATA format 17
+ 3.3.10. NULL RDATA format (EXPERIMENTAL) 17
+ 3.3.11. NS RDATA format 18
+ 3.3.12. PTR RDATA format 18
+ 3.3.13. SOA RDATA format 19
+ 3.3.14. TXT RDATA format 20
+ 3.4. ARPA Internet specific RRs 20
+ 3.4.1. A RDATA format 20
+ 3.4.2. WKS RDATA format 21
+ 3.5. IN-ADDR.ARPA domain 22
+ 3.6. Defining new types, classes, and special namespaces 24
+ 4. MESSAGES 25
+ 4.1. Format 25
+ 4.1.1. Header section format 26
+ 4.1.2. Question section format 28
+ 4.1.3. Resource record format 29
+ 4.1.4. Message compression 30
+ 4.2. Transport 32
+ 4.2.1. UDP usage 32
+ 4.2.2. TCP usage 32
+ 5. MASTER FILES 33
+ 5.1. Format 33
+ 5.2. Use of master files to define zones 35
+ 5.3. Master file example 36
+ 6. NAME SERVER IMPLEMENTATION 37
+ 6.1. Architecture 37
+ 6.1.1. Control 37
+ 6.1.2. Database 37
+ 6.1.3. Time 39
+ 6.2. Standard query processing 39
+ 6.3. Zone refresh and reload processing 39
+ 6.4. Inverse queries (Optional) 40
+ 6.4.1. The contents of inverse queries and responses 40
+ 6.4.2. Inverse query and response example 41
+ 6.4.3. Inverse query processing 42
+
+
+
+
+
+
+Mockapetris [Page 2]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ 6.5. Completion queries and responses 42
+ 7. RESOLVER IMPLEMENTATION 43
+ 7.1. Transforming a user request into a query 43
+ 7.2. Sending the queries 44
+ 7.3. Processing responses 46
+ 7.4. Using the cache 47
+ 8. MAIL SUPPORT 47
+ 8.1. Mail exchange binding 48
+ 8.2. Mailbox binding (Experimental) 48
+ 9. REFERENCES and BIBLIOGRAPHY 50
+ Index 54
+
+2. INTRODUCTION
+
+2.1. Overview
+
+The goal of domain names is to provide a mechanism for naming resources
+in such a way that the names are usable in different hosts, networks,
+protocol families, internets, and administrative organizations.
+
+From the user's point of view, domain names are useful as arguments to a
+local agent, called a resolver, which retrieves information associated
+with the domain name. Thus a user might ask for the host address or
+mail information associated with a particular domain name. To enable
+the user to request a particular type of information, an appropriate
+query type is passed to the resolver with the domain name. To the user,
+the domain tree is a single information space; the resolver is
+responsible for hiding the distribution of data among name servers from
+the user.
+
+From the resolver's point of view, the database that makes up the domain
+space is distributed among various name servers. Different parts of the
+domain space are stored in different name servers, although a particular
+data item will be stored redundantly in two or more name servers. The
+resolver starts with knowledge of at least one name server. When the
+resolver processes a user query it asks a known name server for the
+information; in return, the resolver either receives the desired
+information or a referral to another name server. Using these
+referrals, resolvers learn the identities and contents of other name
+servers. Resolvers are responsible for dealing with the distribution of
+the domain space and dealing with the effects of name server failure by
+consulting redundant databases in other servers.
+
+Name servers manage two kinds of data. The first kind of data held in
+sets called zones; each zone is the complete database for a particular
+"pruned" subtree of the domain space. This data is called
+authoritative. A name server periodically checks to make sure that its
+zones are up to date, and if not, obtains a new copy of updated zones
+
+
+
+Mockapetris [Page 3]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+from master files stored locally or in another name server. The second
+kind of data is cached data which was acquired by a local resolver.
+This data may be incomplete, but improves the performance of the
+retrieval process when non-local data is repeatedly accessed. Cached
+data is eventually discarded by a timeout mechanism.
+
+This functional structure isolates the problems of user interface,
+failure recovery, and distribution in the resolvers and isolates the
+database update and refresh problems in the name servers.
+
+2.2. Common configurations
+
+A host can participate in the domain name system in a number of ways,
+depending on whether the host runs programs that retrieve information
+from the domain system, name servers that answer queries from other
+hosts, or various combinations of both functions. The simplest, and
+perhaps most typical, configuration is shown below:
+
+ Local Host | Foreign
+ |
+ +---------+ +----------+ | +--------+
+ | | user queries | |queries | | |
+ | User |-------------->| |---------|->|Foreign |
+ | Program | | Resolver | | | Name |
+ | |<--------------| |<--------|--| Server |
+ | | user responses| |responses| | |
+ +---------+ +----------+ | +--------+
+ | A |
+ cache additions | | references |
+ V | |
+ +----------+ |
+ | cache | |
+ +----------+ |
+
+User programs interact with the domain name space through resolvers; the
+format of user queries and user responses is specific to the host and
+its operating system. User queries will typically be operating system
+calls, and the resolver and its cache will be part of the host operating
+system. Less capable hosts may choose to implement the resolver as a
+subroutine to be linked in with every program that needs its services.
+Resolvers answer user queries with information they acquire via queries
+to foreign name servers and the local cache.
+
+Note that the resolver may have to make several queries to several
+different foreign name servers to answer a particular user query, and
+hence the resolution of a user query may involve several network
+accesses and an arbitrary amount of time. The queries to foreign name
+servers and the corresponding responses have a standard format described
+
+
+
+Mockapetris [Page 4]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+in this memo, and may be datagrams.
+
+Depending on its capabilities, a name server could be a stand alone
+program on a dedicated machine or a process or processes on a large
+timeshared host. A simple configuration might be:
+
+ Local Host | Foreign
+ |
+ +---------+ |
+ / /| |
+ +---------+ | +----------+ | +--------+
+ | | | | |responses| | |
+ | | | | Name |---------|->|Foreign |
+ | Master |-------------->| Server | | |Resolver|
+ | files | | | |<--------|--| |
+ | |/ | | queries | +--------+
+ +---------+ +----------+ |
+
+Here a primary name server acquires information about one or more zones
+by reading master files from its local file system, and answers queries
+about those zones that arrive from foreign resolvers.
+
+The DNS requires that all zones be redundantly supported by more than
+one name server. Designated secondary servers can acquire zones and
+check for updates from the primary server using the zone transfer
+protocol of the DNS. This configuration is shown below:
+
+ Local Host | Foreign
+ |
+ +---------+ |
+ / /| |
+ +---------+ | +----------+ | +--------+
+ | | | | |responses| | |
+ | | | | Name |---------|->|Foreign |
+ | Master |-------------->| Server | | |Resolver|
+ | files | | | |<--------|--| |
+ | |/ | | queries | +--------+
+ +---------+ +----------+ |
+ A |maintenance | +--------+
+ | +------------|->| |
+ | queries | |Foreign |
+ | | | Name |
+ +------------------|--| Server |
+ maintenance responses | +--------+
+
+In this configuration, the name server periodically establishes a
+virtual circuit to a foreign name server to acquire a copy of a zone or
+to check that an existing copy has not changed. The messages sent for
+
+
+
+Mockapetris [Page 5]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+these maintenance activities follow the same form as queries and
+responses, but the message sequences are somewhat different.
+
+The information flow in a host that supports all aspects of the domain
+name system is shown below:
+
+ Local Host | Foreign
+ |
+ +---------+ +----------+ | +--------+
+ | | user queries | |queries | | |
+ | User |-------------->| |---------|->|Foreign |
+ | Program | | Resolver | | | Name |
+ | |<--------------| |<--------|--| Server |
+ | | user responses| |responses| | |
+ +---------+ +----------+ | +--------+
+ | A |
+ cache additions | | references |
+ V | |
+ +----------+ |
+ | Shared | |
+ | database | |
+ +----------+ |
+ A | |
+ +---------+ refreshes | | references |
+ / /| | V |
+ +---------+ | +----------+ | +--------+
+ | | | | |responses| | |
+ | | | | Name |---------|->|Foreign |
+ | Master |-------------->| Server | | |Resolver|
+ | files | | | |<--------|--| |
+ | |/ | | queries | +--------+
+ +---------+ +----------+ |
+ A |maintenance | +--------+
+ | +------------|->| |
+ | queries | |Foreign |
+ | | | Name |
+ +------------------|--| Server |
+ maintenance responses | +--------+
+
+The shared database holds domain space data for the local name server
+and resolver. The contents of the shared database will typically be a
+mixture of authoritative data maintained by the periodic refresh
+operations of the name server and cached data from previous resolver
+requests. The structure of the domain data and the necessity for
+synchronization between name servers and resolvers imply the general
+characteristics of this database, but the actual format is up to the
+local implementor.
+
+
+
+
+Mockapetris [Page 6]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+Information flow can also be tailored so that a group of hosts act
+together to optimize activities. Sometimes this is done to offload less
+capable hosts so that they do not have to implement a full resolver.
+This can be appropriate for PCs or hosts which want to minimize the
+amount of new network code which is required. This scheme can also
+allow a group of hosts can share a small number of caches rather than
+maintaining a large number of separate caches, on the premise that the
+centralized caches will have a higher hit ratio. In either case,
+resolvers are replaced with stub resolvers which act as front ends to
+resolvers located in a recursive server in one or more name servers
+known to perform that service:
+
+ Local Hosts | Foreign
+ |
+ +---------+ |
+ | | responses |
+ | Stub |<--------------------+ |
+ | Resolver| | |
+ | |----------------+ | |
+ +---------+ recursive | | |
+ queries | | |
+ V | |
+ +---------+ recursive +----------+ | +--------+
+ | | queries | |queries | | |
+ | Stub |-------------->| Recursive|---------|->|Foreign |
+ | Resolver| | Server | | | Name |
+ | |<--------------| |<--------|--| Server |
+ +---------+ responses | |responses| | |
+ +----------+ | +--------+
+ | Central | |
+ | cache | |
+ +----------+ |
+
+In any case, note that domain components are always replicated for
+reliability whenever possible.
+
+2.3. Conventions
+
+The domain system has several conventions dealing with low-level, but
+fundamental, issues. While the implementor is free to violate these
+conventions WITHIN HIS OWN SYSTEM, he must observe these conventions in
+ALL behavior observed from other hosts.
+
+2.3.1. Preferred name syntax
+
+The DNS specifications attempt to be as general as possible in the rules
+for constructing domain names. The idea is that the name of any
+existing object can be expressed as a domain name with minimal changes.
+
+
+
+Mockapetris [Page 7]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+However, when assigning a domain name for an object, the prudent user
+will select a name which satisfies both the rules of the domain system
+and any existing rules for the object, whether these rules are published
+or implied by existing programs.
+
+For example, when naming a mail domain, the user should satisfy both the
+rules of this memo and those in RFC-822. When creating a new host name,
+the old rules for HOSTS.TXT should be followed. This avoids problems
+when old software is converted to use domain names.
+
+The following syntax will result in fewer problems with many
+
+applications that use domain names (e.g., mail, TELNET).
+
+<domain> ::= <subdomain> | " "
+
+<subdomain> ::= <label> | <subdomain> "." <label>
+
+<label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
+
+<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
+
+<let-dig-hyp> ::= <let-dig> | "-"
+
+<let-dig> ::= <letter> | <digit>
+
+<letter> ::= any one of the 52 alphabetic characters A through Z in
+upper case and a through z in lower case
+
+<digit> ::= any one of the ten digits 0 through 9
+
+Note that while upper and lower case letters are allowed in domain
+names, no significance is attached to the case. That is, two names with
+the same spelling but different case are to be treated as if identical.
+
+The labels must follow the rules for ARPANET host names. They must
+start with a letter, end with a letter or digit, and have as interior
+characters only letters, digits, and hyphen. There are also some
+restrictions on the length. Labels must be 63 characters or less.
+
+For example, the following strings identify hosts in the Internet:
+
+A.ISI.EDU XX.LCS.MIT.EDU SRI-NIC.ARPA
+
+2.3.2. Data Transmission Order
+
+The order of transmission of the header and data described in this
+document is resolved to the octet level. Whenever a diagram shows a
+
+
+
+Mockapetris [Page 8]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+group of octets, the order of transmission of those octets is the normal
+order in which they are read in English. For example, in the following
+diagram, the octets are transmitted in the order they are numbered.
+
+ 0 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 1 | 2 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 3 | 4 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 5 | 6 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+Whenever an octet represents a numeric quantity, the left most bit in
+the diagram is the high order or most significant bit. That is, the bit
+labeled 0 is the most significant bit. For example, the following
+diagram represents the value 170 (decimal).
+
+ 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+-+
+ |1 0 1 0 1 0 1 0|
+ +-+-+-+-+-+-+-+-+
+
+Similarly, whenever a multi-octet field represents a numeric quantity
+the left most bit of the whole field is the most significant bit. When
+a multi-octet quantity is transmitted the most significant octet is
+transmitted first.
+
+2.3.3. Character Case
+
+For all parts of the DNS that are part of the official protocol, all
+comparisons between character strings (e.g., labels, domain names, etc.)
+are done in a case-insensitive manner. At present, this rule is in
+force throughout the domain system without exception. However, future
+additions beyond current usage may need to use the full binary octet
+capabilities in names, so attempts to store domain names in 7-bit ASCII
+or use of special bytes to terminate labels, etc., should be avoided.
+
+When data enters the domain system, its original case should be
+preserved whenever possible. In certain circumstances this cannot be
+done. For example, if two RRs are stored in a database, one at x.y and
+one at X.Y, they are actually stored at the same place in the database,
+and hence only one casing would be preserved. The basic rule is that
+case can be discarded only when data is used to define structure in a
+database, and two names are identical when compared in a case
+insensitive manner.
+
+
+
+
+Mockapetris [Page 9]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+Loss of case sensitive data must be minimized. Thus while data for x.y
+and X.Y may both be stored under a single location x.y or X.Y, data for
+a.x and B.X would never be stored under A.x, A.X, b.x, or b.X. In
+general, this preserves the case of the first label of a domain name,
+but forces standardization of interior node labels.
+
+Systems administrators who enter data into the domain database should
+take care to represent the data they supply to the domain system in a
+case-consistent manner if their system is case-sensitive. The data
+distribution system in the domain system will ensure that consistent
+representations are preserved.
+
+2.3.4. Size limits
+
+Various objects and parameters in the DNS have size limits. They are
+listed below. Some could be easily changed, others are more
+fundamental.
+
+labels 63 octets or less
+
+names 255 octets or less
+
+TTL positive values of a signed 32 bit number.
+
+UDP messages 512 octets or less
+
+3. DOMAIN NAME SPACE AND RR DEFINITIONS
+
+3.1. Name space definitions
+
+Domain names in messages are expressed in terms of a sequence of labels.
+Each label is represented as a one octet length field followed by that
+number of octets. Since every domain name ends with the null label of
+the root, a domain name is terminated by a length byte of zero. The
+high order two bits of every length octet must be zero, and the
+remaining six bits of the length field limit the label to 63 octets or
+less.
+
+To simplify implementations, the total length of a domain name (i.e.,
+label octets and label length octets) is restricted to 255 octets or
+less.
+
+Although labels can contain any 8 bit values in octets that make up a
+label, it is strongly recommended that labels follow the preferred
+syntax described elsewhere in this memo, which is compatible with
+existing host naming conventions. Name servers and resolvers must
+compare labels in a case-insensitive manner (i.e., A=a), assuming ASCII
+with zero parity. Non-alphabetic codes must match exactly.
+
+
+
+Mockapetris [Page 10]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.2. RR definitions
+
+3.2.1. Format
+
+All RRs have the same top level format shown below:
+
+ 1 1 1 1 1 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | |
+ / /
+ / NAME /
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | TYPE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | CLASS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | TTL |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | RDLENGTH |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
+ / RDATA /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+
+where:
+
+NAME an owner name, i.e., the name of the node to which this
+ resource record pertains.
+
+TYPE two octets containing one of the RR TYPE codes.
+
+CLASS two octets containing one of the RR CLASS codes.
+
+TTL a 32 bit signed integer that specifies the time interval
+ that the resource record may be cached before the source
+ of the information should again be consulted. Zero
+ values are interpreted to mean that the RR can only be
+ used for the transaction in progress, and should not be
+ cached. For example, SOA records are always distributed
+ with a zero TTL to prohibit caching. Zero values can
+ also be used for extremely volatile data.
+
+RDLENGTH an unsigned 16 bit integer that specifies the length in
+ octets of the RDATA field.
+
+
+
+Mockapetris [Page 11]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+RDATA a variable length string of octets that describes the
+ resource. The format of this information varies
+ according to the TYPE and CLASS of the resource record.
+
+3.2.2. TYPE values
+
+TYPE fields are used in resource records. Note that these types are a
+subset of QTYPEs.
+
+TYPE value and meaning
+
+A 1 a host address
+
+NS 2 an authoritative name server
+
+MD 3 a mail destination (Obsolete - use MX)
+
+MF 4 a mail forwarder (Obsolete - use MX)
+
+CNAME 5 the canonical name for an alias
+
+SOA 6 marks the start of a zone of authority
+
+MB 7 a mailbox domain name (EXPERIMENTAL)
+
+MG 8 a mail group member (EXPERIMENTAL)
+
+MR 9 a mail rename domain name (EXPERIMENTAL)
+
+NULL 10 a null RR (EXPERIMENTAL)
+
+WKS 11 a well known service description
+
+PTR 12 a domain name pointer
+
+HINFO 13 host information
+
+MINFO 14 mailbox or mail list information
+
+MX 15 mail exchange
+
+TXT 16 text strings
+
+3.2.3. QTYPE values
+
+QTYPE fields appear in the question part of a query. QTYPES are a
+superset of TYPEs, hence all TYPEs are valid QTYPEs. In addition, the
+following QTYPEs are defined:
+
+
+
+Mockapetris [Page 12]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+AXFR 252 A request for a transfer of an entire zone
+
+MAILB 253 A request for mailbox-related records (MB, MG or MR)
+
+MAILA 254 A request for mail agent RRs (Obsolete - see MX)
+
+* 255 A request for all records
+
+3.2.4. CLASS values
+
+CLASS fields appear in resource records. The following CLASS mnemonics
+and values are defined:
+
+IN 1 the Internet
+
+CS 2 the CSNET class (Obsolete - used only for examples in
+ some obsolete RFCs)
+
+CH 3 the CHAOS class
+
+HS 4 Hesiod [Dyer 87]
+
+3.2.5. QCLASS values
+
+QCLASS fields appear in the question section of a query. QCLASS values
+are a superset of CLASS values; every CLASS is a valid QCLASS. In
+addition to CLASS values, the following QCLASSes are defined:
+
+* 255 any class
+
+3.3. Standard RRs
+
+The following RR definitions are expected to occur, at least
+potentially, in all classes. In particular, NS, SOA, CNAME, and PTR
+will be used in all classes, and have the same format in all classes.
+Because their RDATA format is known, all domain names in the RDATA
+section of these RRs may be compressed.
+
+<domain-name> is a domain name represented as a series of labels, and
+terminated by a label with zero length. <character-string> is a single
+length octet followed by that number of characters. <character-string>
+is treated as binary information, and can be up to 256 characters in
+length (including the length octet).
+
+
+
+
+
+
+
+
+Mockapetris [Page 13]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.3.1. CNAME RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / CNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+CNAME A <domain-name> which specifies the canonical or primary
+ name for the owner. The owner name is an alias.
+
+CNAME RRs cause no additional section processing, but name servers may
+choose to restart the query at the canonical name in certain cases. See
+the description of name server logic in [RFC-1034] for details.
+
+3.3.2. HINFO RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / CPU /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / OS /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+CPU A <character-string> which specifies the CPU type.
+
+OS A <character-string> which specifies the operating
+ system type.
+
+Standard values for CPU and OS can be found in [RFC-1010].
+
+HINFO records are used to acquire general information about a host. The
+main use is for protocols such as FTP that can use special procedures
+when talking between machines or operating systems of the same type.
+
+3.3.3. MB RDATA format (EXPERIMENTAL)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / MADNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+MADNAME A <domain-name> which specifies a host which has the
+ specified mailbox.
+
+
+
+Mockapetris [Page 14]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+MB records cause additional section processing which looks up an A type
+RRs corresponding to MADNAME.
+
+3.3.4. MD RDATA format (Obsolete)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / MADNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+MADNAME A <domain-name> which specifies a host which has a mail
+ agent for the domain which should be able to deliver
+ mail for the domain.
+
+MD records cause additional section processing which looks up an A type
+record corresponding to MADNAME.
+
+MD is obsolete. See the definition of MX and [RFC-974] for details of
+the new scheme. The recommended policy for dealing with MD RRs found in
+a master file is to reject them, or to convert them to MX RRs with a
+preference of 0.
+
+3.3.5. MF RDATA format (Obsolete)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / MADNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+MADNAME A <domain-name> which specifies a host which has a mail
+ agent for the domain which will accept mail for
+ forwarding to the domain.
+
+MF records cause additional section processing which looks up an A type
+record corresponding to MADNAME.
+
+MF is obsolete. See the definition of MX and [RFC-974] for details ofw
+the new scheme. The recommended policy for dealing with MD RRs found in
+a master file is to reject them, or to convert them to MX RRs with a
+preference of 10.
+
+
+
+
+
+
+
+Mockapetris [Page 15]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.3.6. MG RDATA format (EXPERIMENTAL)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / MGMNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+MGMNAME A <domain-name> which specifies a mailbox which is a
+ member of the mail group specified by the domain name.
+
+MG records cause no additional section processing.
+
+3.3.7. MINFO RDATA format (EXPERIMENTAL)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / RMAILBX /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / EMAILBX /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+RMAILBX A <domain-name> which specifies a mailbox which is
+ responsible for the mailing list or mailbox. If this
+ domain name names the root, the owner of the MINFO RR is
+ responsible for itself. Note that many existing mailing
+ lists use a mailbox X-request for the RMAILBX field of
+ mailing list X, e.g., Msgroup-request for Msgroup. This
+ field provides a more general mechanism.
+
+
+EMAILBX A <domain-name> which specifies a mailbox which is to
+ receive error messages related to the mailing list or
+ mailbox specified by the owner of the MINFO RR (similar
+ to the ERRORS-TO: field which has been proposed). If
+ this domain name names the root, errors should be
+ returned to the sender of the message.
+
+MINFO records cause no additional section processing. Although these
+records can be associated with a simple mailbox, they are usually used
+with a mailing list.
+
+
+
+
+
+
+
+
+Mockapetris [Page 16]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.3.8. MR RDATA format (EXPERIMENTAL)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / NEWNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+NEWNAME A <domain-name> which specifies a mailbox which is the
+ proper rename of the specified mailbox.
+
+MR records cause no additional section processing. The main use for MR
+is as a forwarding entry for a user who has moved to a different
+mailbox.
+
+3.3.9. MX RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | PREFERENCE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / EXCHANGE /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+PREFERENCE A 16 bit integer which specifies the preference given to
+ this RR among others at the same owner. Lower values
+ are preferred.
+
+EXCHANGE A <domain-name> which specifies a host willing to act as
+ a mail exchange for the owner name.
+
+MX records cause type A additional section processing for the host
+specified by EXCHANGE. The use of MX RRs is explained in detail in
+[RFC-974].
+
+3.3.10. NULL RDATA format (EXPERIMENTAL)
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / <anything> /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+Anything at all may be in the RDATA field so long as it is 65535 octets
+or less.
+
+
+
+
+Mockapetris [Page 17]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+NULL records cause no additional section processing. NULL RRs are not
+allowed in master files. NULLs are used as placeholders in some
+experimental extensions of the DNS.
+
+3.3.11. NS RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / NSDNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+NSDNAME A <domain-name> which specifies a host which should be
+ authoritative for the specified class and domain.
+
+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.
+
+The NS RR states that the named host should be expected to have a zone
+starting at owner name of the specified class. Note that the class may
+not indicate the protocol family which should be used to communicate
+with the host, although it is typically a strong hint. For example,
+hosts which are name servers for either Internet (IN) or Hesiod (HS)
+class information are normally queried using IN class protocols.
+
+3.3.12. PTR RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / PTRDNAME /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+PTRDNAME A <domain-name> which points to some location in the
+ domain name space.
+
+PTR records cause no additional section processing. These RRs are used
+in special domains to point to some other location in the domain space.
+These records are simple data, and don't imply any special processing
+similar to that performed by CNAME, which identifies aliases. See the
+description of the IN-ADDR.ARPA domain for an example.
+
+
+
+
+
+
+
+
+Mockapetris [Page 18]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.3.13. SOA RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / MNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / RNAME /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | SERIAL |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | REFRESH |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | RETRY |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | EXPIRE |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | MINIMUM |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+MNAME The <domain-name> of the name server that was the
+ original or primary source of data for this zone.
+
+RNAME A <domain-name> which specifies the mailbox of the
+ person responsible for this zone.
+
+SERIAL The unsigned 32 bit version number of the original copy
+ of the zone. Zone transfers preserve this value. This
+ value wraps and should be compared using sequence space
+ arithmetic.
+
+REFRESH A 32 bit time interval before the zone should be
+ refreshed.
+
+RETRY A 32 bit time interval that should elapse before a
+ failed refresh should be retried.
+
+EXPIRE A 32 bit time value that specifies the upper limit on
+ the time interval that can elapse before the zone is no
+ longer authoritative.
+
+
+
+
+
+Mockapetris [Page 19]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+MINIMUM The unsigned 32 bit minimum TTL field that should be
+ exported with any RR from this zone.
+
+SOA records cause no additional section processing.
+
+All times are in units of seconds.
+
+Most of these fields are pertinent only for name server maintenance
+operations. However, MINIMUM is used in all query operations that
+retrieve RRs from a zone. Whenever a RR is sent in a response to a
+query, the TTL field is set to the maximum of the TTL field from the RR
+and the MINIMUM field in the appropriate SOA. Thus MINIMUM is a lower
+bound on the TTL field for all RRs in a zone. Note that this use of
+MINIMUM should occur when the RRs are copied into the response and not
+when the zone is loaded from a master file or via a zone transfer. The
+reason for this provison is to allow future dynamic update facilities to
+change the SOA RR with known semantics.
+
+
+3.3.14. TXT RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / TXT-DATA /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+TXT-DATA One or more <character-string>s.
+
+TXT RRs are used to hold descriptive text. The semantics of the text
+depends on the domain where it is found.
+
+3.4. Internet specific RRs
+
+3.4.1. A RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ADDRESS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+ADDRESS A 32 bit Internet address.
+
+Hosts that have multiple Internet addresses will have multiple A
+records.
+
+
+
+
+
+Mockapetris [Page 20]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+A records cause no additional section processing. The RDATA section of
+an A line in a master file is an Internet address expressed as four
+decimal numbers separated by dots without any embedded spaces (e.g.,
+"10.2.0.52" or "192.0.5.6").
+
+3.4.2. WKS RDATA format
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ADDRESS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | PROTOCOL | |
+ +--+--+--+--+--+--+--+--+ |
+ | |
+ / <BIT MAP> /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+ADDRESS An 32 bit Internet address
+
+PROTOCOL An 8 bit IP protocol number
+
+<BIT MAP> A variable length bit map. The bit map must be a
+ multiple of 8 bits long.
+
+The WKS record is used to describe the well known services supported by
+a particular protocol on a particular internet address. The PROTOCOL
+field specifies an IP protocol number, and the bit map has one bit per
+port of the specified protocol. The first bit corresponds to port 0,
+the second to port 1, etc. If the bit map does not include a bit for a
+protocol of interest, that bit is assumed zero. The appropriate values
+and mnemonics for ports and protocols are specified in [RFC-1010].
+
+For example, if PROTOCOL=TCP (6), the 26th bit corresponds to TCP port
+25 (SMTP). If this bit is set, a SMTP server should be listening on TCP
+port 25; if zero, SMTP service is not supported on the specified
+address.
+
+The purpose of WKS RRs is to provide availability information for
+servers for TCP and UDP. If a server supports both TCP and UDP, or has
+multiple Internet addresses, then multiple WKS RRs are used.
+
+WKS RRs cause no additional section processing.
+
+In master files, both ports and protocols are expressed using mnemonics
+or decimal numbers.
+
+
+
+
+Mockapetris [Page 21]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.5. IN-ADDR.ARPA domain
+
+The Internet uses a special domain to support gateway location and
+Internet address to host mapping. Other classes may employ a similar
+strategy in other domains. The intent of this domain is to provide a
+guaranteed method to perform host address to host name mapping, and to
+facilitate queries to locate all gateways on a particular network in the
+Internet.
+
+Note that both of these services are similar to functions that could be
+performed by inverse queries; the difference is that this part of the
+domain name space is structured according to address, and hence can
+guarantee that the appropriate data can be located without an exhaustive
+search of the domain space.
+
+The domain begins at IN-ADDR.ARPA and has a substructure which follows
+the Internet addressing structure.
+
+Domain names in the IN-ADDR.ARPA domain are defined to have up to four
+labels in addition to the IN-ADDR.ARPA suffix. Each label represents
+one octet of an Internet address, and is expressed as a character string
+for a decimal value in the range 0-255 (with leading zeros omitted
+except in the case of a zero octet which is represented by a single
+zero).
+
+Host addresses are represented by domain names that have all four labels
+specified. Thus data for Internet address 10.2.0.52 is located at
+domain name 52.0.2.10.IN-ADDR.ARPA. The reversal, though awkward to
+read, allows zones to be delegated which are exactly one network of
+address space. For example, 10.IN-ADDR.ARPA can be a zone containing
+data for the ARPANET, while 26.IN-ADDR.ARPA can be a separate zone for
+MILNET. Address nodes are used to hold pointers to primary host names
+in the normal domain space.
+
+Network numbers correspond to some non-terminal nodes at various depths
+in the IN-ADDR.ARPA domain, since Internet network numbers are either 1,
+2, or 3 octets. Network nodes are used to hold pointers to the primary
+host names of gateways attached to that network. Since a gateway is, by
+definition, on more than one network, it will typically have two or more
+network nodes which point at it. Gateways will also have host level
+pointers at their fully qualified addresses.
+
+Both the gateway pointers at network nodes and the normal host pointers
+at full address nodes use the PTR RR to point back to the primary domain
+names of the corresponding hosts.
+
+For example, the IN-ADDR.ARPA domain will contain information about the
+ISI gateway between net 10 and 26, an MIT gateway from net 10 to MIT's
+
+
+
+Mockapetris [Page 22]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+net 18, and hosts A.ISI.EDU and MULTICS.MIT.EDU. Assuming that ISI
+gateway has addresses 10.2.0.22 and 26.0.0.103, and a name MILNET-
+GW.ISI.EDU, and the MIT gateway has addresses 10.0.0.77 and 18.10.0.4
+and a name GW.LCS.MIT.EDU, the domain database would contain:
+
+ 10.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
+ 10.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
+ 18.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
+ 26.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
+ 22.0.2.10.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
+ 103.0.0.26.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
+ 77.0.0.10.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
+ 4.0.10.18.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
+ 103.0.3.26.IN-ADDR.ARPA. PTR A.ISI.EDU.
+ 6.0.0.10.IN-ADDR.ARPA. PTR MULTICS.MIT.EDU.
+
+Thus a program which wanted to locate gateways on net 10 would originate
+a query of the form QTYPE=PTR, QCLASS=IN, QNAME=10.IN-ADDR.ARPA. It
+would receive two RRs in response:
+
+ 10.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU.
+ 10.IN-ADDR.ARPA. PTR GW.LCS.MIT.EDU.
+
+The program could then originate QTYPE=A, QCLASS=IN queries for MILNET-
+GW.ISI.EDU. and GW.LCS.MIT.EDU. to discover the Internet addresses of
+these gateways.
+
+A resolver which wanted to find the host name corresponding to Internet
+host address 10.0.0.6 would pursue a query of the form QTYPE=PTR,
+QCLASS=IN, QNAME=6.0.0.10.IN-ADDR.ARPA, and would receive:
+
+ 6.0.0.10.IN-ADDR.ARPA. PTR MULTICS.MIT.EDU.
+
+Several cautions apply to the use of these services:
+ - Since the IN-ADDR.ARPA special domain and the normal domain
+ for a particular host or gateway will be in different zones,
+ the possibility exists that that the data may be inconsistent.
+
+ - Gateways will often have two names in separate domains, only
+ one of which can be primary.
+
+ - Systems that use the domain database to initialize their
+ routing tables must start with enough gateway information to
+ guarantee that they can access the appropriate name server.
+
+ - The gateway data only reflects the existence of a gateway in a
+ manner equivalent to the current HOSTS.TXT file. It doesn't
+ replace the dynamic availability information from GGP or EGP.
+
+
+
+Mockapetris [Page 23]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+3.6. Defining new types, classes, and special namespaces
+
+The previously defined types and classes are the ones in use as of the
+date of this memo. New definitions should be expected. This section
+makes some recommendations to designers considering additions to the
+existing facilities. The mailing list NAMEDROPPERS@SRI-NIC.ARPA is the
+forum where general discussion of design issues takes place.
+
+In general, a new type is appropriate when new information is to be
+added to the database about an existing object, or we need new data
+formats for some totally new object. Designers should attempt to define
+types and their RDATA formats that are generally applicable to all
+classes, and which avoid duplication of information. New classes are
+appropriate when the DNS is to be used for a new protocol, etc which
+requires new class-specific data formats, or when a copy of the existing
+name space is desired, but a separate management domain is necessary.
+
+New types and classes need mnemonics for master files; the format of the
+master files requires that the mnemonics for type and class be disjoint.
+
+TYPE and CLASS values must be a proper subset of QTYPEs and QCLASSes
+respectively.
+
+The present system uses multiple RRs to represent multiple values of a
+type rather than storing multiple values in the RDATA section of a
+single RR. This is less efficient for most applications, but does keep
+RRs shorter. The multiple RRs assumption is incorporated in some
+experimental work on dynamic update methods.
+
+The present system attempts to minimize the duplication of data in the
+database in order to insure consistency. Thus, in order to find the
+address of the host for a mail exchange, you map the mail domain name to
+a host name, then the host name to addresses, rather than a direct
+mapping to host address. This approach is preferred because it avoids
+the opportunity for inconsistency.
+
+In defining a new type of data, multiple RR types should not be used to
+create an ordering between entries or express different formats for
+equivalent bindings, instead this information should be carried in the
+body of the RR and a single type used. This policy avoids problems with
+caching multiple types and defining QTYPEs to match multiple types.
+
+For example, the original form of mail exchange binding used two RR
+types one to represent a "closer" exchange (MD) and one to represent a
+"less close" exchange (MF). The difficulty is that the presence of one
+RR type in a cache doesn't convey any information about the other
+because the query which acquired the cached information might have used
+a QTYPE of MF, MD, or MAILA (which matched both). The redesigned
+
+
+
+Mockapetris [Page 24]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+service used a single type (MX) with a "preference" value in the RDATA
+section which can order different RRs. However, if any MX RRs are found
+in the cache, then all should be there.
+
+4. MESSAGES
+
+4.1. Format
+
+All communications inside of the domain protocol are carried in a single
+format called a message. The top level format of message is divided
+into 5 sections (some of which are empty in certain cases) shown below:
+
+ +---------------------+
+ | Header |
+ +---------------------+
+ | Question | the question for the name server
+ +---------------------+
+ | Answer | RRs answering the question
+ +---------------------+
+ | Authority | RRs pointing toward an authority
+ +---------------------+
+ | Additional | RRs holding additional information
+ +---------------------+
+
+The header section is always present. The header includes fields that
+specify which of the remaining sections are present, and also specify
+whether the message is a query or a response, a standard query or some
+other opcode, etc.
+
+The names of the sections after the header are derived from their use in
+standard queries. The question section contains fields that describe a
+question to a name server. These fields are a query type (QTYPE), a
+query class (QCLASS), and a query domain name (QNAME). The last three
+sections have the same format: a possibly empty list of concatenated
+resource records (RRs). The answer section contains RRs that answer the
+question; the authority section contains RRs that point toward an
+authoritative name server; the additional records section contains RRs
+which relate to the query, but are not strictly answers for the
+question.
+
+
+
+
+
+
+
+
+
+
+
+
+Mockapetris [Page 25]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+4.1.1. Header section format
+
+The header contains the following fields:
+
+ 1 1 1 1 1 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ID |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | QDCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ANCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | NSCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ARCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+ID A 16 bit identifier assigned by the program that
+ generates any kind of query. This identifier is copied
+ the corresponding reply and can be used by the requester
+ to match up replies to outstanding queries.
+
+QR A one bit field that specifies whether this message is a
+ query (0), or a response (1).
+
+OPCODE A four bit field that specifies kind of query in this
+ message. This value is set by the originator of a query
+ and copied into the response. The values are:
+
+ 0 a standard query (QUERY)
+
+ 1 an inverse query (IQUERY)
+
+ 2 a server status request (STATUS)
+
+ 3-15 reserved for future use
+
+AA Authoritative Answer - this bit is valid in responses,
+ and specifies that the responding name server is an
+ authority for the domain name in question section.
+
+ Note that the contents of the answer section may have
+ multiple owner names because of aliases. The AA bit
+
+
+
+Mockapetris [Page 26]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ corresponds to the name which matches the query name, or
+ the first owner name in the answer section.
+
+TC TrunCation - specifies that this message was truncated
+ due to length greater than that permitted on the
+ transmission channel.
+
+RD Recursion Desired - this bit may be set in a query and
+ is copied into the response. If RD is set, it directs
+ the name server to pursue the query recursively.
+ Recursive query support is optional.
+
+RA Recursion Available - this be is set or cleared in a
+ response, and denotes whether recursive query support is
+ available in the name server.
+
+Z Reserved for future use. Must be zero in all queries
+ and responses.
+
+RCODE Response code - this 4 bit field is set as part of
+ responses. The values have the following
+ interpretation:
+
+ 0 No error condition
+
+ 1 Format error - The name server was
+ unable to interpret the query.
+
+ 2 Server failure - The name server was
+ unable to process this query due to a
+ problem with the name server.
+
+ 3 Name Error - Meaningful only for
+ responses from an authoritative name
+ server, this code signifies that the
+ domain name referenced in the query does
+ not exist.
+
+ 4 Not Implemented - The name server does
+ not support the requested kind of query.
+
+ 5 Refused - The name server refuses to
+ perform the specified operation for
+ policy reasons. For example, a name
+ server may not wish to provide the
+ information to the particular requester,
+ or a name server may not wish to perform
+ a particular operation (e.g., zone
+
+
+
+Mockapetris [Page 27]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ transfer) for particular data.
+
+ 6-15 Reserved for future use.
+
+QDCOUNT an unsigned 16 bit integer specifying the number of
+ entries in the question section.
+
+ANCOUNT an unsigned 16 bit integer specifying the number of
+ resource records in the answer section.
+
+NSCOUNT an unsigned 16 bit integer specifying the number of name
+ server resource records in the authority records
+ section.
+
+ARCOUNT an unsigned 16 bit integer specifying the number of
+ resource records in the additional records section.
+
+4.1.2. Question section format
+
+The question section is used to carry the "question" in most queries,
+i.e., the parameters that define what is being asked. The section
+contains QDCOUNT (usually 1) entries, each of the following format:
+
+ 1 1 1 1 1 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | |
+ / QNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | QTYPE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | QCLASS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+QNAME a domain name represented as a sequence of labels, where
+ each label consists of a length octet followed by that
+ number of octets. The domain name terminates with the
+ zero length octet for the null label of the root. Note
+ that this field may be an odd number of octets; no
+ padding is used.
+
+QTYPE a two octet code which specifies the type of the query.
+ The values for this field include all codes valid for a
+ TYPE field, together with some more general codes which
+ can match more than one type of RR.
+
+
+
+Mockapetris [Page 28]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+QCLASS a two octet code that specifies the class of the query.
+ For example, the QCLASS field is IN for the Internet.
+
+4.1.3. Resource record format
+
+The answer, authority, and additional sections all share the same
+format: a variable number of resource records, where the number of
+records is specified in the corresponding count field in the header.
+Each resource record has the following format:
+ 1 1 1 1 1 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | |
+ / /
+ / NAME /
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | TYPE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | CLASS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | TTL |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | RDLENGTH |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
+ / RDATA /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+where:
+
+NAME a domain name to which this resource record pertains.
+
+TYPE two octets containing one of the RR type codes. This
+ field specifies the meaning of the data in the RDATA
+ field.
+
+CLASS two octets which specify the class of the data in the
+ RDATA field.
+
+TTL a 32 bit unsigned integer that specifies the time
+ interval (in seconds) that the resource record may be
+ cached before it should be discarded. Zero values are
+ interpreted to mean that the RR can only be used for the
+ transaction in progress, and should not be cached.
+
+
+
+
+
+Mockapetris [Page 29]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+RDLENGTH an unsigned 16 bit integer that specifies the length in
+ octets of the RDATA field.
+
+RDATA a variable length string of octets that describes the
+ resource. The format of this information varies
+ according to the TYPE and CLASS of the resource record.
+ For example, the if the TYPE is A and the CLASS is IN,
+ the RDATA field is a 4 octet ARPA Internet address.
+
+4.1.4. Message compression
+
+In order to reduce the size of messages, the domain system utilizes a
+compression scheme which eliminates the repetition of domain names in a
+message. 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 occurrence
+of the same name.
+
+The pointer takes the form of a two octet sequence:
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | 1 1| OFFSET |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+The first two bits are ones. This allows a pointer to be distinguished
+from a label, since the label must begin with two zero bits because
+labels are restricted to 63 octets or less. (The 10 and 01 combinations
+are reserved for future use.) The OFFSET field specifies an offset from
+the start of the message (i.e., the first octet of the ID field in the
+domain header). A zero offset specifies the first byte of the ID field,
+etc.
+
+The compression scheme allows a domain name in a message to be
+represented as either:
+
+ - a sequence of labels ending in a zero octet
+
+ - a pointer
+
+ - a sequence of labels ending with a pointer
+
+Pointers can only be used for occurrences of a domain name where the
+format is not class specific. If this were not the case, a name server
+or resolver would be required to know the format of all RRs it handled.
+As yet, there are no such cases, but they may occur in future RDATA
+formats.
+
+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
+
+
+
+Mockapetris [Page 30]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+used, the length of the compressed name is used in the length
+calculation, rather than the length of the expanded name.
+
+Programs are free to avoid using pointers in messages they generate,
+although this will reduce datagram capacity, and may cause truncation.
+However all programs are required to understand arriving messages that
+contain pointers.
+
+For example, a datagram might need to use the domain names F.ISI.ARPA,
+FOO.F.ISI.ARPA, ARPA, and the root. Ignoring the other fields of the
+message, these domain names might be represented as:
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 20 | 1 | F |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 22 | 3 | I |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 24 | S | I |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 26 | 4 | A |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 28 | R | P |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 30 | A | 0 |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 40 | 3 | F |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 42 | O | O |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 44 | 1 1| 20 |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 64 | 1 1| 26 |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ 92 | 0 | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+The domain name for F.ISI.ARPA is shown at offset 20. The domain name
+FOO.F.ISI.ARPA is shown at offset 40; this definition uses a pointer to
+concatenate a label for FOO to the previously defined F.ISI.ARPA. The
+domain name ARPA is defined at offset 64 using a pointer to the ARPA
+component of the name F.ISI.ARPA at 20; note that this pointer relies on
+ARPA being the last label in the string at 20. The root domain name is
+
+
+
+Mockapetris [Page 31]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+defined by a single octet of zeros at 92; the root domain name has no
+labels.
+
+4.2. Transport
+
+The DNS assumes that messages will be transmitted as datagrams or in a
+byte stream carried by a virtual circuit. While virtual circuits can be
+used for any DNS activity, datagrams are preferred for queries due to
+their lower overhead and better performance. Zone refresh activities
+must use virtual circuits because of the need for reliable transfer.
+
+The Internet supports name server access using TCP [RFC-793] on server
+port 53 (decimal) as well as datagram access using UDP [RFC-768] on UDP
+port 53 (decimal).
+
+4.2.1. UDP usage
+
+Messages sent using UDP user server port 53 (decimal).
+
+Messages carried by UDP are restricted to 512 bytes (not counting the IP
+or UDP headers). Longer messages are truncated and the TC bit is set in
+the header.
+
+UDP is not acceptable for zone transfers, but is the recommended method
+for standard queries in the Internet. Queries sent using UDP may be
+lost, and hence a retransmission strategy is required. Queries or their
+responses may be reordered by the network, or by processing in name
+servers, so resolvers should not depend on them being returned in order.
+
+The optimal UDP retransmission policy will vary with performance of the
+Internet and the needs of the client, but the following are recommended:
+
+ - The client should try other servers and server addresses
+ before repeating a query to a specific address of a server.
+
+ - The retransmission interval should be based on prior
+ statistics if possible. Too aggressive retransmission can
+ easily slow responses for the community at large. Depending
+ on how well connected the client is to its expected servers,
+ the minimum retransmission interval should be 2-5 seconds.
+
+More suggestions on server selection and retransmission policy can be
+found in the resolver section of this memo.
+
+4.2.2. TCP usage
+
+Messages sent over TCP connections use server port 53 (decimal). The
+message is prefixed with a two byte length field which gives the message
+
+
+
+Mockapetris [Page 32]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+length, excluding the two byte length field. This length field allows
+the low-level processing to assemble a complete message before beginning
+to parse it.
+
+Several connection management policies are recommended:
+
+ - The server should not block other activities waiting for TCP
+ data.
+
+ - The server should support multiple connections.
+
+ - The server should assume that the client will initiate
+ connection closing, and should delay closing its end of the
+ connection until all outstanding client requests have been
+ satisfied.
+
+ - If the server needs to close a dormant connection to reclaim
+ resources, it should wait until the connection has been idle
+ for a period on the order of two minutes. In particular, the
+ server should allow the SOA and AXFR request sequence (which
+ begins a refresh operation) to be made on a single connection.
+ Since the server would be unable to answer queries anyway, a
+ unilateral close or reset may be used instead of a graceful
+ close.
+
+5. MASTER FILES
+
+Master files are text files that contain RRs in text form. Since the
+contents of a zone can be expressed in the form of a list of RRs a
+master file is most often used to define a zone, though it can be used
+to list a cache's contents. Hence, this section first discusses the
+format of RRs in a master file, and then the special considerations when
+a master file is used to create a zone in some name server.
+
+5.1. Format
+
+The format of these files is a sequence of entries. Entries are
+predominantly line-oriented, though parentheses can be used to continue
+a list of items across a line boundary, and text literals can contain
+CRLF within the text. Any combination of tabs and spaces act as a
+delimiter between the separate items that make up an entry. The end of
+any line in the master file can end with a comment. The comment starts
+with a ";" (semicolon).
+
+The following entries are defined:
+
+ <blank>[<comment>]
+
+
+
+
+Mockapetris [Page 33]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ $ORIGIN <domain-name> [<comment>]
+
+ $INCLUDE <file-name> [<domain-name>] [<comment>]
+
+ <domain-name><rr> [<comment>]
+
+ <blank><rr> [<comment>]
+
+Blank lines, with or without comments, are allowed anywhere in the file.
+
+Two control entries are defined: $ORIGIN and $INCLUDE. $ORIGIN is
+followed by a domain name, and resets the current origin for relative
+domain names to the stated name. $INCLUDE inserts the named file into
+the current file, and may optionally specify a domain name that sets the
+relative domain name origin for the included file. $INCLUDE may also
+have a comment. Note that a $INCLUDE entry never changes the relative
+origin of the parent file, regardless of changes to the relative origin
+made within the included file.
+
+The last two forms represent RRs. If an entry for an RR begins with a
+blank, then the RR is assumed to be owned by the last stated owner. If
+an RR entry begins with a <domain-name>, then the owner name is reset.
+
+<rr> contents take one of the following forms:
+
+ [<TTL>] [<class>] <type> <RDATA>
+
+ [<class>] [<TTL>] <type> <RDATA>
+
+The RR begins with optional TTL and class fields, followed by a type and
+RDATA field appropriate to the type and class. Class and type use the
+standard mnemonics, TTL is a decimal integer. Omitted class and TTL
+values are default to the last explicitly stated values. Since type and
+class mnemonics are disjoint, the parse is unique. (Note that this
+order is different from the order used in examples and the order used in
+the actual RRs; the given order allows easier parsing and defaulting.)
+
+<domain-name>s make up a large share of the data in the master file.
+The labels in the domain name are expressed as character strings and
+separated by dots. Quoting conventions allow arbitrary characters to be
+stored in domain names. Domain names that end in a dot are called
+absolute, and are taken as complete. Domain names which do not end in a
+dot are called relative; the actual domain name is the concatenation of
+the relative part with an origin specified in a $ORIGIN, $INCLUDE, or as
+an argument to the master file loading routine. A relative name is an
+error when no origin is available.
+
+
+
+
+
+Mockapetris [Page 34]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+<character-string> is expressed in one or two ways: as a contiguous set
+of characters without interior spaces, or as a string beginning with a "
+and ending with a ". Inside a " delimited string any character can
+occur, except for a " itself, which must be quoted using \ (back slash).
+
+Because these files are text files several special encodings are
+necessary to allow arbitrary data to be loaded. In particular:
+
+ of the root.
+
+@ A free standing @ is used to denote the current origin.
+
+\X where X is any character other than a digit (0-9), is
+ used to quote that character so that its special meaning
+ does not apply. For example, "\." can be used to place
+ a dot character in a label.
+
+\DDD where each D is a digit is the octet corresponding to
+ the decimal number described by DDD. The resulting
+ octet is assumed to be text and is not checked for
+ special meaning.
+
+( ) Parentheses are used to group data that crosses a line
+ boundary. In effect, line terminations are not
+ recognized within parentheses.
+
+; Semicolon is used to start a comment; the remainder of
+ the line is ignored.
+
+5.2. Use of master files to define zones
+
+When a master file is used to load a zone, the operation should be
+suppressed if any errors are encountered in the master file. The
+rationale for this is that a single error can have widespread
+consequences. For example, suppose that the RRs defining a delegation
+have syntax errors; then the server will return authoritative name
+errors for all names in the subzone (except in the case where the
+subzone is also present on the server).
+
+Several other validity checks that should be performed in addition to
+insuring that the file is syntactically correct:
+
+ 1. All RRs in the file should have the same class.
+
+ 2. Exactly one SOA RR should be present at the top of the zone.
+
+ 3. If delegations are present and glue information is required,
+ it should be present.
+
+
+
+Mockapetris [Page 35]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ 4. Information present outside of the authoritative nodes in the
+ zone should be glue information, rather than the result of an
+ origin or similar error.
+
+5.3. Master file example
+
+The following is an example file which might be used to define the
+ISI.EDU zone.and is loaded with an origin of ISI.EDU:
+
+@ IN SOA VENERA Action\.domains (
+ 20 ; SERIAL
+ 7200 ; REFRESH
+ 600 ; RETRY
+ 3600000; EXPIRE
+ 60) ; MINIMUM
+
+ NS A.ISI.EDU.
+ NS VENERA
+ NS VAXA
+ MX 10 VENERA
+ MX 20 VAXA
+
+A A 26.3.0.103
+
+VENERA A 10.1.0.52
+ A 128.9.0.32
+
+VAXA A 10.2.0.27
+ A 128.9.0.33
+
+
+$INCLUDE <SUBSYS>ISI-MAILBOXES.TXT
+
+Where the file <SUBSYS>ISI-MAILBOXES.TXT is:
+
+ MOE MB A.ISI.EDU.
+ LARRY MB A.ISI.EDU.
+ CURLEY MB A.ISI.EDU.
+ STOOGES MG MOE
+ MG LARRY
+ MG CURLEY
+
+Note the use of the \ character in the SOA RR to specify the responsible
+person mailbox "Action.domains@E.ISI.EDU".
+
+
+
+
+
+
+
+Mockapetris [Page 36]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+6. NAME SERVER IMPLEMENTATION
+
+6.1. Architecture
+
+The optimal structure for the name server will depend on the host
+operating system and whether the name server is integrated with resolver
+operations, either by supporting recursive service, or by sharing its
+database with a resolver. This section discusses implementation
+considerations for a name server which shares a database with a
+resolver, but most of these concerns are present in any name server.
+
+6.1.1. Control
+
+A name server must employ multiple concurrent activities, whether they
+are implemented as separate tasks in the host's OS or multiplexing
+inside a single name server program. It is simply not acceptable for a
+name server to block the service of UDP requests while it waits for TCP
+data for refreshing or query activities. Similarly, a name server
+should not attempt to provide recursive service without processing such
+requests in parallel, though it may choose to serialize requests from a
+single client, or to regard identical requests from the same client as
+duplicates. A name server should not substantially delay requests while
+it reloads a zone from master files or while it incorporates a newly
+refreshed zone into its database.
+
+6.1.2. Database
+
+While name server implementations are free to use any internal data
+structures they choose, the suggested structure consists of three major
+parts:
+
+ - A "catalog" data structure which lists the zones available to
+ this server, and a "pointer" to the zone data structure. The
+ main purpose of this structure is to find the nearest ancestor
+ zone, if any, for arriving standard queries.
+
+ - Separate data structures for each of the zones held by the
+ name server.
+
+ - A data structure for cached data. (or perhaps separate caches
+ for different classes)
+
+All of these data structures can be implemented an identical tree
+structure format, with different data chained off the nodes in different
+parts: in the catalog the data is pointers to zones, while in the zone
+and cache data structures, the data will be RRs. In designing the tree
+framework the designer should recognize that query processing will need
+to traverse the tree using case-insensitive label comparisons; and that
+
+
+
+Mockapetris [Page 37]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+in real data, a few nodes have a very high branching factor (100-1000 or
+more), but the vast majority have a very low branching factor (0-1).
+
+One way to solve the case problem is to store the labels for each node
+in two pieces: a standardized-case representation of the label where all
+ASCII characters are in a single case, together with a bit mask that
+denotes which characters are actually of a different case. The
+branching factor diversity can be handled using a simple linked list for
+a node until the branching factor exceeds some threshold, and
+transitioning to a hash structure after the threshold is exceeded. In
+any case, hash structures used to store tree sections must insure that
+hash functions and procedures preserve the casing conventions of the
+DNS.
+
+The use of separate structures for the different parts of the database
+is motivated by several factors:
+
+ - The catalog structure can be an almost static structure that
+ need change only when the system administrator changes the
+ zones supported by the server. This structure can also be
+ used to store parameters used to control refreshing
+ activities.
+
+ - The individual data structures for zones allow a zone to be
+ replaced simply by changing a pointer in the catalog. Zone
+ refresh operations can build a new structure and, when
+ complete, splice it into the database via a simple pointer
+ replacement. It is very important that when a zone is
+ refreshed, queries should not use old and new data
+ simultaneously.
+
+ - With the proper search procedures, authoritative data in zones
+ will always "hide", and hence take precedence over, cached
+ data.
+
+ - Errors in zone definitions that cause overlapping zones, etc.,
+ may cause erroneous responses to queries, but problem
+ determination is simplified, and the contents of one "bad"
+ zone can't corrupt another.
+
+ - Since the cache is most frequently updated, it is most
+ vulnerable to corruption during system restarts. It can also
+ become full of expired RR data. In either case, it can easily
+ be discarded without disturbing zone data.
+
+A major aspect of database design is selecting a structure which allows
+the name server to deal with crashes of the name server's host. State
+information which a name server should save across system crashes
+
+
+
+Mockapetris [Page 38]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+includes the catalog structure (including the state of refreshing for
+each zone) and the zone data itself.
+
+6.1.3. Time
+
+Both the TTL data for RRs and the timing data for refreshing activities
+depends on 32 bit timers in units of seconds. Inside the database,
+refresh timers and TTLs for cached data conceptually "count down", while
+data in the zone stays with constant TTLs.
+
+A recommended implementation strategy is to store time in two ways: as
+a relative increment and as an absolute time. One way to do this is to
+use positive 32 bit numbers for one type and negative numbers for the
+other. The RRs in zones use relative times; the refresh timers and
+cache data use absolute times. Absolute numbers are taken with respect
+to some known origin and converted to relative values when placed in the
+response to a query. When an absolute TTL is negative after conversion
+to relative, then the data is expired and should be ignored.
+
+6.2. Standard query processing
+
+The major algorithm for standard query processing is presented in
+[RFC-1034].
+
+When processing queries with QCLASS=*, or some other QCLASS which
+matches multiple classes, the response should never be authoritative
+unless the server can guarantee that the response covers all classes.
+
+When composing a response, RRs which are to be inserted in the
+additional section, but duplicate RRs in the answer or authority
+sections, may be omitted from the additional section.
+
+When a response is so long that truncation is required, the truncation
+should start at the end of the response and work forward in the
+datagram. Thus if there is any data for the authority section, the
+answer section is guaranteed to be unique.
+
+The MINIMUM value in the SOA should be used to set a floor on the TTL of
+data distributed from a zone. This floor function should be done when
+the data is copied into a response. This will allow future dynamic
+update protocols to change the SOA MINIMUM field without ambiguous
+semantics.
+
+6.3. Zone refresh and reload processing
+
+In spite of a server's best efforts, it may be unable to load zone data
+from a master file due to syntax errors, etc., or be unable to refresh a
+zone within the its expiration parameter. In this case, the name server
+
+
+
+Mockapetris [Page 39]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+should answer queries as if it were not supposed to possess the zone.
+
+If a master is sending a zone out via AXFR, and a new version is created
+during the transfer, the master should continue to send the old version
+if possible. In any case, it should never send part of one version and
+part of another. If completion is not possible, the master should reset
+the connection on which the zone transfer is taking place.
+
+6.4. Inverse queries (Optional)
+
+Inverse queries are an optional part of the DNS. Name servers are not
+required to support any form of inverse queries. If a name server
+receives an inverse query that it does not support, it returns an error
+response with the "Not Implemented" error set in the header. While
+inverse query support is optional, all name servers must be at least
+able to return the error response.
+
+6.4.1. The contents of inverse queries and responses Inverse
+queries reverse the mappings performed by standard query operations;
+while a standard query maps a domain name to a resource, an inverse
+query maps a resource to a domain name. For example, a standard query
+might bind a domain name to a host address; the corresponding inverse
+query binds the host address to a domain name.
+
+Inverse queries take the form of a single RR in the answer section of
+the message, with an empty question section. The owner name of the
+query RR and its TTL are not significant. The response carries
+questions in the question section which identify all names possessing
+the query RR WHICH THE NAME SERVER KNOWS. Since no name server knows
+about all of the domain name space, the response can never be assumed to
+be complete. Thus inverse queries are primarily useful for database
+management and debugging activities. Inverse queries are NOT an
+acceptable method of mapping host addresses to host names; use the IN-
+ADDR.ARPA domain instead.
+
+Where possible, name servers should provide case-insensitive comparisons
+for inverse queries. Thus an inverse query asking for an MX RR of
+"Venera.isi.edu" should get the same response as a query for
+"VENERA.ISI.EDU"; an inverse query for HINFO RR "IBM-PC UNIX" should
+produce the same result as an inverse query for "IBM-pc unix". However,
+this cannot be guaranteed because name servers may possess RRs that
+contain character strings but the name server does not know that the
+data is character.
+
+When a name server processes an inverse query, it either returns:
+
+ 1. zero, one, or multiple domain names for the specified
+ resource as QNAMEs in the question section
+
+
+
+Mockapetris [Page 40]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ 2. an error code indicating that the name server doesn't support
+ inverse mapping of the specified resource type.
+
+When the response to an inverse query contains one or more QNAMEs, the
+owner name and TTL of the RR in the answer section which defines the
+inverse query is modified to exactly match an RR found at the first
+QNAME.
+
+RRs returned in the inverse queries cannot be cached using the same
+mechanism as is used for the replies to standard queries. One reason
+for this is that a name might have multiple RRs of the same type, and
+only one would appear. For example, an inverse query for a single
+address of a multiply homed host might create the impression that only
+one address existed.
+
+6.4.2. Inverse query and response example The overall structure
+of an inverse query for retrieving the domain name that corresponds to
+Internet address 10.1.0.52 is shown below:
+
+ +-----------------------------------------+
+ Header | OPCODE=IQUERY, ID=997 |
+ +-----------------------------------------+
+ Question | <empty> |
+ +-----------------------------------------+
+ Answer | <anyname> A IN 10.1.0.52 |
+ +-----------------------------------------+
+ Authority | <empty> |
+ +-----------------------------------------+
+ Additional | <empty> |
+ +-----------------------------------------+
+
+This query asks for a question whose answer is the Internet style
+address 10.1.0.52. Since the owner name is not known, any domain name
+can be used as a placeholder (and is ignored). A single octet of zero,
+signifying the root, is usually used because it minimizes the length of
+the message. The TTL of the RR is not significant. The response to
+this query might be:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Mockapetris [Page 41]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ +-----------------------------------------+
+ Header | OPCODE=RESPONSE, ID=997 |
+ +-----------------------------------------+
+ Question |QTYPE=A, QCLASS=IN, QNAME=VENERA.ISI.EDU |
+ +-----------------------------------------+
+ Answer | VENERA.ISI.EDU A IN 10.1.0.52 |
+ +-----------------------------------------+
+ Authority | <empty> |
+ +-----------------------------------------+
+ Additional | <empty> |
+ +-----------------------------------------+
+
+Note that the QTYPE in a response to an inverse query is the same as the
+TYPE field in the answer section of the inverse query. Responses to
+inverse queries may contain multiple questions when the inverse is not
+unique. If the question section in the response is not empty, then the
+RR in the answer section is modified to correspond to be an exact copy
+of an RR at the first QNAME.
+
+6.4.3. Inverse query processing
+
+Name servers that support inverse queries can support these operations
+through exhaustive searches of their databases, but this becomes
+impractical as the size of the database increases. An alternative
+approach is to invert the database according to the search key.
+
+For name servers that support multiple zones and a large amount of data,
+the recommended approach is separate inversions for each zone. When a
+particular zone is changed during a refresh, only its inversions need to
+be redone.
+
+Support for transfer of this type of inversion may be included in future
+versions of the domain system, but is not supported in this version.
+
+6.5. Completion queries and responses
+
+The optional completion services described in RFC-882 and RFC-883 have
+been deleted. Redesigned services may become available in the future.
+
+
+
+
+
+
+
+
+
+
+
+
+
+Mockapetris [Page 42]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+7. RESOLVER IMPLEMENTATION
+
+The top levels of the recommended resolver algorithm are discussed in
+[RFC-1034]. This section discusses implementation details assuming the
+database structure suggested in the name server implementation section
+of this memo.
+
+7.1. Transforming a user request into a query
+
+The first step a resolver takes is to transform the client's request,
+stated in a format suitable to the local OS, into a search specification
+for RRs at a specific name which match a specific QTYPE and QCLASS.
+Where possible, the QTYPE and QCLASS should correspond to a single type
+and a single class, because this makes the use of cached data much
+simpler. The reason for this is that the presence of data of one type
+in a cache doesn't confirm the existence or non-existence of data of
+other types, hence the only way to be sure is to consult an
+authoritative source. If QCLASS=* is used, then authoritative answers
+won't be available.
+
+Since a resolver must be able to multiplex multiple requests if it is to
+perform its function efficiently, each pending request is usually
+represented in some block of state information. This state block will
+typically contain:
+
+ - A timestamp indicating the time the request began.
+ The timestamp is used to decide whether RRs in the database
+ can be used or are out of date. This timestamp uses the
+ absolute time format previously discussed for RR storage in
+ zones and caches. Note that when an RRs TTL indicates a
+ relative time, the RR must be timely, since it is part of a
+ zone. When the RR has an absolute time, it is part of a
+ cache, and the TTL of the RR is compared against the timestamp
+ for the start of the request.
+
+ Note that using the timestamp is superior to using a current
+ time, since it allows RRs with TTLs of zero to be entered in
+ the cache in the usual manner, but still used by the current
+ request, even after intervals of many seconds due to system
+ load, query retransmission timeouts, etc.
+
+ - Some sort of parameters to limit the amount of work which will
+ be performed for this request.
+
+ The amount of work which a resolver will do in response to a
+ client request must be limited to guard against errors in the
+ database, such as circular CNAME references, and operational
+ problems, such as network partition which prevents the
+
+
+
+Mockapetris [Page 43]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ resolver from accessing the name servers it needs. While
+ local limits on the number of times a resolver will retransmit
+ a particular query to a particular name server address are
+ essential, the resolver should have a global per-request
+ counter to limit work on a single request. The counter should
+ be set to some initial value and decremented whenever the
+ resolver performs any action (retransmission timeout,
+ retransmission, etc.) If the counter passes zero, the request
+ is terminated with a temporary error.
+
+ Note that if the resolver structure allows one request to
+ start others in parallel, such as when the need to access a
+ name server for one request causes a parallel resolve for the
+ name server's addresses, the spawned request should be started
+ with a lower counter. This prevents circular references in
+ the database from starting a chain reaction of resolver
+ activity.
+
+ - The SLIST data structure discussed in [RFC-1034].
+
+ This structure keeps track of the state of a request if it
+ must wait for answers from foreign name servers.
+
+7.2. Sending the queries
+
+As described in [RFC-1034], the basic task of the resolver is to
+formulate a query which will answer the client's request and direct that
+query to name servers which can provide the information. The resolver
+will usually only have very strong hints about which servers to ask, in
+the form of NS RRs, and may have to revise the query, in response to
+CNAMEs, or revise the set of name servers the resolver is asking, in
+response to delegation responses which point the resolver to name
+servers closer to the desired information. In addition to the
+information requested by the client, the resolver may have to call upon
+its own services to determine the address of name servers it wishes to
+contact.
+
+In any case, the model used in this memo assumes that the resolver is
+multiplexing attention between multiple requests, some from the client,
+and some internally generated. Each request is represented by some
+state information, and the desired behavior is that the resolver
+transmit queries to name servers in a way that maximizes the probability
+that the request is answered, minimizes the time that the request takes,
+and avoids excessive transmissions. The key algorithm uses the state
+information of the request to select the next name server address to
+query, and also computes a timeout which will cause the next action
+should a response not arrive. The next action will usually be a
+transmission to some other server, but may be a temporary error to the
+
+
+
+Mockapetris [Page 44]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+client.
+
+The resolver always starts with a list of server names to query (SLIST).
+This list will be all NS RRs which correspond to the nearest ancestor
+zone that the resolver knows about. To avoid startup problems, the
+resolver should have a set of default servers which it will ask should
+it have no current NS RRs which are appropriate. The resolver then adds
+to SLIST all of the known addresses for the name servers, and may start
+parallel requests to acquire the addresses of the servers when the
+resolver has the name, but no addresses, for the name servers.
+
+To complete initialization of SLIST, the resolver attaches whatever
+history information it has to the each address in SLIST. This will
+usually consist of some sort of weighted averages for the response time
+of the address, and the batting average of the address (i.e., how often
+the address responded at all to the request). Note that this
+information should be kept on a per address basis, rather than on a per
+name server basis, because the response time and batting average of a
+particular server may vary considerably from address to address. Note
+also that this information is actually specific to a resolver address /
+server address pair, so a resolver with multiple addresses may wish to
+keep separate histories for each of its addresses. Part of this step
+must deal with addresses which have no such history; in this case an
+expected round trip time of 5-10 seconds should be the worst case, with
+lower estimates for the same local network, etc.
+
+Note that whenever a delegation is followed, the resolver algorithm
+reinitializes SLIST.
+
+The information establishes a partial ranking of the available name
+server addresses. Each time an address is chosen and the state should
+be altered to prevent its selection again until all other addresses have
+been tried. The timeout for each transmission should be 50-100% greater
+than the average predicted value to allow for variance in response.
+
+Some fine points:
+
+ - The resolver may encounter a situation where no addresses are
+ available for any of the name servers named in SLIST, and
+ where the servers in the list are precisely those which would
+ normally be used to look up their own addresses. This
+ situation typically occurs when the glue address RRs have a
+ smaller TTL than the NS RRs marking delegation, or when the
+ resolver caches the result of a NS search. The resolver
+ should detect this condition and restart the search at the
+ next ancestor zone, or alternatively at the root.
+
+
+
+
+
+Mockapetris [Page 45]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ - If a resolver gets a server error or other bizarre response
+ from a name server, it should remove it from SLIST, and may
+ wish to schedule an immediate transmission to the next
+ candidate server address.
+
+7.3. Processing responses
+
+The first step in processing arriving response datagrams is to parse the
+response. This procedure should include:
+
+ - Check the header for reasonableness. Discard datagrams which
+ are queries when responses are expected.
+
+ - Parse the sections of the message, and insure that all RRs are
+ correctly formatted.
+
+ - As an optional step, check the TTLs of arriving data looking
+ for RRs with excessively long TTLs. If a RR has an
+ excessively long TTL, say greater than 1 week, either discard
+ the whole response, or limit all TTLs in the response to 1
+ week.
+
+The next step is to match the response to a current resolver request.
+The recommended strategy is to do a preliminary matching using the ID
+field in the domain header, and then to verify that the question section
+corresponds to the information currently desired. This requires that
+the transmission algorithm devote several bits of the domain ID field to
+a request identifier of some sort. This step has several fine points:
+
+ - Some name servers send their responses from different
+ addresses than the one used to receive the query. That is, a
+ resolver cannot rely that a response will come from the same
+ address which it sent the corresponding query to. This name
+ server bug is typically encountered in UNIX systems.
+
+ - If the resolver retransmits a particular request to a name
+ server it should be able to use a response from any of the
+ transmissions. However, if it is using the response to sample
+ the round trip time to access the name server, it must be able
+ to determine which transmission matches the response (and keep
+ transmission times for each outgoing message), or only
+ calculate round trip times based on initial transmissions.
+
+ - A name server will occasionally not have a current copy of a
+ zone which it should have according to some NS RRs. The
+ resolver should simply remove the name server from the current
+ SLIST, and continue.
+
+
+
+
+Mockapetris [Page 46]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+7.4. Using the cache
+
+In general, we expect a resolver to cache all data which it receives in
+responses since it may be useful in answering future client requests.
+However, there are several types of data which should not be cached:
+
+ - When several RRs of the same type are available for a
+ particular owner name, the resolver should either cache them
+ all or none at all. When a response is truncated, and a
+ resolver doesn't know whether it has a complete set, it should
+ not cache a possibly partial set of RRs.
+
+ - Cached data should never be used in preference to
+ authoritative data, so if caching would cause this to happen
+ the data should not be cached.
+
+ - The results of an inverse query should not be cached.
+
+ - The results of standard queries where the QNAME contains "*"
+ labels if the data might be used to construct wildcards. The
+ reason is that the cache does not necessarily contain existing
+ RRs or zone boundary information which is necessary to
+ restrict the application of the wildcard RRs.
+
+ - RR data in responses of dubious reliability. When a resolver
+ receives unsolicited responses or RR data other than that
+ requested, it should discard it without caching it. The basic
+ implication is that all sanity checks on a packet should be
+ performed before any of it is cached.
+
+In a similar vein, when a resolver has a set of RRs for some name in a
+response, and wants to cache the RRs, it should check its cache for
+already existing RRs. Depending on the circumstances, either the data
+in the response or the cache is preferred, but the two should never be
+combined. If the data in the response is from authoritative data in the
+answer section, it is always preferred.
+
+8. MAIL SUPPORT
+
+The domain system defines a standard for mapping mailboxes into domain
+names, and two methods for using the mailbox information to derive mail
+routing information. The first method is called mail exchange binding
+and the other method is mailbox binding. The mailbox encoding standard
+and mail exchange binding are part of the DNS official protocol, and are
+the recommended method for mail routing in the Internet. Mailbox
+binding is an experimental feature which is still under development and
+subject to change.
+
+
+
+
+Mockapetris [Page 47]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+The mailbox encoding standard assumes a mailbox name of the form
+"<local-part>@<mail-domain>". While the syntax allowed in each of these
+sections varies substantially between the various mail internets, the
+preferred syntax for the ARPA Internet is given in [RFC-822].
+
+The DNS encodes the <local-part> as a single label, and encodes the
+<mail-domain> as a domain name. The single label from the <local-part>
+is prefaced to the domain name from <mail-domain> to form the domain
+name corresponding to the mailbox. Thus the mailbox HOSTMASTER@SRI-
+NIC.ARPA is mapped into the domain name HOSTMASTER.SRI-NIC.ARPA. If the
+<local-part> contains dots or other special characters, its
+representation in a master file will require the use of backslash
+quoting to ensure that the domain name is properly encoded. For
+example, the mailbox Action.domains@ISI.EDU would be represented as
+Action\.domains.ISI.EDU.
+
+8.1. Mail exchange binding
+
+Mail exchange binding uses the <mail-domain> part of a mailbox
+specification to determine where mail should be sent. The <local-part>
+is not even consulted. [RFC-974] specifies this method in detail, and
+should be consulted before attempting to use mail exchange support.
+
+One of the advantages of this method is that it decouples mail
+destination naming from the hosts used to support mail service, at the
+cost of another layer of indirection in the lookup function. However,
+the addition layer should eliminate the need for complicated "%", "!",
+etc encodings in <local-part>.
+
+The essence of the method is that the <mail-domain> is used as a domain
+name to locate type MX RRs which list hosts willing to accept mail for
+<mail-domain>, together with preference values which rank the hosts
+according to an order specified by the administrators for <mail-domain>.
+
+In this memo, the <mail-domain> ISI.EDU is used in examples, together
+with the hosts VENERA.ISI.EDU and VAXA.ISI.EDU as mail exchanges for
+ISI.EDU. If a mailer had a message for Mockapetris@ISI.EDU, it would
+route it by looking up MX RRs for ISI.EDU. The MX RRs at ISI.EDU name
+VENERA.ISI.EDU and VAXA.ISI.EDU, and type A queries can find the host
+addresses.
+
+8.2. Mailbox binding (Experimental)
+
+In mailbox binding, the mailer uses the entire mail destination
+specification to construct a domain name. The encoded domain name for
+the mailbox is used as the QNAME field in a QTYPE=MAILB query.
+
+Several outcomes are possible for this query:
+
+
+
+Mockapetris [Page 48]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ 1. The query can return a name error indicating that the mailbox
+ does not exist as a domain name.
+
+ In the long term, this would indicate that the specified
+ mailbox doesn't exist. However, until the use of mailbox
+ binding is universal, this error condition should be
+ interpreted to mean that the organization identified by the
+ global part does not support mailbox binding. The
+ appropriate procedure is to revert to exchange binding at
+ this point.
+
+ 2. The query can return a Mail Rename (MR) RR.
+
+ The MR RR carries new mailbox specification in its RDATA
+ field. The mailer should replace the old mailbox with the
+ new one and retry the operation.
+
+ 3. The query can return a MB RR.
+
+ The MB RR carries a domain name for a host in its RDATA
+ field. The mailer should deliver the message to that host
+ via whatever protocol is applicable, e.g., b,SMTP.
+
+ 4. The query can return one or more Mail Group (MG) RRs.
+
+ This condition means that the mailbox was actually a mailing
+ list or mail group, rather than a single mailbox. Each MG RR
+ has a RDATA field that identifies a mailbox that is a member
+ of the group. The mailer should deliver a copy of the
+ message to each member.
+
+ 5. The query can return a MB RR as well as one or more MG RRs.
+
+ This condition means the the mailbox was actually a mailing
+ list. The mailer can either deliver the message to the host
+ specified by the MB RR, which will in turn do the delivery to
+ all members, or the mailer can use the MG RRs to do the
+ expansion itself.
+
+In any of these cases, the response may include a Mail Information
+(MINFO) RR. This RR is usually associated with a mail group, but is
+legal with a MB. The MINFO RR identifies two mailboxes. One of these
+identifies a responsible person for the original mailbox name. This
+mailbox should be used for requests to be added to a mail group, etc.
+The second mailbox name in the MINFO RR identifies a mailbox that should
+receive error messages for mail failures. This is particularly
+appropriate for mailing lists when errors in member names should be
+reported to a person other than the one who sends a message to the list.
+
+
+
+Mockapetris [Page 49]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+New fields may be added to this RR in the future.
+
+
+9. REFERENCES and BIBLIOGRAPHY
+
+[Dyer 87] S. Dyer, F. Hsu, "Hesiod", Project Athena
+ Technical Plan - Name Service, April 1987, version 1.9.
+
+ Describes the fundamentals of the Hesiod name service.
+
+[IEN-116] J. Postel, "Internet Name Server", IEN-116,
+ USC/Information Sciences Institute, August 1979.
+
+ A name service obsoleted by the Domain Name System, but
+ still in use.
+
+[Quarterman 86] J. Quarterman, and J. Hoskins, "Notable Computer Networks",
+ Communications of the ACM, October 1986, volume 29, number
+ 10.
+
+[RFC-742] K. Harrenstien, "NAME/FINGER", RFC-742, Network
+ Information Center, SRI International, December 1977.
+
+[RFC-768] J. Postel, "User Datagram Protocol", RFC-768,
+ USC/Information Sciences Institute, August 1980.
+
+[RFC-793] J. Postel, "Transmission Control Protocol", RFC-793,
+ USC/Information Sciences Institute, September 1981.
+
+[RFC-799] D. Mills, "Internet Name Domains", RFC-799, COMSAT,
+ September 1981.
+
+ Suggests introduction of a hierarchy in place of a flat
+ name space for the Internet.
+
+[RFC-805] J. Postel, "Computer Mail Meeting Notes", RFC-805,
+ USC/Information Sciences Institute, February 1982.
+
+[RFC-810] E. Feinler, K. Harrenstien, Z. Su, and V. White, "DOD
+ Internet Host Table Specification", RFC-810, Network
+ Information Center, SRI International, March 1982.
+
+ Obsolete. See RFC-952.
+
+[RFC-811] K. Harrenstien, V. White, and E. Feinler, "Hostnames
+ Server", RFC-811, Network Information Center, SRI
+ International, March 1982.
+
+
+
+
+Mockapetris [Page 50]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ Obsolete. See RFC-953.
+
+[RFC-812] K. Harrenstien, and V. White, "NICNAME/WHOIS", RFC-812,
+ Network Information Center, SRI International, March
+ 1982.
+
+[RFC-819] Z. Su, and J. Postel, "The Domain Naming Convention for
+ Internet User Applications", RFC-819, Network
+ Information Center, SRI International, August 1982.
+
+ Early thoughts on the design of the domain system.
+ Current implementation is completely different.
+
+[RFC-821] J. Postel, "Simple Mail Transfer Protocol", RFC-821,
+ USC/Information Sciences Institute, August 1980.
+
+[RFC-830] Z. Su, "A Distributed System for Internet Name Service",
+ RFC-830, Network Information Center, SRI International,
+ October 1982.
+
+ Early thoughts on the design of the domain system.
+ Current implementation is completely different.
+
+[RFC-882] P. Mockapetris, "Domain names - Concepts and
+ Facilities," RFC-882, USC/Information Sciences
+ Institute, November 1983.
+
+ Superseded by this memo.
+
+[RFC-883] P. Mockapetris, "Domain names - Implementation and
+ Specification," RFC-883, USC/Information Sciences
+ Institute, November 1983.
+
+ Superseded by this memo.
+
+[RFC-920] J. Postel and J. Reynolds, "Domain Requirements",
+ RFC-920, USC/Information Sciences Institute,
+ October 1984.
+
+ Explains the naming scheme for top level domains.
+
+[RFC-952] K. Harrenstien, M. Stahl, E. Feinler, "DoD Internet Host
+ Table Specification", RFC-952, SRI, October 1985.
+
+ Specifies the format of HOSTS.TXT, the host/address
+ table replaced by the DNS.
+
+
+
+
+
+Mockapetris [Page 51]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+[RFC-953] K. Harrenstien, M. Stahl, E. Feinler, "HOSTNAME Server",
+ RFC-953, SRI, October 1985.
+
+ This RFC contains the official specification of the
+ hostname server protocol, which is obsoleted by the DNS.
+ This TCP based protocol accesses information stored in
+ the RFC-952 format, and is used to obtain copies of the
+ host table.
+
+[RFC-973] P. Mockapetris, "Domain System Changes and
+ Observations", RFC-973, USC/Information Sciences
+ Institute, January 1986.
+
+ Describes changes to RFC-882 and RFC-883 and reasons for
+ them.
+
+[RFC-974] C. Partridge, "Mail routing and the domain system",
+ RFC-974, CSNET CIC BBN Labs, January 1986.
+
+ Describes the transition from HOSTS.TXT based mail
+ addressing to the more powerful MX system used with the
+ domain system.
+
+[RFC-1001] NetBIOS Working Group, "Protocol standard for a NetBIOS
+ service on a TCP/UDP transport: Concepts and Methods",
+ RFC-1001, March 1987.
+
+ This RFC and RFC-1002 are a preliminary design for
+ NETBIOS on top of TCP/IP which proposes to base NetBIOS
+ name service on top of the DNS.
+
+[RFC-1002] NetBIOS Working Group, "Protocol standard for a NetBIOS
+ service on a TCP/UDP transport: Detailed
+ Specifications", RFC-1002, March 1987.
+
+[RFC-1010] J. Reynolds, and J. Postel, "Assigned Numbers", RFC-1010,
+ USC/Information Sciences Institute, May 1987.
+
+ Contains socket numbers and mnemonics for host names,
+ operating systems, etc.
+
+[RFC-1031] W. Lazear, "MILNET Name Domain Transition", RFC-1031,
+ November 1987.
+
+ Describes a plan for converting the MILNET to the DNS.
+
+[RFC-1032] M. Stahl, "Establishing a Domain - Guidelines for
+ Administrators", RFC-1032, November 1987.
+
+
+
+Mockapetris [Page 52]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ Describes the registration policies used by the NIC to
+ administer the top level domains and delegate subzones.
+
+[RFC-1033] M. Lottor, "Domain Administrators Operations Guide",
+ RFC-1033, November 1987.
+
+ A cookbook for domain administrators.
+
+[Solomon 82] M. Solomon, L. Landweber, and D. Neuhengen, "The CSNET
+ Name Server", Computer Networks, vol 6, nr 3, July 1982.
+
+ Describes a name service for CSNET which is independent
+ from the DNS and DNS use in the CSNET.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Mockapetris [Page 53]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+Index
+
+ * 13
+
+ ; 33, 35
+
+ <character-string> 35
+ <domain-name> 34
+
+ @ 35
+
+ \ 35
+
+ A 12
+
+ Byte order 8
+
+ CH 13
+ Character case 9
+ CLASS 11
+ CNAME 12
+ Completion 42
+ CS 13
+
+ Hesiod 13
+ HINFO 12
+ HS 13
+
+ IN 13
+ IN-ADDR.ARPA domain 22
+ Inverse queries 40
+
+ Mailbox names 47
+ MB 12
+ MD 12
+ MF 12
+ MG 12
+ MINFO 12
+ MINIMUM 20
+ MR 12
+ MX 12
+
+ NS 12
+ NULL 12
+
+ Port numbers 32
+ Primary server 5
+ PTR 12, 18
+
+
+
+Mockapetris [Page 54]
+
+RFC 1035 Domain Implementation and Specification November 1987
+
+
+ QCLASS 13
+ QTYPE 12
+
+ RDATA 12
+ RDLENGTH 11
+
+ Secondary server 5
+ SOA 12
+ Stub resolvers 7
+
+ TCP 32
+ TXT 12
+ TYPE 11
+
+ UDP 32
+
+ WKS 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Mockapetris [Page 55]
+
diff --git a/lib/dns/tests/testdata/dstrandom/random.data b/lib/dns/tests/testdata/dstrandom/random.data
new file mode 100644
index 0000000..354add0
--- /dev/null
+++ b/lib/dns/tests/testdata/dstrandom/random.data
Binary files differ
diff --git a/lib/dns/tests/testdata/master/master1.data b/lib/dns/tests/testdata/master/master1.data
new file mode 100644
index 0000000..030bc68
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master1.data
@@ -0,0 +1,11 @@
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+ in ns ns.vix.com.
+ in ns ns2.vix.com.
+ in ns ns3.vix.com.
+b in a 1.2.3.4
diff --git a/lib/dns/tests/testdata/master/master10.data b/lib/dns/tests/testdata/master/master10.data
new file mode 100644
index 0000000..9ee052f
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master10.data
@@ -0,0 +1,7 @@
+;
+; the following black line contains spaces
+
+;
+@ 300 IN A 10.0.0.1
+ ;
+;
diff --git a/lib/dns/tests/testdata/master/master11.data b/lib/dns/tests/testdata/master/master11.data
new file mode 100644
index 0000000..0aaec25
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master11.data
@@ -0,0 +1,6 @@
+;
+; The following serial number contains a leading 0 and a 9 so the
+; we can catch cases where it is incorrectly treated as a octal
+; number.
+;
+@ 300 IN SOA ns hostmaster 00090000 1200 3600 604800 300
diff --git a/lib/dns/tests/testdata/master/master12.data.in b/lib/dns/tests/testdata/master/master12.data.in
new file mode 100644
index 0000000..3634388
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master12.data.in
@@ -0,0 +1 @@
+00000002000000004ed7306600000051000100060000000003e80000000100060474657374000035096c6f63616c686f7374000a706f73746d6173746572096c6f63616c686f73740076cb8ab100000e100000070800093a8000000e1000000046000100020000000003e8000000030006047465737400000c026e730376697803636f6d00000d036e73320376697803636f6d00000d036e73330376697803636f6d0000000022000100010000000003e80000000100080162047465737400000401020304
diff --git a/lib/dns/tests/testdata/master/master13.data.in b/lib/dns/tests/testdata/master/master13.data.in
new file mode 100644
index 0000000..d1c262f
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master13.data.in
@@ -0,0 +1 @@
+00000002000000014ed7337f00000000000000000000000000000051000100060000000003e80000000100060474657374000035096c6f63616c686f7374000a706f73746d6173746572096c6f63616c686f73740076cb8ab100000e100000070800093a8000000e1000000046000100020000000003e8000000030006047465737400000c026e730376697803636f6d00000d036e73320376697803636f6d00000d036e73330376697803636f6d0000000022000100010000000003e80000000100080162047465737400000401020304
diff --git a/lib/dns/tests/testdata/master/master14.data.in b/lib/dns/tests/testdata/master/master14.data.in
new file mode 100644
index 0000000..149a25f
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master14.data.in
@@ -0,0 +1 @@
+00000002000000014ed7337f0000000277df41e50000000000000051000100060000000003e80000000100060474657374000035096c6f63616c686f7374000a706f73746d6173746572096c6f63616c686f73740076cb8ab100000e100000070800093a8000000e1000000046000100020000000003e8000000030006047465737400000c026e730376697803636f6d00000d036e73320376697803636f6d00000d036e73330376697803636f6d0000000022000100010000000003e80000000100080162047465737400000401020304
diff --git a/lib/dns/tests/testdata/master/master15.data b/lib/dns/tests/testdata/master/master15.data
new file mode 100644
index 0000000..cf413ce
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master15.data
@@ -0,0 +1,1609 @@
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+ in ns ns.vix.com.
+ in ns ns2.vix.com.
+ in ns ns3.vix.com.
+b in a 1.2.3.4
+c in txt ( TOOBIGTOOBIGTOOBIGTOOBIGTOOBIGTOOBI
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890 )
diff --git a/lib/dns/tests/testdata/master/master16.data b/lib/dns/tests/testdata/master/master16.data
new file mode 100644
index 0000000..e969bd3
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master16.data
@@ -0,0 +1,1609 @@
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+ in ns ns.vix.com.
+ in ns ns2.vix.com.
+ in ns ns3.vix.com.
+b in a 1.2.3.4
+c in txt ( MAXSIZSEMAXSIZSEMAXSIZSEMAXSIZSMAX
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890 )
diff --git a/lib/dns/tests/testdata/master/master17.data b/lib/dns/tests/testdata/master/master17.data
new file mode 100644
index 0000000..4b2b63d
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master17.data
@@ -0,0 +1,14 @@
+$ORIGIN test.
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+ in ns ns.test.
+ in ns ns2.test.
+ in ns ns3.test.
+b in a 1.2.3.4
+$ORIGIN sub.test.
+ in a 4.3.2.1
diff --git a/lib/dns/tests/testdata/master/master18.data b/lib/dns/tests/testdata/master/master18.data
new file mode 100644
index 0000000..dddf04e
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master18.data
@@ -0,0 +1,10 @@
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+
+$INCLUDE "testkeys/Kexample.+008+20386.key";
+$INCLUDE "testkeys/Kexample.+008+37464.key";
diff --git a/lib/dns/tests/testdata/master/master2.data b/lib/dns/tests/testdata/master/master2.data
new file mode 100644
index 0000000..b8ca38d
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master2.data
@@ -0,0 +1,11 @@
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+a in ns
+a in ns ns2vix.com.
+a in ns ns3vix.com.
+b in a 1.2.3.4
diff --git a/lib/dns/tests/testdata/master/master3.data b/lib/dns/tests/testdata/master/master3.data
new file mode 100644
index 0000000..7283af6
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master3.data
@@ -0,0 +1,11 @@
+$TTL 1000
+ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+ in ns ns.vix.com
+ in ns ns2vix.com.
+a in ns ns3vix.com.
+b in a 1.2.3.4
diff --git a/lib/dns/tests/testdata/master/master4.data b/lib/dns/tests/testdata/master/master4.data
new file mode 100644
index 0000000..3a694ea
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master4.data
@@ -0,0 +1,11 @@
+
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+a in ns ns.vix.com.
+a in ns ns2vix.com.
+a in ns ns3vix.com.
+b in a 1.2.3.4
diff --git a/lib/dns/tests/testdata/master/master5.data b/lib/dns/tests/testdata/master/master5.data
new file mode 100644
index 0000000..95234bd
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master5.data
@@ -0,0 +1,11 @@
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+a any ns ns.vix.com.
+a in ns ns2vix.com.
+a in ns ns3vix.com.
+b in a 1.2.3.4
diff --git a/lib/dns/tests/testdata/master/master6.data b/lib/dns/tests/testdata/master/master6.data
new file mode 100644
index 0000000..a9a37bb
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master6.data
@@ -0,0 +1,33 @@
+
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+
+secure1 3600 IN DNSKEY (
+ FLAG2|FLAG4|FLAG5|NTYP3|FLAG8|FLAG9|FLAG10|FLAG11|SIG15
+ 3 3
+ ArT0a8FtOZWEONG2YQVl9+RA34op30JPz4NPEroCxm2yImT2
+ 2OYggnPIzrgayyepgKU1PfTTypnJDTwrSrtISyEsj7tjM7/n
+ 03DP8VWSn0aLwpUuc7Sx9vtM1Wi+YeiA4Bv2Oz1VB9de4qql
+ sIq+KLn8J4wz95bGnJ0mHUB7oTDJ3Hl1zeaCMdX69Kr46yAY
+ AvGJJdGGDYxYgxzx2zNdzypkYSkxpdsNqUt38tabSfdvCn12
+ pnmSWjlVJsjHhsaYnrPhouN5acOXMNbxNVbGU5LZ8Es6EYbV
+ /7YMt8VUkA8/8UCszBBT7XAJ3OFjiMO8mvxrZZFzvwJlPBQ1
+ oFq/TNZlSe+N )
+
+secure2 3600 in DNSKEY (
+ flag2|flag4|flag5|ntyp3|flag8|flag9|flag10|flag11|sig15
+ 3 3
+ ArT0a8FtOZWEONG2YQVl9+RA34op30JPz4NPEroCxm2yImT2
+ 2OYggnPIzrgayyepgKU1PfTTypnJDTwrSrtISyEsj7tjM7/n
+ 03DP8VWSn0aLwpUuc7Sx9vtM1Wi+YeiA4Bv2Oz1VB9de4qql
+ sIq+KLn8J4wz95bGnJ0mHUB7oTDJ3Hl1zeaCMdX69Kr46yAY
+ AvGJJdGGDYxYgxzx2zNdzypkYSkxpdsNqUt38tabSfdvCn12
+ pnmSWjlVJsjHhsaYnrPhouN5acOXMNbxNVbGU5LZ8Es6EYbV
+ /7YMt8VUkA8/8UCszBBT7XAJ3OFjiMO8mvxrZZFzvwJlPBQ1
+ oFq/TNZlSe+N )
+
diff --git a/lib/dns/tests/testdata/master/master7.data b/lib/dns/tests/testdata/master/master7.data
new file mode 100644
index 0000000..2638b5d
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master7.data
@@ -0,0 +1,17 @@
+
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+
+secure1 3600 IN DNSKEY (
+ NOKEY|FLAG2|FLAG4|FLAG5|NTYP3|FLAG8|FLAG9|FLAG10|FLAG11|SIG15
+ 3 3 )
+
+secure2 3600 in DNSKEY (
+ nokey|flag2|flag4|flag5|ntyp3|flag8|flag9|flag10|flag11|sig15
+ 3 3 )
+
diff --git a/lib/dns/tests/testdata/master/master8.data b/lib/dns/tests/testdata/master/master8.data
new file mode 100644
index 0000000..d16b6f3
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master8.data
@@ -0,0 +1,4 @@
+;
+; master6.data contains a good zone file
+;
+$include testdata/master/master6.data
diff --git a/lib/dns/tests/testdata/master/master9.data b/lib/dns/tests/testdata/master/master9.data
new file mode 100644
index 0000000..b22688b
--- /dev/null
+++ b/lib/dns/tests/testdata/master/master9.data
@@ -0,0 +1,4 @@
+;
+; master5.data is bad
+;
+$include testdata/master/master5.data
diff --git a/lib/dns/tests/testdata/nsec3/1024.db b/lib/dns/tests/testdata/nsec3/1024.db
new file mode 100644
index 0000000..2576328
--- /dev/null
+++ b/lib/dns/tests/testdata/nsec3/1024.db
@@ -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.
+
+$TTL 0
+test. SOA . . 0 0 0 0 0
+test. NS .
+; 1024 bit key.
+test. IN DNSKEY 256 3 5 AwEAAd5oKx06HRE6NRrTDz49lljdRmxgp/4YB/cyMkpwUMkaLhDNCfTq hql84ab2LRbtUWLHFXGWENvxPGQzVHeleXu+3ThNfFOwIaySedxHmLGT lTtBRDhPc8iSb+2IYDemmA+ut8kwHhCVz/tDMbD/dgAswdOtmXCpQyJk Q1HqY3Xj
diff --git a/lib/dns/tests/testdata/nsec3/2048.db b/lib/dns/tests/testdata/nsec3/2048.db
new file mode 100644
index 0000000..26dd980
--- /dev/null
+++ b/lib/dns/tests/testdata/nsec3/2048.db
@@ -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.
+
+$TTL 0
+test. SOA . . 0 0 0 0 0
+test. NS .
+; 2048 bits
+test. IN DNSKEY 256 3 5 AwEAAcfQX59iZr9gK+XzhTZQ5KWrfCLA0iYHTqheEIhC2dXS8gUSppQS g9SmzH2129u/LSSb7gqJSoLLAsn36iinqCqUXl2BT6xzwznbSP3mn0hn N6DegsykcYhHycKH6ifjZiMN+SGGeNsi5rhoW5Cj9ptw3C3yQnrFNDbS GZCT97z5lpQU3ZcvP4RDNk7dhri7Bh3SJeaCFoqx00NgFvlBR48hosSG bGUbUKzNf58GBTkW4Us2jIWsreZx8LLLev232Hy7NU9L19k+hVq7pJOf Uvtrn5fmGSutWOzsR+8EacOnh0lwssCKjutk5MSmfdFC5P7CTZkdq58L 8he13HGmr00=
diff --git a/lib/dns/tests/testdata/nsec3/4096.db b/lib/dns/tests/testdata/nsec3/4096.db
new file mode 100644
index 0000000..d628c33
--- /dev/null
+++ b/lib/dns/tests/testdata/nsec3/4096.db
@@ -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.
+
+$TTL 0
+test. SOA . . 0 0 0 0 0
+test. NS .
+; 4096 bits
+test. IN DNSKEY 256 3 5 AwEAAbYlqbKxXoq9mzkqdsAaSZ3XywBVAb2sCTgrQBCExyGEYNpWw3LN +imCrLQi7jHKQW6GZIqKNgQaiFEwr3zK8nPWbwNwyKU9a2hhINv/gim1 5iA87Vu7DiiJrQ0O79ospvsGsKknBQ41zaaQMp3Q/W1S6WNe4uyh4C/f R0qmxT+8MyXEqCpTGb+e+YT6BuqpNQPuYYYvUJ1/HJltzY/lY2b9RZ+Q ZJ23Zje79YIRM0kJapqj11fDUDeynhDL1DUikYCwRfQiO/blChhOHjIa uTK1qqRY3fqanLGOufpLTr7GRpL7RxeRIMJfDzmcjFLmCsMA1AJ56Bxq jiXr3ODgn9D30vAB74Lr7lqLQSWyrSlJjoZLLhmPrEP/nnuCxEhOhDRA XJpJWpcQ4Hdu+yb5K/qldnsGLLI1Hr0GmhLTDHsxDb6BxM7/8rv8QeQY GKSGshBqD2lO1xUVT8inbi8uXI1iyN68vHX6xoFT5wsjls70PxSZPO5i F40vn6BWNsHtKWOCDqMKYx8hYwiv0zETVwxBaj58vylFwYGU+g1wIQmF Pgi2HKv4KaxgikUvdFISre5rxVoG5VrmmXWiNJcLTbwZ+tE1xujCNU1c V31CaIB5hdSnkEvQADr5V64RTxWAKuSLNMU+XUqTkaJHasSm3OPJOteo SPj2uoesuxNFYps3
diff --git a/lib/dns/tests/testdata/nsec3/min-1024.db b/lib/dns/tests/testdata/nsec3/min-1024.db
new file mode 100644
index 0000000..360c282
--- /dev/null
+++ b/lib/dns/tests/testdata/nsec3/min-1024.db
@@ -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.
+
+$TTL 0
+test. SOA . . 0 0 0 0 0
+test. NS .
+; 1024 bit key.
+test. IN DNSKEY 256 3 5 AwEAAd5oKx06HRE6NRrTDz49lljdRmxgp/4YB/cyMkpwUMkaLhDNCfTq hql84ab2LRbtUWLHFXGWENvxPGQzVHeleXu+3ThNfFOwIaySedxHmLGT lTtBRDhPc8iSb+2IYDemmA+ut8kwHhCVz/tDMbD/dgAswdOtmXCpQyJk Q1HqY3Xj
+; 2048 bits
+test. IN DNSKEY 256 3 5 AwEAAcfQX59iZr9gK+XzhTZQ5KWrfCLA0iYHTqheEIhC2dXS8gUSppQS g9SmzH2129u/LSSb7gqJSoLLAsn36iinqCqUXl2BT6xzwznbSP3mn0hn N6DegsykcYhHycKH6ifjZiMN+SGGeNsi5rhoW5Cj9ptw3C3yQnrFNDbS GZCT97z5lpQU3ZcvP4RDNk7dhri7Bh3SJeaCFoqx00NgFvlBR48hosSG bGUbUKzNf58GBTkW4Us2jIWsreZx8LLLev232Hy7NU9L19k+hVq7pJOf Uvtrn5fmGSutWOzsR+8EacOnh0lwssCKjutk5MSmfdFC5P7CTZkdq58L 8he13HGmr00=
+; 4096 bits
+test. IN DNSKEY 256 3 5 AwEAAbYlqbKxXoq9mzkqdsAaSZ3XywBVAb2sCTgrQBCExyGEYNpWw3LN +imCrLQi7jHKQW6GZIqKNgQaiFEwr3zK8nPWbwNwyKU9a2hhINv/gim1 5iA87Vu7DiiJrQ0O79ospvsGsKknBQ41zaaQMp3Q/W1S6WNe4uyh4C/f R0qmxT+8MyXEqCpTGb+e+YT6BuqpNQPuYYYvUJ1/HJltzY/lY2b9RZ+Q ZJ23Zje79YIRM0kJapqj11fDUDeynhDL1DUikYCwRfQiO/blChhOHjIa uTK1qqRY3fqanLGOufpLTr7GRpL7RxeRIMJfDzmcjFLmCsMA1AJ56Bxq jiXr3ODgn9D30vAB74Lr7lqLQSWyrSlJjoZLLhmPrEP/nnuCxEhOhDRA XJpJWpcQ4Hdu+yb5K/qldnsGLLI1Hr0GmhLTDHsxDb6BxM7/8rv8QeQY GKSGshBqD2lO1xUVT8inbi8uXI1iyN68vHX6xoFT5wsjls70PxSZPO5i F40vn6BWNsHtKWOCDqMKYx8hYwiv0zETVwxBaj58vylFwYGU+g1wIQmF Pgi2HKv4KaxgikUvdFISre5rxVoG5VrmmXWiNJcLTbwZ+tE1xujCNU1c V31CaIB5hdSnkEvQADr5V64RTxWAKuSLNMU+XUqTkaJHasSm3OPJOteo SPj2uoesuxNFYps3
diff --git a/lib/dns/tests/testdata/nsec3/min-2048.db b/lib/dns/tests/testdata/nsec3/min-2048.db
new file mode 100644
index 0000000..606264e
--- /dev/null
+++ b/lib/dns/tests/testdata/nsec3/min-2048.db
@@ -0,0 +1,18 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 0
+test. SOA . . 0 0 0 0 0
+test. NS .
+; 2048 bits
+test. IN DNSKEY 256 3 5 AwEAAcfQX59iZr9gK+XzhTZQ5KWrfCLA0iYHTqheEIhC2dXS8gUSppQS g9SmzH2129u/LSSb7gqJSoLLAsn36iinqCqUXl2BT6xzwznbSP3mn0hn N6DegsykcYhHycKH6ifjZiMN+SGGeNsi5rhoW5Cj9ptw3C3yQnrFNDbS GZCT97z5lpQU3ZcvP4RDNk7dhri7Bh3SJeaCFoqx00NgFvlBR48hosSG bGUbUKzNf58GBTkW4Us2jIWsreZx8LLLev232Hy7NU9L19k+hVq7pJOf Uvtrn5fmGSutWOzsR+8EacOnh0lwssCKjutk5MSmfdFC5P7CTZkdq58L 8he13HGmr00=
+; 4096 bits
+test. IN DNSKEY 256 3 5 AwEAAbYlqbKxXoq9mzkqdsAaSZ3XywBVAb2sCTgrQBCExyGEYNpWw3LN +imCrLQi7jHKQW6GZIqKNgQaiFEwr3zK8nPWbwNwyKU9a2hhINv/gim1 5iA87Vu7DiiJrQ0O79ospvsGsKknBQ41zaaQMp3Q/W1S6WNe4uyh4C/f R0qmxT+8MyXEqCpTGb+e+YT6BuqpNQPuYYYvUJ1/HJltzY/lY2b9RZ+Q ZJ23Zje79YIRM0kJapqj11fDUDeynhDL1DUikYCwRfQiO/blChhOHjIa uTK1qqRY3fqanLGOufpLTr7GRpL7RxeRIMJfDzmcjFLmCsMA1AJ56Bxq jiXr3ODgn9D30vAB74Lr7lqLQSWyrSlJjoZLLhmPrEP/nnuCxEhOhDRA XJpJWpcQ4Hdu+yb5K/qldnsGLLI1Hr0GmhLTDHsxDb6BxM7/8rv8QeQY GKSGshBqD2lO1xUVT8inbi8uXI1iyN68vHX6xoFT5wsjls70PxSZPO5i F40vn6BWNsHtKWOCDqMKYx8hYwiv0zETVwxBaj58vylFwYGU+g1wIQmF Pgi2HKv4KaxgikUvdFISre5rxVoG5VrmmXWiNJcLTbwZ+tE1xujCNU1c V31CaIB5hdSnkEvQADr5V64RTxWAKuSLNMU+XUqTkaJHasSm3OPJOteo SPj2uoesuxNFYps3
diff --git a/lib/dns/tests/testdata/nsec3param/nsec3.db.signed b/lib/dns/tests/testdata/nsec3param/nsec3.db.signed
new file mode 100644
index 0000000..aeced0e
--- /dev/null
+++ b/lib/dns/tests/testdata/nsec3param/nsec3.db.signed
@@ -0,0 +1,73 @@
+; File written on Mon Nov 16 16:04:21 2020
+; dnssec_signzone version 9.16.8
+nsec3. 1000 IN SOA nsec3. postmaster.nsec3. (
+ 1993050801 ; serial
+ 3600 ; refresh (1 hour)
+ 1800 ; retry (30 minutes)
+ 604800 ; expire (1 week)
+ 3600 ; minimum (1 hour)
+ )
+ 1000 RRSIG SOA 13 1 1000 (
+ 20201216140421 20201116140421 40382 nsec3.
+ qh61ZPgQaNLAoIQvAoTLbR3sLBY7XATaMGSS
+ fYOssQWvgAzpAzhalmF/cSXmQ/RZQOyIdpVg
+ v3rgyTxA2vGNnA== )
+ 1000 NS ns1.nsec3.
+ 1000 NS ns2.nsec3.
+ 1000 RRSIG NS 13 1 1000 (
+ 20201216140421 20201116140421 40382 nsec3.
+ 4Le+e5Lu/taEvrvrmBn/z+QP4zhzUqwO6v70
+ WYrzCggUls8+fUd2unBHDPWag1oSKfNpGGWA
+ crihrs4RhMPfZA== )
+ 1000 DNSKEY 257 3 13 (
+ VKkttSi/v3lAyzUYnykwdwowXfDOQ7wdN9BT
+ +eb8fVfgRApvuun9hjUBlv7ogriU/GAb60B8
+ juj9bXZADT+OGg==
+ ) ; KSK; alg = ECDSAP256SHA256 ; key id = 40382
+ 1000 RRSIG DNSKEY 13 1 1000 (
+ 20201216140421 20201116140421 40382 nsec3.
+ ZnBqGgWvHwjjQBSIRPXe2fx6+MsQp1QQdzJ0
+ QaEyaOmud5JPatUXaV9eFRcPNCsi+2HZSZVp
+ vsAGUCge7w6u9A== )
+ 0 NSEC3PARAM 1 0 5 FEDCBA98
+ 0 RRSIG NSEC3PARAM 13 1 0 (
+ 20201216140421 20201116140421 40382 nsec3.
+ WPTD+5vr54YtvGqCUJHPvGdF7Wd4piZYltcs
+ cztBRfdM7FRJ/zvrDS72rt6zm0TYSXzawqt/
+ MiwOkYKv2vxfUg== )
+ns2.nsec3. 1000 IN A 1.2.3.5
+ 1000 RRSIG A 13 2 1000 (
+ 20201216140421 20201116140421 40382 nsec3.
+ l9Mc2Y5JFmllSxJj3GUdH6RtEsYfhjJU39sa
+ vAVa4zxv6S9vU+vLvTA05aQ+DPLvKTX+WNH7
+ dDa+Yy5ffBs68g== )
+QVCH33BSJ0Q2C74FEDFDBCFQHO255NEB.nsec3. 3600 IN NSEC3 1 0 5 FEDCBA98 (
+ STH5N5QDVC5DGEN5VGUC7JGALSM3R8AP
+ A RRSIG )
+ 3600 RRSIG NSEC3 13 2 3600 (
+ 20201216140421 20201116140421 40382 nsec3.
+ F/wKQtv+RlBHG1WCz0CkHlTSoUiRx0z+qBI1
+ GTHoXSjgG1NSHqTI4C32AasZSMp+uuF2R8KW
+ 9z4gOLucl0Xmfg== )
+STH5N5QDVC5DGEN5VGUC7JGALSM3R8AP.nsec3. 3600 IN NSEC3 1 0 5 FEDCBA98 (
+ A084TNR6VJ2ND5K1U0AI4HO4EPVKBG4U
+ NS SOA RRSIG DNSKEY NSEC3PARAM )
+ 3600 RRSIG NSEC3 13 2 3600 (
+ 20201216140421 20201116140421 40382 nsec3.
+ 9TgGFGY3vwkxMFlXy3oKMgHPqvcPozKDHZzc
+ Ny6eJn3TXNX5bLhiT5rw5+CCtyOEQmn3pf0X
+ njK7jZBAcBV+5Q== )
+A084TNR6VJ2ND5K1U0AI4HO4EPVKBG4U.nsec3. 3600 IN NSEC3 1 0 5 FEDCBA98 (
+ QVCH33BSJ0Q2C74FEDFDBCFQHO255NEB
+ A RRSIG )
+ 3600 RRSIG NSEC3 13 2 3600 (
+ 20201216140421 20201116140421 40382 nsec3.
+ auf+5lrkMESIfdFK8bf4yg1a+NLGWzgUmohS
+ ydcKaJz0XcnULegatWdfE75jmZoDeqKNpwdL
+ 5lQ77GF4cEh1OQ== )
+ns1.nsec3. 1000 IN A 1.2.3.4
+ 1000 RRSIG A 13 2 1000 (
+ 20201216140421 20201116140421 40382 nsec3.
+ yAmr1EE8qe+Jl+wQXOdj/uSjMFUmns0D1lx6
+ zAVe9BaQwvF3wR7ZUk/u9G0RrUBchmEj0+yq
+ KEsw32Tru4Romg== )
diff --git a/lib/dns/tests/testdata/zt/zone1.db b/lib/dns/tests/testdata/zt/zone1.db
new file mode 100644
index 0000000..85e7951
--- /dev/null
+++ b/lib/dns/tests/testdata/zt/zone1.db
@@ -0,0 +1,22 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+ in ns ns.vix.com.
+ in ns ns2.vix.com.
+ in ns ns3.vix.com.
+a in a 1.2.3.4
diff --git a/lib/dns/tests/testkeys/Kexample.+008+20386.key b/lib/dns/tests/testkeys/Kexample.+008+20386.key
new file mode 100644
index 0000000..3404dca
--- /dev/null
+++ b/lib/dns/tests/testkeys/Kexample.+008+20386.key
@@ -0,0 +1,5 @@
+; This is a key-signing key, keyid 20386, for example.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example. IN DNSKEY 257 3 8 AwEAAZd7/hBRvMooz0sepkD/2r3Bp021f8lGzDj6sZEVbg1hcqZTzURc eGkS541wyOqjvJv2KBi5qLLE2HthmexmOBycjTQ7EiKd1P9bE8RgF8Et j73X/CHLiX6YL7cb93TXWiUvbRh4E6D2URgOmxMdMOXTuCvjvDaGVCOt Jc77UUosuBeurZzP8g8t/zccAUTzu2cdRyI5/ZxOBfJaDtc9TlRdWsaN Af+nT0C14ccH7QVlKjjaYV4lXueruDW3yTTzu9bQ1ikgegsCLi/tcD/1 dWTOI9whV06szs+ouhuJkZuhIjrGDtOHCpjPjIxOOrIZceU1YSY30kAR QNVzshJqyx8=
diff --git a/lib/dns/tests/testkeys/Kexample.+008+20386.private b/lib/dns/tests/testkeys/Kexample.+008+20386.private
new file mode 100644
index 0000000..d8cff93
--- /dev/null
+++ b/lib/dns/tests/testkeys/Kexample.+008+20386.private
@@ -0,0 +1,13 @@
+Private-key-format: v1.3
+Algorithm: 8 (RSASHA256)
+Modulus: l3v+EFG8yijPSx6mQP/avcGnTbV/yUbMOPqxkRVuDWFyplPNRFx4aRLnjXDI6qO8m/YoGLmossTYe2GZ7GY4HJyNNDsSIp3U/1sTxGAXwS2Pvdf8IcuJfpgvtxv3dNdaJS9tGHgToPZRGA6bEx0w5dO4K+O8NoZUI60lzvtRSiy4F66tnM/yDy3/NxwBRPO7Zx1HIjn9nE4F8loO1z1OVF1axo0B/6dPQLXhxwftBWUqONphXiVe56u4NbfJNPO71tDWKSB6CwIuL+1wP/V1ZM4j3CFXTqzOz6i6G4mRm6EiOsYO04cKmM+MjE46shlx5TVhJjfSQBFA1XOyEmrLHw==
+PublicExponent: AQAB
+PrivateExponent: aSkynrGfldfuz/9e+xCjEcg2FMRDCb+UVpnyWv29gJx9sunKPgLTtF3jUVVSpVE1xi+EdmWsry3n+v8uk+YCXhpwDCpV1KItE3huqIzs8LZoaypdZjieIrwTo9JOX1aAxf++hJYXSk60zTaWgRZqs6He4Nkf99oY3wt8i8v8CrkfQy76K/qK9xUVv5GHrEZzCGLfLv77eqDab/J84ANxc0kUtQvgt2/JTHofXmcA6/YDh5PWB8KRw1PjQTck61/xIgfI6ky/yIF1riCQCYXwTv7jcmMV/QvQ+dfN+HZ2CSGp7xcH2Yxe9OhAY823ZkmkOQ2YZPjIj6dEoRMmSiaagQ==
+Prime1: x2GMnpRPwvUhM+yPRa7nh5Jjl4mbofeOtVrxe1hEVy8l2UGFh+FDZCbyoLRNUTYDji00NHpGtmcAyoY9pLdOn7ci4zqGVnNJcIY75Ie4p6J7pPfDh9d+AGtJ5NpNhr1sjD0bFncJC2FGY9vj4eC0CkatMu/Qovrd2FwZ8VpDsAk=
+Prime2: woB8MYsEfSYGD0hZGtmgK6UQ+Oo9smxdPmahLYXnLSAdqtqZbZX+ABk/kFduT+XwlHOXmp3HMmUtQTRZBaQyBrsFWfWjOGevByEsT9aLQSZOEgnqy4xrc9XNwDs4/WkrEgw/TOVnZYdaCyLxsFl4bpTX8Fj3yVqg/tJvuUMWG+c=
+Exponent1: iQO7a9rF+VcVSyZ8yslIaL0r3Z5+Kk8CbhSiMD5XMIbA/sztI5SlCDVPtSpSm8V/qfvcjVeeMokUXRjlUcV6rX1f50F3wf8V79L/Y6v1NJYPXC273CU1fLo+HJv8fOS9rJ3teIGy4HQnuEYLE1WkxA8PxRpSiT3WqHGajmaWb2k=
+Exponent2: elMWSI5Wz2KXkwr8Rz+xVWGl7/ZZwRoX9oPTQG8jeiTlo6uBrQMVUPiQGnZyQTuq96JPKYWrXs11DbofdsXSVJtQfUhYU8QZtxEs7jVPNTUjCoNEMKnqdlpz4T8d03pOBTbApNruEVNz1OcwO6m5bUqdGGLLy838zOaKL2i6wec=
+Coefficient: q2mejAmT3A4H2C0rT1hm8XQFuISHjAAEyM9t09Q8tEeQ0lHi4gMVA3bXoAn9U21eBkFQDwvyB0vqlVSGgRqHovOKx9uXAU9eoDxGcJsFlGsM0aUsUjGVXv5kVmaw8a5PHBbvYAbgAZUmKqrVF0PWD3o+/DbzP9PCmlJcqxoAulU=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/testkeys/Kexample.+008+37464.key b/lib/dns/tests/testkeys/Kexample.+008+37464.key
new file mode 100644
index 0000000..3dd0619
--- /dev/null
+++ b/lib/dns/tests/testkeys/Kexample.+008+37464.key
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 37464, for example.
+; Created: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000)
+; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000)
+example. IN DNSKEY 256 3 8 AwEAAbxHOF8G0xw9ekCodhL8KivuZ3o0jmGlycLiXBjBN8c5R5fjLjUh D0gy3IDbDC+kLaPhHGF/MwrSEjrgSowxZ8nrxDzsq5ZdpeUsYaNrbQEY /mqf35T/9/Ulm4v06x58v/NTugWd05Xq04aAyfm7EViyGFzmVOVfPnll h9xQtvWEWoRWPseFw+dY5/nc/+xB/IsQMihoH2rO+cek/lsP3R9DsHCG RbQ/ks/+rrp6/O+QJZyZrzsONl7mlMDXNy3Pz9J4qMW2W6Mz702LN324 7/9UsetDGGbuZfrCLMpKWXzdsJm36DOk4aMooS9111plfXaXQgQNcL5G 021utpTau+8=
diff --git a/lib/dns/tests/testkeys/Kexample.+008+37464.private b/lib/dns/tests/testkeys/Kexample.+008+37464.private
new file mode 100644
index 0000000..ecc2ad0
--- /dev/null
+++ b/lib/dns/tests/testkeys/Kexample.+008+37464.private
@@ -0,0 +1,13 @@
+Private-key-format: v1.3
+Algorithm: 8 (RSASHA256)
+Modulus: vEc4XwbTHD16QKh2EvwqK+5nejSOYaXJwuJcGME3xzlHl+MuNSEPSDLcgNsML6Qto+EcYX8zCtISOuBKjDFnyevEPOyrll2l5Sxho2ttARj+ap/flP/39SWbi/TrHny/81O6BZ3TlerThoDJ+bsRWLIYXOZU5V8+eWWH3FC29YRahFY+x4XD51jn+dz/7EH8ixAyKGgfas75x6T+Ww/dH0OwcIZFtD+Sz/6uunr875AlnJmvOw42XuaUwNc3Lc/P0nioxbZbozPvTYs3fbjv/1Sx60MYZu5l+sIsykpZfN2wmbfoM6ThoyihL3XXWmV9dpdCBA1wvkbTbW62lNq77w==
+PublicExponent: AQAB
+PrivateExponent: AhR3VvVoV6OGOjiiNUt728hidEMoX4PJWtHNWqinyRek5tSnqgaXeKC3NuU0mUIjDvBps9oH4lK3yNa5fBr/nodwP4wNyTd3obR/z6JcLersxJjHi4nYX2ju8vjdsBSIulNudqlrsPhLJe0+Tff3FRfClSQmQ/JtakHo4lIx8zxiOJY8aWFeHGdWJDkAf6NStt3eVYyOyAwISfv3muaGPZKShiIOfLyTvqFqzwYFgdTWmvFqTdwgjIMc5XAwqw73WP2BPCN+fdCiMtrw0fCrhWzw/gfMJBHdOPH0diUZysAJhM0vdVKQzEi/g3YOo00fahZiPzaxNtZnLNj2mA54YQ==
+Prime1: 5YpfVjEtL1owW9gSFbIMx65POr+fiktxirgy1bc5fSsVqUgG6zhbaN/VpWcNZG0Zg5xd6S7C8V3djGlnJN8wZIyjIh7+Z3WWjqbOD9oY7rC1fR+W0OvbCmZiEzOpRJ5qoMOh1MzkkanhMy0/ICpaa8eQ9zEb80oTIQpFgoLn7K0=
+Prime2: 0fs3ncL5/2qzq2dmPXLYcOfc1EGSuESO0VpREP8EpTkyPKeVw5LaF9TgZRqPWlRf2T0LPoZ766xLAn090u0pLQ5fWM96NMas7kS+rxtRssat6MiQo3YfoU3ysk3xuPzrMBHyn/N42CjSG+bJEToHR7V16KsCT6dBIPkI3tj/Yos=
+Exponent1: Bdsp44ENrg+W/EDe9T69pLqFuvH4mAaktu1MHre198OJoe/8fTPK4ToUsUuXw+Akrn7mxnQy9QV4CYUG5KHtEiOkZdJ0mx8c4DbROwZNbImFl9OefWYHCJTkG6lNwDpqbf+PuWYgzraO0EdvPNrXw7grsqLGG8bgBg/FBjdgw2E=
+Exponent2: uV1pxW0fwGhzX3aR/ODrTRCCEyYn3V84LHvsYHKfqTOKs5zFSrbSrIMR7G676ePeESogSPvzXSLlvLbO4urVlJ7BcOcHXJuegWBSbMZTItzdHUgg1wwp8/2Zp+nC36j1/aN6adVG8ptmj5b2HKz7TERWaCS+j454oiD1wbQSDu0=
+Coefficient: JO6RxBIaoEd/Z4ITcsYT8TslP1KmIuAqdhMt3FSpqeogUDut7f3FZIEyNi4wsrSK5peIQSVmO2pQLupS+eRIPHXZ1vh5kcFAsgd7XBb7Fvsg26/WSjhB4wjx+wgWzVomK0519pfdtH854fePWPkdDKtLNL2zh0APne3GjwrbNEM=
+Created: 20000101000000
+Publish: 20000101000000
+Activate: 20000101000000
diff --git a/lib/dns/tests/time_test.c b/lib/dns/tests/time_test.c
new file mode 100644
index 0000000..a1e50f8
--- /dev/null
+++ b/lib/dns/tests/time_test.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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/util.h>
+
+#include <dns/time.h>
+
+#include "dnstest.h"
+
+#define TEST_ORIGIN "test"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+/* value = 0xfffffffff <-> 19691231235959 */
+static void
+epoch_minus_one_test(void **state) {
+ const char *test_text = "19691231235959";
+ const uint32_t test_time = 0xffffffff;
+ isc_result_t result;
+ isc_buffer_t target;
+ uint32_t when;
+ char buf[128];
+
+ UNUSED(state);
+
+ memset(buf, 0, sizeof(buf));
+ isc_buffer_init(&target, buf, sizeof(buf));
+ result = dns_time32_totext(test_time, &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, test_text);
+ result = dns_time32_fromtext(test_text, &when);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(when, test_time);
+}
+
+/* value = 0x000000000 <-> 19700101000000*/
+static void
+epoch_test(void **state) {
+ const char *test_text = "19700101000000";
+ const uint32_t test_time = 0x00000000;
+ isc_result_t result;
+ isc_buffer_t target;
+ uint32_t when;
+ char buf[128];
+
+ UNUSED(state);
+
+ memset(buf, 0, sizeof(buf));
+ isc_buffer_init(&target, buf, sizeof(buf));
+ result = dns_time32_totext(test_time, &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, test_text);
+ result = dns_time32_fromtext(test_text, &when);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(when, test_time);
+}
+
+/* value = 0x7fffffff <-> 20380119031407 */
+static void
+half_maxint_test(void **state) {
+ const char *test_text = "20380119031407";
+ const uint32_t test_time = 0x7fffffff;
+ isc_result_t result;
+ isc_buffer_t target;
+ uint32_t when;
+ char buf[128];
+
+ UNUSED(state);
+
+ memset(buf, 0, sizeof(buf));
+ isc_buffer_init(&target, buf, sizeof(buf));
+ result = dns_time32_totext(test_time, &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, test_text);
+ result = dns_time32_fromtext(test_text, &when);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(when, test_time);
+}
+
+/* value = 0x80000000 <-> 20380119031408 */
+static void
+half_plus_one_test(void **state) {
+ const char *test_text = "20380119031408";
+ const uint32_t test_time = 0x80000000;
+ isc_result_t result;
+ isc_buffer_t target;
+ uint32_t when;
+ char buf[128];
+
+ UNUSED(state);
+
+ memset(buf, 0, sizeof(buf));
+ isc_buffer_init(&target, buf, sizeof(buf));
+ result = dns_time32_totext(test_time, &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, test_text);
+ result = dns_time32_fromtext(test_text, &when);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(when, test_time);
+}
+
+/* value = 0xef68f5d0 <-> 19610307130000 */
+static void
+fifty_before_test(void **state) {
+ isc_result_t result;
+ const char *test_text = "19610307130000";
+ const uint32_t test_time = 0xef68f5d0;
+ isc_buffer_t target;
+ uint32_t when;
+ char buf[128];
+
+ UNUSED(state);
+
+ memset(buf, 0, sizeof(buf));
+ isc_buffer_init(&target, buf, sizeof(buf));
+ result = dns_time32_totext(test_time, &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, test_text);
+ result = dns_time32_fromtext(test_text, &when);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(when, test_time);
+}
+
+/* value = 0x4d74d6d0 <-> 20110307130000 */
+static void
+some_ago_test(void **state) {
+ const char *test_text = "20110307130000";
+ const uint32_t test_time = 0x4d74d6d0;
+ isc_result_t result;
+ isc_buffer_t target;
+ uint32_t when;
+ char buf[128];
+
+ UNUSED(state);
+
+ memset(buf, 0, sizeof(buf));
+ isc_buffer_init(&target, buf, sizeof(buf));
+ result = dns_time32_totext(test_time, &target);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, test_text);
+ result = dns_time32_fromtext(test_text, &when);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(when, test_time);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(epoch_minus_one_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(epoch_test, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(half_maxint_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(half_plus_one_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(fifty_before_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(some_ago_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/tsig_test.c b/lib/dns/tests/tsig_test.c
new file mode 100644
index 0000000..1354ef7
--- /dev/null
+++ b/lib/dns/tests/tsig_test.c
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/util.h>
+
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/tsig.h>
+
+#include "../tsig_p.h"
+#include "dnstest.h"
+
+#define CHECK(r) \
+ do { \
+ result = (r); \
+ if (result != ISC_R_SUCCESS) { \
+ goto cleanup; \
+ } \
+ } while (0)
+
+#define TEST_ORIGIN "test"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static int debug = 0;
+
+static isc_result_t
+add_mac(dst_context_t *tsigctx, isc_buffer_t *buf) {
+ dns_rdata_any_tsig_t tsig;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_buffer_t databuf;
+ isc_region_t r;
+ isc_result_t result;
+ unsigned char tsigbuf[1024];
+
+ isc_buffer_usedregion(buf, &r);
+ dns_rdata_fromregion(&rdata, dns_rdataclass_any, dns_rdatatype_tsig,
+ &r);
+ isc_buffer_init(&databuf, tsigbuf, sizeof(tsigbuf));
+ CHECK(dns_rdata_tostruct(&rdata, &tsig, NULL));
+ isc_buffer_putuint16(&databuf, tsig.siglen);
+ isc_buffer_putmem(&databuf, tsig.signature, tsig.siglen);
+ isc_buffer_usedregion(&databuf, &r);
+ result = dst_context_adddata(tsigctx, &r);
+ dns_rdata_freestruct(&tsig);
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+add_tsig(dst_context_t *tsigctx, dns_tsigkey_t *key, isc_buffer_t *target) {
+ dns_compress_t cctx;
+ dns_rdata_any_tsig_t tsig;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdatalist_t rdatalist;
+ dns_rdataset_t rdataset;
+ isc_buffer_t *dynbuf = NULL;
+ isc_buffer_t databuf;
+ isc_buffer_t sigbuf;
+ isc_region_t r;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_stdtime_t now;
+ unsigned char tsigbuf[1024];
+ unsigned int count;
+ unsigned int sigsize = 0;
+ bool invalidate_ctx = false;
+
+ memset(&tsig, 0, sizeof(tsig));
+
+ CHECK(dns_compress_init(&cctx, -1, dt_mctx));
+ invalidate_ctx = true;
+
+ 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);
+
+ isc_stdtime_get(&now);
+ tsig.timesigned = now;
+ tsig.fudge = DNS_TSIG_FUDGE;
+ tsig.originalid = 50;
+ tsig.error = dns_rcode_noerror;
+ tsig.otherlen = 0;
+ tsig.other = NULL;
+
+ isc_buffer_init(&databuf, tsigbuf, sizeof(tsigbuf));
+ isc_buffer_putuint48(&databuf, tsig.timesigned);
+ isc_buffer_putuint16(&databuf, tsig.fudge);
+ isc_buffer_usedregion(&databuf, &r);
+ CHECK(dst_context_adddata(tsigctx, &r));
+
+ CHECK(dst_key_sigsize(key->key, &sigsize));
+ tsig.signature = isc_mem_get(dt_mctx, sigsize);
+ isc_buffer_init(&sigbuf, tsig.signature, sigsize);
+ CHECK(dst_context_sign(tsigctx, &sigbuf));
+ tsig.siglen = isc_buffer_usedlength(&sigbuf);
+ assert_int_equal(sigsize, tsig.siglen);
+
+ isc_buffer_allocate(dt_mctx, &dynbuf, 512);
+ CHECK(dns_rdata_fromstruct(&rdata, dns_rdataclass_any,
+ dns_rdatatype_tsig, &tsig, dynbuf));
+ dns_rdatalist_init(&rdatalist);
+ rdatalist.rdclass = dns_rdataclass_any;
+ rdatalist.type = dns_rdatatype_tsig;
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
+ dns_rdataset_init(&rdataset);
+ CHECK(dns_rdatalist_tordataset(&rdatalist, &rdataset));
+ CHECK(dns_rdataset_towire(&rdataset, &key->name, &cctx, target, 0,
+ &count));
+
+ /*
+ * Fixup additional record count.
+ */
+ ((unsigned char *)target->base)[11]++;
+ if (((unsigned char *)target->base)[11] == 0) {
+ ((unsigned char *)target->base)[10]++;
+ }
+cleanup:
+ if (tsig.signature != NULL) {
+ isc_mem_put(dt_mctx, tsig.signature, sigsize);
+ }
+ if (dynbuf != NULL) {
+ isc_buffer_free(&dynbuf);
+ }
+ if (invalidate_ctx) {
+ dns_compress_invalidate(&cctx);
+ }
+
+ return (result);
+}
+
+static void
+printmessage(dns_message_t *msg) {
+ isc_buffer_t b;
+ char *buf = NULL;
+ int len = 1024;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ if (!debug) {
+ return;
+ }
+
+ do {
+ buf = isc_mem_get(dt_mctx, len);
+
+ isc_buffer_init(&b, buf, len);
+ result = dns_message_totext(msg, &dns_master_style_debug, 0,
+ &b);
+ if (result == ISC_R_NOSPACE) {
+ isc_mem_put(dt_mctx, buf, len);
+ len *= 2;
+ } else if (result == ISC_R_SUCCESS) {
+ printf("%.*s\n", (int)isc_buffer_usedlength(&b), buf);
+ }
+ } while (result == ISC_R_NOSPACE);
+
+ if (buf != NULL) {
+ isc_mem_put(dt_mctx, buf, len);
+ }
+}
+
+static void
+render(isc_buffer_t *buf, unsigned flags, dns_tsigkey_t *key,
+ isc_buffer_t **tsigin, isc_buffer_t **tsigout, dst_context_t *tsigctx) {
+ dns_message_t *msg = NULL;
+ dns_compress_t cctx;
+ isc_result_t result;
+
+ dns_message_create(dt_mctx, DNS_MESSAGE_INTENTRENDER, &msg);
+ assert_non_null(msg);
+
+ msg->id = 50;
+ msg->rcode = dns_rcode_noerror;
+ msg->flags = flags;
+
+ /*
+ * XXXMPA: this hack needs to be replaced with use of
+ * dns_message_reply() at some point.
+ */
+ if ((flags & DNS_MESSAGEFLAG_QR) != 0) {
+ msg->verified_sig = 1;
+ }
+
+ if (tsigin == tsigout) {
+ msg->tcp_continuation = 1;
+ }
+
+ if (tsigctx == NULL) {
+ result = dns_message_settsigkey(msg, key);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_message_setquerytsig(msg, *tsigin);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ result = dns_compress_init(&cctx, -1, dt_mctx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_message_renderbegin(msg, &cctx, buf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_message_renderend(msg);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ if (tsigctx != NULL) {
+ isc_region_t r;
+
+ isc_buffer_usedregion(buf, &r);
+ result = dst_context_adddata(tsigctx, &r);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ } else {
+ if (tsigin == tsigout && *tsigin != NULL) {
+ isc_buffer_free(tsigin);
+ }
+
+ result = dns_message_getquerytsig(msg, dt_mctx, tsigout);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ dns_compress_invalidate(&cctx);
+ dns_message_detach(&msg);
+}
+
+/*
+ * Test tsig tcp-continuation validation:
+ * Check that a simulated three message TCP sequence where the first
+ * and last messages contain TSIGs but the intermediate message doesn't
+ * correctly verifies.
+ */
+static void
+tsig_tcp_test(void **state) {
+ const dns_name_t *tsigowner = NULL;
+ dns_fixedname_t fkeyname;
+ dns_message_t *msg = NULL;
+ dns_name_t *keyname;
+ dns_tsig_keyring_t *ring = NULL;
+ dns_tsigkey_t *key = NULL;
+ isc_buffer_t *buf = NULL;
+ isc_buffer_t *querytsig = NULL;
+ isc_buffer_t *tsigin = NULL;
+ isc_buffer_t *tsigout = NULL;
+ isc_result_t result;
+ unsigned char secret[16] = { 0 };
+ dst_context_t *tsigctx = NULL;
+ dst_context_t *outctx = NULL;
+
+ UNUSED(state);
+
+ /* isc_log_setdebuglevel(lctx, 99); */
+
+ keyname = dns_fixedname_initname(&fkeyname);
+ result = dns_name_fromstring(keyname, "test", 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_tsigkeyring_create(dt_mctx, &ring);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_tsigkey_create(keyname, dns_tsig_hmacsha256_name, secret,
+ sizeof(secret), false, NULL, 0, 0, dt_mctx,
+ ring, &key);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_non_null(key);
+
+ /*
+ * Create request.
+ */
+ isc_buffer_allocate(dt_mctx, &buf, 65535);
+ render(buf, 0, key, &tsigout, &querytsig, NULL);
+ isc_buffer_free(&buf);
+
+ /*
+ * Create response message 1.
+ */
+ isc_buffer_allocate(dt_mctx, &buf, 65535);
+ render(buf, DNS_MESSAGEFLAG_QR, key, &querytsig, &tsigout, NULL);
+ assert_non_null(tsigout);
+
+ /*
+ * Process response message 1.
+ */
+ dns_message_create(dt_mctx, DNS_MESSAGE_INTENTPARSE, &msg);
+ assert_non_null(msg);
+
+ result = dns_message_settsigkey(msg, key);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_message_parse(msg, buf, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ printmessage(msg);
+
+ result = dns_message_setquerytsig(msg, querytsig);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_tsig_verify(buf, msg, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(msg->verified_sig, 1);
+ assert_int_equal(msg->tsigstatus, dns_rcode_noerror);
+
+ /*
+ * Check that we have a TSIG in the first message.
+ */
+ assert_non_null(dns_message_gettsig(msg, &tsigowner));
+
+ result = dns_message_getquerytsig(msg, dt_mctx, &tsigin);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ tsigctx = msg->tsigctx;
+ msg->tsigctx = NULL;
+ isc_buffer_free(&buf);
+ dns_message_detach(&msg);
+
+ result = dst_context_create(key->key, dt_mctx, DNS_LOGCATEGORY_DNSSEC,
+ false, 0, &outctx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_non_null(outctx);
+
+ /*
+ * Start digesting.
+ */
+ result = add_mac(outctx, tsigout);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Create response message 2.
+ */
+ isc_buffer_allocate(dt_mctx, &buf, 65535);
+
+ assert_int_equal(result, ISC_R_SUCCESS);
+ render(buf, DNS_MESSAGEFLAG_QR, key, &tsigout, &tsigout, outctx);
+
+ /*
+ * Process response message 2.
+ */
+ dns_message_create(dt_mctx, DNS_MESSAGE_INTENTPARSE, &msg);
+ assert_non_null(msg);
+
+ msg->tcp_continuation = 1;
+ msg->tsigctx = tsigctx;
+ tsigctx = NULL;
+
+ result = dns_message_settsigkey(msg, key);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_message_parse(msg, buf, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ printmessage(msg);
+
+ result = dns_message_setquerytsig(msg, tsigin);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_tsig_verify(buf, msg, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(msg->verified_sig, 0);
+ assert_int_equal(msg->tsigstatus, dns_rcode_noerror);
+
+ /*
+ * Check that we don't have a TSIG in the second message.
+ */
+ tsigowner = NULL;
+ assert_true(dns_message_gettsig(msg, &tsigowner) == NULL);
+
+ tsigctx = msg->tsigctx;
+ msg->tsigctx = NULL;
+ isc_buffer_free(&buf);
+ dns_message_detach(&msg);
+
+ /*
+ * Create response message 3.
+ */
+ isc_buffer_allocate(dt_mctx, &buf, 65535);
+ render(buf, DNS_MESSAGEFLAG_QR, key, &tsigout, &tsigout, outctx);
+
+ result = add_tsig(outctx, key, buf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Process response message 3.
+ */
+ dns_message_create(dt_mctx, DNS_MESSAGE_INTENTPARSE, &msg);
+ assert_non_null(msg);
+
+ msg->tcp_continuation = 1;
+ msg->tsigctx = tsigctx;
+ tsigctx = NULL;
+
+ result = dns_message_settsigkey(msg, key);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_message_parse(msg, buf, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ printmessage(msg);
+
+ /*
+ * Check that we had a TSIG in the third message.
+ */
+ assert_non_null(dns_message_gettsig(msg, &tsigowner));
+
+ result = dns_message_setquerytsig(msg, tsigin);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_tsig_verify(buf, msg, NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(msg->verified_sig, 1);
+ assert_int_equal(msg->tsigstatus, dns_rcode_noerror);
+
+ if (tsigin != NULL) {
+ isc_buffer_free(&tsigin);
+ }
+
+ result = dns_message_getquerytsig(msg, dt_mctx, &tsigin);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_free(&buf);
+ dns_message_detach(&msg);
+
+ if (outctx != NULL) {
+ dst_context_destroy(&outctx);
+ }
+ if (querytsig != NULL) {
+ isc_buffer_free(&querytsig);
+ }
+ if (tsigin != NULL) {
+ isc_buffer_free(&tsigin);
+ }
+ if (tsigout != NULL) {
+ isc_buffer_free(&tsigout);
+ }
+ dns_tsigkey_detach(&key);
+ if (ring != NULL) {
+ dns_tsigkeyring_detach(&ring);
+ }
+}
+
+/* Tests the dns__tsig_algvalid function */
+static void
+algvalid_test(void **state) {
+ UNUSED(state);
+
+ assert_true(dns__tsig_algvalid(DST_ALG_HMACMD5));
+
+ assert_true(dns__tsig_algvalid(DST_ALG_HMACSHA1));
+ assert_true(dns__tsig_algvalid(DST_ALG_HMACSHA224));
+ assert_true(dns__tsig_algvalid(DST_ALG_HMACSHA256));
+ assert_true(dns__tsig_algvalid(DST_ALG_HMACSHA384));
+ assert_true(dns__tsig_algvalid(DST_ALG_HMACSHA512));
+
+ assert_false(dns__tsig_algvalid(DST_ALG_GSSAPI));
+}
+
+/* Tests the dns__tsig_algfromname function */
+static void
+algfromname_test(void **state) {
+ UNUSED(state);
+
+ assert_int_equal(dns__tsig_algfromname(DNS_TSIG_HMACMD5_NAME),
+ DST_ALG_HMACMD5);
+ assert_int_equal(dns__tsig_algfromname(DNS_TSIG_HMACSHA1_NAME),
+ DST_ALG_HMACSHA1);
+ assert_int_equal(dns__tsig_algfromname(DNS_TSIG_HMACSHA224_NAME),
+ DST_ALG_HMACSHA224);
+ assert_int_equal(dns__tsig_algfromname(DNS_TSIG_HMACSHA256_NAME),
+ DST_ALG_HMACSHA256);
+ assert_int_equal(dns__tsig_algfromname(DNS_TSIG_HMACSHA384_NAME),
+ DST_ALG_HMACSHA384);
+ assert_int_equal(dns__tsig_algfromname(DNS_TSIG_HMACSHA512_NAME),
+ DST_ALG_HMACSHA512);
+
+ assert_int_equal(dns__tsig_algfromname(DNS_TSIG_GSSAPI_NAME),
+ DST_ALG_GSSAPI);
+ assert_int_equal(dns__tsig_algfromname(DNS_TSIG_GSSAPIMS_NAME),
+ DST_ALG_GSSAPI);
+
+ assert_int_equal(dns__tsig_algfromname(dns_rootname), 0);
+}
+
+/* Tests the dns__tsig_algnamefromname function */
+
+/*
+ * Helper function to create a dns_name_t from a string and see if
+ * the dns__tsig_algnamefromname function can correctly match it against the
+ * static table of known algorithms.
+ */
+static void
+test_name(const char *name_string, const dns_name_t *expected) {
+ dns_name_t name;
+ dns_name_init(&name, NULL);
+ assert_int_equal(dns_name_fromstring(&name, name_string, 0, dt_mctx),
+ ISC_R_SUCCESS);
+ assert_ptr_equal(dns__tsig_algnamefromname(&name), expected);
+ dns_name_free(&name, dt_mctx);
+}
+
+static void
+algnamefromname_test(void **state) {
+ UNUSED(state);
+
+ /* test the standard algorithms */
+ test_name("hmac-md5.sig-alg.reg.int", DNS_TSIG_HMACMD5_NAME);
+ test_name("hmac-sha1", DNS_TSIG_HMACSHA1_NAME);
+ test_name("hmac-sha224", DNS_TSIG_HMACSHA224_NAME);
+ test_name("hmac-sha256", DNS_TSIG_HMACSHA256_NAME);
+ test_name("hmac-sha384", DNS_TSIG_HMACSHA384_NAME);
+ test_name("hmac-sha512", DNS_TSIG_HMACSHA512_NAME);
+
+ test_name("gss-tsig", DNS_TSIG_GSSAPI_NAME);
+ test_name("gss.microsoft.com", DNS_TSIG_GSSAPIMS_NAME);
+
+ /* try another name that isn't a standard algorithm name */
+ assert_null(dns__tsig_algnamefromname(dns_rootname));
+}
+
+/* Tests the dns__tsig_algallocated function */
+static void
+algallocated_test(void **state) {
+ UNUSED(state);
+
+ /* test the standard algorithms */
+ assert_false(dns__tsig_algallocated(DNS_TSIG_HMACMD5_NAME));
+ assert_false(dns__tsig_algallocated(DNS_TSIG_HMACSHA1_NAME));
+ assert_false(dns__tsig_algallocated(DNS_TSIG_HMACSHA224_NAME));
+ assert_false(dns__tsig_algallocated(DNS_TSIG_HMACSHA256_NAME));
+ assert_false(dns__tsig_algallocated(DNS_TSIG_HMACSHA384_NAME));
+ assert_false(dns__tsig_algallocated(DNS_TSIG_HMACSHA512_NAME));
+
+ assert_false(dns__tsig_algallocated(DNS_TSIG_HMACSHA512_NAME));
+ assert_false(dns__tsig_algallocated(DNS_TSIG_HMACSHA512_NAME));
+
+ /* try another name that isn't a standard algorithm name */
+ assert_true(dns__tsig_algallocated(dns_rootname));
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(tsig_tcp_test, _setup,
+ _teardown),
+ cmocka_unit_test(algvalid_test),
+ cmocka_unit_test(algfromname_test),
+ cmocka_unit_test_setup_teardown(algnamefromname_test, _setup,
+ _teardown),
+ cmocka_unit_test(algallocated_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/update_test.c b/lib/dns/tests/update_test.c
new file mode 100644
index 0000000..bc792a1
--- /dev/null
+++ b/lib/dns/tests/update_test.c
@@ -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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/serial.h>
+#include <isc/stdtime.h>
+#include <isc/util.h>
+
+#include <dns/update.h>
+
+#include "dnstest.h"
+
+/*
+ * Fix the linking order problem for overridden isc_stdtime_get() by making
+ * everything local. This also allows static functions from update.c to be
+ * tested.
+ */
+#include "../update.c"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ setenv("TZ", "", 1);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static uint32_t mystdtime;
+
+static void
+set_mystdtime(int year, int month, int day) {
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = year - 1900;
+ tm.tm_mon = month - 1;
+ tm.tm_mday = day;
+ mystdtime = timegm(&tm);
+}
+
+/*
+ * Override isc_stdtime_get() from lib/isc/[unix/win32]/stdtime.c
+ * with our own for testing purposes.
+ */
+void
+isc_stdtime_get(isc_stdtime_t *now) {
+ *now = mystdtime;
+}
+
+/*
+ * Because update_test.o requires dns_update_*() symbols, the linker is able
+ * to resolve them using libdns.a(update.o). That object has other symbol
+ * dependencies (dst_key_*()), so it pulls libdns.a(dst_api.o).
+ * That object file requires the isc_stdtime_tostring() symbol.
+ *
+ * Define a local version here so that we don't have to depend on
+ * libisc.a(stdtime.o). If isc_stdtime_tostring() would be left undefined,
+ * the linker has to get the required object file, and that will result in a
+ * multiple definition error because the isc_stdtime_get() symbol exported
+ * there is already in the exported list.
+ */
+void
+isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen) {
+ UNUSED(t);
+ UNUSED(out);
+ UNUSED(outlen);
+}
+
+/* simple increment by 1 */
+static void
+increment_test(void **state) {
+ uint32_t old = 50;
+ uint32_t serial;
+
+ UNUSED(state);
+
+ serial = dns_update_soaserial(old, dns_updatemethod_increment, NULL);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, 51);
+}
+
+/* increment past zero, 0xfffffffff -> 1 */
+static void
+increment_past_zero_test(void **state) {
+ uint32_t old = 0xffffffffu;
+ uint32_t serial;
+
+ UNUSED(state);
+
+ serial = dns_update_soaserial(old, dns_updatemethod_increment, NULL);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, 1u);
+}
+
+/* past to unixtime */
+static void
+past_to_unix_test(void **state) {
+ uint32_t old;
+ uint32_t serial;
+
+ UNUSED(state);
+
+ set_mystdtime(2011, 6, 22);
+ old = mystdtime - 1;
+
+ serial = dns_update_soaserial(old, dns_updatemethod_unixtime, NULL);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, mystdtime);
+}
+
+/* now to unixtime */
+static void
+now_to_unix_test(void **state) {
+ uint32_t old;
+ uint32_t serial;
+
+ UNUSED(state);
+
+ set_mystdtime(2011, 6, 22);
+ old = mystdtime;
+
+ serial = dns_update_soaserial(old, dns_updatemethod_unixtime, NULL);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, old + 1);
+}
+
+/* future to unixtime */
+static void
+future_to_unix_test(void **state) {
+ uint32_t old;
+ uint32_t serial;
+
+ UNUSED(state);
+
+ set_mystdtime(2011, 6, 22);
+ old = mystdtime + 1;
+
+ serial = dns_update_soaserial(old, dns_updatemethod_unixtime, NULL);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, old + 1);
+}
+
+/* undefined plus 1 to unixtime */
+static void
+undefined_plus1_to_unix_test(void **state) {
+ uint32_t old;
+ uint32_t serial;
+
+ UNUSED(state);
+
+ set_mystdtime(2011, 6, 22);
+ old = mystdtime ^ 0x80000000u;
+ old += 1;
+
+ serial = dns_update_soaserial(old, dns_updatemethod_unixtime, NULL);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, mystdtime);
+}
+
+/* undefined minus 1 to unixtime */
+static void
+undefined_minus1_to_unix_test(void **state) {
+ uint32_t old;
+ uint32_t serial;
+
+ UNUSED(state);
+
+ set_mystdtime(2011, 6, 22);
+ old = mystdtime ^ 0x80000000u;
+ old -= 1;
+
+ serial = dns_update_soaserial(old, dns_updatemethod_unixtime, NULL);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, old + 1);
+}
+
+/* undefined to unixtime */
+static void
+undefined_to_unix_test(void **state) {
+ uint32_t old;
+ uint32_t serial;
+
+ UNUSED(state);
+
+ set_mystdtime(2011, 6, 22);
+ old = mystdtime ^ 0x80000000u;
+
+ serial = dns_update_soaserial(old, dns_updatemethod_unixtime, NULL);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, old + 1);
+}
+
+/* handle unixtime being zero */
+static void
+unixtime_zero_test(void **state) {
+ uint32_t old;
+ uint32_t serial;
+
+ UNUSED(state);
+
+ mystdtime = 0;
+ old = 0xfffffff0;
+
+ serial = dns_update_soaserial(old, dns_updatemethod_unixtime, NULL);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, old + 1);
+}
+
+/* past to date */
+static void
+past_to_date_test(void **state) {
+ uint32_t old, serial;
+ dns_updatemethod_t used = dns_updatemethod_none;
+
+ UNUSED(state);
+
+ set_mystdtime(2014, 3, 31);
+ old = dns_update_soaserial(0, dns_updatemethod_date, NULL);
+ set_mystdtime(2014, 4, 1);
+
+ serial = dns_update_soaserial(old, dns_updatemethod_date, &used);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, 2014040100);
+ assert_int_equal(dns_updatemethod_date, used);
+}
+
+/* now to date */
+static void
+now_to_date_test(void **state) {
+ uint32_t old;
+ uint32_t serial;
+ dns_updatemethod_t used = dns_updatemethod_none;
+
+ UNUSED(state);
+
+ set_mystdtime(2014, 4, 1);
+ old = dns_update_soaserial(0, dns_updatemethod_date, NULL);
+
+ serial = dns_update_soaserial(old, dns_updatemethod_date, &used);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, 2014040101);
+ assert_int_equal(dns_updatemethod_date, used);
+
+ old = 2014040198;
+ serial = dns_update_soaserial(old, dns_updatemethod_date, &used);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, 2014040199);
+ assert_int_equal(dns_updatemethod_date, used);
+
+ /*
+ * Stealing from "tomorrow".
+ */
+ old = 2014040199;
+ serial = dns_update_soaserial(old, dns_updatemethod_date, &used);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, 2014040200);
+ assert_int_equal(dns_updatemethod_increment, used);
+}
+
+/* future to date */
+static void
+future_to_date_test(void **state) {
+ uint32_t old;
+ uint32_t serial;
+ dns_updatemethod_t used = dns_updatemethod_none;
+
+ UNUSED(state);
+
+ set_mystdtime(2014, 4, 1);
+ old = dns_update_soaserial(0, dns_updatemethod_date, NULL);
+ set_mystdtime(2014, 3, 31);
+
+ serial = dns_update_soaserial(old, dns_updatemethod_date, &used);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, 2014040101);
+ assert_int_equal(dns_updatemethod_increment, used);
+
+ old = serial;
+ serial = dns_update_soaserial(old, dns_updatemethod_date, &used);
+ assert_true(isc_serial_lt(old, serial));
+ assert_int_not_equal(serial, 0);
+ assert_int_equal(serial, 2014040102);
+ assert_int_equal(dns_updatemethod_increment, used);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(increment_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(increment_past_zero_test,
+ _setup, _teardown),
+ cmocka_unit_test_setup_teardown(past_to_unix_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(now_to_unix_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(future_to_unix_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(undefined_to_unix_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(undefined_plus1_to_unix_test,
+ _setup, _teardown),
+ cmocka_unit_test_setup_teardown(undefined_minus1_to_unix_test,
+ _setup, _teardown),
+ cmocka_unit_test_setup_teardown(unixtime_zero_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(past_to_date_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(now_to_date_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(future_to_date_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/zonemgr_test.c b/lib/dns/tests/zonemgr_test.c
new file mode 100644
index 0000000..3424342
--- /dev/null
+++ b/lib/dns/tests/zonemgr_test.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/name.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+
+#include "dnstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+/* create zone manager */
+static void
+zonemgr_create(void **state) {
+ dns_zonemgr_t *myzonemgr = NULL;
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr,
+ &myzonemgr);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_zonemgr_shutdown(myzonemgr);
+ dns_zonemgr_detach(&myzonemgr);
+ assert_null(myzonemgr);
+}
+
+/* manage and release a zone */
+static void
+zonemgr_managezone(void **state) {
+ dns_zonemgr_t *myzonemgr = NULL;
+ dns_zone_t *zone = NULL;
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr,
+ &myzonemgr);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_test_makezone("foo", &zone, NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* This should not succeed until the dns_zonemgr_setsize() is run */
+ result = dns_zonemgr_managezone(myzonemgr, zone);
+ assert_int_equal(result, ISC_R_FAILURE);
+
+ assert_int_equal(dns_zonemgr_getcount(myzonemgr, DNS_ZONESTATE_ANY), 0);
+
+ result = dns_zonemgr_setsize(myzonemgr, 1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Now it should succeed */
+ result = dns_zonemgr_managezone(myzonemgr, zone);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_int_equal(dns_zonemgr_getcount(myzonemgr, DNS_ZONESTATE_ANY), 1);
+
+ dns_zonemgr_releasezone(myzonemgr, zone);
+ dns_zone_detach(&zone);
+
+ assert_int_equal(dns_zonemgr_getcount(myzonemgr, DNS_ZONESTATE_ANY), 0);
+
+ dns_zonemgr_shutdown(myzonemgr);
+ dns_zonemgr_detach(&myzonemgr);
+ assert_null(myzonemgr);
+}
+
+/* create and release a zone */
+static void
+zonemgr_createzone(void **state) {
+ dns_zonemgr_t *myzonemgr = NULL;
+ dns_zone_t *zone = NULL;
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr,
+ &myzonemgr);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* This should not succeed until the dns_zonemgr_setsize() is run */
+ result = dns_zonemgr_createzone(myzonemgr, &zone);
+ assert_int_equal(result, ISC_R_FAILURE);
+
+ result = dns_zonemgr_setsize(myzonemgr, 1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* Now it should succeed */
+ result = dns_zonemgr_createzone(myzonemgr, &zone);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_non_null(zone);
+
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+
+ dns_zonemgr_shutdown(myzonemgr);
+ dns_zonemgr_detach(&myzonemgr);
+ assert_null(myzonemgr);
+}
+
+/* manage and release a zone */
+static void
+zonemgr_unreachable(void **state) {
+ dns_zonemgr_t *myzonemgr = NULL;
+ dns_zone_t *zone = NULL;
+ isc_sockaddr_t addr1, addr2;
+ struct in_addr in;
+ isc_result_t result;
+ isc_time_t now;
+
+ UNUSED(state);
+
+ TIME_NOW(&now);
+
+ result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr,
+ &myzonemgr);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_test_makezone("foo", &zone, NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_zonemgr_setsize(myzonemgr, 1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_zonemgr_managezone(myzonemgr, zone);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ in.s_addr = inet_addr("10.53.0.1");
+ isc_sockaddr_fromin(&addr1, &in, 2112);
+ in.s_addr = inet_addr("10.53.0.2");
+ isc_sockaddr_fromin(&addr2, &in, 5150);
+ assert_false(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now));
+ /*
+ * We require multiple unreachableadd calls to mark a server as
+ * unreachable.
+ */
+ dns_zonemgr_unreachableadd(myzonemgr, &addr1, &addr2, &now);
+ assert_false(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now));
+ dns_zonemgr_unreachableadd(myzonemgr, &addr1, &addr2, &now);
+ assert_true(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now));
+
+ in.s_addr = inet_addr("10.53.0.3");
+ isc_sockaddr_fromin(&addr2, &in, 5150);
+ assert_false(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now));
+ /*
+ * We require multiple unreachableadd calls to mark a server as
+ * unreachable.
+ */
+ dns_zonemgr_unreachableadd(myzonemgr, &addr1, &addr2, &now);
+ dns_zonemgr_unreachableadd(myzonemgr, &addr1, &addr2, &now);
+ assert_true(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now));
+
+ dns_zonemgr_unreachabledel(myzonemgr, &addr1, &addr2);
+ assert_false(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now));
+
+ in.s_addr = inet_addr("10.53.0.2");
+ isc_sockaddr_fromin(&addr2, &in, 5150);
+ assert_true(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now));
+ dns_zonemgr_unreachabledel(myzonemgr, &addr1, &addr2);
+ assert_false(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now));
+
+ dns_zonemgr_releasezone(myzonemgr, zone);
+ dns_zone_detach(&zone);
+ dns_zonemgr_shutdown(myzonemgr);
+ dns_zonemgr_detach(&myzonemgr);
+ assert_null(myzonemgr);
+}
+
+/*
+ * XXX:
+ * dns_zonemgr API calls that are not yet part of this unit test:
+ *
+ * - dns_zonemgr_attach
+ * - dns_zonemgr_forcemaint
+ * - dns_zonemgr_resumexfrs
+ * - dns_zonemgr_shutdown
+ * - dns_zonemgr_setsize
+ * - dns_zonemgr_settransfersin
+ * - dns_zonemgr_getttransfersin
+ * - dns_zonemgr_settransfersperns
+ * - dns_zonemgr_getttransfersperns
+ * - dns_zonemgr_setiolimit
+ * - dns_zonemgr_getiolimit
+ * - dns_zonemgr_dbdestroyed
+ * - dns_zonemgr_setserialqueryrate
+ * - dns_zonemgr_getserialqueryrate
+ */
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(zonemgr_create, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(zonemgr_managezone, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(zonemgr_createzone, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(zonemgr_unreachable, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/tests/zt_test.c b/lib/dns/tests/zt_test.c
new file mode 100644
index 0000000..e79adb2
--- /dev/null
+++ b/lib/dns/tests/zt_test.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/app.h>
+#include <isc/atomic.h>
+#include <isc/buffer.h>
+#include <isc/print.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/name.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+#include <dns/zt.h>
+
+#include "dnstest.h"
+
+struct args {
+ void *arg1;
+ void *arg2;
+ bool arg3;
+};
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = dns_test_begin(NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ dns_test_end();
+
+ return (0);
+}
+
+static isc_result_t
+count_zone(dns_zone_t *zone, void *uap) {
+ int *nzones = (int *)uap;
+
+ UNUSED(zone);
+
+ *nzones += 1;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+load_done(dns_zt_t *zt, dns_zone_t *zone, isc_task_t *task) {
+ /* We treat zt as a pointer to a boolean for testing purposes */
+ atomic_bool *done = (atomic_bool *)zt;
+
+ UNUSED(zone);
+ UNUSED(task);
+
+ atomic_store(done, true);
+ isc_app_shutdown();
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+all_done(void *arg) {
+ atomic_bool *done = (atomic_bool *)arg;
+
+ atomic_store(done, true);
+ isc_app_shutdown();
+ return (ISC_R_SUCCESS);
+}
+
+static void
+start_zt_asyncload(isc_task_t *task, isc_event_t *event) {
+ struct args *args = (struct args *)(event->ev_arg);
+
+ UNUSED(task);
+
+ dns_zt_asyncload(args->arg1, false, all_done, args->arg2);
+
+ isc_event_free(&event);
+}
+
+static void
+start_zone_asyncload(isc_task_t *task, isc_event_t *event) {
+ struct args *args = (struct args *)(event->ev_arg);
+
+ UNUSED(task);
+
+ dns_zone_asyncload(args->arg1, args->arg3, load_done, args->arg2);
+ isc_event_free(&event);
+}
+
+/* apply a function to a zone table */
+static void
+apply(void **state) {
+ isc_result_t result;
+ dns_zone_t *zone = NULL;
+ dns_view_t *view = NULL;
+ int nzones = 0;
+
+ UNUSED(state);
+
+ result = dns_test_makezone("foo", &zone, NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ view = dns_zone_getview(zone);
+ assert_non_null(view->zonetable);
+
+ assert_int_equal(nzones, 0);
+ result = dns_zt_apply(view->zonetable, isc_rwlocktype_read, false, NULL,
+ count_zone, &nzones);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(nzones, 1);
+
+ /* These steps are necessary so the zone can be detached properly */
+ result = dns_test_setupzonemgr();
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_test_managezone(zone);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_test_releasezone(zone);
+ dns_test_closezonemgr();
+
+ /* The view was left attached in dns_test_makezone() */
+ dns_view_detach(&view);
+ dns_zone_detach(&zone);
+}
+
+/* asynchronous zone load */
+static void
+asyncload_zone(void **state) {
+ isc_result_t result;
+ int n;
+ dns_zone_t *zone = NULL;
+ dns_view_t *view = NULL;
+ dns_db_t *db = NULL;
+ FILE *zonefile, *origfile;
+ char buf[4096];
+ atomic_bool done;
+ int i = 0;
+ struct args args;
+
+ UNUSED(state);
+
+ atomic_init(&done, false);
+
+ result = dns_test_makezone("foo", &zone, NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = dns_test_setupzonemgr();
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_test_managezone(zone);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ view = dns_zone_getview(zone);
+ assert_non_null(view->zonetable);
+
+ assert_false(dns__zone_loadpending(zone));
+ assert_false(atomic_load(&done));
+ zonefile = fopen("./zone.data", "wb");
+ assert_non_null(zonefile);
+ origfile = fopen("./testdata/zt/zone1.db", "r+b");
+ assert_non_null(origfile);
+ n = fread(buf, 1, 4096, origfile);
+ fclose(origfile);
+ fwrite(buf, 1, n, zonefile);
+ fflush(zonefile);
+
+ dns_zone_setfile(zone, "./zone.data", dns_masterformat_text,
+ &dns_master_style_default);
+
+ args.arg1 = zone;
+ args.arg2 = &done;
+ args.arg3 = false;
+ isc_app_onrun(dt_mctx, maintask, start_zone_asyncload, &args);
+
+ isc_app_run();
+ while (dns__zone_loadpending(zone) && i++ < 5000) {
+ dns_test_nap(1000);
+ }
+ assert_true(atomic_load(&done));
+ /* The zone should now be loaded; test it */
+ result = dns_zone_getdb(zone, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_db_detach(&db);
+ /*
+ * Add something to zone file, reload zone with newonly - it should
+ * not be reloaded.
+ */
+ fprintf(zonefile, "\nb in b 1.2.3.4\n");
+ fflush(zonefile);
+ fclose(zonefile);
+
+ args.arg1 = zone;
+ args.arg2 = &done;
+ args.arg3 = true;
+ isc_app_onrun(dt_mctx, maintask, start_zone_asyncload, &args);
+
+ isc_app_run();
+
+ while (dns__zone_loadpending(zone) && i++ < 5000) {
+ dns_test_nap(1000);
+ }
+ assert_true(atomic_load(&done));
+ /* The zone should now be loaded; test it */
+ result = dns_zone_getdb(zone, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_db_detach(&db);
+
+ /* Now reload it without newonly - it should be reloaded */
+ args.arg1 = zone;
+ args.arg2 = &done;
+ args.arg3 = false;
+ isc_app_onrun(dt_mctx, maintask, start_zone_asyncload, &args);
+
+ isc_app_run();
+
+ while (dns__zone_loadpending(zone) && i++ < 5000) {
+ dns_test_nap(1000);
+ }
+ assert_true(atomic_load(&done));
+ /* The zone should now be loaded; test it */
+ result = dns_zone_getdb(zone, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_non_null(db);
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ dns_test_releasezone(zone);
+ dns_test_closezonemgr();
+
+ dns_zone_detach(&zone);
+ dns_view_detach(&view);
+}
+
+/* asynchronous zone table load */
+static void
+asyncload_zt(void **state) {
+ isc_result_t result;
+ dns_zone_t *zone1 = NULL, *zone2 = NULL, *zone3 = NULL;
+ dns_view_t *view;
+ dns_zt_t *zt = NULL;
+ dns_db_t *db = NULL;
+ atomic_bool done;
+ int i = 0;
+ struct args args;
+
+ UNUSED(state);
+
+ atomic_init(&done, false);
+
+ result = dns_test_makezone("foo", &zone1, NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_zone_setfile(zone1, "testdata/zt/zone1.db", dns_masterformat_text,
+ &dns_master_style_default);
+ view = dns_zone_getview(zone1);
+
+ result = dns_test_makezone("bar", &zone2, view, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_zone_setfile(zone2, "testdata/zt/zone1.db", dns_masterformat_text,
+ &dns_master_style_default);
+
+ /* This one will fail to load */
+ result = dns_test_makezone("fake", &zone3, view, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ dns_zone_setfile(zone3, "testdata/zt/nonexistent.db",
+ dns_masterformat_text, &dns_master_style_default);
+
+ zt = view->zonetable;
+ assert_non_null(zt);
+
+ result = dns_test_setupzonemgr();
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_test_managezone(zone1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_test_managezone(zone2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_test_managezone(zone3);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_false(dns__zone_loadpending(zone1));
+ assert_false(dns__zone_loadpending(zone2));
+ assert_false(atomic_load(&done));
+
+ args.arg1 = zt;
+ args.arg2 = &done;
+ isc_app_onrun(dt_mctx, maintask, start_zt_asyncload, &args);
+
+ isc_app_run();
+ while (!atomic_load(&done) && i++ < 5000) {
+ dns_test_nap(1000);
+ }
+ assert_true(atomic_load(&done));
+
+ /* Both zones should now be loaded; test them */
+ result = dns_zone_getdb(zone1, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_non_null(db);
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ result = dns_zone_getdb(zone2, &db);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_non_null(db);
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ dns_test_releasezone(zone3);
+ dns_test_releasezone(zone2);
+ dns_test_releasezone(zone1);
+ dns_test_closezonemgr();
+
+ dns_zone_detach(&zone1);
+ dns_zone_detach(&zone2);
+ dns_zone_detach(&zone3);
+ dns_view_detach(&view);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(apply, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(asyncload_zone, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(asyncload_zt, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/dns/time.c b/lib/dns/time.c
new file mode 100644
index 0000000..8564a95
--- /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/serial.h>
+#include <isc/stdtime.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/util.h>
+
+#include <dns/result.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/timer.c b/lib/dns/timer.c
new file mode 100644
index 0000000..ef2895d
--- /dev/null
+++ b/lib/dns/timer.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/time.h>
+#include <isc/timer.h>
+
+#include <dns/timer.h>
+#include <dns/types.h>
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+isc_result_t
+dns_timer_setidle(isc_timer_t *timer, unsigned int maxtime,
+ unsigned int idletime, bool purge) {
+ isc_result_t result;
+ isc_interval_t maxinterval, idleinterval;
+ isc_time_t expires;
+
+ /* Compute the time of expiry. */
+ isc_interval_set(&maxinterval, maxtime, 0);
+ CHECK(isc_time_nowplusinterval(&expires, &maxinterval));
+
+ /*
+ * Compute the idle interval, and add a spare nanosecond to
+ * work around the silly limitation of the ISC timer interface
+ * that you cannot specify an idle interval of zero.
+ */
+ isc_interval_set(&idleinterval, idletime, 1);
+
+ CHECK(isc_timer_reset(timer, isc_timertype_once, &expires,
+ &idleinterval, purge));
+failure:
+ return (result);
+}
diff --git a/lib/dns/tkey.c b/lib/dns/tkey.c
new file mode 100644
index 0000000..d91ed31
--- /dev/null
+++ b/lib/dns/tkey.c
@@ -0,0 +1,1604 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/md.h>
+#include <isc/mem.h>
+#include <isc/nonce.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <pk11/site.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
+
+#if USE_PKCS11
+#include <pk11/pk11.h>
+#endif /* if USE_PKCS11 */
+
+#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",
+ dns_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_copynf(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 (ISC_R_NOPERM);
+ }
+
+ 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) {
+#ifdef GSSAPI
+ OM_uint32 gret, minor, lifetime;
+#endif /* ifdef 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;
+#ifdef GSSAPI
+ gret = gss_context_time(&minor, gss_ctx, &lifetime);
+ if (gret == GSS_S_COMPLETE && now + lifetime < expire) {
+ expire = now + lifetime;
+ }
+#endif /* ifdef 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_copynf(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_copynf(name, qname);
+ dns_name_copynf(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 (ISC_RESULTCLASS_DNSRCODE + 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 (ISC_RESULTCLASS_DNSRCODE + 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 (ISC_RESULTCLASS_DNSRCODE + 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 (ISC_RESULTCLASS_DNSRCODE + 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_copynf(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/tsec.c b/lib/dns/tsec.c
new file mode 100644
index 0000000..249a2ea
--- /dev/null
+++ b/lib/dns/tsec.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/util.h>
+
+#include <pk11/site.h>
+
+#include <dns/result.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..e102d09
--- /dev/null
+++ b/lib/dns/tsig.c
@@ -0,0 +1,1902 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/serial.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <pk11/site.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/result.h>
+#include <dns/tsig.h>
+
+#include <dst/result.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);
+LIBDNS_EXTERNAL_DATA 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);
+LIBDNS_EXTERNAL_DATA 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);
+LIBDNS_EXTERNAL_DATA 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);
+LIBDNS_EXTERNAL_DATA 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);
+LIBDNS_EXTERNAL_DATA 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);
+LIBDNS_EXTERNAL_DATA 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);
+LIBDNS_EXTERNAL_DATA 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);
+LIBDNS_EXTERNAL_DATA 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 && 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));
+ }
+ }
+ 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);
+
+ 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_copynf(&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.
+ */
+ 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?
+ */
+ /* cppcheck-suppress uninitStructMember
+ * symbolName=tsig.originalid */
+ 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?
+ */
+ 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;
+
+ 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..7cd9774
--- /dev/null
+++ b/lib/dns/tsig_p.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_TSIG_P_H
+#define DNS_TSIG_P_H
+
+/*! \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
+
+#endif /* DNS_TSIG_P_H */
diff --git a/lib/dns/ttl.c b/lib/dns/ttl.c
new file mode 100644
index 0000000..86c0372
--- /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/string.h>
+#include <isc/util.h>
+
+#include <dns/result.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..9d71238
--- /dev/null
+++ b/lib/dns/update.c
@@ -0,0 +1,2280 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/platform.h>
+#include <isc/print.h>
+#include <isc/random.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/result.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..6cf717f
--- /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/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/result.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)",
+ dns_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(dns_fixedname_name(&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_copynf(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_copynf(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_copynf(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_copynf(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/version.c b/lib/dns/version.c
new file mode 100644
index 0000000..2be1656
--- /dev/null
+++ b/lib/dns/version.c
@@ -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.
+ */
+
+/*! \file */
+
+#include <dns/version.h>
+
+const char dns_version[] = VERSION;
+const char dns_major[] = MAJOR;
+const char dns_mapapi[] = MAPAPI;
diff --git a/lib/dns/view.c b/lib/dns/view.c
new file mode 100644
index 0000000..a67dd73
--- /dev/null
+++ b/lib/dns/view.c
@@ -0,0 +1,2642 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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>
+
+#include <dns/lmdb.h>
+#endif /* HAVE_LMDB */
+
+#include <isc/atomic.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/lex.h>
+#include <isc/print.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/result.h>
+#include <dns/rpz.h>
+#include <dns/rrl.h>
+#include <dns/stats.h>
+#include <dns/time.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);
+
+ view->zonetable = NULL;
+ result = dns_zt_create(mctx, rdclass, &view->zonetable);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "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(__FILE__, __LINE__,
+ "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->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->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_init(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_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->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_detach_rpzs(&view->rpzs);
+ }
+ if (view->catzs != NULL) {
+ dns_catz_catzs_detach(&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->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_destroy(&view->aclenv);
+ if (view->failcache != NULL) {
+ dns_badcache_destroy(&view->failcache);
+ }
+ isc_mutex_destroy(&view->new_zone_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;
+
+ 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) {
+ if (view->flush) {
+ dns_zt_flushanddetach(&view->zonetable);
+ } else {
+ dns_zt_detach(&view->zonetable);
+ }
+ }
+ 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_catzs_detach(&view->catzs);
+ }
+ if (view->ntatable_priv != NULL) {
+ dns_ntatable_shutdown(view->ntatable_priv);
+ }
+ UNLOCK(&view->lock);
+
+ /* Need to detach zones outside view lock */
+ 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_socketmgr_t *socketmgr, 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, socketmgr,
+ 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);
+
+ result = dns_adb_create(mctx, view, timermgr, taskmgr, &view->adb);
+ isc_mem_setname(mctx, "ADB", NULL);
+ 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, timermgr, socketmgr,
+ 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_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_copynf(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_copynf(zfname, fname);
+ if (dcname != NULL) {
+ dns_name_copynf(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_copynf(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_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);
+}
+
+isc_result_t
+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 (ISC_R_SUCCESS);
+ }
+ 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);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+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 (ISC_R_SUCCESS);
+ }
+ 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);
+ return (ISC_R_SUCCESS);
+}
+
+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_copynf(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);
+ }
+ if (view->zonetable != NULL) {
+ dns_zt_setviewcommit(view->zonetable);
+ }
+
+ UNLOCK(&view->lock);
+
+ 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);
+}
diff --git a/lib/dns/win32/DLLMain.c b/lib/dns/win32/DLLMain.c
new file mode 100644
index 0000000..62c6e53
--- /dev/null
+++ b/lib/dns/win32/DLLMain.c
@@ -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.
+ */
+
+#include <signal.h>
+#include <windows.h>
+
+/*
+ * Called when we enter the DLL
+ */
+__declspec(dllexport) BOOL WINAPI
+ DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
+ switch (fdwReason) {
+ /*
+ * The DLL is loading due to process
+ * initialization or a call to LoadLibrary.
+ */
+ case DLL_PROCESS_ATTACH:
+ break;
+
+ /* The attached process creates a new thread. */
+ case DLL_THREAD_ATTACH:
+ break;
+
+ /* The thread of the attached process terminates. */
+ case DLL_THREAD_DETACH:
+ break;
+
+ /*
+ * The DLL is unloading from a process due to
+ * process termination or a call to FreeLibrary.
+ */
+ case DLL_PROCESS_DETACH:
+ break;
+
+ default:
+ break;
+ }
+ return (TRUE);
+}
diff --git a/lib/dns/win32/gen.vcxproj.filters.in b/lib/dns/win32/gen.vcxproj.filters.in
new file mode 100644
index 0000000..f6b3bb0
--- /dev/null
+++ b/lib/dns/win32/gen.vcxproj.filters.in
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\gen.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\gen-win32.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/lib/dns/win32/gen.vcxproj.in b/lib/dns/win32/gen.vcxproj.in
new file mode 100644
index 0000000..ea3adc3
--- /dev/null
+++ b/lib/dns/win32/gen.vcxproj.in
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="@TOOLS_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|@BUILD_PLATFORM@">
+ <Configuration>Debug</Configuration>
+ <Platform>@BUILD_PLATFORM@</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|@BUILD_PLATFORM@">
+ <Configuration>Release</Configuration>
+ <Platform>@BUILD_PLATFORM@</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{A3F71D12-F38A-4C77-8D87-8E8854CA74A1}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>gen</RootNamespace>
+ @WINDOWS_TARGET_PLATFORM_VERSION@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@BUILD_PLATFORM@'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@BUILD_PLATFORM@'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|@BUILD_PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|@BUILD_PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@BUILD_PLATFORM@'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>..\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@BUILD_PLATFORM@'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>..\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@BUILD_PLATFORM@'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>.\;..\..\..\;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BrowseInformation>true</BrowseInformation>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <OutputFile>..\$(TargetName)$(TargetExt)</OutputFile>
+ </Link>
+ <PostBuildEvent>
+ <Command>cd ..
+gen -s . -t &gt; include\dns\enumtype.h
+gen -s . -c &gt; include\dns\enumclass.h
+gen -s . -i -P ./rdata/rdatastructpre.h -S ./rdata/rdatastructsuf.h &gt; include\dns\rdatastruct.h
+gen -s . &gt; code.h
+</Command>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|@BUILD_PLATFORM@'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>@INTRINSIC@</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>.\;..\..\..\;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <StringPooling>true</StringPooling>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <WholeProgramOptimization>false</WholeProgramOptimization>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <OutputFile>..\$(TargetName)$(TargetExt)</OutputFile>
+ <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
+ </Link>
+ <PostBuildEvent>
+ <Command>cd ..
+gen -s . -t &gt; include\dns\enumtype.h
+gen -s . -c &gt; include\dns\enumclass.h
+gen -s . -i -P ./rdata/rdatastructpre.h -S ./rdata/rdatastructsuf.h &gt; include\dns\rdatastruct.h
+gen -s . &gt; code.h
+</Command>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\gen.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\gen-win32.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/lib/dns/win32/gen.vcxproj.user b/lib/dns/win32/gen.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/lib/dns/win32/gen.vcxproj.user
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+</Project> \ No newline at end of file
diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in
new file mode 100644
index 0000000..5c0ba7c
--- /dev/null
+++ b/lib/dns/win32/libdns.def.in
@@ -0,0 +1,1548 @@
+LIBRARY libdns
+
+; Exported Functions
+EXPORTS
+
+; test only
+dns__rbt_checkproperties
+dns__rbt_getheight
+dns__rbtnode_getdistance
+dns__rbtnode_namelen
+dns__zone_findkeys
+dns__zone_loadpending
+dns__zone_updatesigs
+
+dns_acl_allowed
+dns_acl_any
+dns_acl_attach
+dns_acl_create
+dns_acl_detach
+dns_acl_isany
+dns_acl_isinsecure
+dns_acl_isnone
+dns_acl_match
+dns_acl_merge
+dns_acl_none
+dns_aclelement_match
+dns_aclenv_copy
+dns_aclenv_destroy
+dns_aclenv_init
+dns_adb_adjustsrtt
+dns_adb_agesrtt
+dns_adb_attach
+dns_adb_beginudpfetch
+dns_adb_cancelfind
+dns_adb_changeflags
+dns_adb_create
+dns_adb_createfind
+dns_adb_destroyfind
+dns_adb_detach
+dns_adb_dump
+dns_adb_dumpfind
+dns_adb_ednsto
+dns_adb_endudpfetch
+dns_adb_findaddrinfo
+dns_adb_flush
+dns_adb_flushname
+dns_adb_flushnames
+dns_adb_freeaddrinfo
+dns_adb_getcookie
+dns_adb_getudpsize
+dns_adb_marklame
+dns_adb_noedns
+dns_adb_plainresponse
+dns_adb_probesize
+dns_adb_setadbsize
+dns_adb_setcookie
+dns_adb_setquota
+dns_adb_setudpsize
+dns_adb_shutdown
+dns_adb_timeout
+dns_adb_whenshutdown
+dns_adbentry_overquota
+dns_badcache_add
+dns_badcache_destroy
+dns_badcache_find
+dns_badcache_flush
+dns_badcache_flushname
+dns_badcache_flushtree
+dns_badcache_init
+dns_badcache_print
+dns_byaddr_cancel
+dns_byaddr_create
+dns_byaddr_createptrname
+dns_byaddr_destroy
+dns_cache_attach
+dns_cache_attachdb
+dns_cache_clean
+dns_cache_create
+dns_cache_detach
+dns_cache_dump
+dns_cache_dumpstats
+dns_cache_flush
+dns_cache_flushname
+dns_cache_flushnode
+dns_cache_getcachesize
+dns_cache_getname
+dns_cache_getservestalerefresh
+dns_cache_getservestalettl
+dns_cache_getstats
+dns_cache_load
+@IF NOTYET
+dns_cache_renderjson
+@END NOTYET
+@IF LIBXML2
+dns_cache_renderxml
+@END LIBXML2
+dns_cache_setcachesize
+dns_cache_setfilename
+dns_cache_setservestalerefresh
+dns_cache_setservestalettl
+dns_cache_updatestats
+dns_catz_add_zone
+dns_catz_catzs_attach
+dns_catz_catzs_detach
+dns_catz_catzs_set_view
+dns_catz_dbupdate_callback
+dns_catz_entry_attach
+dns_catz_entry_cmp
+dns_catz_entry_copy
+dns_catz_entry_detach
+dns_catz_entry_getname
+dns_catz_entry_new
+dns_catz_entry_validate
+dns_catz_generate_masterfilename
+dns_catz_generate_zonecfg
+dns_catz_get_iterator
+dns_catz_get_zone
+dns_catz_new_zone
+dns_catz_new_zones
+dns_catz_options_copy
+dns_catz_options_free
+dns_catz_options_init
+dns_catz_options_setdefault
+dns_catz_postreconfig
+dns_catz_prereconfig
+dns_catz_update_from_db
+dns_catz_update_process
+dns_catz_update_taskaction
+dns_catz_zone_attach
+dns_catz_zone_detach
+dns_catz_zone_getdefoptions
+dns_catz_zone_getname
+dns_catz_zone_resetdefoptions
+dns_catz_zones_merge
+dns_cert_fromtext
+dns_cert_totext
+dns_client_addtrustedkey
+dns_client_cancelresolve
+dns_client_clearservers
+dns_client_create
+dns_client_destroy
+dns_client_destroyrestrans
+dns_client_freeresanswer
+dns_client_resolve
+dns_client_setservers
+dns_client_startresolve
+dns_clientinfo_init
+dns_clientinfomethods_init
+dns_compress_add
+dns_compress_disable
+dns_compress_findglobal
+dns_compress_getedns
+dns_compress_getmethods
+dns_compress_getsensitive
+dns_compress_init
+dns_compress_invalidate
+dns_compress_rollback
+dns_compress_setmethods
+dns_compress_setsensitive
+dns_counter_fromtext
+dns_db_addrdataset
+dns_db_adjusthashsize
+dns_db_allrdatasets
+dns_db_attach
+dns_db_attachnode
+dns_db_attachversion
+dns_db_beginload
+dns_db_class
+dns_db_closeversion
+dns_db_create
+dns_db_createiterator
+dns_db_createsoatuple
+dns_db_currentversion
+dns_db_deleterdataset
+dns_db_detach
+dns_db_detachnode
+dns_db_diff
+dns_db_diffx
+dns_db_dump
+dns_db_endload
+dns_db_expirenode
+dns_db_find
+dns_db_findext
+dns_db_findnode
+dns_db_findnodeext
+dns_db_findnsec3node
+dns_db_findrdataset
+dns_db_findzonecut
+dns_db_getnsec3parameters
+dns_db_getoriginnode
+dns_db_getrrsetstats
+dns_db_getservestalettl
+dns_db_getservestalerefresh
+dns_db_getsigningtime
+dns_db_getsize
+dns_db_getsoaserial
+dns_db_hashsize
+dns_db_iscache
+dns_db_isdnssec
+dns_db_ispersistent
+dns_db_issecure
+dns_db_isstub
+dns_db_iszone
+dns_db_load
+dns_db_newversion
+dns_db_nodecount
+dns_db_nodefullname
+dns_db_origin
+dns_db_overmem
+dns_db_printnode
+dns_db_register
+dns_db_resigned
+dns_db_rpz_attach
+dns_db_rpz_ready
+dns_db_serialize
+dns_db_setcachestats
+dns_db_setgluecachestats
+dns_db_setservestalettl
+dns_db_setservestalerefresh
+dns_db_setsigningtime
+dns_db_settask
+dns_db_subtractrdataset
+dns_db_transfernode
+dns_db_unregister
+dns_db_updatenotify_register
+dns_db_updatenotify_unregister
+dns_dbiterator_current
+dns_dbiterator_destroy
+dns_dbiterator_first
+dns_dbiterator_last
+dns_dbiterator_next
+dns_dbiterator_origin
+dns_dbiterator_pause
+dns_dbiterator_prev
+dns_dbiterator_seek
+dns_dbiterator_setcleanmode
+dns_dbtable_add
+dns_dbtable_adddefault
+dns_dbtable_attach
+dns_dbtable_create
+dns_dbtable_detach
+dns_dbtable_find
+dns_dbtable_getdefault
+dns_dbtable_remove
+dns_dbtable_removedefault
+dns_decompress_edns
+dns_decompress_getmethods
+dns_decompress_init
+dns_decompress_invalidate
+dns_decompress_setmethods
+dns_decompress_type
+dns_diff_append
+dns_diff_appendminimal
+dns_diff_apply
+dns_diff_applysilently
+dns_diff_clear
+dns_diff_init
+dns_diff_load
+dns_diff_print
+dns_diff_sort
+dns_difftuple_copy
+dns_difftuple_create
+dns_difftuple_free
+dns_dispatch_addresponse
+dns_dispatch_attach
+dns_dispatch_cancel
+dns_dispatch_changeattributes
+dns_dispatch_createtcp
+dns_dispatch_detach
+dns_dispatch_getattributes
+dns_dispatch_getdscp
+dns_dispatch_getentrysocket
+dns_dispatch_getlocaladdress
+dns_dispatch_getnext
+dns_dispatch_getsocket
+dns_dispatch_gettcp
+dns_dispatch_getudp
+dns_dispatch_getudp_dup
+dns_dispatch_importrecv
+dns_dispatch_removeresponse
+dns_dispatch_setdscp
+dns_dispatch_starttcp
+dns_dispatchmgr_create
+dns_dispatchmgr_destroy
+dns_dispatchmgr_getblackhole
+dns_dispatchmgr_getblackportlist
+dns_dispatchmgr_setavailports
+dns_dispatchmgr_setblackhole
+dns_dispatchmgr_setblackportlist
+dns_dispatchmgr_setstats
+dns_dispatchset_cancelall
+dns_dispatchset_create
+dns_dispatchset_destroy
+dns_dispatchset_get
+dns_dlz_ssumatch
+dns_dlz_writeablezone
+dns_dlzallowzonexfr
+dns_dlzconfigure
+dns_dlzcreate
+dns_dlzdestroy
+dns_dlzregister
+dns_dlzstrtoargv
+dns_dlzunregister
+dns_dns64_aaaafroma
+dns_dns64_aaaaok
+dns_dns64_append
+dns_dns64_create
+dns_dns64_destroy
+dns_dns64_next
+dns_dns64_unlink
+@IF NOTYET
+dns_dnsrps_2policy
+dns_dnsrps_trig2type
+dns_dnsrps_type2trig
+dns_dnsrps_server_create
+dns_dnsrps_server_destroy
+dns_dnsrps_view_init
+dns_dnsrps_connect
+dns_dnsrps_rewrite_init
+@END NOTYET
+@IF NOTYET
+dns_resolver_setfuzzing
+@END NOTYET
+dns_dnssec_findmatchingkeys
+dns_dnssec_findzonekeys
+dns_dnssec_get_hints
+dns_dnssec_keyactive
+dns_dnssec_keyfromrdata
+dns_dnssec_keylistfromrdataset
+dns_dnssec_matchdskey
+dns_dnssec_selfsigns
+dns_dnssec_sign
+dns_dnssec_signmessage
+dns_dnssec_signs
+dns_dnssec_syncdelete
+dns_dnssec_syncupdate
+dns_dnssec_updatekeys
+dns_dnssec_verify
+dns_dnssec_verifymessage
+dns_dnsseckey_create
+dns_dnsseckey_destroy
+dns_dnssecsignstats_clear
+dns_dnssecsignstats_create
+dns_dnssecsignstats_dump
+dns_dnssecsignstats_increment
+dns_ds_buildrdata
+dns_ds_fromkeyrdata
+dns_dsdigest_format
+dns_dsdigest_fromtext
+dns_dsdigest_totext
+@IF NOTYET
+dns_dt_attach
+dns_dt_close
+dns_dt_create
+dns_dt_datatotext
+dns_dt_detach
+dns_dt_getframe
+dns_dt_getstats
+dns_dt_open
+dns_dt_parse
+dns_dt_reopen
+dns_dt_send
+dns_dt_setidentity
+dns_dt_setupfile
+dns_dt_setversion
+dns_dtdata_free
+@END NOTYET
+dns_dumpctx_attach
+dns_dumpctx_cancel
+dns_dumpctx_db
+dns_dumpctx_detach
+dns_dumpctx_version
+dns_dyndb_load
+dns_dyndb_cleanup
+dns_dyndb_createctx
+dns_dyndb_destroyctx
+dns_ecdb_register
+dns_ecdb_unregister
+dns_ecs_equals
+dns_ecs_init
+dns_ecs_format
+dns_fixedname_init
+dns_fixedname_invalidate
+dns_fixedname_name
+dns_fixedname_initname
+dns_fwdtable_add
+dns_fwdtable_addfwd
+dns_fwdtable_create
+dns_fwdtable_delete
+dns_fwdtable_destroy
+dns_fwdtable_find
+dns_generalstats_create
+dns_generalstats_dump
+dns_generalstats_increment
+@IF GEOIP
+dns_geoip_match
+@END GEOIP
+dns_hashalg_fromtext
+dns_ipkeylist_clear
+dns_ipkeylist_copy
+dns_ipkeylist_init
+dns_ipkeylist_resize
+dns_iptable_addprefix
+dns_iptable_attach
+dns_iptable_create
+dns_iptable_detach
+dns_iptable_merge
+dns_journal_begin_transaction
+dns_journal_commit
+dns_journal_compact
+dns_journal_current_rr
+dns_journal_destroy
+dns_journal_empty
+dns_journal_first_rr
+dns_journal_first_serial
+dns_journal_get_sourceserial
+dns_journal_iter_init
+dns_journal_last_serial
+dns_journal_next_rr
+dns_journal_open
+dns_journal_print
+dns_journal_recovered
+dns_journal_rollforward
+dns_journal_set_sourceserial
+dns_journal_write_transaction
+dns_journal_writediff
+dns_kasp_addkey
+dns_kasp_attach
+dns_kasp_create
+dns_kasp_detach
+dns_kasp_dnskeyttl
+dns_kasp_dsttl
+dns_kasp_freeze
+dns_kasp_getname
+dns_kasp_key_algorithm
+dns_kasp_key_create
+dns_kasp_key_destroy
+dns_kasp_key_ksk
+dns_kasp_key_lifetime
+dns_kasp_key_size
+dns_kasp_key_zsk
+dns_kasp_keylist_empty
+dns_kasp_keys
+dns_kasp_nsec3
+dns_kasp_nsec3flags
+dns_kasp_nsec3iter
+dns_kasp_nsec3saltlen
+dns_kasp_parentpropagationdelay
+dns_kasp_publishsafety
+dns_kasp_purgekeys
+dns_kasp_retiresafety
+dns_kasp_setdnskeyttl
+dns_kasp_setdsttl
+dns_kasp_setnsec3
+dns_kasp_setnsec3param
+dns_kasp_setparentpropagationdelay
+dns_kasp_setpublishsafety
+dns_kasp_setpurgekeys
+dns_kasp_setretiresafety
+dns_kasp_setsigrefresh
+dns_kasp_setsigvalidity
+dns_kasp_setsigvalidity_dnskey
+dns_kasp_setzonemaxttl
+dns_kasp_setzonepropagationdelay
+dns_kasp_signdelay
+dns_kasp_sigrefresh
+dns_kasp_sigvalidity
+dns_kasp_sigvalidity_dnskey
+dns_kasp_thaw
+dns_kasp_zonemaxttl
+dns_kasp_zonepropagationdelay
+dns_kasplist_find
+dns_keydata_fromdnskey
+dns_keydata_todnskey
+dns_keyflags_fromtext
+dns_keymgr_checkds
+dns_keymgr_checkds_id
+dns_keymgr_rollover
+dns_keymgr_run
+dns_keymgr_status
+dns_keynode_dsset
+dns_keynode_initial
+dns_keynode_managed
+dns_keynode_trust
+dns_keyring_restore
+dns_keytable_add
+dns_keytable_attach
+dns_keytable_create
+dns_keytable_delete
+dns_keytable_deletekey
+dns_keytable_detach
+dns_keytable_detachkeynode
+dns_keytable_dump
+dns_keytable_find
+dns_keytable_finddeepestmatch
+dns_keytable_forall
+dns_keytable_issecuredomain
+dns_keytable_marksecure
+dns_keytable_totext
+dns_lib_init
+dns_lib_shutdown
+dns_loadctx_attach
+dns_loadctx_cancel
+dns_loadctx_detach
+dns_log_init
+dns_log_setcontext
+dns_lookup_cancel
+dns_lookup_create
+dns_lookup_destroy
+dns_master_dump
+dns_master_dumpasync
+dns_master_dumpnode
+dns_master_dumpnodetostream
+dns_master_dumptostream
+dns_master_dumptostreamasync
+dns_master_initrawheader
+dns_master_loadbuffer
+dns_master_loadbufferinc
+dns_master_loadfile
+dns_master_loadfileinc
+dns_master_loadlexer
+dns_master_loadlexerinc
+dns_master_loadstream
+dns_master_loadstreaminc
+dns_master_questiontotext
+dns_master_rdatasettotext
+dns_master_stylecreate
+dns_master_styledestroy
+dns_master_styleflags
+dns_message_addname
+dns_message_attach
+dns_message_buildopt
+dns_message_checksig
+dns_message_clonebuffer
+dns_message_create
+dns_message_currentname
+dns_message_detach
+dns_message_find
+dns_message_findname
+dns_message_findtype
+dns_message_firstname
+dns_message_getopt
+dns_message_getquerytsig
+dns_message_getrawmessage
+dns_message_getsig0
+dns_message_getsig0key
+dns_message_gettempname
+dns_message_gettemprdata
+dns_message_gettemprdatalist
+dns_message_gettemprdataset
+dns_message_gettimeadjust
+dns_message_gettsig
+dns_message_gettsigkey
+dns_message_headertotext
+dns_message_logfmtpacket
+dns_message_logpacket
+dns_message_movename
+dns_message_nextname
+dns_message_parse
+dns_message_peekheader
+dns_message_pseudosectiontotext
+dns_message_puttempname
+dns_message_puttemprdata
+dns_message_puttemprdatalist
+dns_message_puttemprdataset
+dns_message_rechecksig
+dns_message_removename
+dns_message_renderbegin
+dns_message_renderchangebuffer
+dns_message_renderend
+dns_message_renderheader
+dns_message_renderrelease
+dns_message_renderreserve
+dns_message_renderreset
+dns_message_rendersection
+dns_message_reply
+dns_message_reset
+dns_message_resetsig
+dns_message_sectiontotext
+dns_message_setclass
+dns_message_setopt
+dns_message_setpadding
+dns_message_setquerytsig
+dns_message_setsig0key
+dns_message_setsortorder
+dns_message_settimeadjust
+dns_message_settsigkey
+dns_message_signer
+dns_message_takebuffer
+dns_message_totext
+dns_name_caseequal
+dns_name_clone
+dns_name_compare
+dns_name_concatenate
+dns_name_copy
+dns_name_copynf
+dns_name_countlabels
+dns_name_digest
+dns_name_downcase
+dns_name_dup
+dns_name_dupwithoffsets
+dns_name_dynamic
+dns_name_equal
+dns_name_format
+dns_name_free
+dns_name_fromregion
+dns_name_fromstring
+dns_name_fromstring2
+dns_name_fromtext
+dns_name_fromwire
+dns_name_fullcompare
+dns_name_fullhash
+dns_name_getlabel
+dns_name_getlabelsequence
+dns_name_hasbuffer
+dns_name_hash
+dns_name_init
+dns_name_internalwildcard
+dns_name_invalidate
+dns_name_isabsolute
+dns_name_isdnssd
+dns_name_ishostname
+dns_name_ismailbox
+dns_name_isrfc1918
+dns_name_issubdomain
+dns_name_istat
+dns_name_isula
+dns_name_isvalid
+dns_name_iswildcard
+dns_name_matcheswildcard
+dns_name_print
+dns_name_rdatacompare
+dns_name_reset
+dns_name_setbuffer
+dns_name_settotextfilter
+dns_name_split
+dns_name_tofilenametext
+dns_name_toprincipal
+dns_name_toregion
+dns_name_tostring
+dns_name_totext
+dns_name_totext2
+dns_name_towire
+dns_name_towire2
+dns_ncache_add
+dns_ncache_addoptout
+dns_ncache_current
+dns_ncache_getrdataset
+dns_ncache_getsigrdataset
+dns_ncache_towire
+dns_nsec3_active
+dns_nsec3_activex
+dns_nsec3_addnsec3
+dns_nsec3_addnsec3s
+dns_nsec3_addnsec3sx
+dns_nsec3_buildrdata
+dns_nsec3_delnsec3
+dns_nsec3_delnsec3s
+dns_nsec3_delnsec3sx
+dns_nsec3_generate_salt
+dns_nsec3_hashlength
+dns_nsec3_hashname
+dns_nsec3_maxiterations
+dns_nsec3_noexistnodata
+dns_nsec3_supportedhash
+dns_nsec3_typepresent
+dns_nsec3param_deletechains
+dns_nsec3param_fromprivate
+dns_nsec3param_salttotext
+dns_nsec3param_toprivate
+dns_nsec_build
+dns_nsec_buildrdata
+dns_nsec_compressbitmap
+dns_nsec_isset
+dns_nsec_noexistnodata
+dns_nsec_nseconly
+dns_nsec_setbit
+dns_nsec_typepresent
+dns_ntatable_add
+dns_ntatable_attach
+dns_ntatable_covered
+dns_ntatable_create
+dns_ntatable_delete
+dns_ntatable_detach
+dns_ntatable_dump
+dns_ntatable_save
+dns_ntatable_shutdown
+dns_ntatable_totext
+dns_opcode_totext
+dns_opcodestats_create
+dns_opcodestats_dump
+dns_opcodestats_increment
+dns_order_add
+dns_order_attach
+dns_order_create
+dns_order_detach
+dns_order_find
+dns_peer_attach
+dns_peer_detach
+dns_peer_getbogus
+dns_peer_getednsversion
+dns_peer_getforcetcp
+dns_peer_getkey
+dns_peer_getmaxudp
+dns_peer_getnotifydscp
+dns_peer_getnotifysource
+dns_peer_getpadding
+dns_peer_getprovideixfr
+dns_peer_getquerydscp
+dns_peer_getquerysource
+dns_peer_getrequestexpire
+dns_peer_getrequestixfr
+dns_peer_getrequestnsid
+dns_peer_getsendcookie
+dns_peer_getsupportedns
+dns_peer_gettcpkeepalive
+dns_peer_gettransferdscp
+dns_peer_gettransferformat
+dns_peer_gettransfers
+dns_peer_gettransfersource
+dns_peer_getudpsize
+dns_peer_new
+dns_peer_newprefix
+dns_peer_setbogus
+dns_peer_setednsversion
+dns_peer_setforcetcp
+dns_peer_setkey
+dns_peer_setkeybycharp
+dns_peer_setmaxudp
+dns_peer_setnotifydscp
+dns_peer_setnotifysource
+dns_peer_setpadding
+dns_peer_setprovideixfr
+dns_peer_setquerydscp
+dns_peer_setquerysource
+dns_peer_setrequestexpire
+dns_peer_setrequestixfr
+dns_peer_setrequestnsid
+dns_peer_setsendcookie
+dns_peer_setsupportedns
+dns_peer_settcpkeepalive
+dns_peer_settransferdscp
+dns_peer_settransferformat
+dns_peer_settransfers
+dns_peer_settransfersource
+dns_peer_setudpsize
+dns_peerlist_addpeer
+dns_peerlist_attach
+dns_peerlist_currpeer
+dns_peerlist_detach
+dns_peerlist_new
+dns_peerlist_peerbyaddr
+dns_portlist_add
+dns_portlist_attach
+dns_portlist_create
+dns_portlist_detach
+dns_portlist_match
+dns_portlist_remove
+dns_private_chains
+dns_private_totext
+dns_rbt_addname
+dns_rbt_addnode
+dns_rbt_adjusthashsize
+dns_rbt_create
+dns_rbt_deletename
+dns_rbt_deletenode
+dns_rbt_deserialize_tree
+dns_rbt_destroy
+dns_rbt_destroy2
+dns_rbt_findname
+dns_rbt_findnode
+dns_rbt_formatnodename
+dns_rbt_fullnamefromnode
+dns_rbt_hashsize
+dns_rbt_namefromnode
+dns_rbt_nodecount
+dns_rbt_printdot
+dns_rbt_printnodeinfo
+dns_rbt_printtext
+dns_rbt_serialize_align
+dns_rbt_serialize_tree
+dns_rbtnodechain_current
+dns_rbtnodechain_down
+dns_rbtnodechain_first
+dns_rbtnodechain_init
+dns_rbtnodechain_invalidate
+dns_rbtnodechain_last
+dns_rbtnodechain_next
+dns_rbtnodechain_nextflat
+dns_rbtnodechain_prev
+dns_rbtnodechain_reset
+dns_rcode_fromtext
+dns_rcode_totext
+dns_rcodestats_create
+dns_rcodestats_dump
+dns_rcodestats_increment
+dns_rdata_additionaldata
+dns_rdata_apl_current
+dns_rdata_apl_first
+dns_rdata_apl_next
+dns_rdata_casecompare
+dns_rdata_checknames
+dns_rdata_checkowner
+dns_rdata_clone
+dns_rdata_compare
+dns_rdata_covers
+dns_rdata_deleterrset
+dns_rdata_digest
+dns_rdata_exists
+dns_rdata_freestruct
+dns_rdata_fromregion
+dns_rdata_fromstruct
+dns_rdata_fromtext
+dns_rdata_fromwire
+dns_rdata_hip_current
+dns_rdata_hip_first
+dns_rdata_hip_next
+dns_rdata_init
+dns_rdata_makedelete
+dns_rdata_notexist
+dns_rdata_opt_current
+dns_rdata_opt_first
+dns_rdata_opt_next
+dns_rdata_reset
+dns_rdata_tofmttext
+dns_rdata_toregion
+dns_rdata_tostruct
+dns_rdata_totext
+dns_rdata_towire
+dns_rdata_txt_current
+dns_rdata_txt_first
+dns_rdata_txt_next
+dns_rdata_updateop
+dns_rdatacallbacks_init
+dns_rdatacallbacks_init_stdio
+dns_rdataclass_format
+dns_rdataclass_fromtext
+dns_rdataclass_ismeta
+dns_rdataclass_totext
+dns_rdataclass_tounknowntext
+dns_rdatalist_fromrdataset
+dns_rdatalist_init
+dns_rdatalist_tordataset
+dns_rdataset_addclosest
+dns_rdataset_addglue
+dns_rdataset_additionaldata
+dns_rdataset_addnoqname
+dns_rdataset_clearprefetch
+dns_rdataset_clone
+dns_rdataset_count
+dns_rdataset_current
+dns_rdataset_disassociate
+dns_rdataset_expire
+dns_rdataset_first
+dns_rdataset_getclosest
+dns_rdataset_getnoqname
+dns_rdataset_getownercase
+dns_rdataset_init
+dns_rdataset_invalidate
+dns_rdataset_isassociated
+dns_rdataset_makequestion
+dns_rdataset_next
+dns_rdataset_setownercase
+dns_rdataset_settrust
+dns_rdataset_totext
+dns_rdataset_towire
+dns_rdataset_towirepartial
+dns_rdataset_towiresorted
+dns_rdataset_trimttl
+dns_rdatasetiter_current
+dns_rdatasetiter_destroy
+dns_rdatasetiter_first
+dns_rdatasetiter_next
+dns_rdatasetstats_create
+dns_rdatasetstats_decrement
+dns_rdatasetstats_dump
+dns_rdatasetstats_increment
+dns_rdataslab_count
+dns_rdataslab_equal
+dns_rdataslab_equalx
+dns_rdataslab_fromrdataset
+dns_rdataslab_merge
+dns_rdataslab_rdatasize
+dns_rdataslab_size
+dns_rdataslab_subtract
+dns_rdatatype_atcname
+dns_rdatatype_atparent
+dns_rdatatype_attributes
+dns_rdatatype_followadditional
+dns_rdatatype_format
+dns_rdatatype_fromtext
+dns_rdatatype_isdnssec
+dns_rdatatype_isknown
+dns_rdatatype_ismeta
+dns_rdatatype_issingleton
+dns_rdatatype_iszonecutauth
+dns_rdatatype_notquestion
+dns_rdatatype_questiononly
+dns_rdatatype_totext
+dns_rdatatype_tounknowntext
+dns_rdatatypestats_create
+dns_rdatatypestats_dump
+dns_rdatatypestats_increment
+dns_request_cancel
+dns_request_create
+dns_request_createraw
+dns_request_createvia
+dns_request_destroy
+dns_request_getanswer
+dns_request_getresponse
+dns_request_usedtcp
+dns_requestmgr_attach
+dns_requestmgr_create
+dns_requestmgr_detach
+dns_requestmgr_shutdown
+dns_requestmgr_whenshutdown
+dns_resolver_addalternate
+dns_resolver_addbadcache
+dns_resolver_algorithm_supported
+dns_resolver_attach
+dns_resolver_cancelfetch
+dns_resolver_create
+dns_resolver_createfetch
+dns_resolver_destroyfetch
+dns_resolver_detach
+dns_resolver_disable_algorithm
+dns_resolver_disable_ds_digest
+dns_resolver_dispatchmgr
+dns_resolver_dispatchv4
+dns_resolver_dispatchv6
+dns_resolver_ds_digest_supported
+dns_resolver_dumpfetches
+dns_resolver_flushbadcache
+dns_resolver_flushbadnames
+dns_resolver_freeze
+dns_resolver_getbadcache
+dns_resolver_getclientsperquery
+dns_resolver_getlamettl
+dns_resolver_getmaxdepth
+dns_resolver_getmaxqueries
+dns_resolver_getmustbesecure
+dns_resolver_getnonbackofftries
+dns_resolver_getoptions
+dns_resolver_getquerydscp4
+dns_resolver_getquerydscp6
+dns_resolver_getquotaresponse
+dns_resolver_getretryinterval
+dns_resolver_gettimeout
+dns_resolver_getudpsize
+dns_resolver_getzeronosoattl
+dns_resolver_logfetch
+dns_resolver_prime
+dns_resolver_printbadcache
+dns_resolver_reset_algorithms
+dns_resolver_reset_ds_digests
+dns_resolver_resetmustbesecure
+dns_resolver_setclientsperquery
+dns_resolver_setfetchesperzone
+dns_resolver_setlamettl
+dns_resolver_setmaxdepth
+dns_resolver_setmaxqueries
+dns_resolver_setmustbesecure
+dns_resolver_setnonbackofftries
+dns_resolver_setquerydscp4
+dns_resolver_setquerydscp6
+dns_resolver_setquotaresponse
+dns_resolver_setretryinterval
+dns_resolver_settimeout
+dns_resolver_setudpsize
+dns_resolver_setzeronosoattl
+dns_resolver_shutdown
+dns_resolver_socketmgr
+dns_resolver_taskmgr
+dns_resolver_whenshutdown
+dns_result_register
+dns_result_torcode
+dns_result_totext
+dns_root_checkhints
+dns_rootns_create
+dns_rpz_add
+dns_rpz_attach_rpzs
+dns_rpz_beginload
+dns_rpz_dbupdate_callback
+dns_rpz_decode_cname
+dns_rpz_delete
+dns_rpz_detach_rpzs
+dns_rpz_find_ip
+dns_rpz_find_name
+dns_rpz_new_zone
+dns_rpz_new_zones
+dns_rpz_policy2str
+dns_rpz_ready
+dns_rpz_str2policy
+dns_rpz_type2str
+dns_rriterator_current
+dns_rriterator_destroy
+dns_rriterator_first
+dns_rriterator_init
+dns_rriterator_next
+dns_rriterator_nextrrset
+dns_rriterator_pause
+dns_rrl
+dns_rrl_init
+dns_rrl_view_destroy
+dns_sdb_putnamedrdata
+dns_sdb_putnamedrr
+dns_sdb_putrdata
+dns_sdb_putrr
+dns_sdb_putsoa
+dns_sdb_register
+dns_sdb_unregister
+dns_sdlz_putnamedrr
+dns_sdlz_putrr
+dns_sdlz_putsoa
+dns_sdlz_setdb
+dns_sdlzregister
+dns_sdlzunregister
+dns_secalg_format
+dns_secalg_fromtext
+dns_secalg_totext
+dns_secproto_fromtext
+dns_secproto_totext
+dns_soa_buildrdata
+dns_soa_getexpire
+dns_soa_getminimum
+dns_soa_getrefresh
+dns_soa_getretry
+dns_soa_getserial
+dns_soa_setexpire
+dns_soa_setminimum
+dns_soa_setrefresh
+dns_soa_setretry
+dns_soa_setserial
+dns_ssu_external_match
+dns_ssu_mtypefromstring
+dns_ssurule_isgrant
+dns_ssurule_identity
+dns_ssurule_matchtype
+dns_ssurule_name
+dns_ssurule_types
+dns_ssutable_firstrule
+dns_ssutable_nextrule
+dns_ssutable_addrule
+dns_ssutable_attach
+dns_ssutable_checkrules
+dns_ssutable_create
+dns_ssutable_createdlz
+dns_ssutable_detach
+dns_stats_alloccounters
+dns_stats_attach
+dns_stats_detach
+dns_stats_freecounters
+dns_tcpmsg_cancelread
+dns_tcpmsg_init
+dns_tcpmsg_invalidate
+dns_tcpmsg_keepbuffer
+dns_tcpmsg_readmessage
+dns_tcpmsg_setmaxsize
+dns_time32_fromtext
+dns_time32_totext
+dns_time64_from32
+dns_time64_fromtext
+dns_time64_totext
+dns_timer_setidle
+dns_tkey_builddeletequery
+dns_tkey_builddhquery
+dns_tkey_buildgssquery
+dns_tkey_gssnegotiate
+dns_tkey_processdeleteresponse
+dns_tkey_processdhresponse
+dns_tkey_processgssresponse
+dns_tkey_processquery
+dns_tkeyctx_create
+dns_tkeyctx_destroy
+dns_trust_totext
+dns_tsec_create
+dns_tsec_destroy
+dns_tsec_getkey
+dns_tsec_gettype
+dns_tsig_sign
+dns_tsig_verify
+dns_tsigkey_attach
+dns_tsigkey_create
+dns_tsigkey_createfromkey
+dns_tsigkey_detach
+dns_tsigkey_find
+dns_tsigkey_identity
+dns_tsigkey_setdeleted
+dns_tsigkeyring_add
+dns_tsigkeyring_attach
+dns_tsigkeyring_create
+dns_tsigkeyring_detach
+dns_tsigkeyring_dumpanddetach
+dns_tsigrcode_fromtext
+dns_tsigrcode_totext
+dns_ttl_fromtext
+dns_ttl_totext
+dns_update_signatures
+dns_update_signaturesinc
+dns_update_soaserial
+dns_validator_cancel
+dns_validator_create
+dns_validator_destroy
+dns_validator_send
+dns_view_adddelegationonly
+dns_view_addzone
+dns_view_asyncload
+dns_view_attach
+dns_view_checksig
+dns_view_create
+dns_view_createresolver
+dns_view_createzonetable
+dns_view_detach
+dns_view_dialup
+dns_view_dumpdbtostream
+dns_view_excludedelegationonly
+dns_view_find
+dns_view_findzone
+dns_view_findzonecut
+dns_view_flushanddetach
+dns_view_flushcache
+dns_view_flushname
+dns_view_flushnode
+dns_view_freeze
+dns_view_freezezones
+dns_view_getadbstats
+dns_view_getdynamickeyring
+dns_view_getfailttl
+dns_view_getnewzonedir
+dns_view_getntatable
+dns_view_getpeertsig
+dns_view_getresquerystats
+dns_view_getresstats
+dns_view_getrootdelonly
+dns_view_getsecroots
+dns_view_gettsig
+dns_view_initntatable
+dns_view_initsecroots
+dns_view_iscacheshared
+dns_view_isdelegationonly
+dns_view_issecuredomain
+dns_view_istrusted
+dns_view_load
+dns_view_loadnta
+dns_view_ntacovers
+dns_view_restorekeyring
+dns_view_saventa
+dns_view_searchdlz
+dns_view_setadbstats
+dns_view_setcache
+dns_view_setdstport
+dns_view_setdynamickeyring
+dns_view_setfailttl
+dns_view_sethints
+dns_view_setkeyring
+dns_view_setnewzonedir
+dns_view_setnewzones
+dns_view_setresquerystats
+dns_view_setresstats
+dns_view_setrootdelonly
+dns_view_setviewcommit
+dns_view_setviewrevert
+dns_view_simplefind
+dns_view_staleanswerenabled
+dns_view_thaw
+dns_view_untrust
+dns_view_weakattach
+dns_view_weakdetach
+dns_viewlist_find
+dns_viewlist_findzone
+dns_xfrin_attach
+dns_xfrin_create
+dns_xfrin_detach
+dns_xfrin_shutdown
+dns_zone_addnsec3chain
+dns_zone_asyncload
+dns_zone_attach
+dns_zone_catz_disable
+dns_zone_catz_enable
+dns_zone_catz_enable_db
+dns_zone_catz_is_enabled
+dns_zone_cdscheck
+dns_zone_checknames
+dns_zone_clearforwardacl
+dns_zone_clearnotifyacl
+dns_zone_clearqueryacl
+dns_zone_clearqueryonacl
+dns_zone_clearupdateacl
+dns_zone_clearxfracl
+dns_zone_create
+dns_zone_detach
+dns_zone_dialup
+dns_zone_dlzpostload
+dns_zone_dump
+dns_zone_dumptostream
+dns_zone_expire
+dns_zone_first
+dns_zone_flush
+dns_zone_forcereload
+dns_zone_forwardupdate
+dns_zone_get_parentcatz
+dns_zone_get_rpz_num
+dns_zone_getadded
+dns_zone_getaltxfrsource4
+dns_zone_getaltxfrsource4dscp
+dns_zone_getaltxfrsource6
+dns_zone_getaltxfrsource6dscp
+dns_zone_getautomatic
+dns_zone_getchecknames
+dns_zone_getclass
+dns_zone_getdb
+dns_zone_getdbtype
+dns_zone_getdnsseckeys
+dns_zone_getdnssecsignstats
+dns_zone_getexpiretime
+dns_zone_getfile
+dns_zone_getforwardacl
+dns_zone_getgluecachestats
+dns_zone_getidlein
+dns_zone_getidleout
+dns_zone_getincludes
+dns_zone_getixfrratio
+dns_zone_getjournal
+dns_zone_getjournalsize
+dns_zone_getkasp
+dns_zone_getkeydirectory
+dns_zone_getkeyopts
+dns_zone_getkeyvalidityinterval
+dns_zone_getloadtime
+dns_zone_getmaxrecords
+dns_zone_getmaxttl
+dns_zone_getmaxxfrin
+dns_zone_getmaxxfrout
+dns_zone_getmctx
+dns_zone_getmgr
+dns_zone_getnotifyacl
+dns_zone_getnotifydelay
+dns_zone_getnotifysrc4
+dns_zone_getnotifysrc4dscp
+dns_zone_getnotifysrc6
+dns_zone_getnotifysrc6dscp
+dns_zone_getoptions
+dns_zone_getorigin
+dns_zone_getparentalsrc4
+dns_zone_getparentalsrc4dscp
+dns_zone_getparentalsrc6
+dns_zone_getparentalsrc6dscp
+dns_zone_getprivatetype
+dns_zone_getqueryacl
+dns_zone_getqueryonacl
+dns_zone_getraw
+dns_zone_getrcvquerystats
+dns_zone_getredirecttype
+dns_zone_getrefreshkeytime
+dns_zone_getrefreshtime
+dns_zone_getrequestexpire
+dns_zone_getrequestixfr
+dns_zone_getrequeststats
+dns_zone_getserial
+dns_zone_getserialupdatemethod
+dns_zone_getsignatures
+dns_zone_getsigresigninginterval
+dns_zone_getsigvalidityinterval
+dns_zone_getssutable
+dns_zone_getstatlevel
+dns_zone_getstatscounters
+dns_zone_gettask
+dns_zone_gettype
+dns_zone_getupdateacl
+dns_zone_getupdatedisabled
+dns_zone_getview
+dns_zone_getxfracl
+dns_zone_getxfrsource4
+dns_zone_getxfrsource4dscp
+dns_zone_getxfrsource6
+dns_zone_getxfrsource6dscp
+dns_zone_getzeronosoattl
+dns_zone_iattach
+dns_zone_idetach
+dns_zone_isdynamic
+dns_zone_isforced
+dns_zone_isloaded
+dns_zone_keydone
+dns_zone_link
+dns_zone_load
+dns_zone_loadandthaw
+dns_zone_lock_keyfiles
+dns_zone_log
+dns_zone_logc
+dns_zone_logv
+dns_zone_maintenance
+dns_zone_markdirty
+dns_zone_name
+dns_zone_nameonly
+dns_zone_next
+dns_zone_notify
+dns_zone_notifyreceive
+dns_zone_nscheck
+dns_zone_refresh
+dns_zone_rekey
+dns_zone_replacedb
+dns_zone_rpz_enable
+dns_zone_rpz_enable_db
+dns_zone_set_parentcatz
+dns_zone_setadded
+dns_zone_setalsonotify
+dns_zone_setalsonotifydscpkeys
+dns_zone_setalsonotifywithkeys
+dns_zone_setaltxfrsource4
+dns_zone_setaltxfrsource4dscp
+dns_zone_setaltxfrsource6
+dns_zone_setaltxfrsource6dscp
+dns_zone_setautomatic
+dns_zone_setcheckmx
+dns_zone_setchecknames
+dns_zone_setcheckns
+dns_zone_setchecksrv
+dns_zone_setclass
+dns_zone_setdb
+dns_zone_setdbtype
+dns_zone_setdialup
+dns_zone_setdnssecsignstats
+dns_zone_setfile
+dns_zone_setforwardacl
+dns_zone_setidlein
+dns_zone_setidleout
+dns_zone_setisself
+dns_zone_setixfrratio
+dns_zone_setjournal
+dns_zone_setjournalsize
+dns_zone_setkasp
+dns_zone_setkeydirectory
+dns_zone_setkeyopt
+dns_zone_setkeyvalidityinterval
+dns_zone_setmaxrecords
+dns_zone_setmaxrefreshtime
+dns_zone_setmaxretrytime
+dns_zone_setmaxttl
+dns_zone_setmaxxfrin
+dns_zone_setmaxxfrout
+dns_zone_setminrefreshtime
+dns_zone_setminretrytime
+dns_zone_setnodes
+dns_zone_setnotifyacl
+dns_zone_setnotifydelay
+dns_zone_setnotifysrc4
+dns_zone_setnotifysrc4dscp
+dns_zone_setnotifysrc6
+dns_zone_setnotifysrc6dscp
+dns_zone_setnotifytype
+dns_zone_setnsec3param
+dns_zone_setoption
+dns_zone_setorigin
+dns_zone_setparentals
+dns_zone_setparentalsrc4
+dns_zone_setparentalsrc4dscp
+dns_zone_setparentalsrc6
+dns_zone_setparentalsrc6dscp
+dns_zone_setprimaries
+dns_zone_setprimarieswithkeys
+dns_zone_setprivatetype
+dns_zone_setqueryacl
+dns_zone_setqueryonacl
+dns_zone_setrawdata
+dns_zone_setrcvquerystats
+dns_zone_setrefreshkeyinterval
+dns_zone_setrequestexpire
+dns_zone_setrequestixfr
+dns_zone_setrequeststats
+dns_zone_setserial
+dns_zone_setserialupdatemethod
+dns_zone_setsignatures
+dns_zone_setsigresigninginterval
+dns_zone_setsigvalidityinterval
+dns_zone_setssutable
+dns_zone_setstatistics
+dns_zone_setstatlevel
+dns_zone_setstats
+dns_zone_settask
+dns_zone_settype
+dns_zone_setupdateacl
+dns_zone_setupdatedisabled
+dns_zone_setview
+dns_zone_setviewcommit
+dns_zone_setviewrevert
+dns_zone_setxfracl
+dns_zone_setxfrsource4
+dns_zone_setxfrsource4dscp
+dns_zone_setxfrsource6
+dns_zone_setxfrsource6dscp
+dns_zone_setzeronosoattl
+dns_zone_signwithkey
+dns_zone_synckeyzone
+dns_zone_unload
+dns_zone_unlock_keyfiles
+dns_zone_verifydb
+dns_zonekey_iszonekey
+dns_zonemgr_attach
+dns_zonemgr_create
+dns_zonemgr_createzone
+dns_zonemgr_detach
+dns_zonemgr_forcemaint
+dns_zonemgr_getcount
+dns_zonemgr_getiolimit
+dns_zonemgr_getnotifyrate
+dns_zonemgr_getserialqueryrate
+dns_zonemgr_getstartupnotifyrate
+dns_zonemgr_gettaskmgr
+dns_zonemgr_getttransfersin
+dns_zonemgr_getttransfersperns
+dns_zonemgr_managezone
+dns_zonemgr_releasezone
+dns_zonemgr_resumexfrs
+dns_zonemgr_setcheckdsrate
+dns_zonemgr_setiolimit
+dns_zonemgr_setnotifyrate
+dns_zonemgr_setserialqueryrate
+dns_zonemgr_setsize
+dns_zonemgr_setstartupnotifyrate
+dns_zonemgr_settransfersin
+dns_zonemgr_settransfersperns
+dns_zonemgr_shutdown
+dns_zonemgr_unreachable
+dns_zonemgr_unreachableadd
+dns_zonemgr_unreachabledel
+dns_zonetype_name
+dns_zoneverify_dnssec
+dns_zt_apply
+dns_zt_asyncload
+dns_zt_attach
+dns_zt_create
+dns_zt_detach
+dns_zt_find
+dns_zt_flushanddetach
+dns_zt_freezezones
+dns_zt_load
+dns_zt_mount
+dns_zt_setviewcommit
+dns_zt_setviewrevert
+dns_zt_unmount
+dst_algorithm_supported
+dst_context_adddata
+dst_context_create
+dst_context_destroy
+dst_context_sign
+dst_context_verify
+dst_context_verify2
+dst_ds_digest_supported
+dst_gssapi_acceptctx
+dst_gssapi_acquirecred
+dst_gssapi_deletectx
+dst_gssapi_identitymatchesrealmkrb5
+dst_gssapi_identitymatchesrealmms
+dst_gssapi_initctx
+dst_gssapi_releasecred
+dst_key_alg
+dst_key_attach
+dst_key_buildfilename
+dst_key_buildinternal
+dst_key_class
+dst_key_copy_metadata
+dst_key_compare
+dst_key_computesecret
+dst_key_dump
+dst_key_flags
+dst_key_format
+dst_key_free
+dst_key_frombuffer
+dst_key_fromdns
+dst_key_fromfile
+dst_key_fromgssapi
+dst_key_fromlabel
+dst_key_fromnamedfile
+dst_key_generate
+dst_key_getbits
+dst_key_getbool
+dst_key_getfilename
+dst_key_getgssctx
+dst_key_getnum
+dst_key_getprivateformat
+dst_key_getstate
+dst_key_gettime
+dst_key_getttl
+dst_key_goal
+dst_key_haskasp
+dst_key_id
+dst_key_is_active
+dst_key_is_published
+dst_key_is_removed
+dst_key_is_revoked
+dst_key_is_signing
+dst_key_is_unused
+dst_key_inactive
+dst_key_isexternal
+dst_key_ismodified
+dst_key_isnullkey
+dst_key_isprivate
+dst_key_iszonekey
+dst_key_name
+dst_key_paramcompare
+dst_key_privatefrombuffer
+dst_key_proto
+dst_key_pubcompare
+dst_key_read_public
+dst_key_read_state
+dst_key_restore
+dst_key_rid
+dst_key_role
+dst_key_secretsize
+dst_key_setbits
+dst_key_setbool
+dst_key_setexternal
+dst_key_setflags
+dst_key_setinactive
+dst_key_setmodified
+dst_key_setnum
+dst_key_setprivateformat
+dst_key_setstate
+dst_key_settime
+dst_key_setttl
+dst_key_sigsize
+dst_key_size
+dst_key_tkeytoken
+dst_key_tobuffer
+dst_key_todns
+dst_key_tofile
+dst_key_unsetbool
+dst_key_unsetnum
+dst_key_unsetstate
+dst_key_unsettime
+dst_lib_destroy
+dst_lib_init
+dst_region_computeid
+dst_region_computerid
+dst_result_register
+dst_result_totext
+@IF NOLONGER
+; Exported Data
+
+EXPORTS
+
+dns_pps DATA
+dns_master_style_full DATA
+dns_tsig_hmacmd5_name DATA
+dns_zone_mkey_day DATA
+dns_zone_mkey_hour DATA
+dns_zone_mkey_month DATA
+@END NOLONGER
diff --git a/lib/dns/win32/libdns.vcxproj.filters.in b/lib/dns/win32/libdns.vcxproj.filters.in
new file mode 100644
index 0000000..7bbda41
--- /dev/null
+++ b/lib/dns/win32/libdns.vcxproj.filters.in
@@ -0,0 +1,668 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ <Filter Include="Library Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Dst Header Files">
+ <UniqueIdentifier>{c76276a2-cee5-4b70-bf37-e0f2ef1ae4d6}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Dst Source Files">
+ <UniqueIdentifier>{ae84c9c7-5da5-4c0e-9e53-bfc34a5825ae}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Library Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="libdns.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="DLLMain.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="version.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\acl.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\adb.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\badcache.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\byaddr.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\cache.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\callbacks.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\catz.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\client.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\clientinfo.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\compress.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\db.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dbiterator.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dbtable.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\diff.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dispatch.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dlz.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dns64.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dnssec.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ds.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dyndb.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ecdb.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ecs.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\fixedname.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\forward.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+@IF GEOIP
+ <ClCompile Include="..\geoip2.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+@END GEOIP
+ <ClCompile Include="..\ipkeylist.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\iptable.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\journal.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\kasp.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\keydata.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\keymgr.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\keytable.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\lib.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\log.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\lookup.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\master.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\masterdump.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\message.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\name.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ncache.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nsec.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nsec3.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nta.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\order.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\peer.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\portlist.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\private.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rbt.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rbtdb.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rcode.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rdata.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rdatalist.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rdataset.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rdatasetiter.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rdataslab.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\request.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\resolver.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\result.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rootns.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rpz.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rriterator.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rrl.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\sdb.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\sdlz.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\soa.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ssu.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ssu_external.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\stats.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\tcpmsg.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\time.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\timer.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\tkey.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\tsec.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\tsig.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ttl.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\update.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\validator.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\view.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\xfrin.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\zone.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\zonekey.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\zoneverify.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\zt.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dst_api.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dst_parse.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dst_result.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\gssapi_link.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\gssapictx.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\hmac_link.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\key.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\openssl_link.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\openssldh_link.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\opensslecdsa_link.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\openssleddsa_link.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\opensslrsa_link.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+@IF PKCS11
+ <ClCompile Include="..\pkcs11.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\pkcs11ecdsa_link.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\pkcs11eddsa_link.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\pkcs11rsa_link.c">
+ <Filter>Dst Source Files</Filter>
+ </ClCompile>
+@END PKCS11
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\code.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\rbtdb.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\rdatalist_p.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\acl.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\adb.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\badcache.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\bit.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\byaddr.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\cache.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\callbacks.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\catz.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\cert.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\client.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\clientinfo.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\compress.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\db.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\dbiterator.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\dbtable.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\diff.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\dispatch.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\dlz.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\dns64.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\dnssec.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\dnstap.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\ds.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\dsdigest.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\dyndb.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\ecdb.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\ecs.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\edns.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\enumclass.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\enumtype.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\events.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\fixedname.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\forward.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+@IF GEOIP
+ <ClInclude Include="..\include\dns\geoip.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+@END GEOIP
+ <ClInclude Include="..\include\dns\ipkeylist.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\iptable.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\journal.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\kasp.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\keydata.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\keyflags.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\keymgr.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\keytable.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\keyvalues.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\lib.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\log.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\lookup.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\master.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\masterdump.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\message.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\name.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\ncache.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\nsec.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\nsec3.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\nta.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\opcode.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\order.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\peer.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\portlist.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\private.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rbt.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rcode.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rdata.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rdataclass.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rdatalist.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rdataset.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rdatasetiter.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rdataslab.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rdatastruct.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rdatatype.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\request.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\resolver.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\result.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rootns.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rpz.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rriterator.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\rrl.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\sdb.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\sdlz.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\secalg.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\secproto.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\soa.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\ssu.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\stats.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\tcpmsg.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\time.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\timer.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\tkey.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\tsec.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\tsig.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\ttl.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\types.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\update.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\validator.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\version.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\view.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\xfrin.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\zone.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\zonekey.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\zoneverify.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dns\zt.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dst\dst.h">
+ <Filter>Dst Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dst\gssapi.h">
+ <Filter>Dst Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\dst\result.h">
+ <Filter>Dst Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\dst_internal.h">
+ <Filter>Dst Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\dst_openssl.h">
+ <Filter>Dst Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\dst_parse.h">
+ <Filter>Dst Header Files</Filter>
+ </ClInclude>
+@IF PKCS11
+ <ClInclude Include="..\dst_pkcs11.h">
+ <Filter>Dst Header Files</Filter>
+ </ClInclude>
+@END PKCS11
+ </ItemGroup>
+</Project>
diff --git a/lib/dns/win32/libdns.vcxproj.in b/lib/dns/win32/libdns.vcxproj.in
new file mode 100644
index 0000000..27fc3a0
--- /dev/null
+++ b/lib/dns/win32/libdns.vcxproj.in
@@ -0,0 +1,345 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="@TOOLS_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|@PLATFORM@">
+ <Configuration>Debug</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|@PLATFORM@">
+ <Configuration>Release</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{5FEBFD4E-CCB0-48B9-B733-E15EEB85C16A}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>libdns</RootNamespace>
+ @WINDOWS_TARGET_PLATFORM_VERSION@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>BIND9;WIN32;@USE_GSSAPI@_DEBUG;_WINDOWS;_USRDLL;LIBDNS_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;@LIBXML2_INC@@OPENSSL_INC@@GSSAPI_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BrowseInformation>true</BrowseInformation>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalLibraryDirectories>..\..\isc\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libisc.lib;@LIBXML2_LIB@@GSSAPI_LIB@@KRB5_LIB@@GEOIP_LIB@ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>@INTRINSIC@</IntrinsicFunctions>
+ <PreprocessorDefinitions>BIND9;WIN32;@USE_GSSAPI@NDEBUG;_WINDOWS;_USRDLL;LIBDNS_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;@LIBXML2_INC@@OPENSSL_INC@@GSSAPI_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <StringPooling>true</StringPooling>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WholeProgramOptimization>false</WholeProgramOptimization>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalLibraryDirectories>..\..\isc\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libisc.lib;@LIBXML2_LIB@@GSSAPI_LIB@@KRB5_LIB@@GEOIP_LIB@ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <None Include="libdns.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\acl.c" />
+ <ClCompile Include="..\adb.c" />
+ <ClCompile Include="..\badcache.c" />
+ <ClCompile Include="..\byaddr.c" />
+ <ClCompile Include="..\cache.c" />
+ <ClCompile Include="..\callbacks.c" />
+ <ClCompile Include="..\catz.c" />
+ <ClCompile Include="..\client.c" />
+ <ClCompile Include="..\clientinfo.c" />
+ <ClCompile Include="..\compress.c" />
+ <ClCompile Include="..\db.c" />
+ <ClCompile Include="..\dbiterator.c" />
+ <ClCompile Include="..\dbtable.c" />
+ <ClCompile Include="..\diff.c" />
+ <ClCompile Include="..\dispatch.c" />
+ <ClCompile Include="..\dlz.c" />
+ <ClCompile Include="..\dns64.c" />
+ <ClCompile Include="..\dnssec.c" />
+ <ClCompile Include="..\ds.c" />
+ <ClCompile Include="..\dst_api.c" />
+ <ClCompile Include="..\dst_parse.c" />
+ <ClCompile Include="..\dst_result.c" />
+ <ClCompile Include="..\dyndb.c" />
+ <ClCompile Include="..\ecdb.c" />
+ <ClCompile Include="..\ecs.c" />
+ <ClCompile Include="..\fixedname.c" />
+ <ClCompile Include="..\forward.c" />
+@IF GEOIP
+ <ClCompile Include="..\geoip2.c" />
+@END GEOIP
+ <ClCompile Include="..\gssapictx.c" />
+ <ClCompile Include="..\gssapi_link.c" />
+ <ClCompile Include="..\hmac_link.c" />
+ <ClCompile Include="..\ipkeylist.c" />
+ <ClCompile Include="..\iptable.c" />
+ <ClCompile Include="..\journal.c" />
+ <ClCompile Include="..\kasp.c" />
+ <ClCompile Include="..\key.c" />
+ <ClCompile Include="..\keymgr.c" />
+ <ClCompile Include="..\keydata.c" />
+ <ClCompile Include="..\keytable.c" />
+ <ClCompile Include="..\lib.c" />
+ <ClCompile Include="..\log.c" />
+ <ClCompile Include="..\lookup.c" />
+ <ClCompile Include="..\master.c" />
+ <ClCompile Include="..\masterdump.c" />
+ <ClCompile Include="..\message.c" />
+ <ClCompile Include="..\name.c" />
+ <ClCompile Include="..\ncache.c" />
+ <ClCompile Include="..\nsec.c" />
+ <ClCompile Include="..\nsec3.c" />
+ <ClCompile Include="..\nta.c" />
+ <ClCompile Include="..\openssldh_link.c" />
+ <ClCompile Include="..\opensslecdsa_link.c" />
+ <ClCompile Include="..\openssleddsa_link.c" />
+ <ClCompile Include="..\opensslrsa_link.c" />
+ <ClCompile Include="..\openssl_link.c" />
+ <ClCompile Include="..\order.c" />
+ <ClCompile Include="..\peer.c" />
+@IF PKCS11
+ <ClCompile Include="..\pkcs11.c" />
+ <ClCompile Include="..\pkcs11ecdsa_link.c" />
+ <ClCompile Include="..\pkcs11eddsa_link.c" />
+ <ClCompile Include="..\pkcs11rsa_link.c" />
+@END PKCS11
+ <ClCompile Include="..\portlist.c" />
+ <ClCompile Include="..\private.c" />
+ <ClCompile Include="..\rbt.c" />
+ <ClCompile Include="..\rbtdb.c" />
+ <ClCompile Include="..\rcode.c" />
+ <ClCompile Include="..\rdata.c" />
+ <ClCompile Include="..\rdatalist.c" />
+ <ClCompile Include="..\rdataset.c" />
+ <ClCompile Include="..\rdatasetiter.c" />
+ <ClCompile Include="..\rdataslab.c" />
+ <ClCompile Include="..\request.c" />
+ <ClCompile Include="..\resolver.c" />
+ <ClCompile Include="..\result.c" />
+ <ClCompile Include="..\rootns.c" />
+ <ClCompile Include="..\rpz.c" />
+ <ClCompile Include="..\rriterator.c" />
+ <ClCompile Include="..\rrl.c" />
+ <ClCompile Include="..\sdb.c" />
+ <ClCompile Include="..\sdlz.c" />
+ <ClCompile Include="..\soa.c" />
+ <ClCompile Include="..\ssu.c" />
+ <ClCompile Include="..\ssu_external.c" />
+ <ClCompile Include="..\stats.c" />
+ <ClCompile Include="..\tcpmsg.c" />
+ <ClCompile Include="..\time.c" />
+ <ClCompile Include="..\timer.c" />
+ <ClCompile Include="..\tkey.c" />
+ <ClCompile Include="..\tsec.c" />
+ <ClCompile Include="..\tsig.c" />
+ <ClCompile Include="..\ttl.c" />
+ <ClCompile Include="..\update.c" />
+ <ClCompile Include="..\validator.c" />
+ <ClCompile Include="..\view.c" />
+ <ClCompile Include="..\xfrin.c" />
+ <ClCompile Include="..\zone.c" />
+ <ClCompile Include="..\zonekey.c" />
+ <ClCompile Include="..\zoneverify.c" />
+ <ClCompile Include="..\zt.c" />
+ <ClCompile Include="DLLMain.c" />
+ <ClCompile Include="version.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\code.h" />
+ <ClInclude Include="..\dst_internal.h" />
+ <ClInclude Include="..\dst_openssl.h" />
+ <ClInclude Include="..\dst_parse.h" />
+@IF PKCS11
+ <ClInclude Include="..\dst_pkcs11.h" />
+@END PKCS11
+ <ClInclude Include="..\include\dns\acl.h" />
+ <ClInclude Include="..\include\dns\adb.h" />
+ <ClInclude Include="..\include\dns\badcache.h" />
+ <ClInclude Include="..\include\dns\bit.h" />
+ <ClInclude Include="..\include\dns\byaddr.h" />
+ <ClInclude Include="..\include\dns\cache.h" />
+ <ClInclude Include="..\include\dns\callbacks.h" />
+ <ClInclude Include="..\include\dns\catz.h" />
+ <ClInclude Include="..\include\dns\cert.h" />
+ <ClInclude Include="..\include\dns\client.h" />
+ <ClInclude Include="..\include\dns\clientinfo.h" />
+ <ClInclude Include="..\include\dns\compress.h" />
+ <ClInclude Include="..\include\dns\db.h" />
+ <ClInclude Include="..\include\dns\dbiterator.h" />
+ <ClInclude Include="..\include\dns\dbtable.h" />
+ <ClInclude Include="..\include\dns\diff.h" />
+ <ClInclude Include="..\include\dns\dispatch.h" />
+ <ClInclude Include="..\include\dns\dlz.h" />
+ <ClInclude Include="..\include\dns\dns64.h" />
+ <ClInclude Include="..\include\dns\dnssec.h" />
+ <ClInclude Include="..\include\dns\dnstap.h" />
+ <ClInclude Include="..\include\dns\ds.h" />
+ <ClInclude Include="..\include\dns\dsdigest.h" />
+ <ClInclude Include="..\include\dns\dyndb.h" />
+ <ClInclude Include="..\include\dns\ecdb.h" />
+ <ClInclude Include="..\include\dns\ecs.h" />
+ <ClInclude Include="..\include\dns\edns.h" />
+ <ClInclude Include="..\include\dns\enumclass.h" />
+ <ClInclude Include="..\include\dns\enumtype.h" />
+ <ClInclude Include="..\include\dns\events.h" />
+ <ClInclude Include="..\include\dns\fixedname.h" />
+ <ClInclude Include="..\include\dns\forward.h" />
+@IF GEOIP
+ <ClInclude Include="..\include\dns\geoip.h" />
+@END GEOIP
+ <ClInclude Include="..\include\dns\ipkeylist.h" />
+ <ClInclude Include="..\include\dns\iptable.h" />
+ <ClInclude Include="..\include\dns\journal.h" />
+ <ClInclude Include="..\include\dns\kasp.h" />
+ <ClInclude Include="..\include\dns\keydata.h" />
+ <ClInclude Include="..\include\dns\keyflags.h" />
+ <ClInclude Include="..\include\dns\keymgr.h" />
+ <ClInclude Include="..\include\dns\keytable.h" />
+ <ClInclude Include="..\include\dns\keyvalues.h" />
+ <ClInclude Include="..\include\dns\lib.h" />
+ <ClInclude Include="..\include\dns\log.h" />
+ <ClInclude Include="..\include\dns\lookup.h" />
+ <ClInclude Include="..\include\dns\master.h" />
+ <ClInclude Include="..\include\dns\masterdump.h" />
+ <ClInclude Include="..\include\dns\message.h" />
+ <ClInclude Include="..\include\dns\name.h" />
+ <ClInclude Include="..\include\dns\ncache.h" />
+ <ClInclude Include="..\include\dns\nsec.h" />
+ <ClInclude Include="..\include\dns\nsec3.h" />
+ <ClInclude Include="..\include\dns\nta.h" />
+ <ClInclude Include="..\include\dns\opcode.h" />
+ <ClInclude Include="..\include\dns\order.h" />
+ <ClInclude Include="..\include\dns\peer.h" />
+ <ClInclude Include="..\include\dns\portlist.h" />
+ <ClInclude Include="..\include\dns\private.h" />
+ <ClInclude Include="..\include\dns\rbt.h" />
+ <ClInclude Include="..\include\dns\rcode.h" />
+ <ClInclude Include="..\include\dns\rdata.h" />
+ <ClInclude Include="..\include\dns\rdataclass.h" />
+ <ClInclude Include="..\include\dns\rdatalist.h" />
+ <ClInclude Include="..\include\dns\rdataset.h" />
+ <ClInclude Include="..\include\dns\rdatasetiter.h" />
+ <ClInclude Include="..\include\dns\rdataslab.h" />
+ <ClInclude Include="..\include\dns\rdatastruct.h" />
+ <ClInclude Include="..\include\dns\rdatatype.h" />
+ <ClInclude Include="..\include\dns\request.h" />
+ <ClInclude Include="..\include\dns\resolver.h" />
+ <ClInclude Include="..\include\dns\result.h" />
+ <ClInclude Include="..\include\dns\rootns.h" />
+ <ClInclude Include="..\include\dns\rpz.h" />
+ <ClInclude Include="..\include\dns\rriterator.h" />
+ <ClInclude Include="..\include\dns\rrl.h" />
+ <ClInclude Include="..\include\dns\sdb.h" />
+ <ClInclude Include="..\include\dns\sdlz.h" />
+ <ClInclude Include="..\include\dns\secalg.h" />
+ <ClInclude Include="..\include\dns\secproto.h" />
+ <ClInclude Include="..\include\dns\soa.h" />
+ <ClInclude Include="..\include\dns\ssu.h" />
+ <ClInclude Include="..\include\dns\stats.h" />
+ <ClInclude Include="..\include\dns\tcpmsg.h" />
+ <ClInclude Include="..\include\dns\time.h" />
+ <ClInclude Include="..\include\dns\timer.h" />
+ <ClInclude Include="..\include\dns\tkey.h" />
+ <ClInclude Include="..\include\dns\tsec.h" />
+ <ClInclude Include="..\include\dns\tsig.h" />
+ <ClInclude Include="..\include\dns\ttl.h" />
+ <ClInclude Include="..\include\dns\types.h" />
+ <ClInclude Include="..\include\dns\update.h" />
+ <ClInclude Include="..\include\dns\validator.h" />
+ <ClInclude Include="..\include\dns\version.h" />
+ <ClInclude Include="..\include\dns\view.h" />
+ <ClInclude Include="..\include\dns\xfrin.h" />
+ <ClInclude Include="..\include\dns\zone.h" />
+ <ClInclude Include="..\include\dns\zonekey.h" />
+ <ClInclude Include="..\include\dns\zoneverify.h" />
+ <ClInclude Include="..\include\dns\zt.h" />
+ <ClInclude Include="..\include\dst\dst.h" />
+ <ClInclude Include="..\include\dst\gssapi.h" />
+ <ClInclude Include="..\include\dst\result.h" />
+ <ClInclude Include="..\rbtdb.h" />
+ <ClInclude Include="..\rdatalist_p.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/lib/dns/win32/libdns.vcxproj.user b/lib/dns/win32/libdns.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/lib/dns/win32/libdns.vcxproj.user
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+</Project> \ No newline at end of file
diff --git a/lib/dns/win32/version.c b/lib/dns/win32/version.c
new file mode 100644
index 0000000..031042b
--- /dev/null
+++ b/lib/dns/win32/version.c
@@ -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.
+ */
+
+#include <versions.h>
+
+#include <dns/version.h>
+
+LIBDNS_EXTERNAL_DATA const char dns_version[] = VERSION;
+LIBDNS_EXTERNAL_DATA const char dns_major[] = MAJOR;
+LIBDNS_EXTERNAL_DATA const char dns_mapapi[] = MAPAPI;
diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c
new file mode 100644
index 0000000..a54d0d8
--- /dev/null
+++ b/lib/dns/xfrin.c
@@ -0,0 +1,1704 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/random.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/tcpmsg.h>
+#include <dns/timer.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;
+
+ int refcount;
+
+ isc_task_t *task;
+ isc_timer_t *timer;
+ isc_socketmgr_t *socketmgr;
+
+ int connects; /*%< Connect in progress */
+ int sends; /*%< Send in progress */
+ int recvs; /*%< Receive in progress */
+ bool shuttingdown;
+ isc_result_t shutdown_result;
+
+ dns_name_t name; /*%< Name of zone to transfer */
+ dns_rdataclass_t rdclass;
+
+ bool checkid, logit;
+ 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_dscp_t dscp;
+
+ isc_sockaddr_t masteraddr;
+ isc_sockaddr_t sourceaddr;
+ isc_socket_t *socket;
+
+ /*% Buffer for IXFR/AXFR request message */
+ isc_buffer_t qbuffer;
+ unsigned char qbuffer_data[512];
+
+ /*% Incoming reply TCP message */
+ dns_tcpmsg_t tcpmsg;
+ bool tcpmsg_valid;
+
+ /*%
+ * Whether the zone originally had a database attached at the time this
+ * transfer context was created. Used by maybe_free() 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_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;
+};
+
+#define XFRIN_MAGIC ISC_MAGIC('X', 'f', 'r', 'I')
+#define VALID_XFRIN(x) ISC_MAGIC_VALID(x, XFRIN_MAGIC)
+
+/**************************************************************************/
+/*
+ * Forward declarations.
+ */
+
+static isc_result_t
+xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_task_t *task,
+ isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr,
+ dns_name_t *zonename, dns_rdataclass_t rdclass,
+ dns_rdatatype_t reqtype, const isc_sockaddr_t *masteraddr,
+ const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp,
+ dns_tsigkey_t *tsigkey, 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_task_t *task, isc_event_t *event);
+static isc_result_t
+xfrin_send_request(dns_xfrin_ctx_t *xfr);
+static void
+xfrin_send_done(isc_task_t *task, isc_event_t *event);
+static void
+xfrin_recv_done(isc_task_t *task, isc_event_t *event);
+static void
+xfrin_timeout(isc_task_t *task, isc_event_t *event);
+
+static void
+maybe_free(dns_xfrin_ctx_t *xfr);
+
+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 *masteraddr,
+ const char *fmt, va_list ap) ISC_FORMAT_PRINTF(4, 0);
+
+static void
+xfrin_log1(int level, const char *zonetext, const isc_sockaddr_t *masteraddr,
+ 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;
+
+ if (xfr->reqtype != dns_rdatatype_ixfr) {
+ xfrin_log(xfr, ISC_LOG_ERROR,
+ "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))
+ {
+ 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_ERROR,
+ "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, "
+ "master 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_ERROR,
+ "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 master
+ * is not newer than the version in the request.
+ */
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "requested serial %u, "
+ "master has %u, not updating",
+ xfr->ixfr.request_serial, xfr->end_serial);
+ FAIL(DNS_R_UPTODATE);
+ }
+ if (xfr->reqtype == dns_rdatatype_axfr) {
+ xfr->checkid = false;
+ }
+ 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_ERROR,
+ "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_ERROR,
+ "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 *masteraddr,
+ const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp,
+ dns_tsigkey_t *tsigkey, isc_mem_t *mctx,
+ isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr,
+ isc_task_t *task, dns_xfrindone_t done,
+ dns_xfrin_ctx_t **xfrp) {
+ 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);
+
+ (void)dns_zone_getdb(zone, &db);
+
+ if (xfrtype == dns_rdatatype_soa || xfrtype == dns_rdatatype_ixfr) {
+ REQUIRE(db != NULL);
+ }
+
+ CHECK(xfrin_create(mctx, zone, db, task, timermgr, socketmgr, zonename,
+ dns_zone_getclass(zone), xfrtype, masteraddr,
+ sourceaddr, dscp, tsigkey, &xfr));
+
+ if (db != NULL) {
+ xfr->zone_had_db = true;
+ }
+
+ CHECK(xfrin_start(xfr));
+
+ xfr->done = done;
+ if (xfr->done != NULL) {
+ xfr->refcount++;
+ }
+ *xfrp = xfr;
+
+failure:
+ 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, masteraddr,
+ "zone transfer setup failed");
+ }
+ return (result);
+}
+
+void
+dns_xfrin_shutdown(dns_xfrin_ctx_t *xfr) {
+ if (!xfr->shuttingdown) {
+ xfrin_fail(xfr, ISC_R_CANCELED, "shut down");
+ }
+}
+
+void
+dns_xfrin_attach(dns_xfrin_ctx_t *source, dns_xfrin_ctx_t **target) {
+ REQUIRE(target != NULL && *target == NULL);
+ source->refcount++;
+ *target = source;
+}
+
+void
+dns_xfrin_detach(dns_xfrin_ctx_t **xfrp) {
+ dns_xfrin_ctx_t *xfr = *xfrp;
+ *xfrp = NULL;
+ INSIST(xfr->refcount > 0);
+ xfr->refcount--;
+ maybe_free(xfr);
+}
+
+static void
+xfrin_cancelio(dns_xfrin_ctx_t *xfr) {
+ if (xfr->connects > 0) {
+ isc_socket_cancel(xfr->socket, xfr->task,
+ ISC_SOCKCANCEL_CONNECT);
+ } else if (xfr->recvs > 0) {
+ dns_tcpmsg_cancelread(&xfr->tcpmsg);
+ } else if (xfr->sends > 0) {
+ isc_socket_cancel(xfr->socket, xfr->task, ISC_SOCKCANCEL_SEND);
+ }
+}
+
+static void
+xfrin_reset(dns_xfrin_ctx_t *xfr) {
+ REQUIRE(VALID_XFRIN(xfr));
+
+ xfrin_log(xfr, ISC_LOG_INFO, "resetting");
+
+ xfrin_cancelio(xfr);
+
+ if (xfr->socket != NULL) {
+ isc_socket_detach(&xfr->socket);
+ }
+
+ 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->tcpmsg_valid) {
+ dns_tcpmsg_invalidate(&xfr->tcpmsg);
+ xfr->tcpmsg_valid = false;
+ }
+
+ 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) {
+ 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->shuttingdown = true;
+ xfr->shutdown_result = result;
+ maybe_free(xfr);
+}
+
+static isc_result_t
+xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_task_t *task,
+ isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr,
+ dns_name_t *zonename, dns_rdataclass_t rdclass,
+ dns_rdatatype_t reqtype, const isc_sockaddr_t *masteraddr,
+ const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp,
+ dns_tsigkey_t *tsigkey, dns_xfrin_ctx_t **xfrp) {
+ dns_xfrin_ctx_t *xfr = NULL;
+ isc_result_t result;
+
+ xfr = isc_mem_get(mctx, sizeof(*xfr));
+ xfr->mctx = NULL;
+ isc_mem_attach(mctx, &xfr->mctx);
+ xfr->refcount = 0;
+ xfr->zone = NULL;
+ dns_zone_iattach(zone, &xfr->zone);
+ xfr->task = NULL;
+ isc_task_attach(task, &xfr->task);
+ xfr->timer = NULL;
+ xfr->socketmgr = socketmgr;
+ xfr->done = NULL;
+
+ xfr->connects = 0;
+ xfr->sends = 0;
+ xfr->recvs = 0;
+ xfr->shuttingdown = false;
+ xfr->shutdown_result = ISC_R_UNSET;
+
+ dns_name_init(&xfr->name, NULL);
+ xfr->rdclass = rdclass;
+ xfr->checkid = true;
+ xfr->logit = true;
+ xfr->id = (dns_messageid_t)isc_random16();
+ xfr->reqtype = reqtype;
+ xfr->dscp = dscp;
+
+ /* sockaddr */
+ xfr->socket = NULL;
+ /* qbuffer */
+ /* qbuffer_data */
+ /* tcpmsg */
+ xfr->tcpmsg_valid = false;
+
+ xfr->zone_had_db = false;
+ xfr->db = NULL;
+ if (db != NULL) {
+ dns_db_attach(db, &xfr->db);
+ }
+ xfr->ver = NULL;
+ dns_diff_init(xfr->mctx, &xfr->diff);
+ xfr->difflen = 0;
+
+ if (reqtype == dns_rdatatype_soa) {
+ xfr->state = XFRST_SOAQUERY;
+ } else {
+ xfr->state = XFRST_INITIALSOA;
+ }
+ /* end_serial */
+
+ xfr->nmsg = 0;
+ xfr->nrecs = 0;
+ xfr->nbytes = 0;
+ xfr->maxrecords = dns_zone_getmaxrecords(zone);
+ isc_time_now(&xfr->start);
+
+ xfr->tsigkey = NULL;
+ if (tsigkey != NULL) {
+ dns_tsigkey_attach(tsigkey, &xfr->tsigkey);
+ }
+ xfr->lasttsig = NULL;
+ xfr->tsigctx = NULL;
+ xfr->sincetsig = 0;
+ xfr->is_ixfr = false;
+
+ /* ixfr.request_serial */
+ /* ixfr.current_serial */
+ xfr->ixfr.journal = NULL;
+
+ xfr->axfr.add = NULL;
+ xfr->axfr.add_private = NULL;
+ dns_rdata_init(&xfr->firstsoa);
+ xfr->firstsoa_data = NULL;
+
+ dns_name_dup(zonename, mctx, &xfr->name);
+
+ CHECK(isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL,
+ task, xfrin_timeout, xfr, &xfr->timer));
+ CHECK(dns_timer_setidle(xfr->timer, dns_zone_getmaxxfrin(xfr->zone),
+ dns_zone_getidlein(xfr->zone), false));
+
+ xfr->masteraddr = *masteraddr;
+
+ INSIST(isc_sockaddr_pf(masteraddr) == isc_sockaddr_pf(sourceaddr));
+ xfr->sourceaddr = *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);
+
+ xfr->magic = XFRIN_MAGIC;
+ *xfrp = xfr;
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (xfr->timer != NULL) {
+ isc_timer_destroy(&xfr->timer);
+ }
+ if (dns_name_dynamic(&xfr->name)) {
+ dns_name_free(&xfr->name, xfr->mctx);
+ }
+ if (xfr->tsigkey != NULL) {
+ dns_tsigkey_detach(&xfr->tsigkey);
+ }
+ if (xfr->db != NULL) {
+ dns_db_detach(&xfr->db);
+ }
+ isc_task_detach(&xfr->task);
+ dns_zone_idetach(&xfr->zone);
+ isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr));
+
+ return (result);
+}
+
+static isc_result_t
+xfrin_start(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ CHECK(isc_socket_create(xfr->socketmgr,
+ isc_sockaddr_pf(&xfr->sourceaddr),
+ isc_sockettype_tcp, &xfr->socket));
+ isc_socket_setname(xfr->socket, "xfrin", NULL);
+#ifndef BROKEN_TCP_BIND_BEFORE_CONNECT
+ CHECK(isc_socket_bind(xfr->socket, &xfr->sourceaddr,
+ ISC_SOCKET_REUSEADDRESS));
+#endif /* ifndef BROKEN_TCP_BIND_BEFORE_CONNECT */
+ isc_socket_dscp(xfr->socket, xfr->dscp);
+ CHECK(isc_socket_connect(xfr->socket, &xfr->masteraddr, xfr->task,
+ xfrin_connect_done, xfr));
+ xfr->connects++;
+ return (ISC_R_SUCCESS);
+failure:
+ xfrin_fail(xfr, result, "failed setting up socket");
+ 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_task_t *task, isc_event_t *event) {
+ isc_socket_connev_t *cev = (isc_socket_connev_t *)event;
+ dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)event->ev_arg;
+ isc_result_t result = cev->result;
+ char sourcetext[ISC_SOCKADDR_FORMATSIZE];
+ char signerbuf[DNS_NAME_FORMATSIZE];
+ const char *signer = "", *sep = "";
+ isc_sockaddr_t sockaddr;
+ dns_zonemgr_t *zmgr;
+ isc_time_t now;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ UNUSED(task);
+
+ INSIST(event->ev_type == ISC_SOCKEVENT_CONNECT);
+ isc_event_free(&event);
+
+ xfr->connects--;
+ if (xfr->shuttingdown) {
+ maybe_free(xfr);
+ return;
+ }
+
+ zmgr = dns_zone_getmgr(xfr->zone);
+ if (zmgr != NULL) {
+ if (result != ISC_R_SUCCESS) {
+ TIME_NOW(&now);
+ dns_zonemgr_unreachableadd(zmgr, &xfr->masteraddr,
+ &xfr->sourceaddr, &now);
+ goto failure;
+ } else {
+ dns_zonemgr_unreachabledel(zmgr, &xfr->masteraddr,
+ &xfr->sourceaddr);
+ }
+ }
+
+ result = isc_socket_getsockname(xfr->socket, &sockaddr);
+ if (result == ISC_R_SUCCESS) {
+ isc_sockaddr_format(&sockaddr, sourcetext, sizeof(sourcetext));
+ } else {
+ strlcpy(sourcetext, "<UNKNOWN>", 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);
+
+ dns_tcpmsg_init(xfr->mctx, xfr->socket, &xfr->tcpmsg);
+ xfr->tcpmsg_valid = true;
+
+ CHECK(xfrin_send_request(xfr));
+failure:
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed to connect");
+ }
+}
+
+/*
+ * 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;
+
+ /* 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->checkid = true;
+ xfr->logit = true;
+ 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);
+
+ /*
+ * Record message length and adjust region to include TCP
+ * length field.
+ */
+ xfr->qbuffer_data[0] = (region.length >> 8) & 0xff;
+ xfr->qbuffer_data[1] = region.length & 0xff;
+ region.base -= 2;
+ region.length += 2;
+ CHECK(isc_socket_send(xfr->socket, &region, xfr->task, xfrin_send_done,
+ xfr));
+ xfr->sends++;
+
+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_task_t *task, isc_event_t *event) {
+ isc_socketevent_t *sev = (isc_socketevent_t *)event;
+ dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)event->ev_arg;
+ isc_result_t result;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ UNUSED(task);
+
+ INSIST(event->ev_type == ISC_SOCKEVENT_SENDDONE);
+
+ xfr->sends--;
+ xfrin_log(xfr, ISC_LOG_DEBUG(3), "sent request data");
+ CHECK(sev->result);
+
+ CHECK(dns_tcpmsg_readmessage(&xfr->tcpmsg, xfr->task, xfrin_recv_done,
+ xfr));
+ xfr->recvs++;
+failure:
+ isc_event_free(&event);
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed sending request data");
+ }
+}
+
+static void
+xfrin_recv_done(isc_task_t *task, isc_event_t *ev) {
+ dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)ev->ev_arg;
+ isc_result_t result;
+ dns_message_t *msg = NULL;
+ dns_name_t *name;
+ dns_tcpmsg_t *tcpmsg;
+ const dns_name_t *tsigowner = NULL;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ UNUSED(task);
+
+ INSIST(ev->ev_type == DNS_EVENT_TCPMSG);
+ tcpmsg = ev->ev_sender;
+ isc_event_free(&ev);
+
+ xfr->recvs--;
+ if (xfr->shuttingdown) {
+ maybe_free(xfr);
+ return;
+ }
+
+ CHECK(tcpmsg->result);
+
+ xfrin_log(xfr, ISC_LOG_DEBUG(7), "received %u bytes",
+ tcpmsg->buffer.used);
+
+ CHECK(isc_timer_touch(xfr->timer));
+
+ 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;
+ }
+
+ result = dns_message_parse(msg, &tcpmsg->buffer,
+ DNS_MESSAGEPARSE_PRESERVEORDER);
+
+ if (result == ISC_R_SUCCESS) {
+ dns_message_logpacket(msg, "received message from",
+ &tcpmsg->address, 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",
+ dns_result_totext(result));
+ }
+
+ if (result != ISC_R_SUCCESS || msg->rcode != dns_rcode_noerror ||
+ msg->opcode != dns_opcode_query || msg->rdclass != xfr->rdclass ||
+ (xfr->checkid && msg->id != xfr->id))
+ {
+ if (result == ISC_R_SUCCESS && msg->rcode != dns_rcode_noerror)
+ {
+ result = ISC_RESULTCLASS_DNSRCODE + msg->rcode; /*XXX*/
+ } 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:
+ dns_message_detach(&msg);
+ xfrin_reset(xfr);
+ xfr->reqtype = dns_rdatatype_soa;
+ xfr->state = XFRST_SOAQUERY;
+ (void)xfrin_start(xfr);
+ return;
+ } else if (!xfr->checkid && msg->id != xfr->id && xfr->logit) {
+ xfrin_log(xfr, ISC_LOG_WARNING,
+ "detected message ID mismatch on incoming AXFR "
+ "stream, transfer will fail in BIND 9.17.2 and "
+ "later if AXFR source is not fixed");
+ xfr->logit = false;
+ }
+
+ /*
+ * 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;
+
+ 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 += tcpmsg->buffer.used;
+
+ /*
+ * Take the context back.
+ */
+ INSIST(xfr->tsigctx == NULL);
+ xfr->tsigctx = msg->tsigctx;
+ msg->tsigctx = NULL;
+
+ dns_message_detach(&msg);
+
+ 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;
+ }
+ /*
+ * We should have no outstanding events at this
+ * point, thus maybe_free() should succeed.
+ */
+ xfr->shuttingdown = true;
+ xfr->shutdown_result = ISC_R_SUCCESS;
+ maybe_free(xfr);
+ break;
+ default:
+ /*
+ * Read the next message.
+ */
+ CHECK(dns_tcpmsg_readmessage(&xfr->tcpmsg, xfr->task,
+ xfrin_recv_done, xfr));
+ xfr->recvs++;
+ }
+ return;
+
+failure:
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed while receiving responses");
+ }
+}
+
+static void
+xfrin_timeout(isc_task_t *task, isc_event_t *event) {
+ dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)event->ev_arg;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+ /*
+ * This will log "giving up: timeout".
+ */
+ xfrin_fail(xfr, ISC_R_TIMEDOUT, "giving up");
+}
+
+static void
+maybe_free(dns_xfrin_ctx_t *xfr) {
+ uint64_t msecs;
+ uint64_t persec;
+ const char *result_str;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ if (!xfr->shuttingdown || xfr->refcount != 0 || xfr->connects != 0 ||
+ xfr->sends != 0 || xfr->recvs != 0)
+ {
+ return;
+ }
+
+ INSIST(!xfr->shuttingdown || 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 = (xfr->shuttingdown
+ ? isc_result_totext(xfr->shutdown_result)
+ : "unknown");
+ 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->socket != NULL) {
+ isc_socket_detach(&xfr->socket);
+ }
+
+ if (xfr->timer != NULL) {
+ isc_timer_destroy(&xfr->timer);
+ }
+
+ if (xfr->task != NULL) {
+ isc_task_detach(&xfr->task);
+ }
+
+ 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->tcpmsg_valid) {
+ dns_tcpmsg_invalidate(&xfr->tcpmsg);
+ }
+
+ 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->shuttingdown &&
+ 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);
+ }
+
+ 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 *masteraddr,
+ const char *fmt, va_list ap) {
+ char mastertext[ISC_SOCKADDR_FORMATSIZE];
+ char msgtext[2048];
+
+ isc_sockaddr_format(masteraddr, mastertext, sizeof(mastertext));
+ 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,
+ mastertext, 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 *masteraddr,
+ const char *fmt, ...) {
+ va_list ap;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ xfrin_logv(level, zonetext, masteraddr, 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->masteraddr, fmt, ap);
+ va_end(ap);
+}
diff --git a/lib/dns/zone.c b/lib/dns/zone.c
new file mode 100644
index 0000000..73da12e
--- /dev/null
+++ b/lib/dns/zone.c
@@ -0,0 +1,23609 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/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/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/result.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;
+ 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 *masters;
+ isc_dscp_t *masterdscps;
+ dns_name_t **masterkeynames;
+ bool *mastersok;
+ unsigned int masterscnt;
+ unsigned int curmaster;
+ isc_sockaddr_t masteraddr;
+
+ isc_sockaddr_t *parentals;
+ isc_dscp_t *parentaldscps;
+ dns_name_t **parentalkeynames;
+ dns_dnsseckeylist_t checkds_ok;
+ unsigned int parentalscnt;
+ isc_sockaddr_t parentaladdr;
+
+ dns_notifytype_t notifytype;
+ isc_sockaddr_t *notify;
+ dns_name_t **notifykeynames;
+ isc_dscp_t *notifydscp;
+ 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;
+ isc_dscp_t notifysrc4dscp;
+ isc_dscp_t notifysrc6dscp;
+ isc_dscp_t parentalsrc4dscp;
+ isc_dscp_t parentalsrc6dscp;
+ isc_dscp_t xfrsource4dscp;
+ isc_dscp_t xfrsource6dscp;
+ isc_dscp_t altxfrsource4dscp;
+ isc_dscp_t altxfrsource6dscp;
+ dns_xfrin_ctx_t *xfr; /* task locked */
+ dns_tsigkey_t *tsigkey; /* key 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_NOMASTERS = 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_socketmgr_t *socketmgr;
+ 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;
+};
+
+/*%
+ * 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;
+ isc_dscp_t dscp;
+ 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;
+ isc_dscp_t dscp;
+ 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)
+LIBDNS_EXTERNAL_DATA unsigned int dns_zone_mkey_hour = HOUR;
+LIBDNS_EXTERNAL_DATA unsigned int dns_zone_mkey_day = DAY;
+LIBDNS_EXTERNAL_DATA 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_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 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;
+ isc_dscp_t dscp;
+ 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,
+ .notifysrc4dscp = -1,
+ .notifysrc6dscp = -1,
+ .parentalsrc4dscp = -1,
+ .parentalsrc6dscp = -1,
+ .xfrsource4dscp = -1,
+ .xfrsource6dscp = -1,
+ .altxfrsource4dscp = -1,
+ .altxfrsource6dscp = -1,
+ .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_catzs_detach(&zone->catzs);
+ }
+ zone_freedbargs(zone);
+
+ RUNTIME_CHECK(dns_zone_setparentals(zone, NULL, NULL, 0) ==
+ ISC_R_SUCCESS);
+ RUNTIME_CHECK(dns_zone_setprimarieswithkeys(zone, NULL, NULL, 0) ==
+ ISC_R_SUCCESS);
+ RUNTIME_CHECK(dns_zone_setalsonotify(zone, NULL, 0) == ISC_R_SUCCESS);
+ 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_weakdetach(&zone->view);
+ }
+ dns_view_weakattach(view, &zone->view);
+
+ 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));
+
+ 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);
+}
+
+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 slave 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->masters != 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);
+ }
+ if (zone->masterformat == dns_masterformat_map) {
+ 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_db_updatenotify_register(db, dns_catz_dbupdate_callback,
+ 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_db_updatenotify_unregister(db, dns_catz_dbupdate_callback,
+ 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_catzs_attach(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_catzs_detach(&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 slave, 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->masters != NULL)) &&
+ rbt)
+ {
+ if (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) {
+ result = zone_startload(db, zone, loadtime);
+ } else {
+ result = DNS_R_NOMASTERFILE;
+ if (zone->type == dns_zone_primary ||
+ (zone->type == dns_zone_redirect &&
+ zone->masters == 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_master_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->masters == NULL))
+ {
+ options |= DNS_MASTER_SLAVE;
+ }
+ 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_master_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_master_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);
+ }
+ 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_copynf(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_copynf(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",
+ dns_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, &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, &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",
+ dns_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",
+ dns_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",
+ dns_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);
+}
+
+/*
+ * 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));
+
+ 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);
+ 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,
+ dns_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, dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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 nomaster = 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->masters == 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,
+ dns_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,
+ dns_result_totext(result));
+ nomaster = 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));
+
+ 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 (nomaster && 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");
+
+ /*
+ * Master / Slave / 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 slave 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 slaves.",
+ 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->masters != 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(__FILE__, __LINE__, "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->masters != 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_setxfrsource4dscp(dns_zone_t *zone, isc_dscp_t dscp) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->xfrsource4dscp = dscp;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_dscp_t
+dns_zone_getxfrsource4dscp(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->xfrsource4dscp);
+}
+
+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_dscp_t
+dns_zone_getxfrsource6dscp(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->xfrsource6dscp);
+}
+
+isc_result_t
+dns_zone_setxfrsource6dscp(dns_zone_t *zone, isc_dscp_t dscp) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->xfrsource6dscp = dscp;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+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_setaltxfrsource4dscp(dns_zone_t *zone, isc_dscp_t dscp) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->altxfrsource4dscp = dscp;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_dscp_t
+dns_zone_getaltxfrsource4dscp(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->altxfrsource4dscp);
+}
+
+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_setaltxfrsource6dscp(dns_zone_t *zone, isc_dscp_t dscp) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->altxfrsource6dscp = dscp;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_dscp_t
+dns_zone_getaltxfrsource6dscp(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->altxfrsource6dscp);
+}
+
+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_setparentalsrc4dscp(dns_zone_t *zone, isc_dscp_t dscp) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->parentalsrc4dscp = dscp;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_dscp_t
+dns_zone_getparentalsrc4dscp(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->parentalsrc4dscp);
+}
+
+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_setparentalsrc6dscp(dns_zone_t *zone, isc_dscp_t dscp) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->parentalsrc6dscp = dscp;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_dscp_t
+dns_zone_getparentalsrc6dscp(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->parentalsrc6dscp);
+}
+
+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_setnotifysrc4dscp(dns_zone_t *zone, isc_dscp_t dscp) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->notifysrc4dscp = dscp;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_dscp_t
+dns_zone_getnotifysrc4dscp(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->notifysrc4dscp);
+}
+
+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);
+}
+
+isc_result_t
+dns_zone_setnotifysrc6dscp(dns_zone_t *zone, isc_dscp_t dscp) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+
+ LOCK_ZONE(zone);
+ zone->notifysrc6dscp = dscp;
+ UNLOCK_ZONE(zone);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_dscp_t
+dns_zone_getnotifysrc6dscp(dns_zone_t *zone) {
+ REQUIRE(DNS_ZONE_VALID(zone));
+ return (zone->notifysrc6dscp);
+}
+
+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_keynames(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, isc_dscp_t **dscpsp,
+ dns_name_t ***keynamesp, unsigned int *countp,
+ isc_mem_t *mctx) {
+ unsigned int count;
+ isc_sockaddr_t *addrs;
+ isc_dscp_t *dscps;
+ dns_name_t **keynames;
+
+ REQUIRE(countp != NULL && addrsp != NULL && dscpsp != NULL &&
+ keynamesp != NULL);
+
+ count = *countp;
+ *countp = 0;
+ addrs = *addrsp;
+ *addrsp = NULL;
+ dscps = *dscpsp;
+ *dscpsp = NULL;
+ keynames = *keynamesp;
+ *keynamesp = NULL;
+
+ if (addrs != NULL) {
+ isc_mem_put(mctx, addrs, count * sizeof(isc_sockaddr_t));
+ }
+
+ if (dscps != NULL) {
+ isc_mem_put(mctx, dscps, count * sizeof(isc_dscp_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 *));
+ }
+}
+
+static isc_result_t
+set_serverslist(unsigned int count, const isc_sockaddr_t *addrs,
+ isc_sockaddr_t **newaddrsp, const isc_dscp_t *dscp,
+ isc_dscp_t **newdscpp, dns_name_t **names,
+ dns_name_t ***newnamesp, isc_mem_t *mctx) {
+ isc_sockaddr_t *newaddrs = NULL;
+ isc_dscp_t *newdscp = NULL;
+ dns_name_t **newnames = NULL;
+ unsigned int i;
+
+ REQUIRE(newaddrsp != NULL && *newaddrsp == NULL);
+ REQUIRE(newdscpp != NULL && *newdscpp == NULL);
+ REQUIRE(newnamesp != NULL && *newnamesp == NULL);
+
+ newaddrs = isc_mem_get(mctx, count * sizeof(*newaddrs));
+ memmove(newaddrs, addrs, count * sizeof(*newaddrs));
+
+ if (dscp != NULL) {
+ newdscp = isc_mem_get(mctx, count * sizeof(*newdscp));
+ memmove(newdscp, dscp, count * sizeof(*newdscp));
+ } else {
+ newdscp = NULL;
+ }
+
+ if (names != NULL) {
+ newnames = isc_mem_get(mctx, count * sizeof(*newnames));
+ for (i = 0; i < count; i++) {
+ newnames[i] = NULL;
+ }
+ for (i = 0; i < count; i++) {
+ if (names[i] != NULL) {
+ newnames[i] = isc_mem_get(mctx,
+ sizeof(dns_name_t));
+ dns_name_init(newnames[i], NULL);
+ dns_name_dup(names[i], mctx, newnames[i]);
+ }
+ }
+ } else {
+ newnames = NULL;
+ }
+
+ *newdscpp = newdscp;
+ *newaddrsp = newaddrs;
+ *newnamesp = newnames;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_zone_setalsonotify(dns_zone_t *zone, const isc_sockaddr_t *notify,
+ uint32_t count) {
+ return (dns_zone_setalsonotifydscpkeys(zone, notify, NULL, NULL,
+ count));
+}
+
+isc_result_t
+dns_zone_setalsonotifywithkeys(dns_zone_t *zone, const isc_sockaddr_t *notify,
+ dns_name_t **keynames, uint32_t count) {
+ return (dns_zone_setalsonotifydscpkeys(zone, notify, NULL, keynames,
+ count));
+}
+
+isc_result_t
+dns_zone_setalsonotifydscpkeys(dns_zone_t *zone, const isc_sockaddr_t *notify,
+ const isc_dscp_t *dscps, dns_name_t **keynames,
+ uint32_t count) {
+ isc_result_t result;
+ isc_sockaddr_t *newaddrs = NULL;
+ isc_dscp_t *newdscps = NULL;
+ dns_name_t **newnames = 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_keynames(zone->notifykeynames, keynames, count))
+ {
+ goto unlock;
+ }
+
+ clear_serverslist(&zone->notify, &zone->notifydscp,
+ &zone->notifykeynames, &zone->notifycnt, zone->mctx);
+
+ if (count == 0) {
+ goto unlock;
+ }
+
+ /*
+ * Set up the notify and notifykey lists
+ */
+ result = set_serverslist(count, notify, &newaddrs, dscps, &newdscps,
+ keynames, &newnames, zone->mctx);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+
+ /*
+ * Everything is ok so attach to the zone.
+ */
+ zone->notify = newaddrs;
+ zone->notifydscp = newdscps;
+ zone->notifykeynames = newnames;
+ zone->notifycnt = count;
+unlock:
+ UNLOCK_ZONE(zone);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_zone_setprimaries(dns_zone_t *zone, const isc_sockaddr_t *masters,
+ uint32_t count) {
+ isc_result_t result;
+
+ result = dns_zone_setprimarieswithkeys(zone, masters, NULL, count);
+ return (result);
+}
+
+isc_result_t
+dns_zone_setprimarieswithkeys(dns_zone_t *zone, const isc_sockaddr_t *masters,
+ dns_name_t **keynames, uint32_t count) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_sockaddr_t *newaddrs = NULL;
+ isc_dscp_t *newdscps = NULL;
+ dns_name_t **newnames = NULL;
+ bool *newok;
+ unsigned int i;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(count == 0 || masters != NULL);
+ if (keynames != 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->masterscnt ||
+ !same_addrs(zone->masters, masters, count) ||
+ !same_keynames(zone->masterkeynames, keynames, count))
+ {
+ if (zone->request != NULL) {
+ dns_request_cancel(zone->request);
+ }
+ } else {
+ goto unlock;
+ }
+
+ /*
+ * This needs to happen before clear_addresskeylist() sets
+ * zone->masterscnt to 0:
+ */
+ if (zone->mastersok != NULL) {
+ isc_mem_put(zone->mctx, zone->mastersok,
+ zone->masterscnt * sizeof(bool));
+ zone->mastersok = NULL;
+ }
+ clear_serverslist(&zone->masters, &zone->masterdscps,
+ &zone->masterkeynames, &zone->masterscnt, zone->mctx);
+ /*
+ * If count == 0, don't allocate any space for masters, mastersok or
+ * keynames so internally, those pointers are NULL if count == 0
+ */
+ if (count == 0) {
+ goto unlock;
+ }
+
+ /*
+ * mastersok 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
+ */
+ result = set_serverslist(count, masters, &newaddrs, NULL, &newdscps,
+ keynames, &newnames, zone->mctx);
+ INSIST(newdscps == NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(zone->mctx, newok, count * sizeof(*newok));
+ goto unlock;
+ }
+
+ /*
+ * Everything is ok so attach to the zone.
+ */
+ zone->curmaster = 0;
+ zone->mastersok = newok;
+ zone->masters = newaddrs;
+ zone->masterdscps = newdscps;
+ zone->masterkeynames = newnames;
+ zone->masterscnt = count;
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOMASTERS);
+
+unlock:
+ UNLOCK_ZONE(zone);
+ return (result);
+}
+
+isc_result_t
+dns_zone_setparentals(dns_zone_t *zone, const isc_sockaddr_t *parentals,
+ dns_name_t **keynames, uint32_t count) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_sockaddr_t *newaddrs = NULL;
+ isc_dscp_t *newdscps = NULL;
+ dns_name_t **newkeynames = NULL;
+
+ REQUIRE(DNS_ZONE_VALID(zone));
+ REQUIRE(count == 0 || parentals != NULL);
+ if (keynames != NULL) {
+ REQUIRE(count != 0);
+ }
+
+ LOCK_ZONE(zone);
+
+ clear_serverslist(&zone->parentals, &zone->parentaldscps,
+ &zone->parentalkeynames, &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
+ */
+ result = set_serverslist(count, parentals, &newaddrs, NULL, &newdscps,
+ keynames, &newkeynames, zone->mctx);
+ INSIST(newdscps == NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto unlock;
+ }
+
+ /*
+ * Everything is ok so attach to the zone.
+ */
+ zone->parentals = newaddrs;
+ zone->parentaldscps = newdscps;
+ zone->parentalkeynames = newkeynames;
+ zone->parentalscnt = count;
+
+ dns_zone_log(zone, ISC_LOG_NOTICE, "checkds: set %u parentals", count);
+
+unlock:
+ UNLOCK_ZONE(zone);
+ return (result);
+}
+
+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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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, &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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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_copynf(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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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_copynf(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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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);
+}
+
+/*
+ * 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",
+ dns_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",
+ dns_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);
+ 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_copynf(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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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, dns_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,
+ dns_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,
+ dns_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,
+ dns_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, dns_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 "
+ "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",
+ 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 "
+ "missing: 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, dns_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 dumping, load_pending, exiting, viewok;
+ bool need_notify;
+
+ 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->masters == 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->masters == NULL) {
+ break;
+ }
+ FALLTHROUGH;
+ case dns_zone_secondary:
+ case dns_zone_mirror:
+ case dns_zone_stub:
+ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH) &&
+ isc_time_compare(&now, &zone->refreshtime) >= 0)
+ {
+ dns_zone_refresh(zone);
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Secondaries send notifies before backing up to disk,
+ * primaries after.
+ */
+ LOCK_ZONE(zone);
+ need_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 (need_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",
+ dns_result_totext(result));
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Master/redirect zones send notifies now, if needed
+ */
+ switch (zone->type) {
+ case dns_zone_primary:
+ case dns_zone_redirect:
+ if ((DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) ||
+ DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) &&
+ isc_time_compare(&now, &zone->notifytime) >= 0)
+ {
+ zone_notify(zone, &now);
+ }
+ default:
+ break;
+ }
+
+ /*
+ * Do we need to refresh keys?
+ */
+ switch (zone->type) {
+ case dns_zone_key:
+ if (isc_time_compare(&now, &zone->refreshkeytime) >= 0) {
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) &&
+ !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING))
+ {
+ zone_refreshkeys(zone);
+ }
+ }
+ break;
+ case dns_zone_primary:
+ if (!isc_time_isepoch(&zone->refreshkeytime) &&
+ isc_time_compare(&now, &zone->refreshkeytime) >= 0 &&
+ zone->rss_event == NULL)
+ {
+ 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?
+ */
+ if (zone->rss_event != NULL) {
+ break;
+ }
+ if (!isc_time_isepoch(&zone->signingtime) &&
+ isc_time_compare(&now, &zone->signingtime) >= 0)
+ {
+ zone_sign(zone);
+ } else if (!isc_time_isepoch(&zone->resigntime) &&
+ isc_time_compare(&now, &zone->resigntime) >= 0)
+ {
+ zone_resigninc(zone);
+ } else if (!isc_time_isepoch(&zone->nsec3chaintime) &&
+ isc_time_compare(&now, &zone->nsec3chaintime) >= 0)
+ {
+ zone_nsec3chain(zone);
+ }
+ /*
+ * Do we need to issue a key expiry warning?
+ */
+ if (!isc_time_isepoch(&zone->keywarntime) &&
+ isc_time_compare(&now, &zone->keywarntime) >= 0)
+ {
+ 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);
+}
+
+void
+dns_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));
+
+ 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.
+ */
+
+ LOCK_ZONE(zone);
+ oldflags = atomic_load(&zone->flags);
+ if (zone->masterscnt == 0) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOMASTERS);
+ if ((oldflags & DNS_ZONEFLG_NOMASTERS) == 0) {
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "cannot refresh: no primaries");
+ }
+ goto unlock;
+ }
+ 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) {
+ goto unlock;
+ }
+
+ /*
+ * 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",
+ dns_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->curmaster = 0;
+ for (j = 0; j < zone->masterscnt; j++) {
+ zone->mastersok[j] = false;
+ }
+ /* initiate soa query */
+ queue_soa_query(zone);
+unlock:
+ 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",
+ dns_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",
+ dns_result_totext(result));
+ } else {
+ dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+ ISC_LOG_DEBUG(1),
+ "journal rollforward completed "
+ "successfully: %s",
+ dns_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",
+ dns_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",
+ dns_result_totext(result));
+ break;
+ default:
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "dns_journal_compact failed: %s",
+ dns_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_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)
+ {
+ 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);
+ }
+ 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->mctx = NULL;
+ isc_mem_attach(mctx, &notify->mctx);
+ notify->flags = flags;
+ notify->zone = NULL;
+ notify->find = NULL;
+ notify->request = NULL;
+ notify->key = NULL;
+ notify->event = NULL;
+ 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;
+ bool have_notifydscp = false;
+ isc_dscp_t dscp = -1;
+
+ 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;
+ }
+ dns_peer_getnotifydscp(peer, &dscp);
+ if (dscp != -1) {
+ have_notifydscp = 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;
+ }
+ if (!have_notifydscp) {
+ dscp = notify->zone->notifysrc4dscp;
+ }
+ break;
+ case PF_INET6:
+ if (!have_notifysource) {
+ src = notify->zone->notifysrc6;
+ }
+ if (!have_notifydscp) {
+ dscp = notify->zone->notifysrc6dscp;
+ }
+ 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_createvia(
+ notify->zone->view->requestmgr, message, &src, &notify->dst,
+ dscp, options, key, timeout * 3, timeout, 0, 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))
+ {
+ 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 master;
+ 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 master server's name.
+ */
+ dns_name_init(&master, 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, &master);
+ 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_notify_t *notify = NULL;
+
+ if ((zone->notifykeynames != NULL) &&
+ (zone->notifykeynames[i] != NULL))
+ {
+ dns_view_t *view = dns_zone_getview(zone);
+ dns_name_t *keyname = zone->notifykeynames[i];
+ (void)dns_view_gettsig(view, keyname, &key);
+ }
+
+ dst = zone->notify[i];
+ if (notify_isqueued(zone, flags, NULL, &dst, key)) {
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ continue;
+ }
+
+ result = notify_create(zone->mctx, flags, &notify);
+ if (result != ISC_R_SUCCESS) {
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ continue;
+ }
+
+ zone_iattach(zone, &notify->zone);
+ notify->dst = dst;
+
+ INSIST(notify->key == NULL);
+
+ if (key != NULL) {
+ notify->key = key;
+ key = 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 master server unless explicitly
+ * configured to do so.
+ */
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOTIFYTOSOA) &&
+ dns_name_compare(&master, &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);
+ 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(&master)) {
+ dns_name_free(&master, 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 master, 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 master[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->masteraddr, master, sizeof(master));
+ isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source));
+
+ if (revent->result != ISC_R_SUCCESS) {
+ dns_zonemgr_unreachableadd(zone->zmgr, &zone->masteraddr,
+ &zone->sourceaddr, &now);
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "could not refresh stub from master %s"
+ " (source %s): %s",
+ master, source, dns_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, master, 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, master, 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 master %s (source %s)",
+ master, 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 "
+ "master %s (source %s)",
+ master, 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 master %s (source %s)",
+ master, source);
+ goto cleanup;
+ }
+
+ if (addr_count == 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: no %s records in response "
+ "from master %s (source %s)",
+ request->ipv4 ? "A" : "AAAA", master, 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",
+ dns_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",
+ dns_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 master
+ * 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",
+ dns_result_totext(result));
+ goto fail;
+ }
+ }
+
+ atomic_fetch_add_release(&args->stub->pending_requests, 1);
+
+ result = dns_request_createvia(
+ zone->view->requestmgr, message, &zone->sourceaddr,
+ &zone->masteraddr, args->dscp, DNS_REQUESTOPT_TCP,
+ args->tsig_key, args->timeout * 3, args->timeout, 0, zone->task,
+ stub_glue_response_cb, request, &request->request);
+
+ if (result != ISC_R_SUCCESS) {
+ INSIST(atomic_fetch_sub_release(&args->stub->pending_requests,
+ 1) > 1);
+ zone_debuglog(zone, "stub_send_query", 1,
+ "dns_request_createvia() failed: %s",
+ dns_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 master[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)) {
+ zone_debuglog(zone, me, 1, "exiting");
+ exiting = true;
+ goto next_master;
+ }
+
+ isc_sockaddr_format(&zone->masteraddr, master, sizeof(master));
+ isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source));
+
+ if (revent->result != ISC_R_SUCCESS) {
+ if (revent->result == ISC_R_TIMEDOUT &&
+ !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 master %s (source %s)",
+ master, source);
+ goto same_master;
+ }
+ dns_zonemgr_unreachableadd(zone->zmgr, &zone->masteraddr,
+ &zone->sourceaddr, &now);
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "could not refresh stub from master %s"
+ " (source %s): %s",
+ master, source, dns_result_totext(revent->result));
+ goto next_master;
+ }
+
+ dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
+
+ result = dns_request_getresponse(revent->request, msg, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto next_master;
+ }
+
+ /*
+ * 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, master, source);
+ goto next_master;
+ }
+
+ /*
+ * 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))
+ {
+ dns_zone_log(zone, ISC_LOG_DEBUG(1),
+ "refreshing stub: rcode (%.*s) retrying "
+ "without EDNS master %s (source %s)",
+ (int)rb.used, rcode, master, source);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ goto same_master;
+ }
+
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "unexpected rcode (%.*s) from %s (source %s)",
+ (int)rb.used, rcode, master, source);
+ goto next_master;
+ }
+
+ /*
+ * 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 master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEVC);
+ goto same_master;
+ }
+
+ /*
+ * If non-auth log and next master.
+ */
+ if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: "
+ "non-authoritative answer from "
+ "master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+
+ /*
+ * 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 master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+
+ if (nscnt == 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refreshing stub: no NS records in response "
+ "from master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+
+ 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 master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+
+ 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;
+
+next_master:
+ 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 master.
+ */
+ do {
+ zone->curmaster++;
+ } while (zone->curmaster < zone->masterscnt &&
+ zone->mastersok[zone->curmaster]);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ if (exiting || zone->curmaster >= zone->masterscnt) {
+ 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->masterscnt; j++) {
+ if (!zone->mastersok[j]) {
+ {
+ done = false;
+ break;
+ }
+ }
+ }
+ } else {
+ done = true;
+ }
+ if (!done) {
+ zone->curmaster = 0;
+ /*
+ * Find the next failed master.
+ */
+ while (zone->curmaster < zone->masterscnt &&
+ zone->mastersok[zone->curmaster])
+ {
+ zone->curmaster++;
+ }
+ 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_master:
+ 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, dns_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 master[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)) {
+ isc_event_free(&event);
+ dns_request_destroy(&zone->request);
+ goto detach;
+ }
+
+ /*
+ * if timeout log and next master;
+ */
+
+ isc_sockaddr_format(&zone->masteraddr, master, sizeof(master));
+ isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source));
+
+ if (revent->result != ISC_R_SUCCESS) {
+ if (revent->result == ISC_R_TIMEDOUT &&
+ !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 "
+ "master %s (source %s)",
+ master, source);
+ goto same_master;
+ }
+ if (revent->result == ISC_R_TIMEDOUT &&
+ !dns_request_usedtcp(revent->request))
+ {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: retry limit for "
+ "master %s exceeded (source %s)",
+ master, source);
+ /* Try with slave 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->masteraddr,
+ &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 master %s (source %s) is "
+ "unreachable (cached)",
+ master, source);
+ }
+ } else {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: failure trying master "
+ "%s (source %s): %s",
+ master, source,
+ dns_result_totext(revent->result));
+ }
+ goto next_master;
+ }
+
+ 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 master "
+ "%s (source %s): %s",
+ master, source, dns_result_totext(result));
+ goto next_master;
+ }
+
+ /*
+ * 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, master, source);
+ goto next_master;
+ }
+
+ /*
+ * 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))
+ {
+ dns_zone_log(zone, ISC_LOG_DEBUG(1),
+ "refresh: rcode (%.*s) retrying without "
+ "EDNS master %s (source %s)",
+ (int)rb.used, rcode, master, source);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ goto same_master;
+ }
+ 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 master %s (source %s)",
+ (int)rb.used, rcode, master, source);
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ goto same_master;
+ }
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: unexpected rcode (%.*s) from "
+ "master %s (source %s)",
+ (int)rb.used, rcode, master, 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_master;
+ }
+
+ /*
+ * 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 master %s (source %s)",
+ master, 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 master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEVC);
+ goto same_master;
+ }
+ }
+
+ /*
+ * if non-auth log and next master;
+ */
+ if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: non-authoritative answer from "
+ "master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+
+ 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 master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+
+ /*
+ * if referral log and next master;
+ */
+ if (soacnt == 0 && soacount == 0 && nscount != 0) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: referral response "
+ "from master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+
+ /*
+ * if nodata log and next master;
+ */
+ if (soacnt == 0 && (nscount == 0 || soacount != 0)) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: NODATA response "
+ "from master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+
+ /*
+ * Only one soa at top of zone.
+ */
+ if (soacnt != 1) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: answer SOA count (%d) != 1 "
+ "from master %s (source %s)",
+ soacnt, master, source);
+ goto next_master;
+ }
+
+ /*
+ * 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 master %s (source %s)",
+ master, source);
+ goto next_master;
+ }
+
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: dns_rdataset_first() failed");
+ goto next_master;
+ }
+
+ 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->masteraddr,
+ &zone->sourceaddr, &now))
+ {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refresh: skipping %s as master %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",
+ master, source);
+ goto next_master;
+ }
+ 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->mastersok[zone->curmaster] = true;
+ goto next_master;
+ } else {
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MULTIMASTER)) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "serial number (%u) "
+ "received from master %s < ours (%u)",
+ soa.serial, master, oldserial);
+ } else {
+ zone_debuglog(zone, me, 1, "ahead");
+ }
+ zone->mastersok[zone->curmaster] = true;
+ goto next_master;
+ }
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ goto detach;
+
+next_master:
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ isc_event_free(&event);
+ dns_request_destroy(&zone->request);
+ /*
+ * Skip to next failed / untried master.
+ */
+ do {
+ zone->curmaster++;
+ } while (zone->curmaster < zone->masterscnt &&
+ zone->mastersok[zone->curmaster]);
+ DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS);
+ if (zone->curmaster >= zone->masterscnt) {
+ 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->masterscnt; j++) {
+ if (!zone->mastersok[j]) {
+ {
+ done = false;
+ break;
+ }
+ }
+ }
+ } else {
+ done = true;
+ }
+ if (!done) {
+ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC);
+ zone->curmaster = 0;
+ /*
+ * Find the next failed master.
+ */
+ while (zone->curmaster < zone->masterscnt &&
+ zone->mastersok[zone->curmaster])
+ {
+ zone->curmaster++;
+ }
+ 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;
+
+same_master:
+ 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 masterip;
+ dns_tsigkey_t *key = NULL;
+ uint32_t options;
+ bool cancel = true;
+ int timeout;
+ bool have_xfrsource, have_xfrdscp, reqnsid, reqexpire;
+ uint16_t udpsize = SEND_BUFFER_SIZE;
+ isc_dscp_t dscp = -1;
+
+ 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:
+ result = create_query(zone, dns_rdatatype_soa, &zone->origin, &message);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ INSIST(zone->masterscnt > 0);
+ INSIST(zone->curmaster < zone->masterscnt);
+
+ zone->masteraddr = zone->masters[zone->curmaster];
+
+ isc_netaddr_fromsockaddr(&masterip, &zone->masteraddr);
+ /*
+ * First, look for a tsig key in the master statement, then
+ * try for a server key.
+ */
+ if ((zone->masterkeynames != NULL) &&
+ (zone->masterkeynames[zone->curmaster] != NULL))
+ {
+ dns_view_t *view = dns_zone_getview(zone);
+ dns_name_t *keyname = zone->masterkeynames[zone->curmaster];
+ 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_master;
+ }
+ }
+ if (key == NULL) {
+ result = dns_view_getpeertsig(zone->view, &masterip, &key);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ char addrbuf[ISC_NETADDR_FORMATSIZE];
+ isc_netaddr_format(&masterip, addrbuf, sizeof(addrbuf));
+ dns_zone_log(zone, ISC_LOG_ERROR,
+ "unable to find TSIG key for %s", addrbuf);
+ goto skip_master;
+ }
+ }
+
+ options = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEVC) ? DNS_REQUESTOPT_TCP
+ : 0;
+ have_xfrsource = have_xfrdscp = false;
+ 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, &masterip,
+ &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;
+ }
+ (void)dns_peer_gettransferdscp(peer, &dscp);
+ if (dscp != -1) {
+ have_xfrdscp = 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->masteraddr)) {
+ case PF_INET:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
+ if (isc_sockaddr_equal(&zone->altxfrsource4,
+ &zone->xfrsource4))
+ {
+ goto skip_master;
+ }
+ zone->sourceaddr = zone->altxfrsource4;
+ if (!have_xfrdscp) {
+ dscp = zone->altxfrsource4dscp;
+ }
+ } else if (!have_xfrsource) {
+ zone->sourceaddr = zone->xfrsource4;
+ if (!have_xfrdscp) {
+ dscp = zone->xfrsource4dscp;
+ }
+ }
+ break;
+ case PF_INET6:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
+ if (isc_sockaddr_equal(&zone->altxfrsource6,
+ &zone->xfrsource6))
+ {
+ goto skip_master;
+ }
+ zone->sourceaddr = zone->altxfrsource6;
+ if (!have_xfrdscp) {
+ dscp = zone->altxfrsource6dscp;
+ }
+ } else if (!have_xfrsource) {
+ zone->sourceaddr = zone->xfrsource6;
+ if (!have_xfrdscp) {
+ dscp = zone->xfrsource6dscp;
+ }
+ }
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ 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",
+ dns_result_totext(result));
+ }
+ }
+
+ zone_iattach(zone, &dummy);
+ timeout = 15;
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)) {
+ timeout = 30;
+ }
+ result = dns_request_createvia(
+ zone->view->requestmgr, message, &zone->sourceaddr,
+ &zone->masteraddr, dscp, options, key, timeout * 3, timeout, 0,
+ zone->task, refresh_callback, zone, &zone->request);
+ if (result != ISC_R_SUCCESS) {
+ zone_idetach(&dummy);
+ zone_debuglog(zone, me, 1,
+ "dns_request_createvia4() failed: %s",
+ dns_result_totext(result));
+ goto skip_master;
+ } else {
+ if (isc_sockaddr_pf(&zone->masteraddr) == PF_INET) {
+ inc_stats(zone, dns_zonestatscounter_soaoutv4);
+ } else {
+ inc_stats(zone, dns_zonestatscounter_soaoutv6);
+ }
+ }
+ cancel = false;
+
+cleanup:
+ 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);
+ dns_zone_idetach(&zone);
+ return;
+
+skip_master:
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ dns_message_detach(&message);
+ /*
+ * Skip to next failed / untried master.
+ */
+ do {
+ zone->curmaster++;
+ } while (zone->curmaster < zone->masterscnt &&
+ zone->mastersok[zone->curmaster]);
+ if (zone->curmaster < zone->masterscnt) {
+ goto again;
+ }
+ zone->curmaster = 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 masterip;
+ dns_tsigkey_t *key = NULL;
+ dns_dbnode_t *node = NULL;
+ int timeout;
+ bool have_xfrsource = false, have_xfrdscp = false;
+ bool reqnsid;
+ uint16_t udpsize = SEND_BUFFER_SIZE;
+ isc_dscp_t dscp = -1;
+ 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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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->masterscnt > 0);
+ INSIST(zone->curmaster < zone->masterscnt);
+ zone->masteraddr = zone->masters[zone->curmaster];
+
+ isc_netaddr_fromsockaddr(&masterip, &zone->masteraddr);
+ /*
+ * First, look for a tsig key in the master statement, then
+ * try for a server key.
+ */
+ if ((zone->masterkeynames != NULL) &&
+ (zone->masterkeynames[zone->curmaster] != NULL))
+ {
+ dns_view_t *view = dns_zone_getview(zone);
+ dns_name_t *keyname = zone->masterkeynames[zone->curmaster];
+ 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, &masterip, &key);
+ }
+
+ reqnsid = zone->view->requestnsid;
+ if (zone->view->peers != NULL) {
+ dns_peer_t *peer = NULL;
+ bool edns;
+ result = dns_peerlist_peerbyaddr(zone->view->peers, &masterip,
+ &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;
+ }
+ result = dns_peer_gettransferdscp(peer, &dscp);
+ if (result == ISC_R_SUCCESS && dscp != -1) {
+ have_xfrdscp = 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",
+ dns_result_totext(result));
+ }
+ }
+
+ /*
+ * Always use TCP so that we shouldn't truncate in additional section.
+ */
+ switch (isc_sockaddr_pf(&zone->masteraddr)) {
+ case PF_INET:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
+ zone->sourceaddr = zone->altxfrsource4;
+ if (!have_xfrdscp) {
+ dscp = zone->altxfrsource4dscp;
+ }
+ } else if (!have_xfrsource) {
+ zone->sourceaddr = zone->xfrsource4;
+ if (!have_xfrdscp) {
+ dscp = zone->xfrsource4dscp;
+ }
+ }
+ break;
+ case PF_INET6:
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
+ zone->sourceaddr = zone->altxfrsource6;
+ if (!have_xfrdscp) {
+ dscp = zone->altxfrsource6dscp;
+ }
+ } else if (!have_xfrsource) {
+ zone->sourceaddr = zone->xfrsource6;
+ if (!have_xfrdscp) {
+ dscp = zone->xfrsource6dscp;
+ }
+ }
+ 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->dscp = dscp;
+ cb_args->udpsize = udpsize;
+ cb_args->timeout = timeout;
+ cb_args->reqnsid = reqnsid;
+
+ result = dns_request_createvia(
+ zone->view->requestmgr, message, &zone->sourceaddr,
+ &zone->masteraddr, dscp, DNS_REQUESTOPT_TCP, key, timeout * 3,
+ timeout, 0, zone->task, stub_callback, cb_args, &zone->request);
+ if (result != ISC_R_SUCCESS) {
+ zone_debuglog(zone, me, 1, "dns_request_createvia() failed: %s",
+ dns_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) {
+ 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->masters != NULL) {
+ goto treat_as_slave;
+ }
+ 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_slave:
+ 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_NOMASTERS) &&
+ !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->masters).
+ * 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 master 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->masterscnt; i++) {
+ if (isc_sockaddr_eqaddr(from, &zone->masters[i])) {
+ break;
+ }
+ if (zone->view->aclenv.match_mapped &&
+ IN6_IS_ADDR_V4MAPPED(&from->type.sin6.sin6_addr) &&
+ isc_sockaddr_pf(&zone->masters[i]) == AF_INET)
+ {
+ isc_netaddr_t na1, na2;
+ isc_netaddr_fromv4mapped(&na1, &netaddr);
+ isc_netaddr_fromsockaddr(&na2, &zone->masters[i]);
+ if (isc_netaddr_equal(&na1, &na2)) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * Accept notify requests from non masters if they are on
+ * 'zone->notify_acl'.
+ */
+ tsigkey = dns_message_gettsigkey(msg);
+ tsig = dns_tsigkey_identity(tsigkey);
+ if (i >= zone->masterscnt && 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->masterscnt) {
+ UNLOCK_ZONE(zone);
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "refused notify from non-master: %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->masters == 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);
+
+ result = revent->result;
+ if (result == ISC_R_SUCCESS) {
+ result =
+ dns_request_getresponse(revent->request, message,
+ DNS_MESSAGEPARSE_PRESERVEORDER);
+ }
+ if (result == ISC_R_SUCCESS) {
+ 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);
+ } else {
+ notify_log(notify->zone, ISC_LOG_DEBUG(2),
+ "notify to %s failed: %s", addrbuf,
+ dns_result_totext(result));
+ }
+
+ /*
+ * Old bind's return formerr if they see a soa record. Retry w/o
+ * the soa if we see a formerr and had sent a SOA.
+ */
+ isc_event_free(&event);
+ if (message->rcode == dns_rcode_formerr &&
+ (notify->flags & DNS_NOTIFY_NOSOA) == 0)
+ {
+ bool startup;
+
+ notify->flags |= DNS_NOTIFY_NOSOA;
+ dns_request_destroy(&notify->request);
+ startup = (notify->flags & DNS_NOTIFY_STARTUP);
+ result = notify_send_queue(notify, startup);
+ if (result != ISC_R_SUCCESS) {
+ notify_destroy(notify, false);
+ }
+ } else {
+ if (result == ISC_R_TIMEDOUT) {
+ notify_log(notify->zone, ISC_LOG_DEBUG(1),
+ "notify to %s: retries exceeded", addrbuf);
+ }
+ notify_destroy(notify, false);
+ }
+ 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) {
+ level = ISC_LOG_INFO;
+ 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);
+ dns_zone_log(zone, level, "receive_secure_serial: %s",
+ dns_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",
+ dns_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",
+ dns_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 slave 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 master 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->masters != 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", dns_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 the 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_master;
+ }
+
+ /*
+ * 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_master;
+ }
+ 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_master;
+ }
+ 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,
+ dns_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_master;
+
+ 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_master:
+ /*
+ * Skip to next failed / untried master.
+ */
+ do {
+ zone->curmaster++;
+ } while (zone->curmaster < zone->masterscnt &&
+ zone->mastersok[zone->curmaster]);
+ same_master:
+ if (zone->curmaster >= zone->masterscnt) {
+ zone->curmaster = 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->curmaster < zone->masterscnt &&
+ zone->mastersok[zone->curmaster])
+ {
+ zone->curmaster++;
+ }
+ 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);
+ }
+
+ /*
+ * 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 master[ISC_SOCKADDR_FORMATSIZE];
+ char source[ISC_SOCKADDR_FORMATSIZE];
+ dns_rdatatype_t xfrtype;
+ dns_zone_t *zone = event->ev_arg;
+ isc_netaddr_t masterip;
+ isc_sockaddr_t sourceaddr;
+ isc_sockaddr_t masteraddr;
+ isc_time_t now;
+ const char *soa_before = "";
+ isc_dscp_t dscp = -1;
+ bool loaded;
+
+ UNUSED(task);
+
+ INSIST(task == zone->task);
+
+ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
+ result = ISC_R_CANCELED;
+ goto cleanup;
+ }
+
+ TIME_NOW(&now);
+
+ isc_sockaddr_format(&zone->masteraddr, master, sizeof(master));
+ if (dns_zonemgr_unreachable(zone->zmgr, &zone->masteraddr,
+ &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 "
+ "master %s (source %s) is unreachable (cached)",
+ master, source);
+ result = ISC_R_CANCELED;
+ goto cleanup;
+ }
+
+ isc_netaddr_fromsockaddr(&masterip, &zone->masteraddr);
+ (void)dns_peerlist_peerbyaddr(zone->view->peers, &masterip, &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",
+ master);
+ 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",
+ master);
+ 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",
+ master);
+ 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, master);
+ 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", master);
+ 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 master statement, then
+ * try for a server key.
+ */
+ if ((zone->masterkeynames != NULL) &&
+ (zone->masterkeynames[zone->curmaster] != NULL))
+ {
+ dns_view_t *view = dns_zone_getview(zone);
+ dns_name_t *keyname = zone->masterkeynames[zone->curmaster];
+ result = dns_view_gettsig(view, keyname, &zone->tsigkey);
+ }
+ if (zone->tsigkey == NULL) {
+ result = dns_view_getpeertsig(zone->view, &masterip,
+ &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));
+ }
+
+ if (zone->masterdscps != NULL) {
+ dscp = zone->masterdscps[zone->curmaster];
+ }
+
+ LOCK_ZONE(zone);
+ masteraddr = zone->masteraddr;
+ sourceaddr = zone->sourceaddr;
+ switch (isc_sockaddr_pf(&masteraddr)) {
+ case PF_INET:
+ if (dscp == -1) {
+ dscp = zone->xfrsource4dscp;
+ }
+ break;
+ case PF_INET6:
+ if (dscp == -1) {
+ dscp = zone->xfrsource6dscp;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+ UNLOCK_ZONE(zone);
+ INSIST(isc_sockaddr_pf(&masteraddr) == isc_sockaddr_pf(&sourceaddr));
+ result = dns_xfrin_create(zone, xfrtype, &masteraddr, &sourceaddr, dscp,
+ zone->tsigkey, zone->mctx,
+ zone->zmgr->timermgr, zone->zmgr->socketmgr,
+ zone->task, zone_xfrdone, &zone->xfr);
+ if (result == ISC_R_SUCCESS) {
+ LOCK_ZONE(zone);
+ if (xfrtype == dns_rdatatype_axfr) {
+ if (isc_sockaddr_pf(&masteraddr) == 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(&masteraddr) == PF_INET) {
+ inc_stats(zone, dns_zonestatscounter_ixfrreqv4);
+ } else {
+ inc_stats(zone, dns_zonestatscounter_ixfrreqv6);
+ }
+ }
+ UNLOCK_ZONE(zone);
+ }
+cleanup:
+ /*
+ * 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);
+ }
+
+ isc_event_free(&event);
+}
+
+/*
+ * 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
+sendtomaster(dns_forward_t *forward) {
+ isc_result_t result;
+ isc_sockaddr_t src;
+ isc_dscp_t dscp = -1;
+
+ 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->masterscnt) {
+ UNLOCK_ZONE(forward->zone);
+ return (ISC_R_NOMORE);
+ }
+
+ forward->addr = forward->zone->masters[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 the master has to try several masters.
+ */
+ switch (isc_sockaddr_pf(&forward->addr)) {
+ case PF_INET:
+ src = forward->zone->xfrsource4;
+ dscp = forward->zone->xfrsource4dscp;
+ break;
+ case PF_INET6:
+ src = forward->zone->xfrsource6;
+ dscp = forward->zone->xfrsource6dscp;
+ break;
+ default:
+ result = ISC_R_NOTIMPLEMENTED;
+ goto unlock;
+ }
+ result = dns_request_createraw(forward->zone->view->requestmgr,
+ forward->msgbuf, &src, &forward->addr,
+ dscp, 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 master[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, master, sizeof(master));
+
+ if (revent->result != ISC_R_SUCCESS) {
+ dns_zone_log(zone, ISC_LOG_INFO,
+ "could not forward dynamic update to %s: %s",
+ master, dns_result_totext(revent->result));
+ goto next_master;
+ }
+
+ 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_master;
+ }
+
+ /*
+ * 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, master);
+ goto next_master;
+ }
+
+ 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: "
+ "master %s returned: %.*s",
+ master, (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: master %s returned: %.*s",
+ master, (int)rb.used, rcode);
+ goto next_master;
+ }
+
+ /* 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_master;
+ }
+
+ /* 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_master:
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ isc_event_free(&event);
+ forward->which++;
+ dns_request_destroy(&forward->request);
+ result = sendtomaster(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 = sendtomaster(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_copynf(&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_socketmgr_t *socketmgr,
+ 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->socketmgr = socketmgr;
+ 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->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", NULL);
+
+ *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;
+ }
+
+ 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);
+
+ zonemgr_keymgmt_destroy(zmgr);
+
+ mctx = zmgr->mctx;
+ 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_getttransfersin(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_getttransfersperns(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);
+}
+
+/*
+ * 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 master.
+ */
+ 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 masterip;
+ 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(&masterip, &zone->masteraddr);
+ (void)dns_peerlist_peerbyaddr(zone->view->peers, &masterip, &peer);
+ UNLOCK_ZONE(zone);
+
+ /*
+ * Determine the total maximum number of simultaneous
+ * transfers allowed, and the maximum for this specific
+ * master.
+ */
+ 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 master.
+ * We linearly scan a list of all transfers; if this turns
+ * out to be too slow, we could hash on the master 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->masteraddr);
+ UNLOCK_ZONE(x);
+
+ nxfrsin++;
+ if (isc_netaddr_equal(&xip, &masterip)) {
+ 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 master[ISC_SOCKADDR_FORMATSIZE];
+ char source[ISC_SOCKADDR_FORMATSIZE];
+
+ isc_sockaddr_format(remote, master, sizeof(master));
+ 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->masters == 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->masters != 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,
+ dns_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, dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_result_totext(result));
+ goto failure;
+ }
+
+failure:
+ for (i = 0; i < nkeys; i++) {
+ dst_key_free(&zone_keys[i]);
+ }
+ return (result);
+}
+
+/*
+ * Prevent the zone entering a inconsistent state where
+ * NSEC only DNSKEYs are present with NSEC3 chains.
+ * See update.c:check_dnssec()
+ */
+static bool
+dnskey_sane(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
+ dns_diff_t *diff) {
+ isc_result_t result;
+ dns_difftuple_t *tuple;
+ bool nseconly = false, nsec3 = false;
+ dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
+
+ /* Scan the tuples for an NSEC-only DNSKEY */
+ for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL;
+ tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ uint8_t alg;
+ if (tuple->rdata.type != dns_rdatatype_dnskey ||
+ tuple->op != DNS_DIFFOP_ADD)
+ {
+ continue;
+ }
+
+ alg = tuple->rdata.data[3];
+ if (alg == DST_ALG_RSASHA1) {
+ nseconly = true;
+ break;
+ }
+ }
+
+ /* Check existing DB for NSEC-only DNSKEY */
+ if (!nseconly) {
+ result = dns_nsec_nseconly(db, ver, &nseconly);
+ 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));
+ }
+
+ /* Refuse to allow NSEC3 with NSEC-only keys */
+ if (nseconly && nsec3) {
+ dnssec_log(zone, ISC_LOG_ERROR,
+ "NSEC only DNSKEYs and NSEC3 chains not allowed");
+ goto failure;
+ }
+
+ return (true);
+
+failure:
+ return (false);
+}
+
+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);
+ }
+ 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));
+
+ 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;
+ }
+
+ /* 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_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)
+ {
+ 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;
+
+ 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;
+ bool have_checkdsdscp = false;
+ isc_dscp_t dscp = -1;
+
+ 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;
+ }
+ dns_peer_getquerydscp(peer, &dscp);
+ if (dscp != -1) {
+ have_checkdsdscp = 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;
+ }
+ if (!have_checkdsdscp) {
+ dscp = checkds->zone->parentalsrc4dscp;
+ }
+ break;
+ case PF_INET6:
+ if (!have_checkdssource) {
+ src = checkds->zone->parentalsrc6;
+ }
+ if (!have_checkdsdscp) {
+ dscp = checkds->zone->parentalsrc6dscp;
+ }
+ 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_createvia(
+ checkds->zone->view->requestmgr, message, &src, &checkds->dst,
+ dscp, options, key, timeout * 3, timeout, 0,
+ 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_createvia() to %s failed: %s",
+ addrbuf, dns_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;
+ 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);
+ }
+
+ dst = zone->parentals[i];
+
+ if (checkds_isqueued(zone, &dst, key)) {
+ dns_zone_log(zone, ISC_LOG_DEBUG(3),
+ "checkds: DS query to parent "
+ "%d is queued",
+ i);
+ if (key != NULL) {
+ dns_tsigkey_detach(&key);
+ }
+ 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;
+ }
+
+ 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;
+ 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;
+ }
+ }
+
+ if ((newactive || fullsign || !ISC_LIST_EMPTY(diff.tuples)) &&
+ dnskey_sane(zone, db, ver, &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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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",
+ dns_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, &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",
+ dns_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",
+ dns_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",
+ dns_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));
+}
diff --git a/lib/dns/zone_p.h b/lib/dns/zone_p.h
new file mode 100644
index 0000000..67b6028
--- /dev/null
+++ b/lib/dns/zone_p.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef DNS_ZONE_P_H
+#define DNS_ZONE_P_H
+
+#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
+
+#endif /* DNS_ZONE_P_H */
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..19ecdc0
--- /dev/null
+++ b/lib/dns/zoneverify.c
@@ -0,0 +1,2038 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/result.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_copynf(name, zonecut);
+ isdelegation = true;
+ } else if (has_dname(vctx, node)) {
+ zonecut = dns_fixedname_name(&fzonecut);
+ dns_name_copynf(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_copynf(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).",
+ dns_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..7728c15
--- /dev/null
+++ b/lib/dns/zt.c
@@ -0,0 +1,617 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/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/result.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) {
+ 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));
+}
+
+static void
+zt_flushanddetach(dns_zt_t **ztp, bool need_flush) {
+ dns_zt_t *zt;
+
+ REQUIRE(ztp != NULL && VALID_ZT(*ztp));
+
+ zt = *ztp;
+ *ztp = NULL;
+
+ if (need_flush) {
+ atomic_store_release(&zt->flush, true);
+ }
+
+ if (isc_refcount_decrement(&zt->references) == 1) {
+ zt_destroy(zt);
+ }
+}
+
+void
+dns_zt_flushanddetach(dns_zt_t **ztp) {
+ zt_flushanddetach(ztp, true);
+}
+
+void
+dns_zt_detach(dns_zt_t **ztp) {
+ zt_flushanddetach(ztp, false);
+}
+
+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));
+
+ 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);
+}
+
+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/Kyuafile b/lib/irs/Kyuafile
new file mode 100644
index 0000000..c796010
--- /dev/null
+++ b/lib/irs/Kyuafile
@@ -0,0 +1,15 @@
+-- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+--
+-- SPDX-License-Identifier: MPL-2.0
+--
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, you can obtain one at https://mozilla.org/MPL/2.0/.
+--
+-- See the COPYRIGHT file distributed with this work for additional
+-- information regarding copyright ownership.
+
+syntax(2)
+test_suite('bind9')
+
+include('tests/Kyuafile')
diff --git a/lib/irs/Makefile.in b/lib/irs/Makefile.in
new file mode 100644
index 0000000..6152e9a
--- /dev/null
+++ b/lib/irs/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. -I./include -I${srcdir}/include \
+ ${DNS_INCLUDES} ${ISC_INCLUDES} \
+ ${ISCCFG_INCLUDES} \
+ ${OPENSSL_CFLAGS}
+
+CDEFINES =
+CWARNINGS =
+
+ISCLIBS = ../../lib/isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@
+
+ISCDEPLIBS = ../../lib/isc/libisc.@A@
+
+DNSLIBS = ../../lib/dns/libdns.@A@ @NO_LIBTOOL_DNSLIBS@
+
+DNSDEPLIBS = ../../lib/dns/libdns.@A@
+
+ISCCFGLIBS = ../../lib/isccfg/libisccfg.@A@
+
+ISCCFGDEPLIBS = ../../lib/isccfg/libisccfg.@A@
+
+LIBS = @LIBS@
+
+# Alphabetically
+OBJS = context.@O@ \
+ dnsconf.@O@ \
+ gai_strerror.@O@ getaddrinfo.@O@ getnameinfo.@O@ \
+ resconf.@O@
+
+# Alphabetically
+SRCS = context.c \
+ dnsconf.c \
+ gai_strerror.c getaddrinfo.c getnameinfo.c \
+ resconf.c
+
+SUBDIRS = include
+TESTDIRS = @UNITTESTS@
+TARGETS = timestamp
+
+@BIND9_MAKE_RULES@
+
+version.@O@: version.c
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} \
+ -DVERSION=\"${VERSION}\" \
+ -c ${srcdir}/version.c
+
+libirs.@SA@: ${OBJS} version.@O@
+ ${AR} ${ARFLAGS} $@ ${OBJS} version.@O@
+ ${RANLIB} $@
+
+libirs.la: ${OBJS} version.@O@
+ ${LIBTOOL_MODE_LINK} \
+ ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libirs.la -rpath ${libdir} \
+ -release "${VERSION}" \
+ ${OBJS} version.@O@ ${ISCLIBS} ${DNSLIBS} ${ISCCFGLIBS} ${LIBS}
+
+timestamp: libirs.@A@
+ touch timestamp
+
+testdirs: libirs.@A@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}
+
+install:: timestamp installdirs
+ ${LIBTOOL_MODE_INSTALL} ${INSTALL_LIBRARY} libirs.@A@ ${DESTDIR}${libdir}
+
+uninstall::
+ ${LIBTOOL_MODE_UNINSTALL} rm -f ${DESTDIR}${libdir}/libirs.@A@
+
+clean distclean::
+ rm -f libirs.@A@ libirs.la timestamp
diff --git a/lib/irs/context.c b/lib/irs/context.c
new file mode 100644
index 0000000..e783358
--- /dev/null
+++ b/lib/irs/context.c
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/app.h>
+#include <isc/lib.h>
+#include <isc/magic.h>
+#include <isc/managers.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/once.h>
+#include <isc/socket.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/client.h>
+#include <dns/lib.h>
+
+#include <irs/context.h>
+#include <irs/dnsconf.h>
+#include <irs/resconf.h>
+
+#define IRS_CONTEXT_MAGIC ISC_MAGIC('I', 'R', 'S', 'c')
+#define IRS_CONTEXT_VALID(c) ISC_MAGIC_VALID(c, IRS_CONTEXT_MAGIC)
+
+#ifndef RESOLV_CONF
+/*% location of resolve.conf */
+#define RESOLV_CONF "/etc/resolv.conf"
+#endif /* ifndef RESOLV_CONF */
+
+#ifndef DNS_CONF
+/*% location of dns.conf */
+#define DNS_CONF "/etc/dns.conf"
+#endif /* ifndef DNS_CONF */
+
+ISC_THREAD_LOCAL irs_context_t *irs_context = NULL;
+
+struct irs_context {
+ /*
+ * An IRS context is a thread-specific object, and does not need to
+ * be locked.
+ */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_appctx_t *actx;
+ isc_nm_t *netmgr;
+ isc_taskmgr_t *taskmgr;
+ isc_task_t *task;
+ isc_socketmgr_t *socketmgr;
+ isc_timermgr_t *timermgr;
+ dns_client_t *dnsclient;
+ irs_resconf_t *resconf;
+ irs_dnsconf_t *dnsconf;
+};
+
+static void
+ctxs_destroy(isc_mem_t **mctxp, isc_appctx_t **actxp, isc_nm_t **netmgrp,
+ isc_taskmgr_t **taskmgrp, isc_socketmgr_t **socketmgrp,
+ isc_timermgr_t **timermgrp) {
+ isc_managers_destroy(netmgrp == NULL ? NULL : netmgrp,
+ taskmgrp == NULL ? NULL : taskmgrp);
+
+ if (timermgrp != NULL) {
+ isc_timermgr_destroy(timermgrp);
+ }
+
+ if (socketmgrp != NULL) {
+ isc_socketmgr_destroy(socketmgrp);
+ }
+
+ if (actxp != NULL) {
+ isc_appctx_destroy(actxp);
+ }
+
+ if (mctxp != NULL) {
+ isc_mem_destroy(mctxp);
+ }
+}
+
+static isc_result_t
+ctxs_init(isc_mem_t **mctxp, isc_appctx_t **actxp, isc_nm_t **netmgrp,
+ isc_taskmgr_t **taskmgrp, isc_socketmgr_t **socketmgrp,
+ isc_timermgr_t **timermgrp) {
+ isc_result_t result;
+
+ isc_mem_create(mctxp);
+
+ result = isc_appctx_create(*mctxp, actxp);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ result = isc_managers_create(*mctxp, 1, 0, netmgrp, taskmgrp);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ result = isc_socketmgr_create(*mctxp, socketmgrp);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ result = isc_timermgr_create(*mctxp, timermgrp);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ return (ISC_R_SUCCESS);
+
+fail:
+ ctxs_destroy(mctxp, actxp, netmgrp, taskmgrp, socketmgrp, timermgrp);
+
+ return (result);
+}
+
+isc_result_t
+irs_context_get(irs_context_t **contextp) {
+ isc_result_t result;
+
+ REQUIRE(contextp != NULL && *contextp == NULL);
+
+ if (irs_context == NULL) {
+ result = irs_context_create(&irs_context);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ *contextp = irs_context;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+irs_context_create(irs_context_t **contextp) {
+ isc_result_t result;
+ irs_context_t *context;
+ isc_appctx_t *actx = NULL;
+ isc_mem_t *mctx = NULL;
+ isc_nm_t *netmgr = NULL;
+ isc_taskmgr_t *taskmgr = NULL;
+ isc_socketmgr_t *socketmgr = NULL;
+ isc_timermgr_t *timermgr = NULL;
+ dns_client_t *client = NULL;
+ isc_sockaddrlist_t *nameservers;
+ irs_dnsconf_dnskeylist_t *trustedkeys;
+ irs_dnsconf_dnskey_t *trustedkey;
+
+ isc_lib_register();
+ result = dns_lib_init();
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = ctxs_init(&mctx, &actx, &netmgr, &taskmgr, &socketmgr,
+ &timermgr);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = isc_app_ctxstart(actx);
+ if (result != ISC_R_SUCCESS) {
+ ctxs_destroy(&mctx, &actx, &netmgr, &taskmgr, &socketmgr,
+ &timermgr);
+ return (result);
+ }
+
+ context = isc_mem_get(mctx, sizeof(*context));
+
+ context->mctx = mctx;
+ context->actx = actx;
+ context->taskmgr = taskmgr;
+ context->socketmgr = socketmgr;
+ context->timermgr = timermgr;
+ context->resconf = NULL;
+ context->dnsconf = NULL;
+ context->task = NULL;
+ result = isc_task_create(taskmgr, 0, &context->task);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ /* Create a DNS client object */
+ result = dns_client_create(mctx, actx, taskmgr, socketmgr, timermgr, 0,
+ &client, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+ context->dnsclient = client;
+
+ /* Read resolver configuration file */
+ result = irs_resconf_load(mctx, RESOLV_CONF, &context->resconf);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+ /* Set nameservers */
+ nameservers = irs_resconf_getnameservers(context->resconf);
+ result = dns_client_setservers(client, dns_rdataclass_in, NULL,
+ nameservers);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ /* Read advanced DNS configuration (if any) */
+ result = irs_dnsconf_load(mctx, DNS_CONF, &context->dnsconf);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+ trustedkeys = irs_dnsconf_gettrustedkeys(context->dnsconf);
+ for (trustedkey = ISC_LIST_HEAD(*trustedkeys); trustedkey != NULL;
+ trustedkey = ISC_LIST_NEXT(trustedkey, link))
+ {
+ result = dns_client_addtrustedkey(
+ client, dns_rdataclass_in, dns_rdatatype_dnskey,
+ trustedkey->keyname, trustedkey->keydatabuf);
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+ }
+
+ context->magic = IRS_CONTEXT_MAGIC;
+ *contextp = context;
+
+ return (ISC_R_SUCCESS);
+
+fail:
+ if (context->task != NULL) {
+ isc_task_detach(&context->task);
+ }
+ if (context->resconf != NULL) {
+ irs_resconf_destroy(&context->resconf);
+ }
+ if (context->dnsconf != NULL) {
+ irs_dnsconf_destroy(&context->dnsconf);
+ }
+ if (client != NULL) {
+ dns_client_destroy(&client);
+ }
+ ctxs_destroy(NULL, &actx, &netmgr, &taskmgr, &socketmgr, &timermgr);
+ isc_mem_putanddetach(&mctx, context, sizeof(*context));
+
+ return (result);
+}
+
+void
+irs_context_destroy(irs_context_t **contextp) {
+ irs_context_t *context;
+
+ REQUIRE(contextp != NULL);
+ context = *contextp;
+ REQUIRE(IRS_CONTEXT_VALID(context));
+ *contextp = irs_context = NULL;
+
+ isc_task_detach(&context->task);
+ irs_dnsconf_destroy(&context->dnsconf);
+ irs_resconf_destroy(&context->resconf);
+ dns_client_destroy(&context->dnsclient);
+
+ ctxs_destroy(NULL, &context->actx, &context->netmgr, &context->taskmgr,
+ &context->socketmgr, &context->timermgr);
+
+ context->magic = 0;
+
+ isc_mem_putanddetach(&context->mctx, context, sizeof(*context));
+}
+
+isc_mem_t *
+irs_context_getmctx(irs_context_t *context) {
+ REQUIRE(IRS_CONTEXT_VALID(context));
+
+ return (context->mctx);
+}
+
+isc_appctx_t *
+irs_context_getappctx(irs_context_t *context) {
+ REQUIRE(IRS_CONTEXT_VALID(context));
+
+ return (context->actx);
+}
+
+isc_taskmgr_t *
+irs_context_gettaskmgr(irs_context_t *context) {
+ REQUIRE(IRS_CONTEXT_VALID(context));
+
+ return (context->taskmgr);
+}
+
+isc_timermgr_t *
+irs_context_gettimermgr(irs_context_t *context) {
+ REQUIRE(IRS_CONTEXT_VALID(context));
+
+ return (context->timermgr);
+}
+
+isc_task_t *
+irs_context_gettask(irs_context_t *context) {
+ REQUIRE(IRS_CONTEXT_VALID(context));
+
+ return (context->task);
+}
+
+dns_client_t *
+irs_context_getdnsclient(irs_context_t *context) {
+ REQUIRE(IRS_CONTEXT_VALID(context));
+
+ return (context->dnsclient);
+}
+
+irs_resconf_t *
+irs_context_getresconf(irs_context_t *context) {
+ REQUIRE(IRS_CONTEXT_VALID(context));
+
+ return (context->resconf);
+}
+
+irs_dnsconf_t *
+irs_context_getdnsconf(irs_context_t *context) {
+ REQUIRE(IRS_CONTEXT_VALID(context));
+
+ return (context->dnsconf);
+}
diff --git a/lib/irs/dnsconf.c b/lib/irs/dnsconf.c
new file mode 100644
index 0000000..a1421d2
--- /dev/null
+++ b/lib/irs/dnsconf.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.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <string.h>
+
+#include <isc/base64.h>
+#include <isc/buffer.h>
+#include <isc/file.h>
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdatastruct.h>
+
+#include <isccfg/dnsconf.h>
+
+#include <irs/dnsconf.h>
+
+#define IRS_DNSCONF_MAGIC ISC_MAGIC('D', 'c', 'f', 'g')
+#define IRS_DNSCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_DNSCONF_MAGIC)
+
+/*!
+ * configuration data structure
+ */
+
+struct irs_dnsconf {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ irs_dnsconf_dnskeylist_t trusted_keylist;
+};
+
+static isc_result_t
+configure_key(isc_mem_t *mctx, const cfg_obj_t *key, irs_dnsconf_t *conf,
+ dns_rdataclass_t rdclass) {
+ isc_result_t result;
+ uint32_t flags, proto, alg;
+ dns_fixedname_t fkeyname;
+ dns_name_t *keyname_base = NULL, *keyname = NULL;
+ const char *keystr = NULL, *keynamestr = NULL;
+ unsigned char keydata[4096];
+ isc_buffer_t keydatabuf_base, *keydatabuf = NULL;
+ dns_rdata_dnskey_t keystruct;
+ unsigned char rrdata[4096];
+ isc_buffer_t rrdatabuf;
+ isc_region_t r;
+ isc_buffer_t namebuf;
+ irs_dnsconf_dnskey_t *keyent = NULL;
+
+ flags = cfg_obj_asuint32(cfg_tuple_get(key, "flags"));
+ proto = cfg_obj_asuint32(cfg_tuple_get(key, "protocol"));
+ alg = cfg_obj_asuint32(cfg_tuple_get(key, "algorithm"));
+ keynamestr = cfg_obj_asstring(cfg_tuple_get(key, "name"));
+
+ keystruct.common.rdclass = rdclass;
+ keystruct.common.rdtype = dns_rdatatype_dnskey;
+ keystruct.mctx = NULL;
+ ISC_LINK_INIT(&keystruct.common, link);
+
+ if (flags > 0xffff) {
+ return (ISC_R_RANGE);
+ }
+ if (proto > 0xff) {
+ return (ISC_R_RANGE);
+ }
+ if (alg > 0xff) {
+ return (ISC_R_RANGE);
+ }
+ keystruct.flags = (uint16_t)flags;
+ keystruct.protocol = (uint8_t)proto;
+ keystruct.algorithm = (uint8_t)alg;
+
+ isc_buffer_init(&keydatabuf_base, keydata, sizeof(keydata));
+ isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata));
+
+ /* Configure key value */
+ keystr = cfg_obj_asstring(cfg_tuple_get(key, "key"));
+ result = isc_base64_decodestring(keystr, &keydatabuf_base);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_usedregion(&keydatabuf_base, &r);
+ keystruct.datalen = r.length;
+ keystruct.data = r.base;
+
+ result = dns_rdata_fromstruct(NULL, keystruct.common.rdclass,
+ keystruct.common.rdtype, &keystruct,
+ &rrdatabuf);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_usedregion(&rrdatabuf, &r);
+ isc_buffer_allocate(mctx, &keydatabuf, r.length);
+ result = isc_buffer_copyregion(keydatabuf, &r);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* Configure key name */
+ keyname_base = dns_fixedname_initname(&fkeyname);
+ isc_buffer_constinit(&namebuf, keynamestr, strlen(keynamestr));
+ isc_buffer_add(&namebuf, strlen(keynamestr));
+ result = dns_name_fromtext(keyname_base, &namebuf, dns_rootname, 0,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ keyname = isc_mem_get(mctx, sizeof(*keyname));
+ dns_name_init(keyname, NULL);
+ dns_name_dup(keyname_base, mctx, keyname);
+
+ /* Add the key data to the list */
+ keyent = isc_mem_get(mctx, sizeof(*keyent));
+ keyent->keyname = keyname;
+ keyent->keydatabuf = keydatabuf;
+
+ ISC_LIST_APPEND(conf->trusted_keylist, keyent, link);
+
+cleanup:
+ if (keydatabuf != NULL) {
+ isc_buffer_free(&keydatabuf);
+ }
+ if (keyname != NULL) {
+ isc_mem_put(mctx, keyname, sizeof(*keyname));
+ }
+
+ return (result);
+}
+
+static isc_result_t
+configure_keygroup(irs_dnsconf_t *conf, const cfg_obj_t *keys,
+ dns_rdataclass_t rdclass) {
+ isc_result_t result;
+ const cfg_obj_t *key, *keylist;
+ const cfg_listelt_t *element, *element2;
+ isc_mem_t *mctx = conf->mctx;
+
+ for (element = cfg_list_first(keys); element != NULL;
+ element = cfg_list_next(element))
+ {
+ keylist = cfg_listelt_value(element);
+ for (element2 = cfg_list_first(keylist); element2 != NULL;
+ element2 = cfg_list_next(element2))
+ {
+ key = cfg_listelt_value(element2);
+ result = configure_key(mctx, key, conf, rdclass);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+configure_dnsseckeys(irs_dnsconf_t *conf, cfg_obj_t *cfgobj,
+ dns_rdataclass_t rdclass) {
+ isc_result_t result;
+ const cfg_obj_t *keys = NULL;
+
+ cfg_map_get(cfgobj, "trusted-keys", &keys);
+ if (keys == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = configure_keygroup(conf, keys, rdclass);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ keys = NULL;
+ cfg_map_get(cfgobj, "trust-anchors", &keys);
+ if (keys == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = configure_keygroup(conf, keys, rdclass);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ keys = NULL;
+ cfg_map_get(cfgobj, "managed-keys", &keys);
+ if (keys == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = configure_keygroup(conf, keys, rdclass);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+irs_dnsconf_load(isc_mem_t *mctx, const char *filename, irs_dnsconf_t **confp) {
+ irs_dnsconf_t *conf;
+ cfg_parser_t *parser = NULL;
+ cfg_obj_t *cfgobj = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(confp != NULL && *confp == NULL);
+
+ conf = isc_mem_get(mctx, sizeof(*conf));
+
+ conf->mctx = mctx;
+ ISC_LIST_INIT(conf->trusted_keylist);
+
+ /*
+ * If the specified file does not exist, we'll simply with an empty
+ * configuration.
+ */
+ if (!isc_file_exists(filename)) {
+ goto cleanup;
+ }
+
+ result = cfg_parser_create(mctx, NULL, &parser);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = cfg_parse_file(parser, filename, &cfg_type_dnsconf, &cfgobj);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = configure_dnsseckeys(conf, cfgobj, dns_rdataclass_in);
+
+cleanup:
+ if (parser != NULL) {
+ if (cfgobj != NULL) {
+ cfg_obj_destroy(parser, &cfgobj);
+ }
+ cfg_parser_destroy(&parser);
+ }
+
+ conf->magic = IRS_DNSCONF_MAGIC;
+
+ if (result == ISC_R_SUCCESS) {
+ *confp = conf;
+ } else {
+ irs_dnsconf_destroy(&conf);
+ }
+
+ return (result);
+}
+
+void
+irs_dnsconf_destroy(irs_dnsconf_t **confp) {
+ irs_dnsconf_t *conf;
+ irs_dnsconf_dnskey_t *keyent;
+
+ REQUIRE(confp != NULL);
+ conf = *confp;
+ *confp = NULL;
+ REQUIRE(IRS_DNSCONF_VALID(conf));
+
+ while ((keyent = ISC_LIST_HEAD(conf->trusted_keylist)) != NULL) {
+ ISC_LIST_UNLINK(conf->trusted_keylist, keyent, link);
+
+ isc_buffer_free(&keyent->keydatabuf);
+ dns_name_free(keyent->keyname, conf->mctx);
+ isc_mem_put(conf->mctx, keyent->keyname, sizeof(dns_name_t));
+ isc_mem_put(conf->mctx, keyent, sizeof(*keyent));
+ }
+
+ isc_mem_put(conf->mctx, conf, sizeof(*conf));
+}
+
+irs_dnsconf_dnskeylist_t *
+irs_dnsconf_gettrustedkeys(irs_dnsconf_t *conf) {
+ REQUIRE(IRS_DNSCONF_VALID(conf));
+
+ return (&conf->trusted_keylist);
+}
diff --git a/lib/irs/gai_strerror.c b/lib/irs/gai_strerror.c
new file mode 100644
index 0000000..a2b989f
--- /dev/null
+++ b/lib/irs/gai_strerror.c
@@ -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.
+ */
+
+/*! \file gai_strerror.c
+ * gai_strerror() returns an error message corresponding to an
+ * error code returned by getaddrinfo() and getnameinfo(). The following error
+ * codes and their meaning are defined in
+ * \link netdb.h include/irs/netdb.h.\endlink
+ * This implementation is almost an exact copy of lwres/gai_sterror.c except
+ * that it catches up the latest API standard, RFC3493.
+ *
+ * \li #EAI_ADDRFAMILY address family for hostname not supported
+ * \li #EAI_AGAIN temporary failure in name resolution
+ * \li #EAI_BADFLAGS invalid value for ai_flags
+ * \li #EAI_FAIL non-recoverable failure in name resolution
+ * \li #EAI_FAMILY ai_family not supported
+ * \li #EAI_MEMORY memory allocation failure
+ * \li #EAI_NODATA no address associated with hostname (obsoleted in RFC3493)
+ * \li #EAI_NONAME hostname nor servname provided, or not known
+ * \li #EAI_SERVICE servname not supported for ai_socktype
+ * \li #EAI_SOCKTYPE ai_socktype not supported
+ * \li #EAI_SYSTEM system error returned in errno
+ * \li #EAI_BADHINTS Invalid value for hints (non-standard)
+ * \li #EAI_PROTOCOL Resolved protocol is unknown (non-standard)
+ * \li #EAI_OVERFLOW Argument buffer overflow
+ * \li #EAI_INSECUREDATA Insecure Data (experimental)
+ *
+ * The message invalid error code is returned if ecode is out of range.
+ *
+ * ai_flags, ai_family and ai_socktype are elements of the struct
+ * addrinfo used by lwres_getaddrinfo().
+ *
+ * \section gai_strerror_see See Also
+ *
+ * strerror(), getaddrinfo(), getnameinfo(), RFC3493.
+ */
+
+#include <isc/net.h>
+
+#include <irs/netdb.h>
+
+/*% Text of error messages. */
+static const char *gai_messages[] = { "no error",
+ "address family for hostname not "
+ "supported",
+ "temporary failure in name resolution",
+ "invalid value for ai_flags",
+ "non-recoverable failure in name "
+ "resolution",
+ "ai_family not supported",
+ "memory allocation failure",
+ "no address associated with hostname",
+ "hostname nor servname provided, or not "
+ "known",
+ "servname not supported for ai_socktype",
+ "ai_socktype not supported",
+ "system error returned in errno",
+ "bad hints",
+ "bad protocol",
+ "argument buffer overflow",
+ "insecure data provided" };
+
+/*%
+ * Returns an error message corresponding to an error code returned by
+ * getaddrinfo() and getnameinfo()
+ */
+#if defined _WIN32
+char *
+#else /* if defined _WIN32 */
+const char *
+#endif /* if defined _WIN32 */
+gai_strerror(int ecode) {
+ union {
+ const char *const_ptr;
+ char *deconst_ptr;
+ } ptr;
+
+ if ((ecode < 0) ||
+ (ecode >= (int)(sizeof(gai_messages) / sizeof(*gai_messages))))
+ {
+ ptr.const_ptr = "invalid error code";
+ } else {
+ ptr.const_ptr = gai_messages[ecode];
+ }
+ return (ptr.deconst_ptr);
+}
diff --git a/lib/irs/getaddrinfo.c b/lib/irs/getaddrinfo.c
new file mode 100644
index 0000000..f450a55
--- /dev/null
+++ b/lib/irs/getaddrinfo.c
@@ -0,0 +1,1365 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+/**
+ * getaddrinfo() is used to get a list of IP addresses and port
+ * numbers for host hostname and service servname as defined in RFC3493.
+ * hostname and servname are pointers to null-terminated strings
+ * or NULL. hostname is either a host name or a numeric host address
+ * string: a dotted decimal IPv4 address or an IPv6 address. servname is
+ * either a decimal port number or a service name as listed in
+ * /etc/services.
+ *
+ * If the operating system does not provide a struct addrinfo, the
+ * following structure is used:
+ *
+ * \code
+ * struct addrinfo {
+ * int ai_flags; // AI_PASSIVE, AI_CANONNAME
+ * int ai_family; // PF_xxx
+ * int ai_socktype; // SOCK_xxx
+ * int ai_protocol; // 0 or IPPROTO_xxx for IPv4 and IPv6
+ * size_t ai_addrlen; // length of ai_addr
+ * char *ai_canonname; // canonical name for hostname
+ * struct sockaddr *ai_addr; // binary address
+ * struct addrinfo *ai_next; // next structure in linked list
+ * };
+ * \endcode
+ *
+ *
+ * hints is an optional pointer to a struct addrinfo. This structure can
+ * be used to provide hints concerning the type of socket that the caller
+ * supports or wishes to use. The caller can supply the following
+ * structure elements in *hints:
+ *
+ * <ul>
+ * <li>ai_family:
+ * The protocol family that should be used. When ai_family is set
+ * to PF_UNSPEC, it means the caller will accept any protocol
+ * family supported by the operating system.</li>
+ *
+ * <li>ai_socktype:
+ * denotes the type of socket -- SOCK_STREAM, SOCK_DGRAM or
+ * SOCK_RAW -- that is wanted. When ai_socktype is zero the caller
+ * will accept any socket type.</li>
+ *
+ * <li>ai_protocol:
+ * indicates which transport protocol is wanted: IPPROTO_UDP or
+ * IPPROTO_TCP. If ai_protocol is zero the caller will accept any
+ * protocol.</li>
+ *
+ * <li>ai_flags:
+ * Flag bits. If the AI_CANONNAME bit is set, a successful call to
+ * getaddrinfo() will return a null-terminated string
+ * containing the canonical name of the specified hostname in
+ * ai_canonname of the first addrinfo structure returned. Setting
+ * the AI_PASSIVE bit indicates that the returned socket address
+ * structure is intended for used in a call to bind(2). In this
+ * case, if the hostname argument is a NULL pointer, then the IP
+ * address portion of the socket address structure will be set to
+ * INADDR_ANY for an IPv4 address or IN6ADDR_ANY_INIT for an IPv6
+ * address.<br /><br />
+ *
+ * When ai_flags does not set the AI_PASSIVE bit, the returned
+ * socket address structure will be ready for use in a call to
+ * connect(2) for a connection-oriented protocol or connect(2),
+ * sendto(2), or sendmsg(2) if a connectionless protocol was
+ * chosen. The IP address portion of the socket address structure
+ * will be set to the loopback address if hostname is a NULL
+ * pointer and AI_PASSIVE is not set in ai_flags.<br /><br />
+ *
+ * If ai_flags is set to AI_NUMERICHOST it indicates that hostname
+ * should be treated as a numeric string defining an IPv4 or IPv6
+ * address and no name resolution should be attempted.
+ * </li></ul>
+ *
+ * All other elements of the struct addrinfo passed via hints must be
+ * zero.
+ *
+ * A hints of NULL is treated as if the caller provided a struct addrinfo
+ * initialized to zero with ai_familyset to PF_UNSPEC.
+ *
+ * After a successful call to getaddrinfo(), *res is a pointer to a
+ * linked list of one or more addrinfo structures. Each struct addrinfo
+ * in this list cn be processed by following the ai_next pointer, until a
+ * NULL pointer is encountered. The three members ai_family, ai_socktype,
+ * and ai_protocol in each returned addrinfo structure contain the
+ * corresponding arguments for a call to socket(2). For each addrinfo
+ * structure in the list, the ai_addr member points to a filled-in socket
+ * address structure of length ai_addrlen.
+ *
+ * All of the information returned by getaddrinfo() is dynamically
+ * allocated: the addrinfo structures, and the socket address structures
+ * and canonical host name strings pointed to by the addrinfostructures.
+ * Memory allocated for the dynamically allocated structures created by a
+ * successful call to getaddrinfo() is released by freeaddrinfo().
+ * ai is a pointer to a struct addrinfo created by a call to getaddrinfo().
+ *
+ * \section irsreturn RETURN VALUES
+ *
+ * getaddrinfo() returns zero on success or one of the error codes
+ * listed in gai_strerror() if an error occurs. If both hostname and
+ * servname are NULL getaddrinfo() returns #EAI_NONAME.
+ *
+ * \section irssee SEE ALSO
+ *
+ * getaddrinfo(), freeaddrinfo(),
+ * gai_strerror(), RFC3493, getservbyname(3), connect(2),
+ * sendto(2), sendmsg(2), socket(2).
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif /* ifdef _WIN32 */
+
+#include <isc/app.h>
+#include <isc/buffer.h>
+#include <isc/lib.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/print.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/client.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/result.h>
+
+#include <irs/context.h>
+#include <irs/netdb.h>
+#include <irs/resconf.h>
+
+#define SA(addr) ((struct sockaddr *)(addr))
+#define SIN(addr) ((struct sockaddr_in *)(addr))
+#define SIN6(addr) ((struct sockaddr_in6 *)(addr))
+#define SLOCAL(addr) ((struct sockaddr_un *)(addr))
+
+/*! \struct addrinfo
+ */
+static struct addrinfo *
+ai_concat(struct addrinfo *ai1, struct addrinfo *ai2),
+ *ai_reverse(struct addrinfo *oai),
+ *ai_clone(struct addrinfo *oai, int family),
+ *ai_alloc(int family, int addrlen);
+#ifdef AF_LOCAL
+static int
+get_local(const char *name, int socktype, struct addrinfo **res);
+#endif /* ifdef AF_LOCAL */
+
+static int
+resolve_name(int family, const char *hostname, int flags, struct addrinfo **aip,
+ int socktype, int port);
+
+static int
+add_ipv4(const char *hostname, int flags, struct addrinfo **aip, int socktype,
+ int port);
+static int
+add_ipv6(const char *hostname, int flags, struct addrinfo **aip, int socktype,
+ int port);
+static void
+set_order(int, int (**)(const char *, int, struct addrinfo **, int, int));
+static void
+_freeaddrinfo(struct addrinfo *ai);
+
+#define FOUND_IPV4 0x1
+#define FOUND_IPV6 0x2
+#define FOUND_MAX 2
+
+/*%
+ * Try converting the scope identifier in 'src' to a network interface index.
+ * Upon success, return true and store the resulting index in 'dst'. Upon
+ * failure, return false.
+ */
+static bool
+parse_scopeid(const char *src, uint32_t *dst) {
+ uint32_t scopeid = 0;
+
+ REQUIRE(src != NULL);
+ REQUIRE(dst != NULL);
+
+#ifdef HAVE_IF_NAMETOINDEX
+ /*
+ * Try using if_nametoindex() first if it is available. As it does not
+ * handle numeric scopes, we do not simply return if it fails.
+ */
+ scopeid = (uint32_t)if_nametoindex(src);
+#endif /* ifdef HAVE_IF_NAMETOINDEX */
+
+ /*
+ * Fall back to numeric scope processing if if_nametoindex() either
+ * fails or is unavailable.
+ */
+ if (scopeid == 0) {
+ char *endptr = NULL;
+ scopeid = (uint32_t)strtoul(src, &endptr, 10);
+ /*
+ * The scope identifier must not be empty and no trailing
+ * characters are allowed after it.
+ */
+ if (src == endptr || endptr == NULL || *endptr != '\0') {
+ return (false);
+ }
+ }
+
+ *dst = scopeid;
+
+ return (true);
+}
+
+#define ISC_AI_MASK (AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST)
+/*%
+ * Get a list of IP addresses and port numbers for host hostname and
+ * service servname.
+ */
+int
+getaddrinfo(const char *hostname, const char *servname,
+ const struct addrinfo *hints, struct addrinfo **res) {
+ struct servent *sp;
+ const char *proto;
+ int family, socktype, flags, protocol;
+ struct addrinfo *ai, *ai_list;
+ int err = 0;
+ int port, i;
+ int (*net_order[FOUND_MAX + 1])(const char *, int, struct addrinfo **,
+ int, int);
+
+ if (hostname == NULL && servname == NULL) {
+ return (EAI_NONAME);
+ }
+
+ proto = NULL;
+ if (hints != NULL) {
+ if ((hints->ai_flags & ~(ISC_AI_MASK)) != 0) {
+ return (EAI_BADFLAGS);
+ }
+ if (hints->ai_addrlen || hints->ai_canonname ||
+ hints->ai_addr || hints->ai_next)
+ {
+ errno = EINVAL;
+ return (EAI_SYSTEM);
+ }
+ family = hints->ai_family;
+ socktype = hints->ai_socktype;
+ protocol = hints->ai_protocol;
+ flags = hints->ai_flags;
+ switch (family) {
+ case AF_UNSPEC:
+ switch (hints->ai_socktype) {
+ case SOCK_STREAM:
+ proto = "tcp";
+ break;
+ case SOCK_DGRAM:
+ proto = "udp";
+ break;
+ }
+ break;
+ case AF_INET:
+ case AF_INET6:
+ switch (hints->ai_socktype) {
+ case 0:
+ break;
+ case SOCK_STREAM:
+ proto = "tcp";
+ break;
+ case SOCK_DGRAM:
+ proto = "udp";
+ break;
+ case SOCK_RAW:
+ break;
+ default:
+ return (EAI_SOCKTYPE);
+ }
+ break;
+#ifdef AF_LOCAL
+ case AF_LOCAL:
+ switch (hints->ai_socktype) {
+ case 0:
+ break;
+ case SOCK_STREAM:
+ break;
+ case SOCK_DGRAM:
+ break;
+ default:
+ return (EAI_SOCKTYPE);
+ }
+ break;
+#endif /* ifdef AF_LOCAL */
+ default:
+ return (EAI_FAMILY);
+ }
+ } else {
+ protocol = 0;
+ family = 0;
+ socktype = 0;
+ flags = 0;
+ }
+
+#ifdef AF_LOCAL
+ /*!
+ * First, deal with AF_LOCAL. If the family was not set,
+ * then assume AF_LOCAL if the first character of the
+ * hostname/servname is '/'.
+ */
+
+ if (hostname != NULL &&
+ (family == AF_LOCAL || (family == 0 && *hostname == '/')))
+ {
+ return (get_local(hostname, socktype, res));
+ }
+
+ if (servname != NULL &&
+ (family == AF_LOCAL || (family == 0 && *servname == '/')))
+ {
+ return (get_local(servname, socktype, res));
+ }
+#endif /* ifdef AF_LOCAL */
+
+ /*
+ * Ok, only AF_INET and AF_INET6 left.
+ */
+ ai_list = NULL;
+
+ /*
+ * First, look up the service name (port) if it was
+ * requested. If the socket type wasn't specified, then
+ * try and figure it out.
+ */
+ if (servname != NULL) {
+ char *e;
+
+ port = strtol(servname, &e, 10);
+ if (*e == '\0') {
+ if (socktype == 0) {
+ return (EAI_SOCKTYPE);
+ }
+ if (port < 0 || port > 65535) {
+ return (EAI_SERVICE);
+ }
+ port = htons((unsigned short)port);
+ } else {
+#ifdef _WIN32
+ WORD wVersionRequested;
+ WSADATA wsaData;
+
+ wVersionRequested = MAKEWORD(2, 0);
+
+ err = WSAStartup(wVersionRequested, &wsaData);
+ if (err != 0) {
+ return (EAI_FAIL);
+ }
+#endif /* ifdef _WIN32 */
+ sp = getservbyname(servname, proto);
+ if (sp != NULL) {
+ port = sp->s_port;
+ }
+#ifdef _WIN32
+ WSACleanup();
+#endif /* ifdef _WIN32 */
+ if (sp == NULL) {
+ return (EAI_SERVICE);
+ }
+ if (socktype == 0) {
+ if (strcmp(sp->s_proto, "tcp") == 0) {
+ socktype = SOCK_STREAM;
+ } else if (strcmp(sp->s_proto, "udp") == 0) {
+ socktype = SOCK_DGRAM;
+ }
+ }
+ }
+ } else {
+ port = 0;
+ }
+
+ /*
+ * Next, deal with just a service name, and no hostname.
+ * (we verified that one of them was non-null up above).
+ */
+ if (hostname == NULL && (flags & AI_PASSIVE) != 0) {
+ if (family == AF_INET || family == 0) {
+ ai = ai_alloc(AF_INET, sizeof(struct sockaddr_in));
+ if (ai == NULL) {
+ return (EAI_MEMORY);
+ }
+ ai->ai_socktype = socktype;
+ ai->ai_protocol = protocol;
+ SIN(ai->ai_addr)->sin_port = port;
+ ai->ai_next = ai_list;
+ ai_list = ai;
+ }
+
+ if (family == AF_INET6 || family == 0) {
+ ai = ai_alloc(AF_INET6, sizeof(struct sockaddr_in6));
+ if (ai == NULL) {
+ _freeaddrinfo(ai_list);
+ return (EAI_MEMORY);
+ }
+ ai->ai_socktype = socktype;
+ ai->ai_protocol = protocol;
+ SIN6(ai->ai_addr)->sin6_port = port;
+ ai->ai_next = ai_list;
+ ai_list = ai;
+ }
+
+ *res = ai_list;
+ return (0);
+ }
+
+ /*
+ * If the family isn't specified or AI_NUMERICHOST specified, check
+ * first to see if it is a numeric address.
+ * Though the gethostbyname2() routine will recognize numeric addresses,
+ * it will only recognize the format that it is being called for. Thus,
+ * a numeric AF_INET address will be treated by the AF_INET6 call as
+ * a domain name, and vice versa. Checking for both numerics here
+ * avoids that.
+ */
+ if (hostname != NULL && (family == 0 || (flags & AI_NUMERICHOST) != 0))
+ {
+ char abuf[sizeof(struct in6_addr)];
+ char nbuf[NI_MAXHOST];
+ int addrsize, addroff;
+ char ntmp[NI_MAXHOST];
+ uint32_t scopeid = 0;
+
+ /*
+ * Scope identifier portion.
+ */
+ ntmp[0] = '\0';
+ if (strchr(hostname, '%') != NULL) {
+ char *p;
+ strlcpy(ntmp, hostname, sizeof(ntmp));
+ p = strchr(ntmp, '%');
+
+ if (p != NULL && parse_scopeid(p + 1, &scopeid)) {
+ *p = '\0';
+ } else {
+ ntmp[0] = '\0';
+ }
+ }
+
+ if (inet_pton(AF_INET, hostname, (struct in_addr *)abuf) == 1) {
+ if (family == AF_INET6) {
+ /*
+ * Convert to a V4 mapped address.
+ */
+ struct in6_addr *a6 = (struct in6_addr *)abuf;
+ memmove(&a6->s6_addr[12], &a6->s6_addr[0], 4);
+ memset(&a6->s6_addr[10], 0xff, 2);
+ memset(&a6->s6_addr[0], 0, 10);
+ goto inet6_addr;
+ }
+ addrsize = sizeof(struct in_addr);
+ addroff = offsetof(struct sockaddr_in, sin_addr);
+ family = AF_INET;
+ goto common;
+ } else if (ntmp[0] != '\0' &&
+ inet_pton(AF_INET6, ntmp, abuf) == 1)
+ {
+ if (family && family != AF_INET6) {
+ return (EAI_NONAME);
+ }
+ addrsize = sizeof(struct in6_addr);
+ addroff = offsetof(struct sockaddr_in6, sin6_addr);
+ family = AF_INET6;
+ goto common;
+ } else if (inet_pton(AF_INET6, hostname, abuf) == 1) {
+ if (family != 0 && family != AF_INET6) {
+ return (EAI_NONAME);
+ }
+ inet6_addr:
+ addrsize = sizeof(struct in6_addr);
+ addroff = offsetof(struct sockaddr_in6, sin6_addr);
+ family = AF_INET6;
+
+ common:
+ ai = ai_alloc(family,
+ ((family == AF_INET6)
+ ? sizeof(struct sockaddr_in6)
+ : sizeof(struct sockaddr_in)));
+ if (ai == NULL) {
+ return (EAI_MEMORY);
+ }
+ ai_list = ai;
+ ai->ai_socktype = socktype;
+ SIN(ai->ai_addr)->sin_port = port;
+ memmove((char *)ai->ai_addr + addroff, abuf, addrsize);
+ if (ai->ai_family == AF_INET6) {
+ SIN6(ai->ai_addr)->sin6_scope_id = scopeid;
+ }
+ if ((flags & AI_CANONNAME) != 0) {
+ if (getnameinfo(ai->ai_addr,
+ (socklen_t)ai->ai_addrlen, nbuf,
+ sizeof(nbuf), NULL, 0,
+ NI_NUMERICHOST) == 0)
+ {
+ ai->ai_canonname = strdup(nbuf);
+ if (ai->ai_canonname == NULL) {
+ _freeaddrinfo(ai);
+ return (EAI_MEMORY);
+ }
+ } else {
+ /* XXX raise error? */
+ ai->ai_canonname = NULL;
+ }
+ }
+ goto done;
+ } else if ((flags & AI_NUMERICHOST) != 0) {
+ return (EAI_NONAME);
+ }
+ }
+
+ if (hostname == NULL && (flags & AI_PASSIVE) == 0) {
+ set_order(family, net_order);
+ for (i = 0; i < FOUND_MAX; i++) {
+ if (net_order[i] == NULL) {
+ break;
+ }
+ err = (net_order[i])(hostname, flags, &ai_list,
+ socktype, port);
+ if (err != 0) {
+ if (ai_list != NULL) {
+ _freeaddrinfo(ai_list);
+ ai_list = NULL;
+ }
+ break;
+ }
+ }
+ } else {
+ err = resolve_name(family, hostname, flags, &ai_list, socktype,
+ port);
+ }
+
+ if (ai_list == NULL) {
+ if (err == 0) {
+ err = EAI_NONAME;
+ }
+ return (err);
+ }
+
+done:
+ ai_list = ai_reverse(ai_list);
+
+ *res = ai_list;
+ return (0);
+}
+
+typedef struct gai_restrans {
+ dns_clientrestrans_t *xid;
+ bool is_inprogress;
+ int error;
+ struct addrinfo ai_sentinel;
+ struct gai_resstate *resstate;
+} gai_restrans_t;
+
+typedef struct gai_resstate {
+ isc_mem_t *mctx;
+ struct gai_statehead *head;
+ dns_fixedname_t fixedname;
+ dns_name_t *qname;
+ gai_restrans_t *trans4;
+ gai_restrans_t *trans6;
+ ISC_LINK(struct gai_resstate) link;
+} gai_resstate_t;
+
+typedef struct gai_statehead {
+ int ai_family;
+ int ai_flags;
+ int ai_socktype;
+ int ai_port;
+ isc_appctx_t *actx;
+ dns_client_t *dnsclient;
+ isc_mutex_t list_lock;
+ ISC_LIST(struct gai_resstate) resstates;
+ unsigned int activestates;
+} gai_statehead_t;
+
+static isc_result_t
+make_resstate(isc_mem_t *mctx, gai_statehead_t *head, const char *hostname,
+ const char *domain, gai_resstate_t **statep) {
+ isc_result_t result;
+ gai_resstate_t *state;
+ dns_fixedname_t fixeddomain;
+ dns_name_t *qdomain;
+ unsigned int namelen;
+ isc_buffer_t b;
+ bool need_v4 = false;
+ bool need_v6 = false;
+
+ state = isc_mem_get(mctx, sizeof(*state));
+
+ /* Construct base domain name */
+ namelen = strlen(domain);
+ isc_buffer_constinit(&b, domain, namelen);
+ isc_buffer_add(&b, namelen);
+ qdomain = dns_fixedname_initname(&fixeddomain);
+ result = dns_name_fromtext(qdomain, &b, dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(mctx, state, sizeof(*state));
+ return (result);
+ }
+
+ /* Construct query name */
+ namelen = strlen(hostname);
+ isc_buffer_constinit(&b, hostname, namelen);
+ isc_buffer_add(&b, namelen);
+ state->qname = dns_fixedname_initname(&state->fixedname);
+ result = dns_name_fromtext(state->qname, &b, qdomain, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(mctx, state, sizeof(*state));
+ return (result);
+ }
+
+ if (head->ai_family == AF_UNSPEC || head->ai_family == AF_INET) {
+ need_v4 = true;
+ }
+ if (head->ai_family == AF_UNSPEC || head->ai_family == AF_INET6) {
+ need_v6 = true;
+ }
+
+ state->trans6 = NULL;
+ state->trans4 = NULL;
+ if (need_v4) {
+ state->trans4 = isc_mem_get(mctx, sizeof(gai_restrans_t));
+ state->trans4->error = 0;
+ state->trans4->xid = NULL;
+ state->trans4->resstate = state;
+ state->trans4->is_inprogress = true;
+ state->trans4->ai_sentinel.ai_next = NULL;
+ }
+ if (need_v6) {
+ state->trans6 = isc_mem_get(mctx, sizeof(gai_restrans_t));
+ state->trans6->error = 0;
+ state->trans6->xid = NULL;
+ state->trans6->resstate = state;
+ state->trans6->is_inprogress = true;
+ state->trans6->ai_sentinel.ai_next = NULL;
+ }
+
+ state->mctx = mctx;
+ state->head = head;
+ ISC_LINK_INIT(state, link);
+
+ *statep = state;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+make_resstates(isc_mem_t *mctx, const char *hostname, gai_statehead_t *head,
+ irs_resconf_t *resconf) {
+ isc_result_t result;
+ irs_resconf_searchlist_t *searchlist;
+ irs_resconf_search_t *searchent;
+ gai_resstate_t *resstate, *resstate0;
+
+ resstate0 = NULL;
+ result = make_resstate(mctx, head, hostname, ".", &resstate0);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ searchlist = irs_resconf_getsearchlist(resconf);
+ for (searchent = ISC_LIST_HEAD(*searchlist); searchent != NULL;
+ searchent = ISC_LIST_NEXT(searchent, link))
+ {
+ resstate = NULL;
+ result = make_resstate(mctx, head, hostname,
+ (const char *)searchent->domain,
+ &resstate);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ ISC_LIST_APPEND(head->resstates, resstate, link);
+ head->activestates++;
+ }
+
+ /*
+ * Insert the original hostname either at the head or the tail of the
+ * state list, depending on the number of labels contained in the
+ * original name and the 'ndots' configuration parameter.
+ */
+ if (dns_name_countlabels(resstate0->qname) >
+ irs_resconf_getndots(resconf) + 1)
+ {
+ ISC_LIST_PREPEND(head->resstates, resstate0, link);
+ } else {
+ ISC_LIST_APPEND(head->resstates, resstate0, link);
+ }
+ head->activestates++;
+
+ if (result != ISC_R_SUCCESS) {
+ while ((resstate = ISC_LIST_HEAD(head->resstates)) != NULL) {
+ ISC_LIST_UNLINK(head->resstates, resstate, link);
+ if (resstate->trans4 != NULL) {
+ isc_mem_put(mctx, resstate->trans4,
+ sizeof(*resstate->trans4));
+ }
+ if (resstate->trans6 != NULL) {
+ isc_mem_put(mctx, resstate->trans6,
+ sizeof(*resstate->trans6));
+ }
+
+ isc_mem_put(mctx, resstate, sizeof(*resstate));
+ }
+ }
+
+ return (result);
+}
+
+static void
+process_answer(isc_task_t *task, isc_event_t *event) {
+ int error = 0, family;
+ gai_restrans_t *trans = event->ev_arg;
+ gai_resstate_t *resstate;
+ dns_clientresevent_t *rev = (dns_clientresevent_t *)event;
+ dns_rdatatype_t qtype;
+ dns_name_t *name;
+ bool wantcname;
+
+ REQUIRE(trans != NULL);
+ resstate = trans->resstate;
+ REQUIRE(resstate != NULL);
+ REQUIRE(task != NULL);
+
+ if (trans == resstate->trans4) {
+ family = AF_INET;
+ qtype = dns_rdatatype_a;
+ } else {
+ INSIST(trans == resstate->trans6);
+ family = AF_INET6;
+ qtype = dns_rdatatype_aaaa;
+ }
+
+ INSIST(trans->is_inprogress);
+ trans->is_inprogress = false;
+
+ switch (rev->result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_NCACHENXDOMAIN: /* treat this as a fatal error? */
+ case DNS_R_NCACHENXRRSET:
+ break;
+ default:
+ switch (rev->vresult) {
+ case DNS_R_SIGINVALID:
+ case DNS_R_SIGEXPIRED:
+ case DNS_R_SIGFUTURE:
+ case DNS_R_KEYUNAUTHORIZED:
+ case DNS_R_MUSTBESECURE:
+ case DNS_R_COVERINGNSEC:
+ case DNS_R_NOTAUTHORITATIVE:
+ case DNS_R_NOVALIDKEY:
+ case DNS_R_NOVALIDDS:
+ case DNS_R_NOVALIDSIG:
+ error = EAI_INSECUREDATA;
+ break;
+ default:
+ error = EAI_FAIL;
+ }
+ goto done;
+ }
+
+ wantcname = ((resstate->head->ai_flags & AI_CANONNAME) != 0);
+
+ /* Parse the response and construct the addrinfo chain */
+ for (name = ISC_LIST_HEAD(rev->answerlist); name != NULL;
+ name = ISC_LIST_NEXT(name, link))
+ {
+ isc_result_t result;
+ dns_rdataset_t *rdataset;
+ char cname[1024];
+
+ if (wantcname) {
+ isc_buffer_t b;
+
+ isc_buffer_init(&b, cname, sizeof(cname));
+ result = dns_name_totext(name, true, &b);
+ if (result != ISC_R_SUCCESS) {
+ error = EAI_FAIL;
+ goto done;
+ }
+ isc_buffer_putuint8(&b, '\0');
+ }
+
+ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (!dns_rdataset_isassociated(rdataset)) {
+ continue;
+ }
+ if (rdataset->type != qtype) {
+ continue;
+ }
+
+ for (result = dns_rdataset_first(rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ struct addrinfo *ai;
+ dns_rdata_t rdata;
+ dns_rdata_in_a_t rdata_a;
+ dns_rdata_in_aaaa_t rdata_aaaa;
+
+ ai = ai_alloc(
+ family,
+ ((family == AF_INET6)
+ ? sizeof(struct sockaddr_in6)
+ : sizeof(struct sockaddr_in)));
+ if (ai == NULL) {
+ error = EAI_MEMORY;
+ goto done;
+ }
+ ai->ai_socktype = resstate->head->ai_socktype;
+ ai->ai_next = trans->ai_sentinel.ai_next;
+ trans->ai_sentinel.ai_next = ai;
+
+ /*
+ * Set AF-specific parameters
+ * (IPv4/v6 address/port)
+ */
+ dns_rdata_init(&rdata);
+ switch (family) {
+ case AF_INET:
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(
+ &rdata, &rdata_a, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ SIN(ai->ai_addr)->sin_port =
+ resstate->head->ai_port;
+ memmove(&SIN(ai->ai_addr)->sin_addr,
+ &rdata_a.in_addr, 4);
+ dns_rdata_freestruct(&rdata_a);
+ break;
+ case AF_INET6:
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(
+ &rdata, &rdata_aaaa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ SIN6(ai->ai_addr)->sin6_port =
+ resstate->head->ai_port;
+ memmove(&SIN6(ai->ai_addr)->sin6_addr,
+ &rdata_aaaa.in6_addr, 16);
+ dns_rdata_freestruct(&rdata_aaaa);
+ break;
+ }
+
+ if (wantcname) {
+ ai->ai_canonname = strdup(cname);
+ if (ai->ai_canonname == NULL) {
+ error = EAI_MEMORY;
+ goto done;
+ }
+ }
+ }
+ }
+ }
+
+done:
+ dns_client_freeresanswer(resstate->head->dnsclient, &rev->answerlist);
+ dns_client_destroyrestrans(&trans->xid);
+
+ isc_event_free(&event);
+
+ /* Make sure that error == 0 iff we have a non-empty list */
+ if (error == 0) {
+ if (trans->ai_sentinel.ai_next == NULL) {
+ error = EAI_NONAME;
+ }
+ } else {
+ if (trans->ai_sentinel.ai_next != NULL) {
+ _freeaddrinfo(trans->ai_sentinel.ai_next);
+ trans->ai_sentinel.ai_next = NULL;
+ }
+ }
+ trans->error = error;
+
+ /* Check whether we are done */
+ if ((resstate->trans4 == NULL || !resstate->trans4->is_inprogress) &&
+ (resstate->trans6 == NULL || !resstate->trans6->is_inprogress))
+ {
+ /*
+ * We're done for this state. If there is no other outstanding
+ * state, we can exit.
+ */
+ resstate->head->activestates--;
+ if (resstate->head->activestates == 0) {
+ isc_app_ctxsuspend(resstate->head->actx);
+ return;
+ }
+
+ /*
+ * There are outstanding states, but if we are at the head
+ * of the state list (i.e., at the highest search priority)
+ * and have any answer, we can stop now by canceling the
+ * others.
+ */
+ LOCK(&resstate->head->list_lock);
+ if (resstate == ISC_LIST_HEAD(resstate->head->resstates)) {
+ if ((resstate->trans4 != NULL &&
+ resstate->trans4->ai_sentinel.ai_next != NULL) ||
+ (resstate->trans6 != NULL &&
+ resstate->trans6->ai_sentinel.ai_next != NULL))
+ {
+ gai_resstate_t *rest;
+
+ for (rest = ISC_LIST_NEXT(resstate, link);
+ rest != NULL;
+ rest = ISC_LIST_NEXT(rest, link))
+ {
+ if (rest->trans4 != NULL &&
+ rest->trans4->xid != NULL)
+ {
+ dns_client_cancelresolve(
+ rest->trans4->xid);
+ }
+ if (rest->trans6 != NULL &&
+ rest->trans6->xid != NULL)
+ {
+ dns_client_cancelresolve(
+ rest->trans6->xid);
+ }
+ }
+ } else {
+ /*
+ * This search fails, so we move to the tail
+ * of the list so that the next entry will
+ * have the highest priority.
+ */
+ ISC_LIST_UNLINK(resstate->head->resstates,
+ resstate, link);
+ ISC_LIST_APPEND(resstate->head->resstates,
+ resstate, link);
+ }
+ }
+ UNLOCK(&resstate->head->list_lock);
+ }
+}
+
+static int
+resolve_name(int family, const char *hostname, int flags, struct addrinfo **aip,
+ int socktype, int port) {
+ isc_result_t result;
+ irs_context_t *irsctx;
+ irs_resconf_t *conf;
+ isc_mem_t *mctx;
+ isc_appctx_t *actx;
+ isc_task_t *task;
+ int terror = 0;
+ int error = 0;
+ dns_client_t *client;
+ gai_resstate_t *resstate;
+ gai_statehead_t head;
+ bool all_fail = true;
+
+ /* get IRS context and the associated parameters */
+ irsctx = NULL;
+ result = irs_context_get(&irsctx);
+ if (result != ISC_R_SUCCESS) {
+ return (EAI_FAIL);
+ }
+ actx = irs_context_getappctx(irsctx);
+
+ mctx = irs_context_getmctx(irsctx);
+ task = irs_context_gettask(irsctx);
+ conf = irs_context_getresconf(irsctx);
+ client = irs_context_getdnsclient(irsctx);
+
+ /* construct resolution states */
+ head.activestates = 0;
+ head.ai_family = family;
+ head.ai_socktype = socktype;
+ head.ai_flags = flags;
+ head.ai_port = port;
+ head.actx = actx;
+ head.dnsclient = client;
+ isc_mutex_init(&head.list_lock);
+
+ ISC_LIST_INIT(head.resstates);
+ result = make_resstates(mctx, hostname, &head, conf);
+ if (result != ISC_R_SUCCESS) {
+ isc_mutex_destroy(&head.list_lock);
+ return (EAI_FAIL);
+ }
+
+ LOCK(&head.list_lock);
+ for (resstate = ISC_LIST_HEAD(head.resstates); resstate != NULL;
+ resstate = ISC_LIST_NEXT(resstate, link))
+ {
+ if (resstate->trans4 != NULL) {
+ result = dns_client_startresolve(
+ client, resstate->qname, dns_rdataclass_in,
+ dns_rdatatype_a, 0, task, process_answer,
+ resstate->trans4, &resstate->trans4->xid);
+ if (result == ISC_R_SUCCESS) {
+ resstate->trans4->is_inprogress = true;
+ all_fail = false;
+ } else {
+ resstate->trans4->is_inprogress = false;
+ }
+ }
+ if (resstate->trans6 != NULL) {
+ result = dns_client_startresolve(
+ client, resstate->qname, dns_rdataclass_in,
+ dns_rdatatype_aaaa, 0, task, process_answer,
+ resstate->trans6, &resstate->trans6->xid);
+ if (result == ISC_R_SUCCESS) {
+ resstate->trans6->is_inprogress = true;
+ all_fail = false;
+ } else {
+ resstate->trans6->is_inprogress = false;
+ }
+ }
+ }
+ UNLOCK(&head.list_lock);
+
+ if (!all_fail) {
+ /* Start all the events */
+ isc_app_ctxrun(actx);
+ } else {
+ error = EAI_FAIL;
+ }
+
+ /* Cleanup */
+ while ((resstate = ISC_LIST_HEAD(head.resstates)) != NULL) {
+ int terror4 = 0, terror6 = 0;
+
+ ISC_LIST_UNLINK(head.resstates, resstate, link);
+
+ if (*aip == NULL) {
+ struct addrinfo *sentinel4 = NULL;
+ struct addrinfo *sentinel6 = NULL;
+
+ if (resstate->trans4 != NULL) {
+ sentinel4 =
+ resstate->trans4->ai_sentinel.ai_next;
+ resstate->trans4->ai_sentinel.ai_next = NULL;
+ }
+ if (resstate->trans6 != NULL) {
+ sentinel6 =
+ resstate->trans6->ai_sentinel.ai_next;
+ resstate->trans6->ai_sentinel.ai_next = NULL;
+ }
+ *aip = ai_concat(sentinel4, sentinel6);
+ }
+
+ if (resstate->trans4 != NULL) {
+ INSIST(resstate->trans4->xid == NULL);
+ terror4 = resstate->trans4->error;
+ isc_mem_put(mctx, resstate->trans4,
+ sizeof(*resstate->trans4));
+ }
+ if (resstate->trans6 != NULL) {
+ INSIST(resstate->trans6->xid == NULL);
+ terror6 = resstate->trans6->error;
+ isc_mem_put(mctx, resstate->trans6,
+ sizeof(*resstate->trans6));
+ }
+
+ /*
+ * If the entire lookup fails, we need to choose an appropriate
+ * error code from individual codes. We'll try to provide as
+ * specific a code as possible. In general, we are going to
+ * find an error code other than EAI_NONAME (which is too
+ * generic and may actually not be problematic in some cases).
+ * EAI_NONAME will be set below if no better code is found.
+ */
+ if (terror == 0 || terror == EAI_NONAME) {
+ if (terror4 != 0 && terror4 != EAI_NONAME) {
+ terror = terror4;
+ } else if (terror6 != 0 && terror6 != EAI_NONAME) {
+ terror = terror6;
+ }
+ }
+
+ isc_mem_put(mctx, resstate, sizeof(*resstate));
+ }
+
+ if (*aip == NULL) {
+ error = terror;
+ if (error == 0) {
+ error = EAI_NONAME;
+ }
+ }
+
+#if 1 /* XXX: enabled for finding leaks. should be cleaned up later. */
+ isc_app_ctxfinish(actx);
+ irs_context_destroy(&irsctx);
+#endif /* if 1 */
+
+ isc_mutex_destroy(&head.list_lock);
+ return (error);
+}
+
+static void
+set_order(int family,
+ int (**net_order)(const char *, int, struct addrinfo **, int, int)) {
+ char *order, *tok, *last;
+ int found;
+
+ if (family) {
+ switch (family) {
+ case AF_INET:
+ *net_order++ = add_ipv4;
+ break;
+ case AF_INET6:
+ *net_order++ = add_ipv6;
+ break;
+ }
+ } else {
+ order = getenv("NET_ORDER");
+ found = 0;
+ if (order != NULL) {
+ last = NULL;
+ for (tok = strtok_r(order, ":", &last); tok;
+ tok = strtok_r(NULL, ":", &last))
+ {
+ if (strcasecmp(tok, "inet6") == 0) {
+ if ((found & FOUND_IPV6) == 0) {
+ *net_order++ = add_ipv6;
+ }
+ found |= FOUND_IPV6;
+ } else if (strcasecmp(tok, "inet") == 0 ||
+ strcasecmp(tok, "inet4") == 0)
+ {
+ if ((found & FOUND_IPV4) == 0) {
+ *net_order++ = add_ipv4;
+ }
+ found |= FOUND_IPV4;
+ }
+ }
+ }
+
+ /*
+ * Add in anything that we didn't find.
+ */
+ if ((found & FOUND_IPV4) == 0) {
+ *net_order++ = add_ipv4;
+ }
+ if ((found & FOUND_IPV6) == 0) {
+ *net_order++ = add_ipv6;
+ }
+ }
+ *net_order = NULL;
+ return;
+}
+
+static char v4_loop[4] = { 127, 0, 0, 1 };
+
+static int
+add_ipv4(const char *hostname, int flags, struct addrinfo **aip, int socktype,
+ int port) {
+ struct addrinfo *ai;
+
+ UNUSED(hostname);
+ UNUSED(flags);
+
+ ai = ai_clone(*aip, AF_INET); /* don't use ai_clone() */
+ if (ai == NULL) {
+ return (EAI_MEMORY);
+ }
+
+ *aip = ai;
+ ai->ai_socktype = socktype;
+ SIN(ai->ai_addr)->sin_port = port;
+ memmove(&SIN(ai->ai_addr)->sin_addr, v4_loop, 4);
+
+ return (0);
+}
+
+static char v6_loop[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+
+static int
+add_ipv6(const char *hostname, int flags, struct addrinfo **aip, int socktype,
+ int port) {
+ struct addrinfo *ai;
+
+ UNUSED(hostname);
+ UNUSED(flags);
+
+ ai = ai_clone(*aip, AF_INET6); /* don't use ai_clone() */
+ if (ai == NULL) {
+ return (EAI_MEMORY);
+ }
+
+ *aip = ai;
+ ai->ai_socktype = socktype;
+ SIN6(ai->ai_addr)->sin6_port = port;
+ memmove(&SIN6(ai->ai_addr)->sin6_addr, v6_loop, 16);
+
+ return (0);
+}
+
+/*% Free address info. */
+void
+freeaddrinfo(struct addrinfo *ai) {
+ _freeaddrinfo(ai);
+}
+
+static void
+_freeaddrinfo(struct addrinfo *ai) {
+ struct addrinfo *ai_next;
+
+ while (ai != NULL) {
+ ai_next = ai->ai_next;
+ if (ai->ai_addr != NULL) {
+ free(ai->ai_addr);
+ }
+ if (ai->ai_canonname) {
+ free(ai->ai_canonname);
+ }
+ free(ai);
+ ai = ai_next;
+ }
+}
+
+#ifdef AF_LOCAL
+static int
+get_local(const char *name, int socktype, struct addrinfo **res) {
+ struct addrinfo *ai;
+ struct sockaddr_un *slocal;
+
+ if (socktype == 0) {
+ return (EAI_SOCKTYPE);
+ }
+
+ ai = ai_alloc(AF_LOCAL, sizeof(*slocal));
+ if (ai == NULL) {
+ return (EAI_MEMORY);
+ }
+
+ slocal = SLOCAL(ai->ai_addr);
+ strlcpy(slocal->sun_path, name, sizeof(slocal->sun_path));
+
+ ai->ai_socktype = socktype;
+ /*
+ * ai->ai_flags, ai->ai_protocol, ai->ai_canonname,
+ * and ai->ai_next were initialized to zero.
+ */
+
+ *res = ai;
+ return (0);
+}
+#endif /* ifdef AF_LOCAL */
+
+/*!
+ * Allocate an addrinfo structure, and a sockaddr structure
+ * of the specified length. We initialize:
+ * ai_addrlen
+ * ai_family
+ * ai_addr
+ * ai_addr->sa_family
+ * ai_addr->sa_len (IRS_PLATFORM_HAVESALEN)
+ * and everything else is initialized to zero.
+ */
+static struct addrinfo *
+ai_alloc(int family, int addrlen) {
+ struct addrinfo *ai;
+
+ ai = (struct addrinfo *)calloc(1, sizeof(*ai));
+ if (ai == NULL) {
+ return (NULL);
+ }
+
+ ai->ai_addr = SA(calloc(1, addrlen));
+ if (ai->ai_addr == NULL) {
+ free(ai);
+ return (NULL);
+ }
+ ai->ai_addrlen = addrlen;
+ ai->ai_family = family;
+ ai->ai_addr->sa_family = family;
+#ifdef IRS_PLATFORM_HAVESALEN
+ ai->ai_addr->sa_len = addrlen;
+#endif /* ifdef IRS_PLATFORM_HAVESALEN */
+ return (ai);
+}
+
+static struct addrinfo *
+ai_clone(struct addrinfo *oai, int family) {
+ struct addrinfo *ai;
+
+ ai = ai_alloc(family,
+ ((family == AF_INET6) ? sizeof(struct sockaddr_in6)
+ : sizeof(struct sockaddr_in)));
+
+ if (ai == NULL) {
+ return (NULL);
+ }
+ if (oai == NULL) {
+ return (ai);
+ }
+
+ ai->ai_flags = oai->ai_flags;
+ ai->ai_socktype = oai->ai_socktype;
+ ai->ai_protocol = oai->ai_protocol;
+ ai->ai_canonname = NULL;
+ ai->ai_next = oai;
+ return (ai);
+}
+
+static struct addrinfo *
+ai_reverse(struct addrinfo *oai) {
+ struct addrinfo *nai, *tai;
+
+ nai = NULL;
+
+ while (oai != NULL) {
+ /*
+ * Grab one off the old list.
+ */
+ tai = oai;
+ oai = oai->ai_next;
+ /*
+ * Put it on the front of the new list.
+ */
+ tai->ai_next = nai;
+ nai = tai;
+ }
+ return (nai);
+}
+
+static struct addrinfo *
+ai_concat(struct addrinfo *ai1, struct addrinfo *ai2) {
+ struct addrinfo *ai_tmp;
+
+ if (ai1 == NULL) {
+ return (ai2);
+ } else if (ai2 == NULL) {
+ return (ai1);
+ }
+
+ for (ai_tmp = ai1; ai_tmp != NULL && ai_tmp->ai_next != NULL;
+ ai_tmp = ai_tmp->ai_next)
+ {
+ }
+
+ ai_tmp->ai_next = ai2;
+
+ return (ai1);
+}
diff --git a/lib/irs/getnameinfo.c b/lib/irs/getnameinfo.c
new file mode 100644
index 0000000..62c7cea
--- /dev/null
+++ b/lib/irs/getnameinfo.c
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * 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 project 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 PROJECT 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 PROJECT 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.
+ */
+
+/**
+ * getnameinfo() returns the hostname for the struct sockaddr sa which is
+ * salen bytes long. The hostname is of length hostlen and is returned via
+ * *host. The maximum length of the hostname is 1025 bytes: #NI_MAXHOST.
+ *
+ * The name of the service associated with the port number in sa is
+ * returned in *serv. It is servlen bytes long. The maximum length of the
+ * service name is #NI_MAXSERV - 32 bytes.
+ *
+ * The flags argument sets the following bits:
+ *
+ * \li #NI_NOFQDN:
+ * A fully qualified domain name is not required for local hosts.
+ * The local part of the fully qualified domain name is returned
+ * instead.
+ *
+ * \li #NI_NUMERICHOST
+ * Return the address in numeric form, as if calling inet_ntop(),
+ * instead of a host name.
+ *
+ * \li #NI_NAMEREQD
+ * A name is required. If the hostname cannot be found in the DNS
+ * and this flag is set, a non-zero error code is returned. If the
+ * hostname is not found and the flag is not set, the address is
+ * returned in numeric form.
+ *
+ * \li #NI_NUMERICSERV
+ * The service name is returned as a digit string representing the
+ * port number.
+ *
+ * \li #NI_DGRAM
+ * Specifies that the service being looked up is a datagram
+ * service, and causes getservbyport() to be called with a second
+ * argument of "udp" instead of its default of "tcp". This is
+ * required for the few ports (512-514) that have different
+ * services for UDP and TCP.
+ *
+ * \section getnameinfo_return Return Values
+ *
+ * getnameinfo() returns 0 on success or a non-zero error code if
+ * an error occurs.
+ *
+ * \section getname_see See Also
+ *
+ * RFC3493, getservbyport(),
+ * getnamebyaddr(). inet_ntop().
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/byaddr.h>
+#include <dns/client.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/result.h>
+
+#include <irs/context.h>
+#include <irs/netdb.h>
+
+#define SUCCESS 0
+
+/*% afd structure definition */
+static struct afd {
+ int a_af;
+ size_t a_addrlen;
+ size_t a_socklen;
+} afdl[] = {
+ /*!
+ * First entry is linked last...
+ */
+ { AF_INET, sizeof(struct in_addr), sizeof(struct sockaddr_in) },
+ { AF_INET6, sizeof(struct in6_addr), sizeof(struct sockaddr_in6) },
+ { 0, 0, 0 },
+};
+
+/*!
+ * The test against 0 is there to keep the Solaris compiler
+ * from complaining about "end-of-loop code not reached".
+ */
+#define ERR(code) \
+ do { \
+ result = (code); \
+ if (result != 0) \
+ goto cleanup; \
+ } while (0)
+
+#ifdef _WIN32
+int
+getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host,
+ DWORD hostlen, char *serv, DWORD servlen, int flags) {
+#else
+int
+getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host,
+ socklen_t hostlen, char *serv, socklen_t servlen, int flags) {
+#endif
+ struct afd *afd = NULL;
+ struct servent *sp;
+ unsigned short port = 0;
+#ifdef IRS_PLATFORM_HAVESALEN
+ size_t len;
+#endif /* ifdef IRS_PLATFORM_HAVESALEN */
+ int family, i;
+ const void *addr = NULL;
+ char *p;
+#if 0
+ unsigned long v4a;
+ unsigned char pfx;
+#endif /* if 0 */
+ char numserv[sizeof("65000")];
+ char numaddr[sizeof("abcd:abcd:abcd:abcd:abcd:abcd:255.255.255.255") +
+ 1 + sizeof("4294967295")];
+ const char *proto;
+ int result = SUCCESS;
+
+ if (sa == NULL) {
+ ERR(EAI_FAIL);
+ }
+
+#ifdef IRS_PLATFORM_HAVESALEN
+ len = sa->sa_len;
+ if (len != salen) {
+ ERR(EAI_FAIL);
+ }
+#endif /* ifdef IRS_PLATFORM_HAVESALEN */
+
+ family = sa->sa_family;
+ for (i = 0; afdl[i].a_af; i++) {
+ if (afdl[i].a_af == family) {
+ {
+ afd = &afdl[i];
+ goto found;
+ }
+ }
+ }
+ ERR(EAI_FAMILY);
+
+found:
+ if (salen != afd->a_socklen) {
+ ERR(EAI_FAIL);
+ }
+
+ switch (family) {
+ case AF_INET:
+ port = ((const struct sockaddr_in *)sa)->sin_port;
+ addr = &((const struct sockaddr_in *)sa)->sin_addr.s_addr;
+ break;
+
+ case AF_INET6:
+ port = ((const struct sockaddr_in6 *)sa)->sin6_port;
+ addr = ((const struct sockaddr_in6 *)sa)->sin6_addr.s6_addr;
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+ proto = ((flags & NI_DGRAM) != 0) ? "udp" : "tcp";
+
+ if (serv == NULL || servlen == 0U) {
+ /*
+ * Caller does not want service.
+ */
+ } else if ((flags & NI_NUMERICSERV) != 0 ||
+ (sp = getservbyport(port, proto)) == NULL)
+ {
+ snprintf(numserv, sizeof(numserv), "%d", ntohs(port));
+ if ((strlen(numserv) + 1) > servlen) {
+ ERR(EAI_OVERFLOW);
+ }
+ strlcpy(serv, numserv, servlen);
+ } else {
+ if ((strlen(sp->s_name) + 1) > servlen) {
+ ERR(EAI_OVERFLOW);
+ }
+ strlcpy(serv, sp->s_name, servlen);
+ }
+
+#if 0
+ switch (sa->sa_family) {
+ case AF_INET:
+ v4a = ((struct sockaddr_in *)sa)->sin_addr.s_addr;
+ if (IN_MULTICAST(v4a) || IN_EXPERIMENTAL(v4a)) {
+ flags |= NI_NUMERICHOST;
+ }
+ v4a >>= IN_CLASSA_NSHIFT;
+ if (v4a == 0 || v4a == IN_LOOPBACKNET) {
+ flags |= NI_NUMERICHOST;
+ }
+ break;
+
+ case AF_INET6:
+ pfx = ((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr[0];
+ if (pfx == 0 || pfx == 0xfe || pfx == 0xff) {
+ flags |= NI_NUMERICHOST;
+ }
+ break;
+ }
+#endif /* if 0 */
+
+ if (host == NULL || hostlen == 0U) {
+ /*
+ * do nothing in this case.
+ * in case you are wondering if "&&" is more correct than
+ * "||" here: RFC3493 says that host == NULL or hostlen == 0
+ * means that the caller does not want the result.
+ */
+ } else if ((flags & NI_NUMERICHOST) != 0) {
+ if (inet_ntop(afd->a_af, addr, numaddr, sizeof(numaddr)) ==
+ NULL)
+ {
+ ERR(EAI_SYSTEM);
+ }
+#if defined(IRS_HAVE_SIN6_SCOPE_ID)
+ if (afd->a_af == AF_INET6 &&
+ ((const struct sockaddr_in6 *)sa)->sin6_scope_id)
+ {
+ char *p = numaddr + strlen(numaddr);
+ const char *stringscope = NULL;
+#ifdef VENDOR_SPECIFIC
+ /*
+ * Vendors may want to add support for
+ * non-numeric scope identifier.
+ */
+ stringscope = foo;
+#endif /* ifdef VENDOR_SPECIFIC */
+ if (stringscope == NULL) {
+ snprintf(p, sizeof(numaddr) - (p - numaddr),
+ "%%%u",
+ ((const struct sockaddr_in6 *)sa)
+ ->sin6_scope_id);
+ } else {
+ snprintf(p, sizeof(numaddr) - (p - numaddr),
+ "%%%s", stringscope);
+ }
+ }
+#endif /* if defined(IRS_HAVE_SIN6_SCOPE_ID) */
+ if (strlen(numaddr) + 1 > hostlen) {
+ ERR(EAI_OVERFLOW);
+ }
+ strlcpy(host, numaddr, hostlen);
+ } else {
+ isc_netaddr_t netaddr;
+ dns_fixedname_t ptrfname;
+ dns_name_t *ptrname;
+ irs_context_t *irsctx = NULL;
+ dns_client_t *client;
+ bool found = false;
+ dns_namelist_t answerlist;
+ dns_rdataset_t *rdataset;
+ isc_region_t hostregion;
+ char hoststr[1024]; /* is this enough? */
+ isc_result_t iresult;
+
+ /* Get IRS context and the associated DNS client object */
+ iresult = irs_context_get(&irsctx);
+ if (iresult != ISC_R_SUCCESS) {
+ ERR(EAI_FAIL);
+ }
+ client = irs_context_getdnsclient(irsctx);
+
+ /* Make query name */
+ isc_netaddr_fromsockaddr(&netaddr, (const isc_sockaddr_t *)sa);
+ ptrname = dns_fixedname_initname(&ptrfname);
+ iresult = dns_byaddr_createptrname(&netaddr, 0, ptrname);
+ if (iresult != ISC_R_SUCCESS) {
+ ERR(EAI_FAIL);
+ }
+
+ /* Get the PTR RRset */
+ ISC_LIST_INIT(answerlist);
+ iresult = dns_client_resolve(client, ptrname, dns_rdataclass_in,
+ dns_rdatatype_ptr, 0, &answerlist);
+ switch (iresult) {
+ case ISC_R_SUCCESS:
+ /*
+ * a 'non-existent' error is not necessarily fatal for
+ * getnameinfo().
+ */
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ break;
+ case DNS_R_SIGINVALID:
+ case DNS_R_SIGEXPIRED:
+ case DNS_R_SIGFUTURE:
+ case DNS_R_KEYUNAUTHORIZED:
+ case DNS_R_MUSTBESECURE:
+ case DNS_R_COVERINGNSEC:
+ case DNS_R_NOTAUTHORITATIVE:
+ case DNS_R_NOVALIDKEY:
+ case DNS_R_NOVALIDDS:
+ case DNS_R_NOVALIDSIG:
+ /*
+ * Don't use ERR as GCC 7 wants to raise a
+ * warning with ERR about possible falling
+ * through which is impossible.
+ */
+ result = EAI_INSECUREDATA;
+ goto cleanup;
+ default:
+ ERR(EAI_FAIL);
+ }
+
+ /* Parse the answer for the hostname */
+ for (ptrname = ISC_LIST_HEAD(answerlist); ptrname != NULL;
+ ptrname = ISC_LIST_NEXT(ptrname, link))
+ {
+ for (rdataset = ISC_LIST_HEAD(ptrname->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (!dns_rdataset_isassociated(rdataset)) {
+ continue;
+ }
+ if (rdataset->type != dns_rdatatype_ptr) {
+ continue;
+ }
+
+ for (iresult = dns_rdataset_first(rdataset);
+ iresult == ISC_R_SUCCESS;
+ iresult = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_t rdata;
+ dns_rdata_ptr_t rdata_ptr;
+ isc_buffer_t b;
+
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(rdataset, &rdata);
+ dns_rdata_tostruct(&rdata, &rdata_ptr,
+ NULL);
+
+ isc_buffer_init(&b, hoststr,
+ sizeof(hoststr));
+ iresult = dns_name_totext(
+ &rdata_ptr.ptr, true, &b);
+ dns_rdata_freestruct(&rdata_ptr);
+ if (iresult == ISC_R_SUCCESS) {
+ /*
+ * We ignore the rest of the
+ * answer. After all,
+ * getnameinfo() can return
+ * at most one hostname.
+ */
+ found = true;
+ isc_buffer_usedregion(
+ &b, &hostregion);
+ goto ptrfound;
+ }
+ }
+ }
+ }
+ ptrfound:
+ dns_client_freeresanswer(client, &answerlist);
+ if (found) {
+ if ((flags & NI_NOFQDN) != 0) {
+ p = strchr(hoststr, '.');
+ if (p) {
+ *p = '\0';
+ }
+ }
+ if (hostregion.length + 1 > hostlen) {
+ ERR(EAI_OVERFLOW);
+ }
+ snprintf(host, hostlen, "%.*s", (int)hostregion.length,
+ (char *)hostregion.base);
+ } else {
+ if ((flags & NI_NAMEREQD) != 0) {
+ ERR(EAI_NONAME);
+ }
+ if (inet_ntop(afd->a_af, addr, numaddr,
+ sizeof(numaddr)) == NULL)
+ {
+ ERR(EAI_SYSTEM);
+ }
+ if ((strlen(numaddr) + 1) > hostlen) {
+ ERR(EAI_OVERFLOW);
+ }
+ strlcpy(host, numaddr, hostlen);
+ }
+ }
+ result = SUCCESS;
+
+cleanup:
+ return (result);
+}
diff --git a/lib/irs/include/.clang-format b/lib/irs/include/.clang-format
new file mode 120000
index 0000000..0e62f72
--- /dev/null
+++ b/lib/irs/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/irs/include/Makefile.in b/lib/irs/include/Makefile.in
new file mode 100644
index 0000000..53a5348
--- /dev/null
+++ b/lib/irs/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = irs
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/irs/include/irs/Makefile.in b/lib/irs/include/irs/Makefile.in
new file mode 100644
index 0000000..fb37979
--- /dev/null
+++ b/lib/irs/include/irs/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+#
+# Only list headers that are to be installed and are not
+# machine generated. The latter are handled specially in the
+# install target below.
+#
+HEADERS = context.h dnsconf.h resconf.h types.h version.h
+
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/irs
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/irs || exit 1; \
+ done
+ ${INSTALL_DATA} netdb.h ${DESTDIR}${includedir}/irs
+ ${INSTALL_DATA} platform.h ${DESTDIR}${includedir}/irs
+
+uninstall::
+ rm -f ${DESTDIR}${includedir}/irs/platform.h
+ rm -f ${DESTDIR}${includedir}/irs/netdb.h
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/irs/$$i || exit 1; \
+ done
+
+distclean::
+ rm -f netdb.h platform.h
diff --git a/lib/irs/include/irs/context.h b/lib/irs/include/irs/context.h
new file mode 100644
index 0000000..c2d800a
--- /dev/null
+++ b/lib/irs/include/irs/context.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IRS_CONTEXT_H
+#define IRS_CONTEXT_H 1
+
+/*! \file
+ *
+ * \brief
+ * The IRS context module provides an abstract interface to the DNS library
+ * with an application. An IRS context object initializes and holds various
+ * resources used in the DNS library.
+ */
+
+#include <dns/types.h>
+
+#include <irs/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+irs_context_create(irs_context_t **contextp);
+/*%<
+ * Create an IRS context. It internally initializes the ISC and DNS libraries
+ * (if not yet), creates a DNS client object and initializes the client using
+ * the configuration files parsed via the 'resconf' and 'dnsconf' IRS modules.
+ * Some of the internally initialized objects can be used by the application
+ * via irs_context_getxxx() functions (see below).
+ *
+ * Requires:
+ *
+ *\li contextp != NULL && *contextp == NULL.
+ */
+
+isc_result_t
+irs_context_get(irs_context_t **contextp);
+/*%<
+ * Return an IRS context for the calling thread. If no IRS context is
+ * associated to the thread, this function creates a new one by calling
+ * irs_context_create(), and associates it with the thread as a thread specific
+ * data value. This function is provided for standard libraries that are
+ * expected to be thread-safe but do not accept an appropriate IRS context
+ * as a library parameter, e.g., getaddrinfo().
+ *
+ * Requires:
+ *
+ *\li contextp != NULL && *contextp == NULL.
+ */
+
+void
+irs_context_destroy(irs_context_t **contextp);
+/*%<
+ * Destroy an IRS context.
+ *
+ * Requires:
+ *
+ *\li '*contextp' is a valid IRS context.
+ *
+ * Ensures:
+ *\li '*contextp' == NULL.
+ */
+
+isc_mem_t *
+irs_context_getmctx(irs_context_t *context);
+/*%<
+ * Return the memory context held in the context.
+ *
+ * Requires:
+ *
+ *\li 'context' is a valid IRS context.
+ */
+
+isc_appctx_t *
+irs_context_getappctx(irs_context_t *context);
+/*%<
+ * Return the application context held in the context.
+ *
+ * Requires:
+ *
+ *\li 'context' is a valid IRS context.
+ */
+
+isc_taskmgr_t *
+irs_context_gettaskmgr(irs_context_t *context);
+/*%<
+ * Return the task manager held in the context.
+ *
+ * Requires:
+ *
+ *\li 'context' is a valid IRS context.
+ */
+
+isc_timermgr_t *
+irs_context_gettimermgr(irs_context_t *context);
+/*%<
+ * Return the timer manager held in the context.
+ *
+ * Requires:
+ *
+ *\li 'context' is a valid IRS context.
+ */
+
+isc_task_t *
+irs_context_gettask(irs_context_t *context);
+/*%<
+ * Return the task object held in the context.
+ *
+ * Requires:
+ *
+ *\li 'context' is a valid IRS context.
+ */
+
+dns_client_t *
+irs_context_getdnsclient(irs_context_t *context);
+/*%<
+ * Return the DNS client object held in the context.
+ *
+ * Requires:
+ *
+ *\li 'context' is a valid IRS context.
+ */
+
+irs_resconf_t *
+irs_context_getresconf(irs_context_t *context);
+/*%<
+ * Return the resolver configuration object held in the context.
+ *
+ * Requires:
+ *
+ *\li 'context' is a valid IRS context.
+ */
+
+irs_dnsconf_t *
+irs_context_getdnsconf(irs_context_t *context);
+/*%<
+ * Return the advanced DNS configuration object held in the context.
+ *
+ * Requires:
+ *
+ *\li 'context' is a valid IRS context.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* IRS_CONTEXT_H */
diff --git a/lib/irs/include/irs/dnsconf.h b/lib/irs/include/irs/dnsconf.h
new file mode 100644
index 0000000..7456b89
--- /dev/null
+++ b/lib/irs/include/irs/dnsconf.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IRS_DNSCONF_H
+#define IRS_DNSCONF_H 1
+
+/*! \file
+ *
+ * \brief
+ * The IRS dnsconf module parses an "advanced" configuration file related to
+ * the DNS library, such as trust anchors for DNSSEC validation, and creates
+ * the corresponding configuration objects for the DNS library modules.
+ *
+ * Notes:
+ * This module is very experimental and the configuration syntax or library
+ * interfaces may change in future versions. Currently, only static
+ * key configuration is supported; "trusted-keys" and "trust-anchors"/
+ * "managed-keys" statements will be parsed exactly as they are in
+ * named.conf, except that "trust-anchors" and "managed-keys" entries will
+ * be treated as if they were configured with "static-key", even if they
+ * were actually configured with "initial-key".
+ */
+
+#include <irs/types.h>
+
+/*%
+ * A compound structure storing DNS key information mainly for DNSSEC
+ * validation. A dns_key_t object will be created using the 'keyname' and
+ * 'keydatabuf' members with the dst_key_fromdns() function.
+ */
+typedef struct irs_dnsconf_dnskey {
+ dns_name_t *keyname;
+ isc_buffer_t *keydatabuf;
+ ISC_LINK(struct irs_dnsconf_dnskey) link;
+} irs_dnsconf_dnskey_t;
+
+typedef ISC_LIST(irs_dnsconf_dnskey_t) irs_dnsconf_dnskeylist_t;
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+irs_dnsconf_load(isc_mem_t *mctx, const char *filename, irs_dnsconf_t **confp);
+/*%<
+ * Load the "advanced" DNS configuration file 'filename' in the "dns.conf"
+ * format, and create a new irs_dnsconf_t object from the configuration.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'filename' != NULL
+ *
+ *\li 'confp' != NULL && '*confp' == NULL
+ */
+
+void
+irs_dnsconf_destroy(irs_dnsconf_t **confp);
+/*%<
+ * Destroy the dnsconf object.
+ *
+ * Requires:
+ *
+ *\li '*confp' is a valid dnsconf object.
+ *
+ * Ensures:
+ *
+ *\li *confp == NULL
+ */
+
+irs_dnsconf_dnskeylist_t *
+irs_dnsconf_gettrustedkeys(irs_dnsconf_t *conf);
+/*%<
+ * Return a list of key information stored in 'conf'.
+ *
+ * Requires:
+ *
+ *\li 'conf' is a valid dnsconf object.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* IRS_DNSCONF_H */
diff --git a/lib/irs/include/irs/netdb.h.in b/lib/irs/include/irs/netdb.h.in
new file mode 100644
index 0000000..c3b4dae
--- /dev/null
+++ b/lib/irs/include/irs/netdb.h.in
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#ifndef IRS_NETDB_H
+#define IRS_NETDB_H 1
+
+#include <stddef.h> /* Required on FreeBSD (and others?) for size_t. */
+#include <netdb.h> /* Contractual provision. */
+
+/*
+ * Undefine all #defines we are interested in as <netdb.h> may or may not have
+ * defined them.
+ */
+
+/*
+ * Error return codes from gethostbyname() and gethostbyaddr()
+ * (left in extern int h_errno).
+ */
+
+#undef NETDB_INTERNAL
+#undef NETDB_SUCCESS
+#undef HOST_NOT_FOUND
+#undef TRY_AGAIN
+#undef NO_RECOVERY
+#undef NO_DATA
+#undef NO_ADDRESS
+
+#define NETDB_INTERNAL -1 /* see errno */
+#define NETDB_SUCCESS 0 /* no problem */
+#define HOST_NOT_FOUND 1 /* Authoritative Answer Host not found */
+#define TRY_AGAIN 2 /* Non-Authoritive Host not found, or SERVERFAIL */
+#define NO_RECOVERY 3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
+#define NO_DATA 4 /* Valid name, no data record of requested type */
+#define NO_ADDRESS NO_DATA /* no address, look for MX record */
+
+/*
+ * Error return codes from getaddrinfo(). EAI_INSECUREDATA is our own extension
+ * and it's very unlikely to be already defined, but undef it just in case; it
+ * at least doesn't do any harm.
+ */
+
+#undef EAI_ADDRFAMILY
+#undef EAI_AGAIN
+#undef EAI_BADFLAGS
+#undef EAI_FAIL
+#undef EAI_FAMILY
+#undef EAI_MEMORY
+#undef EAI_NODATA
+#undef EAI_NONAME
+#undef EAI_SERVICE
+#undef EAI_SOCKTYPE
+#undef EAI_SYSTEM
+#undef EAI_BADHINTS
+#undef EAI_PROTOCOL
+#undef EAI_OVERFLOW
+#undef EAI_INSECUREDATA
+#undef EAI_MAX
+
+#define EAI_ADDRFAMILY 1 /* address family for hostname not supported */
+#define EAI_AGAIN 2 /* temporary failure in name resolution */
+#define EAI_BADFLAGS 3 /* invalid value for ai_flags */
+#define EAI_FAIL 4 /* non-recoverable failure in name resolution */
+#define EAI_FAMILY 5 /* ai_family not supported */
+#define EAI_MEMORY 6 /* memory allocation failure */
+#define EAI_NODATA 7 /* no address associated with hostname */
+#define EAI_NONAME 8 /* hostname nor servname provided, or not known */
+#define EAI_SERVICE 9 /* servname not supported for ai_socktype */
+#define EAI_SOCKTYPE 10 /* ai_socktype not supported */
+#define EAI_SYSTEM 11 /* system error returned in errno */
+#define EAI_BADHINTS 12
+#define EAI_PROTOCOL 13
+#define EAI_OVERFLOW 14
+#define EAI_INSECUREDATA 15
+#define EAI_MAX 16
+
+/*
+ * Flag values for getaddrinfo()
+ */
+#undef AI_PASSIVE
+#undef AI_CANONNAME
+#undef AI_NUMERICHOST
+
+#define AI_PASSIVE 0x00000001
+#define AI_CANONNAME 0x00000002
+#define AI_NUMERICHOST 0x00000004
+
+/*
+ * Flag values for getipnodebyname()
+ */
+#undef AI_V4MAPPED
+#undef AI_ALL
+#undef AI_ADDRCONFIG
+#undef AI_DEFAULT
+
+#define AI_V4MAPPED 0x00000008
+#define AI_ALL 0x00000010
+#define AI_ADDRCONFIG 0x00000020
+#define AI_DEFAULT (AI_V4MAPPED|AI_ADDRCONFIG)
+
+/*
+ * Constants for getnameinfo()
+ */
+#undef NI_MAXHOST
+#undef NI_MAXSERV
+
+#define NI_MAXHOST 1025
+#define NI_MAXSERV 32
+
+/*
+ * Flag values for getnameinfo()
+ */
+#undef NI_NOFQDN
+#undef NI_NUMERICHOST
+#undef NI_NAMEREQD
+#undef NI_NUMERICSERV
+#undef NI_DGRAM
+#undef NI_NUMERICSCOPE
+
+#define NI_NOFQDN 0x00000001
+#define NI_NUMERICHOST 0x00000002
+#define NI_NAMEREQD 0x00000004
+#define NI_NUMERICSERV 0x00000008
+#define NI_DGRAM 0x00000010
+
+/*
+ * Define to map into irs_ namespace.
+ */
+
+#define IRS_NAMESPACE
+
+#ifdef IRS_NAMESPACE
+
+/*
+ * Use our versions not the ones from the C library.
+ */
+
+#ifdef getnameinfo
+#undef getnameinfo
+#endif
+#define getnameinfo irs_getnameinfo
+
+#ifdef getaddrinfo
+#undef getaddrinfo
+#endif
+#define getaddrinfo irs_getaddrinfo
+
+#ifdef freeaddrinfo
+#undef freeaddrinfo
+#endif
+#define freeaddrinfo irs_freeaddrinfo
+
+#ifdef gai_strerror
+#undef gai_strerror
+#endif
+#define gai_strerror irs_gai_strerror
+
+int
+getaddrinfo(const char *hostname, const char *servname,
+ const struct addrinfo *hints, struct addrinfo **res);
+
+int
+getnameinfo(const struct sockaddr *sa, socklen_t salen,
+ char *host, socklen_t hostlen,
+ char *serv, socklen_t servlen,
+ int flags);
+
+void freeaddrinfo (struct addrinfo *ai);
+
+const char *
+gai_strerror(int ecode);
+
+#endif /* IRS_NAMESPACE */
+
+/*
+ * Tell Emacs to use C mode on this file.
+ * Local variables:
+ * mode: c
+ * End:
+ */
+
+#endif /* IRS_NETDB_H */
diff --git a/lib/irs/include/irs/platform.h.in b/lib/irs/include/irs/platform.h.in
new file mode 100644
index 0000000..54bae37
--- /dev/null
+++ b/lib/irs/include/irs/platform.h.in
@@ -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.
+ */
+
+/*! \file */
+
+#ifndef IRS_PLATFORM_H
+#define IRS_PLATFORM_H 1
+
+/*****
+ ***** Platform-dependent defines.
+ *****/
+
+#define LIBIRS_EXTERNAL_DATA
+
+/*
+ * Tell Emacs to use C mode on this file.
+ * Local Variables:
+ * mode: c
+ * End:
+ */
+
+#endif /* IRS_PLATFORM_H */
diff --git a/lib/irs/include/irs/resconf.h b/lib/irs/include/irs/resconf.h
new file mode 100644
index 0000000..424b795
--- /dev/null
+++ b/lib/irs/include/irs/resconf.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IRS_RESCONF_H
+#define IRS_RESCONF_H 1
+
+/*! \file
+ *
+ * \brief
+ * The IRS resconf module parses the legacy "/etc/resolv.conf" file and
+ * creates the corresponding configuration objects for the DNS library
+ * modules.
+ */
+
+#include <irs/types.h>
+
+/*%
+ * 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.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* IRS_RESCONF_H */
diff --git a/lib/irs/include/irs/types.h b/lib/irs/include/irs/types.h
new file mode 100644
index 0000000..54153f8
--- /dev/null
+++ b/lib/irs/include/irs/types.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef IRS_TYPES_H
+#define IRS_TYPES_H 1
+
+/* Core Types. Alphabetized by defined type. */
+
+/*%< per-thread IRS context */
+typedef struct irs_context irs_context_t;
+/*%< resolv.conf configuration information */
+typedef struct irs_resconf irs_resconf_t;
+/*%< advanced DNS-related configuration information */
+typedef struct irs_dnsconf irs_dnsconf_t;
+
+#endif /* IRS_TYPES_H */
diff --git a/lib/irs/include/irs/version.h b/lib/irs/include/irs/version.h
new file mode 100644
index 0000000..75ba926
--- /dev/null
+++ b/lib/irs/include/irs/version.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <irs/platform.h>
+
+LIBIRS_EXTERNAL_DATA extern const char irs_version[];
diff --git a/lib/irs/resconf.c b/lib/irs/resconf.c
new file mode 100644
index 0000000..f3b7be5
--- /dev/null
+++ b/lib/irs/resconf.c
@@ -0,0 +1,689 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can 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
+ */
+
+#ifndef WIN32
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#endif /* ifndef WIN32 */
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/sockaddr.h>
+#include <isc/util.h>
+
+#include <irs/netdb.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 */
+
+/*!
+ * 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;
+};
+
+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);
+
+#if HAVE_GET_WIN32_NAMESERVERS
+static isc_result_t
+get_win32_nameservers(irs_resconf_t *conf);
+#endif /* if HAVE_GET_WIN32_NAMESERVERS */
+
+/*!
+ * 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) {
+ int ch;
+ char *p;
+
+ 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_parseoption(irs_resconf_t *conf, FILE *fp) {
+ int delim;
+ long ndots;
+ char *p;
+ 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) {
+ ndots = strtol(word + 6, &p, 10);
+ if (*p != '\0') { /* Bad string. */
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ if (ndots < 0 || ndots > 0xff) { /* Out of range. */
+ return (ISC_R_RANGE);
+ }
+ conf->ndots = (uint8_t)ndots;
+ }
+
+ if (delim == EOF || delim == '\n') {
+ break;
+ } else {
+ delim = getword(fp, word, sizeof(word));
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+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;
+ 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 HAVE_GET_WIN32_NAMESERVERS
+ ret = get_win32_nameservers(conf);
+ if (ret != ISC_R_SUCCESS) {
+ goto error;
+ }
+#endif /* if HAVE_GET_WIN32_NAMESERVERS */
+
+ /* 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);
+}
diff --git a/lib/irs/tests/Kyuafile b/lib/irs/tests/Kyuafile
new file mode 100644
index 0000000..a203172
--- /dev/null
+++ b/lib/irs/tests/Kyuafile
@@ -0,0 +1,15 @@
+-- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+--
+-- SPDX-License-Identifier: MPL-2.0
+--
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, you can obtain one at https://mozilla.org/MPL/2.0/.
+--
+-- See the COPYRIGHT file distributed with this work for additional
+-- information regarding copyright ownership.
+
+syntax(2)
+test_suite('bind9')
+
+tap_test_program{name='resconf_test'}
diff --git a/lib/irs/tests/Makefile.in b/lib/irs/tests/Makefile.in
new file mode 100644
index 0000000..327d31d
--- /dev/null
+++ b/lib/irs/tests/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. -Iinclude -I../include ${ISC_INCLUDES} ${IRS_INCLUDES} @CMOCKA_CFLAGS@
+CDEFINES = -DTESTS="\"${top_builddir}/lib/irs/tests/\""
+
+CFGLIBS = ../../isccfg/libisccfg.@A@
+CFGDEPLIBS = ../../isccfg/libisccfg.@A@
+DNSLIBS = ../../dns/libdns.@A@ @NO_LIBTOOL_DNSLIBS@
+DNSDEPLIBS = ../../dns/libdns.@A@
+ISCLIBS = ../../isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@
+ISCDEPLIBS = ../../isc/libisc.@A@
+IRSLIBS = ../libirs.@A@
+IRSDEPLIBS = ../libirs.@A@
+
+LIBS = ${IRSLIBS} ${CFGLIBS} ${DNSLIBS} ${ISCLIBS} @LIBS@ @CMOCKA_LIBS@
+
+OBJS =
+SRCS = resconf_test.c
+
+SUBDIRS =
+TARGETS = resconf_test@EXEEXT@
+
+@BIND9_MAKE_RULES@
+
+resconf_test@EXEEXT@: resconf_test.@O@ ${CFGDEPLIBS} ${DNSDEPLIBS} ${IRSDEPLIBS} ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ resconf_test.@O@ ${LIBS}
+
+unit::
+ sh ${top_builddir}/unit/unittest.sh
+
+clean distclean::
+ rm -f ${TARGETS}
+ rm -f atf.out
diff --git a/lib/irs/tests/resconf_test.c b/lib/irs/tests/resconf_test.c
new file mode 100644
index 0000000..6951758
--- /dev/null
+++ b/lib/irs/tests/resconf_test.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include <irs/resconf.h>
+#include <irs/types.h>
+
+static isc_mem_t *mctx = NULL;
+
+static void
+setup_test() {
+ isc_mem_create(&mctx);
+
+ /*
+ * the caller might run from another directory, but tests
+ * that access test data files must first chdir to the proper
+ * location.
+ */
+ assert_return_code(chdir(TESTS), 0);
+}
+
+/* test irs_resconf_load() */
+static void
+irs_resconf_load_test(void **state) {
+ isc_result_t result;
+ irs_resconf_t *resconf = NULL;
+ unsigned int i;
+ struct {
+ const char *file;
+ isc_result_t loadres;
+ isc_result_t (*check)(irs_resconf_t *resconf);
+ isc_result_t checkres;
+ } tests[] = {
+ { "testdata/domain.conf", ISC_R_SUCCESS, NULL, ISC_R_SUCCESS },
+ { "testdata/nameserver-v4.conf", ISC_R_SUCCESS, NULL,
+ ISC_R_SUCCESS },
+ { "testdata/nameserver-v6.conf", ISC_R_SUCCESS, NULL,
+ ISC_R_SUCCESS },
+ { "testdata/nameserver-v6-scoped.conf", ISC_R_SUCCESS, NULL,
+ ISC_R_SUCCESS },
+ { "testdata/options-debug.conf", ISC_R_SUCCESS, NULL,
+ ISC_R_SUCCESS },
+ { "testdata/options-ndots.conf", ISC_R_SUCCESS, NULL,
+ ISC_R_SUCCESS },
+ { "testdata/options-timeout.conf", ISC_R_SUCCESS, NULL,
+ ISC_R_SUCCESS },
+ { "testdata/options-unknown.conf", ISC_R_SUCCESS, NULL,
+ ISC_R_SUCCESS },
+ { "testdata/options.conf", ISC_R_SUCCESS, NULL, ISC_R_SUCCESS },
+ { "testdata/options-bad-ndots.conf", ISC_R_RANGE, NULL,
+ ISC_R_SUCCESS },
+ { "testdata/options-empty.conf", ISC_R_UNEXPECTEDEND, NULL,
+ ISC_R_SUCCESS },
+ { "testdata/port.conf", ISC_R_SUCCESS, NULL, ISC_R_SUCCESS },
+ { "testdata/resolv.conf", ISC_R_SUCCESS, NULL, ISC_R_SUCCESS },
+ { "testdata/search.conf", ISC_R_SUCCESS, NULL, ISC_R_SUCCESS },
+ { "testdata/sortlist-v4.conf", ISC_R_SUCCESS, NULL,
+ ISC_R_SUCCESS },
+ { "testdata/timeout.conf", ISC_R_SUCCESS, NULL, ISC_R_SUCCESS },
+ { "testdata/unknown.conf", ISC_R_SUCCESS, NULL, ISC_R_SUCCESS }
+ };
+
+ UNUSED(state);
+
+ setup_test();
+
+ for (i = 0; i < sizeof(tests) / sizeof(tests[1]); i++) {
+ result = irs_resconf_load(mctx, tests[i].file, &resconf);
+ if (result != tests[i].loadres) {
+ fail_msg("# unexpected result %s loading %s",
+ isc_result_totext(result), tests[i].file);
+ }
+
+ if (result == ISC_R_SUCCESS && resconf == NULL) {
+ fail_msg("# NULL on success loading %s", tests[i].file);
+ } else if (result != ISC_R_SUCCESS && resconf != NULL) {
+ fail_msg("# non-NULL on failure loading %s",
+ tests[i].file);
+ }
+
+ if (resconf != NULL && tests[i].check != NULL) {
+ result = (tests[i].check)(resconf);
+ if (result != tests[i].checkres) {
+ fail_msg("# unexpected result %s loading %s",
+ isc_result_totext(result),
+ tests[i].file);
+ }
+ }
+ if (resconf != NULL) {
+ irs_resconf_destroy(&resconf);
+ }
+ }
+
+ isc_mem_detach(&mctx);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(irs_resconf_load_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/irs/tests/testdata/domain.conf b/lib/irs/tests/testdata/domain.conf
new file mode 100644
index 0000000..ee43f5c
--- /dev/null
+++ b/lib/irs/tests/testdata/domain.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+domain example.com
diff --git a/lib/irs/tests/testdata/nameserver-v4.conf b/lib/irs/tests/testdata/nameserver-v4.conf
new file mode 100644
index 0000000..5054de0
--- /dev/null
+++ b/lib/irs/tests/testdata/nameserver-v4.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+nameserver 10.0.0.1
diff --git a/lib/irs/tests/testdata/nameserver-v6-scoped.conf b/lib/irs/tests/testdata/nameserver-v6-scoped.conf
new file mode 100644
index 0000000..d5bd97f
--- /dev/null
+++ b/lib/irs/tests/testdata/nameserver-v6-scoped.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+nameserver fe80::1%1
diff --git a/lib/irs/tests/testdata/nameserver-v6.conf b/lib/irs/tests/testdata/nameserver-v6.conf
new file mode 100644
index 0000000..b9ed093
--- /dev/null
+++ b/lib/irs/tests/testdata/nameserver-v6.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+nameserver 2001:DB8::1
diff --git a/lib/irs/tests/testdata/options-bad-ndots.conf b/lib/irs/tests/testdata/options-bad-ndots.conf
new file mode 100644
index 0000000..18d3f8b
--- /dev/null
+++ b/lib/irs/tests/testdata/options-bad-ndots.conf
@@ -0,0 +1,13 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+search example.com example.net
+options ndots:256
diff --git a/lib/irs/tests/testdata/options-debug.conf b/lib/irs/tests/testdata/options-debug.conf
new file mode 100644
index 0000000..04bcf2e
--- /dev/null
+++ b/lib/irs/tests/testdata/options-debug.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+options debug
diff --git a/lib/irs/tests/testdata/options-empty.conf b/lib/irs/tests/testdata/options-empty.conf
new file mode 100644
index 0000000..0b1dc9e
--- /dev/null
+++ b/lib/irs/tests/testdata/options-empty.conf
@@ -0,0 +1,13 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+domain example.com
+options
diff --git a/lib/irs/tests/testdata/options-ndots.conf b/lib/irs/tests/testdata/options-ndots.conf
new file mode 100644
index 0000000..5d18d26
--- /dev/null
+++ b/lib/irs/tests/testdata/options-ndots.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+option ndots:2
diff --git a/lib/irs/tests/testdata/options-timeout.conf b/lib/irs/tests/testdata/options-timeout.conf
new file mode 100644
index 0000000..96787c4
--- /dev/null
+++ b/lib/irs/tests/testdata/options-timeout.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+options timeout:1
diff --git a/lib/irs/tests/testdata/options-unknown.conf b/lib/irs/tests/testdata/options-unknown.conf
new file mode 100644
index 0000000..fdf82b4
--- /dev/null
+++ b/lib/irs/tests/testdata/options-unknown.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+options unknown
diff --git a/lib/irs/tests/testdata/options.conf b/lib/irs/tests/testdata/options.conf
new file mode 100644
index 0000000..7a8d5f3
--- /dev/null
+++ b/lib/irs/tests/testdata/options.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+options unknown debug timeout:1 ndots:2
diff --git a/lib/irs/tests/testdata/port.conf b/lib/irs/tests/testdata/port.conf
new file mode 100644
index 0000000..54e2094
--- /dev/null
+++ b/lib/irs/tests/testdata/port.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+port 5300
diff --git a/lib/irs/tests/testdata/resolv.conf b/lib/irs/tests/testdata/resolv.conf
new file mode 100644
index 0000000..3c14408
--- /dev/null
+++ b/lib/irs/tests/testdata/resolv.conf
@@ -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.
+
+port 5300
+nameserver 10.0.0.1
+nameserver 2001:DB8::1
+search example.com example.net
+sortlist 130.155.160.0/255.255.240.0 130.155.0.0
+timeout 10
+unknown directive
+options unknown debug timeout:1 ndots:2
diff --git a/lib/irs/tests/testdata/search.conf b/lib/irs/tests/testdata/search.conf
new file mode 100644
index 0000000..f019ab9
--- /dev/null
+++ b/lib/irs/tests/testdata/search.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+search example.com example.net
diff --git a/lib/irs/tests/testdata/sortlist-v4.conf b/lib/irs/tests/testdata/sortlist-v4.conf
new file mode 100644
index 0000000..3f4d54a
--- /dev/null
+++ b/lib/irs/tests/testdata/sortlist-v4.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+sortlist 130.155.160.0/255.255.240.0 130.155.0.0
diff --git a/lib/irs/tests/testdata/timeout.conf b/lib/irs/tests/testdata/timeout.conf
new file mode 100644
index 0000000..2ccb92d
--- /dev/null
+++ b/lib/irs/tests/testdata/timeout.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+timeout 10
diff --git a/lib/irs/tests/testdata/unknown.conf b/lib/irs/tests/testdata/unknown.conf
new file mode 100644
index 0000000..0eafb76
--- /dev/null
+++ b/lib/irs/tests/testdata/unknown.conf
@@ -0,0 +1,12 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+unknown directive
diff --git a/lib/irs/version.c b/lib/irs/version.c
new file mode 100644
index 0000000..b0b4fd9
--- /dev/null
+++ b/lib/irs/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <irs/version.h>
+
+const char irs_version[] = VERSION;
diff --git a/lib/irs/win32/DLLMain.c b/lib/irs/win32/DLLMain.c
new file mode 100644
index 0000000..62c6e53
--- /dev/null
+++ b/lib/irs/win32/DLLMain.c
@@ -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.
+ */
+
+#include <signal.h>
+#include <windows.h>
+
+/*
+ * Called when we enter the DLL
+ */
+__declspec(dllexport) BOOL WINAPI
+ DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
+ switch (fdwReason) {
+ /*
+ * The DLL is loading due to process
+ * initialization or a call to LoadLibrary.
+ */
+ case DLL_PROCESS_ATTACH:
+ break;
+
+ /* The attached process creates a new thread. */
+ case DLL_THREAD_ATTACH:
+ break;
+
+ /* The thread of the attached process terminates. */
+ case DLL_THREAD_DETACH:
+ break;
+
+ /*
+ * The DLL is unloading from a process due to
+ * process termination or a call to FreeLibrary.
+ */
+ case DLL_PROCESS_DETACH:
+ break;
+
+ default:
+ break;
+ }
+ return (TRUE);
+}
diff --git a/lib/irs/win32/Makefile.in b/lib/irs/win32/Makefile.in
new file mode 100644
index 0000000..5641e1a
--- /dev/null
+++ b/lib/irs/win32/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = include
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/irs/win32/include/.clang-format b/lib/irs/win32/include/.clang-format
new file mode 120000
index 0000000..e919bba
--- /dev/null
+++ b/lib/irs/win32/include/.clang-format
@@ -0,0 +1 @@
+../../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/irs/win32/include/Makefile.in b/lib/irs/win32/include/Makefile.in
new file mode 100644
index 0000000..53a5348
--- /dev/null
+++ b/lib/irs/win32/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = irs
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/irs/win32/include/irs/Makefile.in b/lib/irs/win32/include/irs/Makefile.in
new file mode 100644
index 0000000..6d87432
--- /dev/null
+++ b/lib/irs/win32/include/irs/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+HEADERS =
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/irs
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/irs || exit 1; \
+ done
diff --git a/lib/irs/win32/include/irs/netdb.h b/lib/irs/win32/include/irs/netdb.h
new file mode 100644
index 0000000..3e3e5d3
--- /dev/null
+++ b/lib/irs/win32/include/irs/netdb.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#ifndef IRS_NETDB_H
+#define IRS_NETDB_H 1
+
+#include <stddef.h> /* Required on FreeBSD (and others?) for size_t. */
+
+/*
+ * Define if <netdb.h> does not declare struct addrinfo.
+ */
+#undef ISC_IRS_NEEDADDRINFO
+
+#ifdef ISC_IRS_NEEDADDRINFO
+struct addrinfo {
+ int ai_flags; /* AI_PASSIVE, AI_CANONNAME */
+ int ai_family; /* PF_xxx */
+ int ai_socktype; /* SOCK_xxx */
+ int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and
+ * IPv6 */
+ size_t ai_addrlen; /* Length of ai_addr */
+ char *ai_canonname; /* Canonical name for hostname */
+ struct sockaddr *ai_addr; /* Binary address */
+ struct addrinfo *ai_next; /* Next structure in linked list */
+};
+#endif /* ifdef ISC_IRS_NEEDADDRINFO */
+
+/*
+ * Undefine all #defines we are interested in as <netdb.h> may or may not have
+ * defined them.
+ */
+
+/*
+ * Error return codes from gethostbyname() and gethostbyaddr()
+ * (left in extern int h_errno).
+ */
+
+#undef NETDB_INTERNAL
+#undef NETDB_SUCCESS
+#undef HOST_NOT_FOUND
+#undef TRY_AGAIN
+#undef NO_RECOVERY
+#undef NO_DATA
+#undef NO_ADDRESS
+
+#define NETDB_INTERNAL -1 /* see errno */
+#define NETDB_SUCCESS 0 /* no problem */
+#define HOST_NOT_FOUND 1 /* Authoritative Answer Host not found */
+#define TRY_AGAIN 2 /* Non-Authoritive Host not found, or SERVERFAIL */
+#define NO_RECOVERY 3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
+#define NO_DATA 4 /* Valid name, no data record of requested type */
+#define NO_ADDRESS NO_DATA /* no address, look for MX record */
+
+/*
+ * Error return codes from getaddrinfo(). EAI_INSECUREDATA is our own extension
+ * and it's very unlikely to be already defined, but undef it just in case; it
+ * at least doesn't do any harm.
+ */
+
+#undef EAI_ADDRFAMILY
+#undef EAI_AGAIN
+#undef EAI_BADFLAGS
+#undef EAI_FAIL
+#undef EAI_FAMILY
+#undef EAI_MEMORY
+#undef EAI_NODATA
+#undef EAI_NONAME
+#undef EAI_SERVICE
+#undef EAI_SOCKTYPE
+#undef EAI_SYSTEM
+#undef EAI_BADHINTS
+#undef EAI_PROTOCOL
+#undef EAI_OVERFLOW
+#undef EAI_INSECUREDATA
+#undef EAI_MAX
+
+#define EAI_ADDRFAMILY 1 /* address family for hostname not supported */
+#define EAI_AGAIN 2 /* temporary failure in name resolution */
+#define EAI_BADFLAGS 3 /* invalid value for ai_flags */
+#define EAI_FAIL 4 /* non-recoverable failure in name resolution */
+#define EAI_FAMILY 5 /* ai_family not supported */
+#define EAI_MEMORY 6 /* memory allocation failure */
+#define EAI_NODATA 7 /* no address associated with hostname */
+#define EAI_NONAME 8 /* hostname nor servname provided, or not known */
+#define EAI_SERVICE 9 /* servname not supported for ai_socktype */
+#define EAI_SOCKTYPE 10 /* ai_socktype not supported */
+#define EAI_SYSTEM 11 /* system error returned in errno */
+#define EAI_BADHINTS 12
+#define EAI_PROTOCOL 13
+#define EAI_OVERFLOW 14
+#define EAI_INSECUREDATA 15
+#define EAI_MAX 16
+
+/*
+ * Flag values for getaddrinfo()
+ */
+#undef AI_PASSIVE
+#undef AI_CANONNAME
+#undef AI_NUMERICHOST
+
+#define AI_PASSIVE 0x00000001
+#define AI_CANONNAME 0x00000002
+#define AI_NUMERICHOST 0x00000004
+
+/*
+ * Flag values for getipnodebyname()
+ */
+#undef AI_V4MAPPED
+#undef AI_ALL
+#undef AI_ADDRCONFIG
+#undef AI_DEFAULT
+
+#define AI_V4MAPPED 0x00000008
+#define AI_ALL 0x00000010
+#define AI_ADDRCONFIG 0x00000020
+#define AI_DEFAULT (AI_V4MAPPED | AI_ADDRCONFIG)
+
+/*
+ * Constants for getnameinfo()
+ */
+#undef NI_MAXHOST
+#undef NI_MAXSERV
+
+#define NI_MAXHOST 1025
+#define NI_MAXSERV 32
+
+/*
+ * Flag values for getnameinfo()
+ */
+#undef NI_NOFQDN
+#undef NI_NUMERICHOST
+#undef NI_NAMEREQD
+#undef NI_NUMERICSERV
+#undef NI_DGRAM
+#undef NI_NUMERICSCOPE
+
+#define NI_NOFQDN 0x00000001
+#define NI_NUMERICHOST 0x00000002
+#define NI_NAMEREQD 0x00000004
+#define NI_NUMERICSERV 0x00000008
+#define NI_DGRAM 0x00000010
+
+/*
+ * Define to map into irs_ namespace.
+ */
+
+#define IRS_NAMESPACE
+
+#ifdef IRS_NAMESPACE
+
+/*
+ * Use our versions not the ones from the C library.
+ */
+
+#ifdef getnameinfo
+#undef getnameinfo
+#endif /* ifdef getnameinfo */
+#define getnameinfo irs_getnameinfo
+
+#ifdef getaddrinfo
+#undef getaddrinfo
+#endif /* ifdef getaddrinfo */
+#define getaddrinfo irs_getaddrinfo
+
+#ifdef freeaddrinfo
+#undef freeaddrinfo
+#endif /* ifdef freeaddrinfo */
+#define freeaddrinfo irs_freeaddrinfo
+
+#ifdef gai_strerror
+#undef gai_strerror
+#endif /* ifdef gai_strerror */
+#define gai_strerror irs_gai_strerror
+
+#endif /* ifdef IRS_NAMESPACE */
+
+int
+getaddrinfo(const char *, const char *, const struct addrinfo *,
+ struct addrinfo **);
+int
+getnameinfo(const struct sockaddr *, socklen_t, char *, DWORD, char *, DWORD,
+ int);
+void
+freeaddrinfo(struct addrinfo *);
+char *
+gai_strerror(int);
+
+/*
+ * Tell Emacs to use C mode on this file.
+ * Local variables:
+ * mode: c
+ * End:
+ */
+
+#endif /* IRS_NETDB_H */
diff --git a/lib/irs/win32/include/irs/platform.h b/lib/irs/win32/include/irs/platform.h
new file mode 100644
index 0000000..2f0cced
--- /dev/null
+++ b/lib/irs/win32/include/irs/platform.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#ifndef IRS_PLATFORM_H
+#define IRS_PLATFORM_H 1
+
+/*****
+***** Platform-dependent defines.
+*****/
+
+#ifdef LIBIRS_EXPORTS
+#define LIBIRS_EXTERNAL_DATA __declspec(dllexport)
+#else /* ifdef LIBIRS_EXPORTS */
+#define LIBIRS_EXTERNAL_DATA __declspec(dllimport)
+#endif /* ifdef LIBIRS_EXPORTS */
+
+/*
+ * Tell Emacs to use C mode on this file.
+ * Local Variables:
+ * mode: c
+ * End:
+ */
+
+#endif /* IRS_PLATFORM_H */
diff --git a/lib/irs/win32/libirs.def b/lib/irs/win32/libirs.def
new file mode 100644
index 0000000..d4e9f6f
--- /dev/null
+++ b/lib/irs/win32/libirs.def
@@ -0,0 +1,27 @@
+LIBRARY libirs
+
+; Exported Functions
+EXPORTS
+irs_context_create
+irs_context_destroy
+irs_context_get
+irs_context_getappctx
+irs_context_getdnsclient
+irs_context_getdnsconf
+irs_context_getmctx
+irs_context_getresconf
+irs_context_gettask
+irs_context_gettaskmgr
+irs_context_gettimermgr
+irs_dnsconf_destroy
+irs_dnsconf_gettrustedkeys
+irs_dnsconf_load
+irs_freeaddrinfo
+irs_gai_strerror
+irs_getaddrinfo
+irs_getnameinfo
+irs_resconf_destroy
+irs_resconf_getnameservers
+irs_resconf_getndots
+irs_resconf_getsearchlist
+irs_resconf_load
diff --git a/lib/irs/win32/libirs.vcxproj.filters.in b/lib/irs/win32/libirs.vcxproj.filters.in
new file mode 100644
index 0000000..0ffbc07
--- /dev/null
+++ b/lib/irs/win32/libirs.vcxproj.filters.in
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="libirs.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="DLLMain.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="version.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\context.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dnsconf.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\gai_strerror.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\getaddrinfo.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\getnameinfo.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="resconf.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\irs\context.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\irs\dnsconf.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\irs\netdb.h.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\irs\platform.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\irs\resconf.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\irs\types.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\irs\version.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project>
diff --git a/lib/irs/win32/libirs.vcxproj.in b/lib/irs/win32/libirs.vcxproj.in
new file mode 100644
index 0000000..078ec92
--- /dev/null
+++ b/lib/irs/win32/libirs.vcxproj.in
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="@TOOLS_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|@PLATFORM@">
+ <Configuration>Debug</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|@PLATFORM@">
+ <Configuration>Release</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{A4F29CEB-7644-4A7F-BE9E-02B6A90E4919}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>libirs</RootNamespace>
+ @WINDOWS_TARGET_PLATFORM_VERSION@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBIRS_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;..\..\isccfg\include;..\..\dns\include;@LIBXML2_INC@@OPENSSL_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BrowseInformation>true</BrowseInformation>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>..\..\isc\win32\$(Configuration);..\..\dns\win32\$(Configuration);..\..\isccfg\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libisc.lib;libdns.lib;libisccfg.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>.\libirs.def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>@INTRINSIC@</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBIRS_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;..\..\isccfg\include;..\..\dns\include;@LIBXML2_INC@@OPENSSL_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <WholeProgramOptimization>false</WholeProgramOptimization>
+ <StringPooling>true</StringPooling>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>..\..\isc\win32\$(Configuration);..\..\dns\win32\$(Configuration);..\..\isccfg\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libisc.lib;libdns.lib;libisccfg.lib;ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>.\libirs.def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <None Include="libirs.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\context.c" />
+ <ClCompile Include="..\dnsconf.c" />
+ <ClCompile Include="..\gai_strerror.c" />
+ <ClCompile Include="..\getaddrinfo.c" />
+ <ClCompile Include="..\getnameinfo.c" />
+ <ClCompile Include="DLLMain.c" />
+ <ClCompile Include="resconf.c" />
+ <ClCompile Include="version.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\irs\context.h" />
+ <ClInclude Include="..\include\irs\dnsconf.h" />
+ <ClInclude Include="..\include\irs\netdb.h" />
+ <ClInclude Include="..\include\irs\platform.h" />
+ <ClInclude Include="..\include\irs\resconf.h" />
+ <ClInclude Include="..\include\irs\types.h" />
+ <ClInclude Include="..\include\irs\version.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/lib/irs/win32/libirs.vcxproj.user b/lib/irs/win32/libirs.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/lib/irs/win32/libirs.vcxproj.user
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+</Project> \ No newline at end of file
diff --git a/lib/irs/win32/resconf.c b/lib/irs/win32/resconf.c
new file mode 100644
index 0000000..3b46b8e
--- /dev/null
+++ b/lib/irs/win32/resconf.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Note that on Win32 there is normally no resolv.conf since all information
+ * is stored in the registry. Therefore there is no ordering like the
+ * contents of resolv.conf. Since the "search" or "domain" keyword, on
+ * Win32 if a search list is found it is used, otherwise the domain name
+ * is used since they are mutually exclusive. The search list can be entered
+ * in the DNS tab of the "Advanced TCP/IP settings" window under the same place
+ * that you add your nameserver list.
+ */
+
+#define HAVE_GET_WIN32_NAMESERVERS 1
+
+#include "../resconf.c"
+#include <iphlpapi.h>
+
+#define TCPIP_SUBKEY "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"
+
+isc_result_t
+get_win32_searchlist(irs_resconf_t *conf) {
+ isc_result_t result = ISC_R_SUCCESS;
+ HKEY hKey;
+ char searchlist[MAX_PATH];
+ DWORD searchlen = MAX_PATH;
+ LSTATUS status;
+ char *cp;
+
+ REQUIRE(conf != NULL);
+
+ memset(searchlist, 0, MAX_PATH);
+ status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TCPIP_SUBKEY, 0, KEY_READ,
+ &hKey);
+ if (status != ERROR_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ status = RegQueryValueEx(hKey, "SearchList", NULL, NULL,
+ (LPBYTE)searchlist, &searchlen);
+ RegCloseKey(hKey);
+ if (status != ERROR_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ cp = strtok((char *)searchlist, ", \0");
+ while (cp != NULL) {
+ result = add_search(conf, cp);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ cp = strtok(NULL, ", \0");
+ }
+ return (result);
+}
+
+isc_result_t
+get_win32_nameservers(irs_resconf_t *conf) {
+ isc_result_t result;
+ FIXED_INFO *FixedInfo;
+ ULONG BufLen = sizeof(FIXED_INFO);
+ DWORD dwRetVal;
+ IP_ADDR_STRING *pIPAddr;
+
+ REQUIRE(conf != NULL);
+
+ FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, BufLen);
+ if (FixedInfo == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ dwRetVal = GetNetworkParams(FixedInfo, &BufLen);
+ if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
+ GlobalFree(FixedInfo);
+ FixedInfo = GlobalAlloc(GPTR, BufLen);
+ if (FixedInfo == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ dwRetVal = GetNetworkParams(FixedInfo, &BufLen);
+ }
+ if (dwRetVal != ERROR_SUCCESS) {
+ GlobalFree(FixedInfo);
+ return (ISC_R_FAILURE);
+ }
+
+ result = get_win32_searchlist(conf);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (ISC_LIST_EMPTY(conf->searchlist) &&
+ strlen(FixedInfo->DomainName) > 0)
+ {
+ result = add_search(conf, FixedInfo->DomainName);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ /* Get the list of nameservers */
+ pIPAddr = &FixedInfo->DnsServerList;
+ while (pIPAddr) {
+ if (conf->numns >= RESCONFMAXNAMESERVERS) {
+ break;
+ }
+
+ result = add_server(conf->mctx, pIPAddr->IpAddress.String,
+ &conf->nameservers);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ conf->numns++;
+ pIPAddr = pIPAddr->Next;
+ }
+
+cleanup:
+ if (FixedInfo != NULL) {
+ GlobalFree(FixedInfo);
+ }
+ return (result);
+}
diff --git a/lib/irs/win32/version.c b/lib/irs/win32/version.c
new file mode 100644
index 0000000..3c635ad
--- /dev/null
+++ b/lib/irs/win32/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <versions.h>
+
+#include <irs/version.h>
+
+LIBIRS_EXTERNAL_DATA const char irs_version[] = VERSION;
diff --git a/lib/isc/Kyuafile b/lib/isc/Kyuafile
new file mode 100644
index 0000000..c796010
--- /dev/null
+++ b/lib/isc/Kyuafile
@@ -0,0 +1,15 @@
+-- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+--
+-- SPDX-License-Identifier: MPL-2.0
+--
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, you can obtain one at https://mozilla.org/MPL/2.0/.
+--
+-- See the COPYRIGHT file distributed with this work for additional
+-- information regarding copyright ownership.
+
+syntax(2)
+test_suite('bind9')
+
+include('tests/Kyuafile')
diff --git a/lib/isc/Makefile.in b/lib/isc/Makefile.in
new file mode 100644
index 0000000..08f1281
--- /dev/null
+++ b/lib/isc/Makefile.in
@@ -0,0 +1,144 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I${srcdir}/unix/include \
+ -I${srcdir}/pthreads/include \
+ -I./include \
+ -I${srcdir}/include \
+ -I${srcdir} \
+ ${DNS_INCLUDES} \
+ ${OPENSSL_CFLAGS} \
+ ${JSON_C_CFLAGS} \
+ ${LIBXML2_CFLAGS} \
+ ${LIBUV_CFLAGS} \
+ ${ZLIB_CFLAGS}
+CDEFINES =
+CWARNINGS =
+
+# Alphabetically
+UNIXOBJS = unix/pk11_api.@O@ \
+ unix/dir.@O@ unix/errno.@O@ \
+ unix/errno2result.@O@ unix/file.@O@ unix/fsaccess.@O@ \
+ unix/interfaceiter.@O@ unix/meminfo.@O@ \
+ unix/net.@O@ unix/os.@O@ unix/resource.@O@ unix/socket.@O@ \
+ unix/stdio.@O@ unix/stdtime.@O@ \
+ unix/syslog.@O@ unix/time.@O@
+
+THREADOBJS = pthreads/condition.@O@ pthreads/mutex.@O@ pthreads/thread.@O@
+
+WIN32OBJS = win32/condition.@O@ win32/dir.@O@ win32/errno.@O@ \
+ win32/file.@O@ win32/fsaccess.@O@ \
+ win32/meminfo.@O@ win32/once.@O@ \
+ win32/stdtime.@O@ win32/thread.@O@ win32/time.@O@
+
+# Alphabetically
+OBJS = pk11.@O@ pk11_result.@O@ \
+ aes.@O@ app.@O@ assertions.@O@ astack.@O@ \
+ backtrace.@O@ base32.@O@ base64.@O@ \
+ bind9.@O@ buffer.@O@ bufferlist.@O@ \
+ commandline.@O@ counter.@O@ crc64.@O@ error.@O@ entropy.@O@ \
+ event.@O@ hash.@O@ ht.@O@ heap.@O@ hex.@O@ \
+ hmac.@O@ httpd.@O@ iterated_hash.@O@ \
+ lex.@O@ lfsr.@O@ lib.@O@ log.@O@ \
+ managers.@O@ md.@O@ mem.@O@ mutexblock.@O@ \
+ netmgr/netmgr.@O@ netmgr/tcp.@O@ netmgr/udp.@O@ \
+ netmgr/tcpdns.@O@ \
+ netmgr/uverr2result.@O@ netmgr/uv-compat.@O@ \
+ netaddr.@O@ netscope.@O@ nonce.@O@ openssl_shim.@O@ pool.@O@ \
+ parseint.@O@ portset.@O@ quota.@O@ \
+ radix.@O@ random.@O@ ratelimiter.@O@ \
+ region.@O@ regex.@O@ result.@O@ rwlock.@O@ \
+ safe.@O@ serial.@O@ siphash.@O@ sockaddr.@O@ stats.@O@ \
+ string.@O@ symtab.@O@ task.@O@ taskpool.@O@ tls.@O@ \
+ trampoline.@O@ \
+ url.@O@ utf8.@O@ tm.@O@ timer.@O@ version.@O@ \
+ ${UNIXOBJS} ${THREADOBJS}
+SYMTBLOBJS = backtrace-emptytbl.@O@
+
+# Alphabetically
+SRCS = pk11.c pk11_result.c \
+ aes.c app.c assertions.c astack.c \
+ backtrace.c base32.c base64.c bind9.c \
+ buffer.c bufferlist.c commandline.c counter.c crc64.c \
+ entropy.c error.c event.c hash.c ht.c heap.c \
+ hex.c hmac.c httpd.c iterated_hash.c \
+ lex.c lfsr.c lib.c log.c \
+ managers.c md.c mem.c mutexblock.c \
+ netaddr.c netscope.c nonce.c openssl_shim.c pool.c \
+ parseint.c portset.c quota.c radix.c random.c \
+ ratelimiter.c region.c regex.c result.c rwlock.c \
+ safe.c serial.c siphash.c sockaddr.c stats.c string.c \
+ symtab.c task.c taskpool.c timer.c tls.c \
+ trampoline.c \
+ url.c utf8.c tm.c version.c
+
+LIBS = ${OPENSSL_LIBS} ${JSON_C_LIBS} ${LIBUV_LIBS} ${LIBXML2_LIBS} ${ZLIB_LIBS} @LIBS@
+
+# Note: the order of SUBDIRS is important.
+# Attempt to disable parallel processing.
+.NOTPARALLEL:
+.NO_PARALLEL:
+SUBDIRS = include netmgr unix pthreads
+TARGETS = timestamp
+TESTDIRS = @UNITTESTS@
+
+@BIND9_MAKE_RULES@
+
+version.@O@: version.c
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} \
+ -DVERSION=\"${VERSION}\" \
+ -c ${srcdir}/version.c
+
+libisc.@SA@: ${OBJS} ${SYMTBLOBJS}
+ ${AR} ${ARFLAGS} $@ ${OBJS} ${SYMTBLOBJS}
+ ${RANLIB} $@
+
+libisc-nosymtbl.@SA@: ${OBJS}
+ ${AR} ${ARFLAGS} $@ ${OBJS}
+ ${RANLIB} $@
+
+libisc.la: ${OBJS} ${SYMTBLOBJS}
+ ${LIBTOOL_MODE_LINK} \
+ ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libisc.la -rpath ${libdir} \
+ -release "${VERSION}" \
+ ${OBJS} ${SYMTBLOBJS} ${LIBS}
+
+libisc-nosymtbl.la: ${OBJS}
+ ${LIBTOOL_MODE_LINK} \
+ ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libisc-nosymtbl.la -rpath ${libdir} \
+ -release "${VERSION}" \
+ ${OBJS} ${LIBS}
+
+timestamp: libisc.@A@ libisc-nosymtbl.@A@
+ touch timestamp
+
+testdirs: libisc.@A@ libisc-nosymtbl.@A@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}
+
+install:: timestamp installdirs
+ ${LIBTOOL_MODE_INSTALL} ${INSTALL_LIBRARY} libisc.@A@ ${DESTDIR}${libdir}
+
+uninstall::
+ ${LIBTOOL_MODE_UNINSTALL} rm -f ${DESTDIR}${libdir}/libisc.@A@
+
+clean distclean::
+ rm -f libisc.@A@ libisc-nosymtbl.@A@ libisc.la \
+ libisc-nosymtbl.la timestamp
diff --git a/lib/isc/aes.c b/lib/isc/aes.c
new file mode 100644
index 0000000..a0e23c5
--- /dev/null
+++ b/lib/isc/aes.c
@@ -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.
+ */
+
+/*! \file isc/aes.c */
+
+#include <openssl/evp.h>
+#include <openssl/opensslv.h>
+
+#include <isc/aes.h>
+#include <isc/assertions.h>
+#include <isc/platform.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..a3cc7f6
--- /dev/null
+++ b/lib/isc/app.c
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifndef WIN32
+#include <inttypes.h>
+#include <signal.h>
+#include <sys/time.h>
+#endif /* WIN32 */
+
+#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/platform.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#ifdef WIN32
+#include <process.h>
+#else /* WIN32 */
+#include <pthread.h>
+#endif /* WIN32 */
+
+/*%
+ * For BIND9 internal applications built with threads, we use a single app
+ * context and let multiple worker, I/O, timer threads do actual jobs.
+ */
+
+static isc_thread_t blockedthread;
+static atomic_bool is_running = 0;
+
+#ifdef WIN32
+/*
+ * We need to remember which thread is the main thread...
+ */
+static isc_thread_t main_thread;
+#endif /* ifdef WIN32 */
+
+/*
+ * 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)
+
+#ifdef WIN32
+#define NUM_EVENTS 2
+
+enum { RELOAD_EVENT, SHUTDOWN_EVENT };
+#endif /* WIN32 */
+
+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;
+#ifdef WIN32
+ HANDLE hEvents[NUM_EVENTS];
+#else /* WIN32 */
+ isc_mutex_t readylock;
+ isc_condition_t ready;
+#endif /* WIN32 */
+};
+
+static isc_appctx_t isc_g_appctx;
+
+#ifndef WIN32
+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) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__,
+ "handle_signal() %d setup: %s", sig, strbuf);
+ }
+}
+#endif /* ifndef WIN32 */
+
+isc_result_t
+isc_app_ctxstart(isc_appctx_t *ctx) {
+ REQUIRE(VALID_APPCTX(ctx));
+
+ /*
+ * Start an ISC library application.
+ */
+
+ isc_mutex_init(&ctx->lock);
+
+#ifndef WIN32
+ isc_mutex_init(&ctx->readylock);
+ isc_condition_init(&ctx->ready);
+#endif /* WIN32 */
+
+ 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);
+
+#ifdef WIN32
+ main_thread = GetCurrentThread();
+
+ /* Create the reload event in a non-signaled state */
+ ctx->hEvents[RELOAD_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ /* Create the shutdown event in a non-signaled state */
+ ctx->hEvents[SHUTDOWN_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL);
+#else /* WIN32 */
+ int presult;
+ sigset_t sset;
+ char strbuf[ISC_STRERRORSIZE];
+
+ /*
+ * 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)
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__,
+ "isc_app_start() sigsetops: %s", strbuf);
+ }
+ presult = pthread_sigmask(SIG_BLOCK, &sset, NULL);
+ if (presult != 0) {
+ strerror_r(presult, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__,
+ "isc_app_start() pthread_sigmask: %s", strbuf);
+ }
+
+#endif /* WIN32 */
+
+ 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));
+
+#ifdef WIN32
+ REQUIRE(main_thread == GetCurrentThread());
+#endif /* ifdef WIN32 */
+
+ 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);
+ }
+
+#ifndef WIN32
+ /*
+ * BIND9 internal tools using multiple contexts do not
+ * rely on signal. */
+ if (isc_bind9 && ctx != &isc_g_appctx) {
+ return (ISC_R_SUCCESS);
+ }
+#endif /* WIN32 */
+
+ /*
+ * 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)) {
+#ifdef WIN32
+ DWORD dwWaitResult = WaitForMultipleObjects(
+ NUM_EVENTS, ctx->hEvents, FALSE, INFINITE);
+
+ /* See why we returned */
+
+ if (WaitSucceeded(dwWaitResult, NUM_EVENTS)) {
+ /*
+ * The return was due to one of the events
+ * being signaled
+ */
+ switch (WaitSucceededIndex(dwWaitResult)) {
+ case RELOAD_EVENT:
+ atomic_store_release(&ctx->want_reload, true);
+
+ break;
+
+ case SHUTDOWN_EVENT:
+ atomic_store_release(&ctx->want_shutdown, true);
+ break;
+ }
+ }
+#else /* WIN32 */
+ if (isc_bind9) {
+ sigset_t sset;
+ int sig;
+ /*
+ * BIND9 internal; single context:
+ * Wait for SIGHUP, SIGINT, or SIGTERM.
+ */
+ if (sigemptyset(&sset) != 0 ||
+ sigaddset(&sset, SIGHUP) != 0 ||
+ sigaddset(&sset, SIGINT) != 0 ||
+ sigaddset(&sset, SIGTERM) != 0)
+ {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__,
+ "isc_app_run() sigsetops: %s",
+ strbuf);
+ }
+
+ 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 {
+ /*
+ * External, or BIND9 using multiple contexts:
+ * 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);
+ }
+ }
+#endif /* WIN32 */
+ 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;
+
+ REQUIRE(atomic_compare_exchange_strong_acq_rel(&is_running,
+ &(bool){ false }, true));
+ result = isc_app_ctxrun(&isc_g_appctx);
+ atomic_store_release(&is_running, false);
+
+ return (result);
+}
+
+bool
+isc_app_isrunning() {
+ 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))
+ {
+#ifdef WIN32
+ SetEvent(ctx->hEvents[SHUTDOWN_EVENT]);
+#else /* WIN32 */
+ if (isc_bind9 && ctx != &isc_g_appctx) {
+ /* BIND9 internal, but using multiple contexts */
+ atomic_store_release(&ctx->want_shutdown, true);
+ } else if (isc_bind9) {
+ /* BIND9 internal, single context */
+ if (kill(getpid(), SIGTERM) < 0) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__,
+ "isc_app_shutdown() "
+ "kill: %s",
+ strbuf);
+ }
+ } else {
+ /* External, multiple contexts */
+ atomic_store_release(&ctx->want_shutdown, true);
+ SIGNAL(&ctx->ready);
+ }
+#endif /* WIN32 */
+ }
+}
+
+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)) {
+#ifdef WIN32
+ SetEvent(ctx->hEvents[RELOAD_EVENT]);
+#else /* WIN32 */
+ if (isc_bind9 && ctx != &isc_g_appctx) {
+ /* BIND9 internal, but using multiple contexts */
+ atomic_store_release(&ctx->want_reload, true);
+ } else if (isc_bind9) {
+ /* BIND9 internal, single context */
+ if (kill(getpid(), SIGHUP) < 0) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__,
+ "isc_app_reload() "
+ "kill: %s",
+ strbuf);
+ }
+ } else {
+ /* External, multiple contexts */
+ atomic_store_release(&ctx->want_reload, true);
+ SIGNAL(&ctx->ready);
+ }
+#endif /* WIN32 */
+ }
+}
+
+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);
+#ifndef WIN32
+ isc_mutex_destroy(&ctx->readylock);
+ isc_condition_destroy(&ctx->ready);
+#endif /* WIN32 */
+}
+
+void
+isc_app_finish(void) {
+ isc_app_ctxfinish(&isc_g_appctx);
+}
+
+void
+isc_app_block(void) {
+ REQUIRE(atomic_load_acquire(&isc_g_appctx.running));
+ REQUIRE(atomic_compare_exchange_strong_acq_rel(&isc_g_appctx.blocked,
+ &(bool){ false }, true));
+
+#ifdef WIN32
+ blockedthread = GetCurrentThread();
+#else /* WIN32 */
+ sigset_t sset;
+ 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);
+#endif /* WIN32 */
+}
+
+void
+isc_app_unblock(void) {
+ REQUIRE(atomic_load_acquire(&isc_g_appctx.running));
+ REQUIRE(atomic_compare_exchange_strong_acq_rel(&isc_g_appctx.blocked,
+ &(bool){ true }, false));
+
+#ifdef WIN32
+ REQUIRE(blockedthread == GetCurrentThread());
+#else /* WIN32 */
+ REQUIRE(blockedthread == pthread_self());
+
+ sigset_t sset;
+ RUNTIME_CHECK(sigemptyset(&sset) == 0 &&
+ sigaddset(&sset, SIGINT) == 0 &&
+ sigaddset(&sset, SIGTERM) == 0);
+ RUNTIME_CHECK(pthread_sigmask(SIG_BLOCK, &sset, NULL) == 0);
+#endif /* WIN32 */
+}
+
+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->magic = APPCTX_MAGIC;
+
+ ctx->mctx = NULL;
+ isc_mem_attach(mctx, &ctx->mctx);
+
+ *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..1f16fb3
--- /dev/null
+++ b/lib/isc/assertions.c
@@ -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.
+ */
+
+/*! \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 i, nframes;
+ const char *logsuffix = ".";
+ const char *fname;
+ isc_result_t result;
+
+ result = isc_backtrace_gettrace(tracebuf, BACKTRACE_MAXFRAME, &nframes);
+ if (result == ISC_R_SUCCESS && nframes > 0) {
+ logsuffix = ", back trace";
+ }
+
+ fprintf(stderr, "%s:%d: %s(%s) failed%s\n", file, line,
+ isc_assertion_typetotext(type), cond, logsuffix);
+
+ if (result == ISC_R_SUCCESS) {
+ for (i = 0; i < nframes; i++) {
+ unsigned long offset;
+
+ fname = NULL;
+ result = isc_backtrace_getsymbol(tracebuf[i], &fname,
+ &offset);
+ if (result == ISC_R_SUCCESS) {
+ fprintf(stderr, "#%d %p in %s()+0x%lx\n", i,
+ tracebuf[i], fname, offset);
+ } else {
+ fprintf(stderr, "#%d %p in ??\n", i,
+ tracebuf[i]);
+ }
+ }
+ }
+ 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-emptytbl.c b/lib/isc/backtrace-emptytbl.c
new file mode 100644
index 0000000..0c6f61f
--- /dev/null
+++ b/lib/isc/backtrace-emptytbl.c
@@ -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.
+ */
+
+/*! \file */
+
+/*
+ * This file defines an empty (default) symbol table used in backtrace.c
+ * If the application wants to have a complete symbol table, it should redefine
+ * isc__backtrace_symtable with the complete table in some way, and link the
+ * version of the library not including this definition
+ * (e.g. libisc-nosymbol.a).
+ */
+
+#include <isc/backtrace.h>
+
+LIBISC_EXTERNAL_DATA const int isc__backtrace_nsymbols = 0;
+LIBISC_EXTERNAL_DATA const isc_backtrace_symmap_t isc__backtrace_symtable[] = {
+ { NULL, "" }
+};
diff --git a/lib/isc/backtrace.c b/lib/isc/backtrace.c
new file mode 100644
index 0000000..69488de
--- /dev/null
+++ b/lib/isc/backtrace.c
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_LIBCTRACE
+#include <execinfo.h>
+#endif /* ifdef HAVE_LIBCTRACE */
+
+#include <isc/backtrace.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#ifdef USE_BACKTRACE
+/*
+ * Getting a back trace of a running process is tricky and highly platform
+ * dependent. Our current approach is as follows:
+ * 1. If the system library supports the "backtrace()" function, use it.
+ * 2. Otherwise, if the compiler is gcc and the architecture is x86_64 or IA64,
+ * then use gcc's (hidden) Unwind_Backtrace() function. Note that this
+ * function doesn't work for C programs on many other architectures.
+ * 3. Otherwise, if the architecture x86 or x86_64, try to unwind the stack
+ * frame following frame pointers. This assumes the executable binary
+ * compiled with frame pointers; this is not always true for x86_64 (rather,
+ * compiler optimizations often disable frame pointers). The validation
+ * checks in getnextframeptr() hopefully rejects bogus values stored in
+ * the RBP register in such a case. If the backtrace function itself crashes
+ * due to this problem, the whole package should be rebuilt with
+ * --disable-backtrace.
+ */
+#ifdef HAVE_LIBCTRACE
+#define BACKTRACE_LIBC
+#elif defined(HAVE_UNWIND_BACKTRACE)
+#define BACKTRACE_GCC
+#elif defined(WIN32)
+#define BACKTRACE_WIN32
+#elif defined(__x86_64__) || defined(__i386__)
+#define BACKTRACE_X86STACK
+#else /* ifdef HAVE_LIBCTRACE */
+#define BACKTRACE_DISABLED
+#endif /* HAVE_LIBCTRACE */
+#else /* USE_BACKTRACE */
+#define BACKTRACE_DISABLED
+#endif /* USE_BACKTRACE */
+
+#ifdef BACKTRACE_LIBC
+isc_result_t
+isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
+ int n;
+
+ /*
+ * Validate the arguments: intentionally avoid using REQUIRE().
+ * See notes in backtrace.h.
+ */
+ if (addrs == NULL || nframes == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * 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 (ISC_R_NOTFOUND);
+ }
+ n--;
+ memmove(addrs, &addrs[1], sizeof(void *) * n);
+ *nframes = n;
+ return (ISC_R_SUCCESS);
+}
+#elif defined(BACKTRACE_GCC)
+extern int
+_Unwind_Backtrace(void *fn, void *a);
+extern void *
+_Unwind_GetIP(void *ctx);
+
+typedef struct {
+ void **result;
+ int max_depth;
+ int skip_count;
+ int count;
+} trace_arg_t;
+
+static int
+btcallback(void *uc, void *opq) {
+ trace_arg_t *arg = (trace_arg_t *)opq;
+
+ if (arg->skip_count > 0) {
+ arg->skip_count--;
+ } else {
+ arg->result[arg->count++] = (void *)_Unwind_GetIP(uc);
+ }
+ if (arg->count == arg->max_depth) {
+ return (5); /* _URC_END_OF_STACK */
+ }
+ return (0); /* _URC_NO_REASON */
+}
+
+isc_result_t
+isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
+ trace_arg_t arg;
+
+ /* Argument validation: see above. */
+ if (addrs == NULL || nframes == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ arg.skip_count = 1;
+ arg.result = addrs;
+ arg.max_depth = maxaddrs;
+ arg.count = 0;
+ _Unwind_Backtrace(btcallback, &arg);
+
+ *nframes = arg.count;
+
+ return (ISC_R_SUCCESS);
+}
+#elif defined(BACKTRACE_WIN32)
+isc_result_t
+isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
+ unsigned long ftc = (unsigned long)maxaddrs;
+
+ *nframes = (int)CaptureStackBackTrace(1, ftc, addrs, NULL);
+ return (ISC_R_SUCCESS);
+}
+#elif defined(BACKTRACE_X86STACK)
+#ifdef __x86_64__
+static unsigned long
+getrbp(void) {
+ unsigned long rbp;
+ __asm("movq %%rbp, %0\n" : "=r"(rbp));
+ return rbp;
+}
+#endif /* ifdef __x86_64__ */
+
+static void **
+getnextframeptr(void **sp) {
+ void **newsp = (void **)*sp;
+
+ /*
+ * Perform sanity check for the new frame pointer, derived from
+ * google glog. This can actually be bogus depending on compiler.
+ */
+
+ /* prohibit the stack frames from growing downwards */
+ if (newsp <= sp) {
+ return (NULL);
+ }
+
+ /* A heuristics to reject "too large" frame: this actually happened. */
+ if ((char *)newsp - (char *)sp > 100000) {
+ return (NULL);
+ }
+
+ /*
+ * Not sure if other checks used in glog are needed at this moment.
+ * For our purposes we don't have to consider non-contiguous frames,
+ * for example.
+ */
+
+ return (newsp);
+}
+
+isc_result_t
+isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
+ int i = 0;
+ void **sp;
+
+ /* Argument validation: see above. */
+ if (addrs == NULL || nframes == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+#ifdef __x86_64__
+ sp = (void **)getrbp();
+ if (sp == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ /*
+ * sp is the frame ptr of this function itself due to the call to
+ * getrbp(), so need to unwind one frame for consistency.
+ */
+ sp = getnextframeptr(sp);
+#else /* ifdef __x86_64__ */
+ /*
+ * i386: the frame pointer is stored 2 words below the address for the
+ * first argument. Note that the body of this function cannot be
+ * inlined since it depends on the address of the function argument.
+ */
+ sp = (void **)&addrs - 2;
+#endif /* ifdef __x86_64__ */
+
+ while (sp != NULL && i < maxaddrs) {
+ addrs[i++] = *(sp + 1);
+ sp = getnextframeptr(sp);
+ }
+
+ *nframes = i;
+
+ return (ISC_R_SUCCESS);
+}
+#elif defined(BACKTRACE_DISABLED)
+isc_result_t
+isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) {
+ /* Argument validation: see above. */
+ if (addrs == NULL || nframes == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ UNUSED(maxaddrs);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+#endif /* ifdef BACKTRACE_LIBC */
+
+isc_result_t
+isc_backtrace_getsymbolfromindex(int idx, const void **addrp,
+ const char **symbolp) {
+ REQUIRE(addrp != NULL && *addrp == NULL);
+ REQUIRE(symbolp != NULL && *symbolp == NULL);
+
+ if (idx < 0 || idx >= isc__backtrace_nsymbols) {
+ return (ISC_R_RANGE);
+ }
+
+ *addrp = isc__backtrace_symtable[idx].addr;
+ *symbolp = isc__backtrace_symtable[idx].symbol;
+ return (ISC_R_SUCCESS);
+}
+
+static int
+symtbl_compare(const void *addr, const void *entryarg) {
+ const isc_backtrace_symmap_t *entry = entryarg;
+ const isc_backtrace_symmap_t *end =
+ &isc__backtrace_symtable[isc__backtrace_nsymbols - 1];
+
+ if (isc__backtrace_nsymbols == 1 || entry == end) {
+ if (addr >= entry->addr) {
+ /*
+ * If addr is equal to or larger than that of the last
+ * entry of the table, we cannot be sure if this is
+ * within a valid range so we consider it valid.
+ */
+ return (0);
+ }
+ return (-1);
+ }
+
+ /* entry + 1 is a valid entry from now on. */
+ if (addr < entry->addr) {
+ return (-1);
+ } else if (addr >= (entry + 1)->addr) {
+ return (1);
+ }
+ return (0);
+}
+
+isc_result_t
+isc_backtrace_getsymbol(const void *addr, const char **symbolp,
+ unsigned long *offsetp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_backtrace_symmap_t *found;
+
+ /*
+ * Validate the arguments: intentionally avoid using REQUIRE().
+ * See notes in backtrace.h.
+ */
+ if (symbolp == NULL || *symbolp != NULL || offsetp == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (isc__backtrace_nsymbols < 1) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /*
+ * Search the table for the entry that meets:
+ * entry.addr <= addr < next_entry.addr.
+ */
+ found = bsearch(addr, isc__backtrace_symtable, isc__backtrace_nsymbols,
+ sizeof(isc__backtrace_symtable[0]), symtbl_compare);
+ if (found == NULL) {
+ result = ISC_R_NOTFOUND;
+ } else {
+ *symbolp = found->symbol;
+ *offsetp = (unsigned long)((const char *)addr -
+ (char *)found->addr);
+ }
+
+ return (result);
+}
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/bind9.c b/lib/isc/bind9.c
new file mode 100644
index 0000000..2b6f474
--- /dev/null
+++ b/lib/isc/bind9.c
@@ -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.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/bind9.h>
+
+/*
+ * This determines whether we are using the libisc/libdns libraries
+ * in BIND9 or in some other application. It is initialized to true
+ * and remains unchanged for BIND9 and related tools; export library
+ * clients will run isc_lib_register(), which sets it to false,
+ * overriding certain BIND9 behaviors.
+ */
+LIBISC_EXTERNAL_DATA bool isc_bind9 = true;
diff --git a/lib/isc/buffer.c b/lib/isc/buffer.c
new file mode 100644
index 0000000..b284840
--- /dev/null
+++ b/lib/isc/buffer.c
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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_init(isc_buffer_t *b, void *base, unsigned int length) {
+ /*
+ * Make 'b' refer to the 'length'-byte region starting at 'base'.
+ * XXXDCL see the comment in buffer.h about base being const.
+ */
+ ISC__BUFFER_INIT(b, base, length);
+}
+
+void
+isc__buffer_initnull(isc_buffer_t *b) {
+ /*
+ * Initialize a new buffer which has no backing store. This can
+ * later be grown as needed and swapped in place.
+ */
+ ISC__BUFFER_INIT(b, NULL, 0);
+}
+
+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_invalidate(isc_buffer_t *b) {
+ /*
+ * Make 'b' an invalid buffer.
+ */
+ ISC__BUFFER_INVALIDATE(b);
+}
+
+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_region(isc_buffer_t *b, isc_region_t *r) {
+ /*
+ * Make 'r' refer to the region of 'b'.
+ */
+ ISC__BUFFER_REGION(b, r);
+}
+
+void
+isc__buffer_usedregion(const isc_buffer_t *b, isc_region_t *r) {
+ /*
+ * Make 'r' refer to the used region of 'b'.
+ */
+ ISC__BUFFER_USEDREGION(b, r);
+}
+
+void
+isc__buffer_availableregion(isc_buffer_t *b, isc_region_t *r) {
+ /*
+ * Make 'r' refer to the available region of 'b'.
+ */
+ ISC__BUFFER_AVAILABLEREGION(b, r);
+}
+
+void
+isc__buffer_add(isc_buffer_t *b, unsigned int n) {
+ /*
+ * Increase the 'used' region of 'b' by 'n' bytes.
+ */
+ ISC__BUFFER_ADD(b, n);
+}
+
+void
+isc__buffer_subtract(isc_buffer_t *b, unsigned int n) {
+ /*
+ * Decrease the 'used' region of 'b' by 'n' bytes.
+ */
+ ISC__BUFFER_SUBTRACT(b, n);
+}
+
+void
+isc__buffer_clear(isc_buffer_t *b) {
+ /*
+ * Make the used region empty.
+ */
+ ISC__BUFFER_CLEAR(b);
+}
+
+void
+isc__buffer_consumedregion(isc_buffer_t *b, isc_region_t *r) {
+ /*
+ * Make 'r' refer to the consumed region of 'b'.
+ */
+ ISC__BUFFER_CONSUMEDREGION(b, r);
+}
+
+void
+isc__buffer_remainingregion(isc_buffer_t *b, isc_region_t *r) {
+ /*
+ * Make 'r' refer to the remaining region of 'b'.
+ */
+ ISC__BUFFER_REMAININGREGION(b, r);
+}
+
+void
+isc__buffer_activeregion(isc_buffer_t *b, isc_region_t *r) {
+ /*
+ * Make 'r' refer to the active region of 'b'.
+ */
+ ISC__BUFFER_ACTIVEREGION(b, r);
+}
+
+void
+isc__buffer_setactive(isc_buffer_t *b, unsigned int n) {
+ /*
+ * Sets the end of the active region 'n' bytes after current.
+ */
+ ISC__BUFFER_SETACTIVE(b, n);
+}
+
+void
+isc__buffer_first(isc_buffer_t *b) {
+ /*
+ * Make the consumed region empty.
+ */
+ ISC__BUFFER_FIRST(b);
+}
+
+void
+isc__buffer_forward(isc_buffer_t *b, unsigned int n) {
+ /*
+ * Increase the 'consumed' region of 'b' by 'n' bytes.
+ */
+ ISC__BUFFER_FORWARD(b, n);
+}
+
+void
+isc__buffer_back(isc_buffer_t *b, unsigned int n) {
+ /*
+ * Decrease the 'consumed' region of 'b' by 'n' bytes.
+ */
+ ISC__BUFFER_BACK(b, n);
+}
+
+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);
+}
+
+void
+isc__buffer_putuint8(isc_buffer_t *b, uint8_t val) {
+ ISC__BUFFER_PUTUINT8(b, val);
+}
+
+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);
+}
+
+void
+isc__buffer_putuint16(isc_buffer_t *b, uint16_t val) {
+ ISC__BUFFER_PUTUINT16(b, val);
+}
+
+void
+isc__buffer_putuint24(isc_buffer_t *b, uint32_t val) {
+ ISC__BUFFER_PUTUINT24(b, val);
+}
+
+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);
+}
+
+void
+isc__buffer_putuint32(isc_buffer_t *b, uint32_t val) {
+ ISC__BUFFER_PUTUINT32(b, val);
+}
+
+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_putuint48(isc_buffer_t *b, uint64_t val) {
+ isc_result_t result;
+ uint16_t valhi;
+ uint32_t vallo;
+
+ REQUIRE(ISC_BUFFER_VALID(b));
+ if (ISC_UNLIKELY(b->autore)) {
+ result = isc_buffer_reserve(&b, 6);
+ REQUIRE(result == ISC_R_SUCCESS);
+ }
+ REQUIRE(isc_buffer_availablelength(b) >= 6);
+
+ valhi = (uint16_t)(val >> 32);
+ vallo = (uint32_t)(val & 0xFFFFFFFF);
+ ISC__BUFFER_PUTUINT16(b, valhi);
+ ISC__BUFFER_PUTUINT32(b, vallo);
+}
+
+void
+isc__buffer_putmem(isc_buffer_t *b, const unsigned char *base,
+ unsigned int length) {
+ ISC__BUFFER_PUTMEM(b, base, length);
+}
+
+void
+isc__buffer_putstr(isc_buffer_t *b, const char *source) {
+ ISC__BUFFER_PUTSTR(b, source);
+}
+
+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 (ISC_UNLIKELY(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 (ISC_UNLIKELY(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) {
+ unsigned char *bdata;
+ uint64_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);
+ }
+
+ /*
+ * XXXMUKS: This is far more expensive than plain realloc() as
+ * it doesn't remap pages, but does ordinary copy. So is
+ * isc_mem_reallocate(), which has additional issues.
+ */
+ bdata = isc_mem_get((*dynbuffer)->mctx, (unsigned int)len);
+
+ memmove(bdata, (*dynbuffer)->base, (*dynbuffer)->length);
+ isc_mem_put((*dynbuffer)->mctx, (*dynbuffer)->base,
+ (*dynbuffer)->length);
+
+ (*dynbuffer)->base = bdata;
+ (*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 (ISC_UNLIKELY(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/bufferlist.c b/lib/isc/bufferlist.c
new file mode 100644
index 0000000..e5f2583
--- /dev/null
+++ b/lib/isc/bufferlist.c
@@ -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.
+ */
+
+/*! \file */
+
+#include <stddef.h>
+
+#include <isc/buffer.h>
+#include <isc/bufferlist.h>
+#include <isc/util.h>
+
+unsigned int
+isc_bufferlist_usedcount(isc_bufferlist_t *bl) {
+ isc_buffer_t *buffer;
+ unsigned int length;
+
+ REQUIRE(bl != NULL);
+
+ length = 0;
+ buffer = ISC_LIST_HEAD(*bl);
+ while (buffer != NULL) {
+ REQUIRE(ISC_BUFFER_VALID(buffer));
+ length += isc_buffer_usedlength(buffer);
+ buffer = ISC_LIST_NEXT(buffer, link);
+ }
+
+ return (length);
+}
+
+unsigned int
+isc_bufferlist_availablecount(isc_bufferlist_t *bl) {
+ isc_buffer_t *buffer;
+ unsigned int length;
+
+ REQUIRE(bl != NULL);
+
+ length = 0;
+ buffer = ISC_LIST_HEAD(*bl);
+ while (buffer != NULL) {
+ REQUIRE(ISC_BUFFER_VALID(buffer));
+ length += isc_buffer_availablelength(buffer);
+ buffer = ISC_LIST_NEXT(buffer, link);
+ }
+
+ return (length);
+}
diff --git a/lib/isc/commandline.c b/lib/isc/commandline.c
new file mode 100644
index 0000000..dfe7023
--- /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. */
+LIBISC_EXTERNAL_DATA int isc_commandline_index = 1;
+/*% Character checked for validity. */
+LIBISC_EXTERNAL_DATA int isc_commandline_option;
+/*% Argument associated with option. */
+LIBISC_EXTERNAL_DATA char *isc_commandline_argument;
+/*% For printing error messages. */
+LIBISC_EXTERNAL_DATA char *isc_commandline_progname;
+/*% Print error messages. */
+LIBISC_EXTERNAL_DATA bool isc_commandline_errprint = true;
+/*% Reset processing. */
+LIBISC_EXTERNAL_DATA 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/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/entropy.c b/lib/isc/entropy.c
new file mode 100644
index 0000000..ce79ba2
--- /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(__FILE__, __LINE__, "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/error.c b/lib/isc/error.c
new file mode 100644
index 0000000..336be84
--- /dev/null
+++ b/lib/isc/error.c
@@ -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.
+ */
+
+/*! \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 *, va_list)
+ ISC_FORMAT_PRINTF(3, 0);
+
+/*% Default fatal callback. */
+static void
+default_fatal_callback(const char *, int, 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 *format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ (unexpected_callback)(file, line, format, args);
+ va_end(args);
+}
+
+void
+isc_error_fatal(const char *file, int line, const char *format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ (fatal_callback)(file, line, format, args);
+ va_end(args);
+ abort();
+}
+
+void
+isc_error_runtimecheck(const char *file, int line, const char *expression) {
+ isc_error_fatal(file, line, "RUNTIME_CHECK(%s) failed", expression);
+}
+
+static void
+default_unexpected_callback(const char *file, int line, const char *format,
+ va_list args) {
+ fprintf(stderr, "%s:%d: ", file, line);
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+static void
+default_fatal_callback(const char *file, int line, const char *format,
+ va_list args) {
+ fprintf(stderr, "%s:%d: fatal error: ", file, line);
+ 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/fsaccess.c b/lib/isc/fsaccess.c
new file mode 100644
index 0000000..54bf38a
--- /dev/null
+++ b/lib/isc/fsaccess.c
@@ -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.
+ */
+
+/*! \file
+ * \brief
+ * This file contains the OS-independent functionality of the API.
+ */
+#include <stdbool.h>
+
+#include <isc/fsaccess.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+/*!
+ * Shorthand. Maybe ISC__FSACCESS_PERMISSIONBITS should not even be in
+ * <isc/fsaccess.h>. Could check consistency with sizeof(isc_fsaccess_t)
+ * and the number of bits in each function.
+ */
+#define STEP (ISC__FSACCESS_PERMISSIONBITS)
+#define GROUP (STEP)
+#define OTHER (STEP * 2)
+
+void
+isc_fsaccess_add(int trustee, int permission, isc_fsaccess_t *access) {
+ REQUIRE(trustee <= 0x7);
+ REQUIRE(permission <= 0xFF);
+
+ if ((trustee & ISC_FSACCESS_OWNER) != 0) {
+ *access |= permission;
+ }
+
+ if ((trustee & ISC_FSACCESS_GROUP) != 0) {
+ *access |= (permission << GROUP);
+ }
+
+ if ((trustee & ISC_FSACCESS_OTHER) != 0) {
+ *access |= (permission << OTHER);
+ }
+}
+
+void
+isc_fsaccess_remove(int trustee, int permission, isc_fsaccess_t *access) {
+ REQUIRE(trustee <= 0x7);
+ REQUIRE(permission <= 0xFF);
+
+ if ((trustee & ISC_FSACCESS_OWNER) != 0) {
+ *access &= ~permission;
+ }
+
+ if ((trustee & ISC_FSACCESS_GROUP) != 0) {
+ *access &= ~(permission << GROUP);
+ }
+
+ if ((trustee & ISC_FSACCESS_OTHER) != 0) {
+ *access &= ~(permission << OTHER);
+ }
+}
+
+static isc_result_t
+check_bad_bits(isc_fsaccess_t access, bool is_dir) {
+ isc_fsaccess_t bits;
+
+ /*
+ * Check for disallowed user bits.
+ */
+ if (is_dir) {
+ bits = ISC_FSACCESS_READ | ISC_FSACCESS_WRITE |
+ ISC_FSACCESS_EXECUTE;
+ } else {
+ bits = ISC_FSACCESS_CREATECHILD | ISC_FSACCESS_ACCESSCHILD |
+ ISC_FSACCESS_DELETECHILD | ISC_FSACCESS_LISTDIRECTORY;
+ }
+
+ /*
+ * Set group bad bits.
+ */
+ bits |= bits << STEP;
+ /*
+ * Set other bad bits.
+ */
+ bits |= bits << STEP;
+
+ if ((access & bits) != 0) {
+ if (is_dir) {
+ return (ISC_R_NOTFILE);
+ } else {
+ return (ISC_R_NOTDIRECTORY);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/hash.c b/lib/isc/hash.c
new file mode 100644
index 0000000..b3fb97a
--- /dev/null
+++ b/lib/isc/hash.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.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#if defined(WIN32) || defined(WIN64)
+#include <malloc.h>
+#endif /* if defined(WIN32) || defined(WIN64) */
+
+#include "entropy_private.h"
+#include "isc/hash.h" /* IWYU pragma: keep */
+#include "isc/likely.h"
+#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 (ISC_UNLIKELY(!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 (ISC_UNLIKELY(!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..442ae7f
--- /dev/null
+++ b/lib/isc/hmac.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.
+ */
+
+#include <openssl/hmac.h>
+#include <openssl/opensslv.h>
+
+#include <isc/assertions.h>
+#include <isc/hmac.h>
+#include <isc/md.h>
+#include <isc/platform.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) {
+ HMAC_CTX *hmac = HMAC_CTX_new();
+ RUNTIME_CHECK(hmac != NULL);
+ return ((struct hmac *)hmac);
+}
+
+void
+isc_hmac_free(isc_hmac_t *hmac) {
+ if (ISC_UNLIKELY(hmac == NULL)) {
+ return;
+ }
+
+ HMAC_CTX_free(hmac);
+}
+
+isc_result_t
+isc_hmac_init(isc_hmac_t *hmac, const void *key, size_t keylen,
+ const isc_md_type_t *md_type) {
+ REQUIRE(hmac != NULL);
+ REQUIRE(key != NULL);
+
+ if (md_type == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (HMAC_Init_ex(hmac, key, keylen, md_type, NULL) != 1) {
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_hmac_reset(isc_hmac_t *hmac) {
+ REQUIRE(hmac != NULL);
+
+ if (HMAC_CTX_reset(hmac) != 1) {
+ 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 (ISC_UNLIKELY(buf == NULL || len == 0)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (HMAC_Update(hmac, buf, len) != 1) {
+ 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);
+
+ if (HMAC_Final(hmac, digest, digestlen) != 1) {
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+const isc_md_type_t *
+isc_hmac_get_md_type(isc_hmac_t *hmac) {
+ REQUIRE(hmac != NULL);
+
+ return (HMAC_CTX_get_md(hmac));
+}
+
+size_t
+isc_hmac_get_size(isc_hmac_t *hmac) {
+ REQUIRE(hmac != NULL);
+
+ return ((size_t)EVP_MD_size(HMAC_CTX_get_md(hmac)));
+}
+
+int
+isc_hmac_get_block_size(isc_hmac_t *hmac) {
+ REQUIRE(hmac != NULL);
+
+ return (EVP_MD_block_size(HMAC_CTX_get_md(hmac)));
+}
+
+isc_result_t
+isc_hmac(const isc_md_type_t *type, const void *key, const int 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..07a36b4
--- /dev/null
+++ b/lib/isc/ht.c
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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)
+
+struct isc_ht_node {
+ void *value;
+ isc_ht_node_t *next;
+ size_t keysize;
+ unsigned char key[FLEXIBLE_ARRAY_MEMBER];
+};
+
+struct isc_ht {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ size_t size;
+ size_t mask;
+ unsigned int count;
+ isc_ht_node_t **table;
+};
+
+struct isc_ht_iter {
+ isc_ht_t *ht;
+ size_t i;
+ isc_ht_node_t *cur;
+};
+
+void
+isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits) {
+ isc_ht_t *ht = NULL;
+ size_t i;
+
+ REQUIRE(htp != NULL && *htp == NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(bits >= 1 && bits <= (sizeof(size_t) * 8 - 1));
+
+ ht = isc_mem_get(mctx, sizeof(struct isc_ht));
+
+ ht->mctx = NULL;
+ isc_mem_attach(mctx, &ht->mctx);
+
+ ht->size = ((size_t)1 << bits);
+ ht->mask = ((size_t)1 << bits) - 1;
+ ht->count = 0;
+
+ ht->table = isc_mem_get(ht->mctx, ht->size * sizeof(isc_ht_node_t *));
+
+ for (i = 0; i < ht->size; i++) {
+ ht->table[i] = NULL;
+ }
+
+ ht->magic = ISC_HT_MAGIC;
+
+ *htp = ht;
+}
+
+void
+isc_ht_destroy(isc_ht_t **htp) {
+ isc_ht_t *ht;
+ size_t i;
+
+ REQUIRE(htp != NULL);
+
+ ht = *htp;
+ *htp = NULL;
+
+ REQUIRE(ISC_HT_VALID(ht));
+
+ ht->magic = 0;
+
+ for (i = 0; i < ht->size; i++) {
+ isc_ht_node_t *node = ht->table[i];
+ while (node != NULL) {
+ isc_ht_node_t *next = node->next;
+ ht->count--;
+ isc_mem_put(ht->mctx, node,
+ offsetof(isc_ht_node_t, key) +
+ node->keysize);
+ node = next;
+ }
+ }
+
+ INSIST(ht->count == 0);
+
+ isc_mem_put(ht->mctx, ht->table, ht->size * sizeof(isc_ht_node_t *));
+ isc_mem_putanddetach(&ht->mctx, ht, sizeof(struct isc_ht));
+}
+
+isc_result_t
+isc_ht_add(isc_ht_t *ht, const unsigned char *key, uint32_t keysize,
+ void *value) {
+ isc_ht_node_t *node;
+ uint32_t hash;
+
+ REQUIRE(ISC_HT_VALID(ht));
+ REQUIRE(key != NULL && keysize > 0);
+
+ hash = isc_hash_function(key, keysize, true);
+ node = ht->table[hash & ht->mask];
+ while (node != NULL) {
+ if (keysize == node->keysize &&
+ memcmp(key, node->key, keysize) == 0)
+ {
+ return (ISC_R_EXISTS);
+ }
+ node = node->next;
+ }
+
+ node = isc_mem_get(ht->mctx, offsetof(isc_ht_node_t, key) + keysize);
+
+ memmove(node->key, key, keysize);
+ node->keysize = keysize;
+ node->next = ht->table[hash & ht->mask];
+ node->value = value;
+
+ ht->count++;
+ ht->table[hash & ht->mask] = node;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_ht_find(const isc_ht_t *ht, const unsigned char *key, uint32_t keysize,
+ void **valuep) {
+ isc_ht_node_t *node;
+ uint32_t hash;
+
+ REQUIRE(ISC_HT_VALID(ht));
+ REQUIRE(key != NULL && keysize > 0);
+ REQUIRE(valuep == NULL || *valuep == NULL);
+
+ hash = isc_hash_function(key, keysize, true);
+ node = ht->table[hash & ht->mask];
+ while (node != NULL) {
+ if (keysize == node->keysize &&
+ memcmp(key, node->key, keysize) == 0)
+ {
+ if (valuep != NULL) {
+ *valuep = node->value;
+ }
+ return (ISC_R_SUCCESS);
+ }
+ node = node->next;
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+isc_ht_delete(isc_ht_t *ht, const unsigned char *key, uint32_t keysize) {
+ isc_ht_node_t *node, *prev;
+ uint32_t hash;
+
+ REQUIRE(ISC_HT_VALID(ht));
+ REQUIRE(key != NULL && keysize > 0);
+
+ prev = NULL;
+ hash = isc_hash_function(key, keysize, true);
+ node = ht->table[hash & ht->mask];
+ while (node != NULL) {
+ if (keysize == node->keysize &&
+ memcmp(key, node->key, keysize) == 0)
+ {
+ if (prev == NULL) {
+ ht->table[hash & ht->mask] = node->next;
+ } else {
+ prev->next = node->next;
+ }
+ isc_mem_put(ht->mctx, node,
+ offsetof(isc_ht_node_t, key) +
+ node->keysize);
+ ht->count--;
+
+ return (ISC_R_SUCCESS);
+ }
+
+ prev = node;
+ node = node->next;
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+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->ht = ht;
+ it->i = 0;
+ it->cur = NULL;
+
+ *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(isc_ht_iter_t));
+}
+
+isc_result_t
+isc_ht_iter_first(isc_ht_iter_t *it) {
+ REQUIRE(it != NULL);
+
+ it->i = 0;
+ while (it->i < it->ht->size && it->ht->table[it->i] == NULL) {
+ it->i++;
+ }
+
+ if (it->i == it->ht->size) {
+ return (ISC_R_NOMORE);
+ }
+
+ it->cur = it->ht->table[it->i];
+
+ return (ISC_R_SUCCESS);
+}
+
+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) {
+ do {
+ it->i++;
+ } while (it->i < it->ht->size && it->ht->table[it->i] == NULL);
+ if (it->i >= it->ht->size) {
+ return (ISC_R_NOMORE);
+ }
+ it->cur = it->ht->table[it->i];
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_ht_iter_delcurrent_next(isc_ht_iter_t *it) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_ht_node_t *to_delete = NULL;
+ isc_ht_node_t *prev = NULL;
+ isc_ht_node_t *node = NULL;
+ uint32_t hash;
+ isc_ht_t *ht;
+ REQUIRE(it != NULL);
+ REQUIRE(it->cur != NULL);
+ to_delete = it->cur;
+ ht = it->ht;
+
+ it->cur = it->cur->next;
+ if (it->cur == NULL) {
+ do {
+ it->i++;
+ } while (it->i < ht->size && ht->table[it->i] == NULL);
+ if (it->i >= ht->size) {
+ result = ISC_R_NOMORE;
+ } else {
+ it->cur = ht->table[it->i];
+ }
+ }
+
+ hash = isc_hash_function(to_delete->key, to_delete->keysize, true);
+ node = ht->table[hash & ht->mask];
+ while (node != to_delete) {
+ prev = node;
+ node = node->next;
+ INSIST(node != NULL);
+ }
+
+ if (prev == NULL) {
+ ht->table[hash & ht->mask] = node->next;
+ } else {
+ prev->next = node->next;
+ }
+ isc_mem_put(ht->mctx, node,
+ offsetof(isc_ht_node_t, key) + node->keysize);
+ ht->count--;
+
+ 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;
+}
+
+unsigned int
+isc_ht_count(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..b5ac5e4
--- /dev/null
+++ b/lib/isc/httpd.c
@@ -0,0 +1,1347 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/httpd.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/socket.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#ifdef HAVE_ZLIB
+#include <zlib.h>
+#endif /* ifdef HAVE_ZLIB */
+
+/*%
+ * TODO:
+ *
+ * o Make the URL processing external functions which will fill-in a buffer
+ * structure we provide, or return an error and we will render a generic
+ * page and close the client.
+ */
+
+#define MSHUTTINGDOWN(cm) ((cm->flags & ISC_HTTPDMGR_FLAGSHUTTINGDOWN) != 0)
+#define MSETSHUTTINGDOWN(cm) (cm->flags |= ISC_HTTPDMGR_FLAGSHUTTINGDOWN)
+
+#define HTTP_RECVLEN 1024
+#define HTTP_SENDGROW 1024
+#define HTTP_SEND_MAXLEN 10240
+
+#define HTTPD_CLOSE 0x0001 /* Got a Connection: close header */
+#define HTTPD_FOUNDHOST 0x0002 /* Got a Host: header */
+#define HTTPD_KEEPALIVE 0x0004 /* Got a Connection: Keep-Alive */
+#define HTTPD_ACCEPT_DEFLATE 0x0008
+
+#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 client */
+struct isc_httpd {
+ unsigned int magic; /* HTTPD_MAGIC */
+ isc_refcount_t references;
+ isc_httpdmgr_t *mgr; /*%< our parent */
+ ISC_LINK(isc_httpd_t) link;
+ unsigned int state;
+ isc_socket_t *sock;
+
+ /*%
+ * Received data state.
+ */
+ char recvbuf[HTTP_RECVLEN]; /*%< receive buffer */
+ uint32_t recvlen; /*%< length recv'd */
+ char *headers; /*%< set in process_request() */
+ unsigned int method;
+ char *url;
+ char *querystring;
+ char *protocol;
+
+ /*
+ * Flags on the httpd client.
+ */
+ int flags;
+
+ /*%
+ * 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.
+ *
+ * The bufflist is the list of buffers we are currently transmitting.
+ * The headerbuffer is where we render our headers to. If we run out
+ * of space when rendering a header, we will change the size of our
+ * buffer. We will not free it until we are finished, and will
+ * allocate an additional HTTP_SENDGROW bytes per header space grow.
+ *
+ * We currently use three buffers total, one for the headers (which
+ * we manage), another 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. This second
+ * buffer is bodybuffer, and we only allocate the buffer itself, not
+ * the backing store.
+ * The third buffer is compbuffer, managed by us, that contains the
+ * compressed HTTP data, if compression is used.
+ *
+ */
+ isc_buffer_t headerbuffer;
+ isc_buffer_t compbuffer;
+ isc_buffer_t *sendbuffer;
+
+ const char *mimetype;
+ unsigned int retcode;
+ const char *retmsg;
+ isc_buffer_t bodybuffer;
+ isc_httpdfree_t *freecb;
+ void *freecb_arg;
+};
+
+/*% lightweight socket manager for httpd output */
+struct isc_httpdmgr {
+ unsigned int magic; /* HTTPDMGR_MAGIC */
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+ isc_socket_t *sock; /*%< listening socket */
+ isc_task_t *task; /*%< owning task */
+ isc_timermgr_t *timermgr;
+
+ 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;
+};
+
+/*%
+ * HTTP methods.
+ */
+#define ISC_HTTPD_METHODUNKNOWN 0
+#define ISC_HTTPD_METHODGET 1
+#define ISC_HTTPD_METHODPOST 2
+
+/*%
+ * Client states.
+ *
+ * _IDLE The client is not doing anything at all. This state should
+ * only occur just after creation, and just before being
+ * destroyed.
+ *
+ * _RECV The client is waiting for data after issuing a socket recv().
+ *
+ * _RECVDONE Data has been received, and is being processed.
+ *
+ * _SEND All data for a response has completed, and a reply was
+ * sent via a socket send() call.
+ *
+ * _SENDDONE Send is completed.
+ *
+ * Badly formatted state table:
+ *
+ * IDLE -> RECV when client has a recv() queued.
+ *
+ * RECV -> RECVDONE when recvdone event received.
+ *
+ * RECVDONE -> SEND if the data for a reply is at hand.
+ *
+ * SEND -> RECV when a senddone event was received.
+ *
+ * At any time -> RECV on error. If RECV fails, the client will
+ * self-destroy, closing the socket and freeing memory.
+ */
+#define ISC_HTTPD_STATEIDLE 0
+#define ISC_HTTPD_STATERECV 1
+#define ISC_HTTPD_STATERECVDONE 2
+#define ISC_HTTPD_STATESEND 3
+#define ISC_HTTPD_STATESENDDONE 4
+
+#define ISC_HTTPD_ISRECV(c) ((c)->state == ISC_HTTPD_STATERECV)
+#define ISC_HTTPD_ISRECVDONE(c) ((c)->state == ISC_HTTPD_STATERECVDONE)
+#define ISC_HTTPD_ISSEND(c) ((c)->state == ISC_HTTPD_STATESEND)
+#define ISC_HTTPD_ISSENDDONE(c) ((c)->state == ISC_HTTPD_STATESENDDONE)
+
+/*%
+ * Overall magic test that means we're not idle.
+ */
+#define ISC_HTTPD_SETRECV(c) ((c)->state = ISC_HTTPD_STATERECV)
+#define ISC_HTTPD_SETRECVDONE(c) ((c)->state = ISC_HTTPD_STATERECVDONE)
+#define ISC_HTTPD_SETSEND(c) ((c)->state = ISC_HTTPD_STATESEND)
+#define ISC_HTTPD_SETSENDDONE(c) ((c)->state = ISC_HTTPD_STATESENDDONE)
+
+static void
+isc_httpd_accept(isc_task_t *, isc_event_t *);
+static void
+isc_httpd_recvdone(isc_task_t *, isc_event_t *);
+static void
+isc_httpd_senddone(isc_task_t *, isc_event_t *);
+static isc_result_t
+process_request(isc_httpd_t *, int);
+static isc_result_t
+grow_headerspace(isc_httpd_t *);
+static void
+reset_client(isc_httpd_t *httpd);
+
+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
+maybe_destroy_httpd(isc_httpd_t *);
+static void
+destroy_httpd(isc_httpd_t *);
+static void
+maybe_destroy_httpdmgr(isc_httpdmgr_t *);
+static void
+destroy_httpdmgr(isc_httpdmgr_t *);
+
+static void
+isc_httpdmgr_attach(isc_httpdmgr_t *, isc_httpdmgr_t **);
+static void
+isc_httpdmgr_detach(isc_httpdmgr_t **);
+
+static void
+maybe_destroy_httpd(isc_httpd_t *httpd) {
+ if (isc_refcount_decrement(&httpd->references) == 1) {
+ destroy_httpd(httpd);
+ }
+}
+
+static void
+free_buffer(isc_mem_t *mctx, isc_buffer_t *buffer) {
+ isc_region_t r;
+
+ isc_buffer_region(buffer, &r);
+ if (r.length > 0) {
+ isc_mem_put(mctx, r.base, r.length);
+ }
+
+ isc_buffer_initnull(buffer);
+}
+
+static void
+destroy_httpd(isc_httpd_t *httpd) {
+ isc_httpdmgr_t *httpdmgr;
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ httpdmgr = httpd->mgr;
+ REQUIRE(VALID_HTTPDMGR(httpdmgr));
+
+ /*
+ * Unlink before calling isc_socket_detach so
+ * isc_httpdmgr_shutdown does not dereference a NULL pointer
+ * when calling isc_socket_cancel().
+ */
+ LOCK(&httpdmgr->lock);
+ ISC_LIST_UNLINK(httpdmgr->running, httpd, link);
+ UNLOCK(&httpdmgr->lock);
+
+ httpd->magic = 0;
+ isc_refcount_destroy(&httpd->references);
+ isc_socket_detach(&httpd->sock);
+
+ free_buffer(httpdmgr->mctx, &httpd->headerbuffer);
+ free_buffer(httpdmgr->mctx, &httpd->compbuffer);
+
+ isc_mem_put(httpdmgr->mctx, httpd, sizeof(isc_httpd_t));
+
+#if ENABLE_AFL
+ if (finishhook != NULL) {
+ finishhook();
+ }
+#endif /* ENABLE_AFL */
+
+ isc_httpdmgr_detach(&httpdmgr);
+}
+
+static isc_result_t
+httpdmgr_socket_accept(isc_task_t *task, isc_httpdmgr_t *httpdmgr) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ /* decremented in isc_httpd_accept */
+ isc_refcount_increment(&httpdmgr->references);
+ result = isc_socket_accept(httpdmgr->sock, task, isc_httpd_accept,
+ httpdmgr);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(isc_refcount_decrement(&httpdmgr->references) > 1);
+ }
+ return (result);
+}
+
+static void
+httpd_socket_recv(isc_httpd_t *httpd, isc_region_t *region, isc_task_t *task) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ /* decremented in isc_httpd_recvdone */
+ (void)isc_refcount_increment(&httpd->references);
+ result = isc_socket_recv(httpd->sock, region, 1, task,
+ isc_httpd_recvdone, httpd);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(isc_refcount_decrement(&httpd->references) > 1);
+ }
+}
+
+static void
+httpd_socket_send(isc_httpd_t *httpd, isc_region_t *region, isc_task_t *task) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ /* decremented in isc_httpd_senddone */
+ (void)isc_refcount_increment(&httpd->references);
+ result = isc_socket_send(httpd->sock, region, task, isc_httpd_senddone,
+ httpd);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(isc_refcount_decrement(&httpd->references) > 1);
+ }
+}
+
+isc_result_t
+isc_httpdmgr_create(isc_mem_t *mctx, isc_socket_t *sock, isc_task_t *task,
+ isc_httpdclientok_t *client_ok,
+ isc_httpdondestroy_t *ondestroy, void *cb_arg,
+ isc_timermgr_t *tmgr, isc_httpdmgr_t **httpdmgrp) {
+ isc_result_t result;
+ isc_httpdmgr_t *httpdmgr;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(sock != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(tmgr != NULL);
+ REQUIRE(httpdmgrp != NULL && *httpdmgrp == NULL);
+
+ httpdmgr = isc_mem_get(mctx, sizeof(isc_httpdmgr_t));
+
+ *httpdmgr = (isc_httpdmgr_t){ .timermgr = tmgr, /* XXXMLG no attach
+ * function? */
+ .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_socket_attach(sock, &httpdmgr->sock);
+ isc_task_attach(task, &httpdmgr->task);
+
+ ISC_LIST_INIT(httpdmgr->running);
+ ISC_LIST_INIT(httpdmgr->urls);
+
+ isc_refcount_init(&httpdmgr->references, 1);
+
+ /* XXXMLG ignore errors on isc_socket_listen() */
+ result = isc_socket_listen(sock, SOMAXCONN);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_socket_listen() failed: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ (void)isc_socket_filter(sock, "httpready");
+
+ httpdmgr->magic = HTTPDMGR_MAGIC;
+
+ result = httpdmgr_socket_accept(task, httpdmgr);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ *httpdmgrp = httpdmgr;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ httpdmgr->magic = 0;
+ isc_refcount_decrementz(&httpdmgr->references);
+ isc_refcount_destroy(&httpdmgr->references);
+ isc_task_detach(&httpdmgr->task);
+ isc_socket_detach(&httpdmgr->sock);
+ isc_mem_detach(&httpdmgr->mctx);
+ isc_mutex_destroy(&httpdmgr->lock);
+ isc_mem_put(mctx, httpdmgr, sizeof(isc_httpdmgr_t));
+ return (result);
+}
+
+static void
+isc_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
+isc_httpdmgr_detach(isc_httpdmgr_t **httpdmgrp) {
+ REQUIRE(httpdmgrp != NULL && VALID_HTTPDMGR(*httpdmgrp));
+ isc_httpdmgr_t *httpdmgr = *httpdmgrp;
+ *httpdmgrp = NULL;
+
+ maybe_destroy_httpdmgr(httpdmgr);
+}
+
+static void
+maybe_destroy_httpdmgr(isc_httpdmgr_t *httpdmgr) {
+ 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);
+
+ httpdmgr->magic = 0;
+
+ INSIST(MSHUTTINGDOWN(httpdmgr));
+ INSIST(ISC_LIST_EMPTY(httpdmgr->running));
+
+ isc_socket_detach(&httpdmgr->sock);
+ isc_task_detach(&httpdmgr->task);
+ httpdmgr->timermgr = NULL;
+
+ /*
+ * 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));
+}
+
+#define LENGTHOK(s) (httpd->recvbuf - (s) < (int)httpd->recvlen)
+#define BUFLENOK(s) (httpd->recvbuf - (s) < HTTP_RECVLEN)
+
+/*
+ * Look for the given header in headers.
+ * If value is specified look for it terminated with a character in eov.
+ */
+static bool
+have_header(isc_httpd_t *httpd, const char *header, const char *value,
+ const char *eov) {
+ char *cr, *nl, *h;
+ size_t hlen, vlen = 0;
+
+ h = httpd->headers;
+ hlen = strlen(header);
+ if (value != NULL) {
+ INSIST(eov != NULL);
+ vlen = strlen(value);
+ }
+
+ for (;;) {
+ if (strncasecmp(h, header, hlen) != 0) {
+ /*
+ * Skip to next line;
+ */
+ cr = strchr(h, '\r');
+ if (cr != NULL && cr[1] == '\n') {
+ cr++;
+ }
+ nl = strchr(h, '\n');
+
+ /* last header? */
+ h = cr;
+ if (h == NULL || (nl != NULL && nl < h)) {
+ h = nl;
+ }
+ if (h == NULL) {
+ return (false);
+ }
+ h++;
+ continue;
+ }
+
+ if (value == NULL) {
+ return (true);
+ }
+
+ /*
+ * Skip optional leading white space.
+ */
+ h += hlen;
+ while (*h == ' ' || *h == '\t') {
+ h++;
+ }
+ /*
+ * Terminate token search on NULL or EOL.
+ */
+ while (*h != 0 && *h != '\r' && *h != '\n') {
+ if (strncasecmp(h, value, vlen) == 0) {
+ if (strchr(eov, h[vlen]) != NULL) {
+ return (true);
+ /*
+ * Skip to next token.
+ */
+ }
+ }
+ /*
+ * Skip to next token.
+ */
+ h += strcspn(h, eov);
+ if (h[0] == '\r' && h[1] == '\n') {
+ h++;
+ }
+ if (h[0] != 0) {
+ h++;
+ }
+ }
+ return (false);
+ }
+}
+
+static isc_result_t
+process_request(isc_httpd_t *httpd, int length) {
+ char *s;
+ char *p;
+ int delim;
+
+ httpd->recvlen += length;
+
+ httpd->recvbuf[httpd->recvlen] = 0;
+ httpd->headers = NULL;
+
+ /*
+ * If we don't find a blank line in our buffer, return that we need
+ * more data.
+ */
+ s = strstr(httpd->recvbuf, "\r\n\r\n");
+ delim = 2;
+ if (s == NULL) {
+ s = strstr(httpd->recvbuf, "\n\n");
+ delim = 1;
+ }
+ if (s == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /*
+ * NUL terminate request at the blank line.
+ */
+ s[delim] = 0;
+
+ /*
+ * Determine if this is a POST or GET method. Any other values will
+ * cause an error to be returned.
+ */
+ if (strncmp(httpd->recvbuf, "GET ", 4) == 0) {
+ httpd->method = ISC_HTTPD_METHODGET;
+ p = httpd->recvbuf + 4;
+ } else if (strncmp(httpd->recvbuf, "POST ", 5) == 0) {
+ httpd->method = ISC_HTTPD_METHODPOST;
+ p = httpd->recvbuf + 5;
+ } else {
+ return (ISC_R_RANGE);
+ }
+
+ /*
+ * From now on, p is the start of our buffer.
+ */
+
+ /*
+ * Extract the URL.
+ */
+ s = p;
+ while (LENGTHOK(s) && BUFLENOK(s) &&
+ (*s != '\n' && *s != '\r' && *s != '\0' && *s != ' '))
+ {
+ s++;
+ }
+ if (!LENGTHOK(s)) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (!BUFLENOK(s)) {
+ return (ISC_R_NOMEMORY);
+ }
+ *s = 0;
+
+ /*
+ * Make the URL relative.
+ */
+ if ((strncmp(p, "http:/", 6) == 0) || (strncmp(p, "https:/", 7) == 0)) {
+ /* Skip first / */
+ while (*p != '/' && *p != 0) {
+ p++;
+ }
+ if (*p == 0) {
+ return (ISC_R_RANGE);
+ }
+ p++;
+ /* Skip second / */
+ while (*p != '/' && *p != 0) {
+ p++;
+ }
+ if (*p == 0) {
+ return (ISC_R_RANGE);
+ }
+ p++;
+ /* Find third / */
+ while (*p != '/' && *p != 0) {
+ p++;
+ }
+ if (*p == 0) {
+ p--;
+ *p = '/';
+ }
+ }
+
+ httpd->url = p;
+ p = s + 1;
+ s = p;
+
+ /*
+ * Now, see if there is a ? mark in the URL. If so, this is
+ * part of the query string, and we will split it from the URL.
+ */
+ httpd->querystring = strchr(httpd->url, '?');
+ if (httpd->querystring != NULL) {
+ *(httpd->querystring) = 0;
+ httpd->querystring++;
+ }
+
+ /*
+ * Extract the HTTP/1.X protocol. We will bounce on anything but
+ * HTTP/1.0 or HTTP/1.1 for now.
+ */
+ while (LENGTHOK(s) && BUFLENOK(s) &&
+ (*s != '\n' && *s != '\r' && *s != '\0'))
+ {
+ s++;
+ }
+ if (!LENGTHOK(s)) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (!BUFLENOK(s)) {
+ return (ISC_R_NOMEMORY);
+ }
+ /*
+ * Check that we have the expected eol delimiter.
+ */
+ if (strncmp(s, delim == 1 ? "\n" : "\r\n", delim) != 0) {
+ return (ISC_R_RANGE);
+ }
+ *s = 0;
+ if ((strncmp(p, "HTTP/1.0", 8) != 0) &&
+ (strncmp(p, "HTTP/1.1", 8) != 0))
+ {
+ return (ISC_R_RANGE);
+ }
+ httpd->protocol = p;
+ p = s + delim; /* skip past eol */
+ s = p;
+
+ httpd->headers = s;
+
+ if (have_header(httpd, "Connection:", "close", ", \t\r\n")) {
+ httpd->flags |= HTTPD_CLOSE;
+ }
+
+ if (have_header(httpd, "Host:", NULL, NULL)) {
+ httpd->flags |= HTTPD_FOUNDHOST;
+ }
+
+ if (strncmp(httpd->protocol, "HTTP/1.0", 8) == 0) {
+ if (have_header(httpd, "Connection:", "Keep-Alive", ", \t\r\n"))
+ {
+ httpd->flags |= HTTPD_KEEPALIVE;
+ } else {
+ httpd->flags |= HTTPD_CLOSE;
+ }
+ }
+
+ /*
+ * Check for Accept-Encoding:
+ */
+#ifdef HAVE_ZLIB
+ if (have_header(httpd, "Accept-Encoding:", "deflate", ";, \t\r\n")) {
+ httpd->flags |= HTTPD_ACCEPT_DEFLATE;
+ }
+#endif /* ifdef HAVE_ZLIB */
+
+ /*
+ * Standards compliance hooks here.
+ */
+ if (strcmp(httpd->protocol, "HTTP/1.1") == 0 &&
+ ((httpd->flags & HTTPD_FOUNDHOST) == 0))
+ {
+ return (ISC_R_RANGE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+isc_httpd_create(isc_httpdmgr_t *httpdmgr, isc_socket_t *sock,
+ isc_httpd_t **httpdp) {
+ isc_httpd_t *httpd;
+ char *headerdata;
+
+ REQUIRE(VALID_HTTPDMGR(httpdmgr));
+ REQUIRE(httpdp != NULL && *httpdp == NULL);
+
+ httpd = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpd_t));
+
+ *httpd = (isc_httpd_t){ .sock = sock };
+
+ isc_httpdmgr_attach(httpdmgr, &httpd->mgr);
+
+ isc_refcount_init(&httpd->references, 1);
+ ISC_HTTPD_SETRECV(httpd);
+ isc_socket_setname(httpd->sock, "httpd", NULL);
+
+ /*
+ * Initialize the buffer for our headers.
+ */
+ headerdata = isc_mem_get(httpdmgr->mctx, HTTP_SENDGROW);
+ isc_buffer_init(&httpd->headerbuffer, headerdata, HTTP_SENDGROW);
+
+ isc_buffer_initnull(&httpd->compbuffer);
+ isc_buffer_initnull(&httpd->bodybuffer);
+ reset_client(httpd);
+
+ ISC_LINK_INIT(httpd, link);
+ ISC_LIST_APPEND(httpdmgr->running, httpd, link);
+
+ httpd->magic = HTTPD_MAGIC;
+
+ *httpdp = httpd;
+}
+
+static void
+isc_httpd_accept(isc_task_t *task, isc_event_t *ev) {
+ isc_httpdmgr_t *httpdmgr = ev->ev_arg;
+ isc_httpd_t *httpd = NULL;
+ isc_region_t r;
+ isc_socket_newconnev_t *nev = (isc_socket_newconnev_t *)ev;
+ isc_sockaddr_t peeraddr;
+
+ REQUIRE(VALID_HTTPDMGR(httpdmgr));
+
+ LOCK(&httpdmgr->lock);
+ if (MSHUTTINGDOWN(httpdmgr)) {
+ goto out;
+ }
+
+ if (nev->result == ISC_R_CANCELED) {
+ goto out;
+ }
+
+ if (nev->result != ISC_R_SUCCESS) {
+ /* XXXMLG log failure */
+ goto requeue;
+ }
+
+ (void)isc_socket_getpeername(nev->newsocket, &peeraddr);
+ if (httpdmgr->client_ok != NULL &&
+ !(httpdmgr->client_ok)(&peeraddr, httpdmgr->cb_arg))
+ {
+ isc_socket_detach(&nev->newsocket);
+ goto requeue;
+ }
+
+ isc_httpd_create(httpdmgr, nev->newsocket, &httpd);
+
+ r.base = (unsigned char *)httpd->recvbuf;
+ r.length = HTTP_RECVLEN - 1;
+
+ httpd_socket_recv(httpd, &r, task);
+
+requeue:
+ (void)httpdmgr_socket_accept(task, httpdmgr);
+
+out:
+ UNLOCK(&httpdmgr->lock);
+
+ if (httpd != NULL) {
+ maybe_destroy_httpd(httpd);
+ }
+ maybe_destroy_httpdmgr(httpdmgr);
+
+ isc_event_free(&ev);
+}
+
+static isc_result_t
+render_404(const char *url, isc_httpdurl_t *urlinfo, const char *querystring,
+ const char *headers, 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(url);
+ UNUSED(urlinfo);
+ UNUSED(querystring);
+ UNUSED(headers);
+ 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 char *url, isc_httpdurl_t *urlinfo, const char *querystring,
+ const char *headers, 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(url);
+ UNUSED(urlinfo);
+ UNUSED(querystring);
+ UNUSED(headers);
+ 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
+/*%<
+ * Reallocates compbuffer to size, does nothing if compbuffer is already
+ * larger than size.
+ *
+ * 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 extend buffer
+ */
+static isc_result_t
+alloc_compspace(isc_httpd_t *httpd, unsigned int size) {
+ char *newspace;
+ isc_region_t r;
+
+ isc_buffer_region(&httpd->compbuffer, &r);
+ if (size < r.length) {
+ return (ISC_R_SUCCESS);
+ }
+
+ newspace = isc_mem_get(httpd->mgr->mctx, size);
+ isc_buffer_reinit(&httpd->compbuffer, newspace, size);
+
+ if (r.base != NULL) {
+ isc_mem_put(httpd->mgr->mctx, r.base, r.length);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%<
+ * 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
+isc_httpd_compress(isc_httpd_t *httpd) {
+ z_stream zstr;
+ isc_region_t r;
+ isc_result_t result;
+ int ret;
+ int inputlen;
+
+ inputlen = isc_buffer_usedlength(&httpd->bodybuffer);
+ result = alloc_compspace(httpd, inputlen);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ isc_buffer_clear(&httpd->compbuffer);
+ isc_buffer_region(&httpd->compbuffer, &r);
+
+ /*
+ * We're setting output buffer size to input size so it fails if the
+ * compressed data size would be bigger than the input size.
+ */
+ memset(&zstr, 0, sizeof(zstr));
+ zstr.total_in = zstr.avail_in = zstr.total_out = zstr.avail_out =
+ inputlen;
+
+ zstr.next_in = isc_buffer_base(&httpd->bodybuffer);
+ zstr.next_out = r.base;
+
+ 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(&httpd->compbuffer, inputlen - zstr.avail_out);
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_FAILURE);
+ }
+}
+#endif /* ifdef HAVE_ZLIB */
+
+static void
+isc_httpd_recvdone(isc_task_t *task, isc_event_t *ev) {
+ isc_result_t result;
+ isc_httpd_t *httpd = ev->ev_arg;
+ isc_socketevent_t *sev = (isc_socketevent_t *)ev;
+ isc_buffer_t *databuffer;
+ isc_httpdurl_t *url;
+ isc_time_t now;
+ isc_region_t r;
+ bool is_compressed = false;
+ char datebuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ INSIST(ISC_HTTPD_ISRECV(httpd));
+
+ if (sev->result != ISC_R_SUCCESS) {
+ goto out;
+ }
+
+ result = process_request(httpd, sev->n);
+ if (result == ISC_R_NOTFOUND) {
+ if (httpd->recvlen >= HTTP_RECVLEN - 1) {
+ goto out;
+ }
+ r.base = (unsigned char *)httpd->recvbuf + httpd->recvlen;
+ r.length = HTTP_RECVLEN - httpd->recvlen - 1;
+
+ httpd_socket_recv(httpd, &r, task);
+ goto out;
+ } else if (result != ISC_R_SUCCESS) {
+ goto out;
+ }
+
+ ISC_HTTPD_SETSEND(httpd);
+
+ /*
+ * XXXMLG Call function here. Provide an add-header function
+ * which will append the common headers to a response we generate.
+ */
+ isc_buffer_initnull(&httpd->bodybuffer);
+ isc_time_now(&now);
+ isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf));
+ LOCK(&httpd->mgr->lock);
+ url = ISC_LIST_HEAD(httpd->mgr->urls);
+ while (url != NULL) {
+ if (strcmp(httpd->url, url->url) == 0) {
+ break;
+ }
+ url = ISC_LIST_NEXT(url, link);
+ }
+ UNLOCK(&httpd->mgr->lock);
+
+ if (url == NULL) {
+ result = httpd->mgr->render_404(
+ httpd->url, NULL, httpd->querystring, NULL, NULL,
+ &httpd->retcode, &httpd->retmsg, &httpd->mimetype,
+ &httpd->bodybuffer, &httpd->freecb, &httpd->freecb_arg);
+ } else {
+ result = url->action(httpd->url, url, httpd->querystring,
+ httpd->headers, url->action_arg,
+ &httpd->retcode, &httpd->retmsg,
+ &httpd->mimetype, &httpd->bodybuffer,
+ &httpd->freecb, &httpd->freecb_arg);
+ }
+ if (result != ISC_R_SUCCESS) {
+ result = httpd->mgr->render_500(
+ httpd->url, url, httpd->querystring, NULL, NULL,
+ &httpd->retcode, &httpd->retmsg, &httpd->mimetype,
+ &httpd->bodybuffer, &httpd->freecb, &httpd->freecb_arg);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+#ifdef HAVE_ZLIB
+ if ((httpd->flags & HTTPD_ACCEPT_DEFLATE) != 0) {
+ result = isc_httpd_compress(httpd);
+ if (result == ISC_R_SUCCESS) {
+ is_compressed = true;
+ }
+ }
+#endif /* ifdef HAVE_ZLIB */
+
+ isc_httpd_response(httpd);
+ if ((httpd->flags & HTTPD_KEEPALIVE) != 0) {
+ isc_httpd_addheader(httpd, "Connection", "Keep-Alive");
+ }
+ isc_httpd_addheader(httpd, "Content-Type", httpd->mimetype);
+ isc_httpd_addheader(httpd, "Date", datebuf);
+ isc_httpd_addheader(httpd, "Expires", datebuf);
+
+ if (url != NULL && url->isstatic) {
+ char loadbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
+ isc_time_formathttptimestamp(&url->loadtime, loadbuf,
+ sizeof(loadbuf));
+ isc_httpd_addheader(httpd, "Last-Modified", loadbuf);
+ isc_httpd_addheader(httpd, "Cache-Control: public", NULL);
+ } else {
+ isc_httpd_addheader(httpd, "Last-Modified", datebuf);
+ isc_httpd_addheader(httpd, "Pragma: no-cache", NULL);
+ isc_httpd_addheader(httpd, "Cache-Control: no-cache", NULL);
+ }
+
+ isc_httpd_addheader(httpd, "Server: libisc", NULL);
+
+ if (is_compressed) {
+ isc_httpd_addheader(httpd, "Content-Encoding", "deflate");
+ isc_httpd_addheaderuint(
+ httpd, "Content-Length",
+ isc_buffer_usedlength(&httpd->compbuffer));
+ } else {
+ isc_httpd_addheaderuint(
+ httpd, "Content-Length",
+ isc_buffer_usedlength(&httpd->bodybuffer));
+ }
+
+ isc_httpd_endheaders(httpd); /* done */
+
+ /*
+ * Append either the compressed or the non-compressed response body to
+ * the response headers and store the result in httpd->sendbuffer.
+ */
+ isc_buffer_dup(httpd->mgr->mctx, &httpd->sendbuffer,
+ &httpd->headerbuffer);
+ isc_buffer_setautorealloc(httpd->sendbuffer, true);
+ databuffer = (is_compressed ? &httpd->compbuffer : &httpd->bodybuffer);
+ isc_buffer_usedregion(databuffer, &r);
+ result = isc_buffer_copyregion(httpd->sendbuffer, &r);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ /*
+ * Determine total response size.
+ */
+ isc_buffer_usedregion(httpd->sendbuffer, &r);
+
+ httpd_socket_send(httpd, &r, task);
+
+out:
+ maybe_destroy_httpd(httpd);
+ isc_event_free(&ev);
+}
+
+void
+isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) {
+ isc_httpdmgr_t *httpdmgr;
+ isc_httpd_t *httpd;
+
+ REQUIRE(httpdmgrp != NULL);
+ httpdmgr = *httpdmgrp;
+ *httpdmgrp = NULL;
+ REQUIRE(VALID_HTTPDMGR(httpdmgr));
+
+ LOCK(&httpdmgr->lock);
+
+ MSETSHUTTINGDOWN(httpdmgr);
+
+ isc_socket_cancel(httpdmgr->sock, httpdmgr->task, ISC_SOCKCANCEL_ALL);
+
+ httpd = ISC_LIST_HEAD(httpdmgr->running);
+ while (httpd != NULL) {
+ isc_socket_cancel(httpd->sock, httpdmgr->task,
+ ISC_SOCKCANCEL_ALL);
+ httpd = ISC_LIST_NEXT(httpd, link);
+ }
+
+ UNLOCK(&httpdmgr->lock);
+
+ maybe_destroy_httpdmgr(httpdmgr);
+}
+
+static isc_result_t
+grow_headerspace(isc_httpd_t *httpd) {
+ char *newspace;
+ unsigned int newlen;
+ isc_region_t r;
+
+ isc_buffer_region(&httpd->headerbuffer, &r);
+ newlen = r.length + HTTP_SENDGROW;
+ if (newlen > HTTP_SEND_MAXLEN) {
+ return (ISC_R_NOSPACE);
+ }
+
+ newspace = isc_mem_get(httpd->mgr->mctx, newlen);
+
+ isc_buffer_reinit(&httpd->headerbuffer, newspace, newlen);
+
+ isc_mem_put(httpd->mgr->mctx, r.base, r.length);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_httpd_response(isc_httpd_t *httpd) {
+ isc_result_t result;
+ unsigned int needlen;
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ needlen = strlen(httpd->protocol) + 1; /* protocol + space */
+ needlen += 3 + 1; /* room for response code, always 3 bytes */
+ needlen += strlen(httpd->retmsg) + 2; /* return msg + CRLF */
+
+ while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) {
+ result = grow_headerspace(httpd);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ return (isc_buffer_printf(&httpd->headerbuffer, "%s %03u %s\r\n",
+ httpd->protocol, httpd->retcode,
+ httpd->retmsg));
+}
+
+isc_result_t
+isc_httpd_addheader(isc_httpd_t *httpd, const char *name, const char *val) {
+ isc_result_t result;
+ unsigned int needlen;
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ needlen = strlen(name); /* name itself */
+ if (val != NULL) {
+ needlen += 2 + strlen(val); /* :<space> and val */
+ }
+ needlen += 2; /* CRLF */
+
+ while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) {
+ result = grow_headerspace(httpd);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if (val != NULL) {
+ return (isc_buffer_printf(&httpd->headerbuffer, "%s: %s\r\n",
+ name, val));
+ } else {
+ return (isc_buffer_printf(&httpd->headerbuffer, "%s\r\n",
+ name));
+ }
+}
+
+isc_result_t
+isc_httpd_endheaders(isc_httpd_t *httpd) {
+ isc_result_t result;
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ while (isc_buffer_availablelength(&httpd->headerbuffer) < 2) {
+ result = grow_headerspace(httpd);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ return (isc_buffer_printf(&httpd->headerbuffer, "\r\n"));
+}
+
+isc_result_t
+isc_httpd_addheaderuint(isc_httpd_t *httpd, const char *name, int val) {
+ isc_result_t result;
+ unsigned int needlen;
+ char buf[sizeof "18446744073709551616"];
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ snprintf(buf, sizeof(buf), "%d", val);
+
+ needlen = strlen(name); /* name itself */
+ needlen += 2 + strlen(buf); /* :<space> and val */
+ needlen += 2; /* CRLF */
+
+ while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) {
+ result = grow_headerspace(httpd);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ return (isc_buffer_printf(&httpd->headerbuffer, "%s: %s\r\n", name,
+ buf));
+}
+
+static void
+isc_httpd_senddone(isc_task_t *task, isc_event_t *ev) {
+ isc_httpd_t *httpd = ev->ev_arg;
+ isc_region_t r;
+ isc_socketevent_t *sev = (isc_socketevent_t *)ev;
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ INSIST(ISC_HTTPD_ISSEND(httpd));
+
+ isc_buffer_free(&httpd->sendbuffer);
+
+ /*
+ * We will always want to clean up our receive buffer, even if we
+ * got an error on send or we are shutting down.
+ *
+ * We will pass in the buffer only if there is data in it. If
+ * there is no data, we will pass in a NULL.
+ */
+ if (httpd->freecb != NULL) {
+ isc_buffer_t *b = NULL;
+ if (isc_buffer_length(&httpd->bodybuffer) > 0) {
+ b = &httpd->bodybuffer;
+ httpd->freecb(b, httpd->freecb_arg);
+ }
+ }
+
+ if (sev->result != ISC_R_SUCCESS) {
+ goto out;
+ }
+
+ if ((httpd->flags & HTTPD_CLOSE) != 0) {
+ goto out;
+ }
+
+ ISC_HTTPD_SETRECV(httpd);
+
+ reset_client(httpd);
+
+ r.base = (unsigned char *)httpd->recvbuf;
+ r.length = HTTP_RECVLEN - 1;
+
+ httpd_socket_recv(httpd, &r, task);
+
+out:
+ maybe_destroy_httpd(httpd);
+ isc_event_free(&ev);
+}
+
+static void
+reset_client(isc_httpd_t *httpd) {
+ /*
+ * Catch errors here. We MUST be in RECV mode, and we MUST NOT have
+ * any outstanding buffers. If we have buffers, we have a leak.
+ */
+ INSIST(ISC_HTTPD_ISRECV(httpd));
+ INSIST(!ISC_LINK_LINKED(&httpd->headerbuffer, link));
+ INSIST(!ISC_LINK_LINKED(&httpd->bodybuffer, link));
+
+ httpd->recvbuf[0] = 0;
+ httpd->recvlen = 0;
+ httpd->headers = NULL;
+ httpd->method = ISC_HTTPD_METHODUNKNOWN;
+ httpd->url = NULL;
+ httpd->querystring = NULL;
+ httpd->protocol = NULL;
+ httpd->flags = 0;
+
+ isc_buffer_clear(&httpd->headerbuffer);
+ isc_buffer_clear(&httpd->compbuffer);
+ isc_buffer_invalidate(&httpd->bodybuffer);
+}
+
+isc_result_t
+isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url,
+ isc_httpdaction_t *func, void *arg) {
+ /* REQUIRE(VALID_HTTPDMGR(httpdmgr)); Dummy function */
+
+ return (isc_httpdmgr_addurl2(httpdmgr, url, false, func, arg));
+}
+
+isc_result_t
+isc_httpdmgr_addurl2(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 */
+}
diff --git a/lib/isc/include/.clang-format b/lib/isc/include/.clang-format
new file mode 120000
index 0000000..0e62f72
--- /dev/null
+++ b/lib/isc/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/isc/include/Makefile.in b/lib/isc/include/Makefile.in
new file mode 100644
index 0000000..b052e73
--- /dev/null
+++ b/lib/isc/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = isc pk11 pkcs11
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/isc/include/isc/Makefile.in b/lib/isc/include/isc/Makefile.in
new file mode 100644
index 0000000..a42c9e0
--- /dev/null
+++ b/lib/isc/include/isc/Makefile.in
@@ -0,0 +1,63 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+#
+# Only list headers that are to be installed and are not
+# machine generated. The latter are handled specially in the
+# install target below.
+#
+HEADERS = aes.h app.h assertions.h astack.h atomic.h backtrace.h \
+ barrier.h base32.h base64.h bind9.h buffer.h bufferlist.h \
+ cmocka.h commandline.h counter.h crc64.h deprecated.h \
+ endian.h errno.h error.h event.h eventclass.h \
+ file.h formatcheck.h fsaccess.h fuzz.h \
+ hash.h heap.h hex.h hmac.h ht.h httpd.h \
+ interfaceiter.h iterated_hash.h \
+ lang.h lex.h lfsr.h lib.h likely.h list.h log.h \
+ magic.h managers.h md.h mem.h meminfo.h \
+ mutexblock.h \
+ netaddr.h netmgr.h netscope.h nonce.h os.h parseint.h \
+ pool.h portset.h print.h quota.h \
+ radix.h random.h ratelimiter.h refcount.h regex.h \
+ region.h resource.h result.h resultclass.h rwlock.h \
+ safe.h serial.h siphash.h sockaddr.h socket.h \
+ stats.h stdio.h strerr.h string.h symtab.h \
+ task.h taskpool.h timer.h tm.h types.h \
+ url.h utf8.h util.h version.h
+
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/isc
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/isc || exit 1; \
+ done
+ ${INSTALL_DATA} platform.h ${DESTDIR}${includedir}/isc
+
+uninstall::
+ rm -f ${DESTDIR}${includedir}/isc/platform.h
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/isc/$$i || exit 1; \
+ done
+
+distclean::
+ rm -f platform.h
diff --git a/lib/isc/include/isc/aes.h b/lib/isc/include/isc/aes.h
new file mode 100644
index 0000000..00449b5
--- /dev/null
+++ b/lib/isc/include/isc/aes.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.
+ */
+
+/*! \file isc/aes.h */
+
+#ifndef ISC_AES_H
+#define ISC_AES_H 1
+
+#include <isc/lang.h>
+#include <isc/platform.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
+
+#endif /* ISC_AES_H */
diff --git a/lib/isc/include/isc/app.h b/lib/isc/include/isc/app.h
new file mode 100644
index 0000000..9e8140b
--- /dev/null
+++ b/lib/isc/include/isc/app.h
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_APP_H
+#define ISC_APP_H 1
+
+/*****
+***** 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>
+
+/***
+ *** 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
+
+#endif /* ISC_APP_H */
diff --git a/lib/isc/include/isc/assertions.h b/lib/isc/include/isc/assertions.h
new file mode 100644
index 0000000..e68adfa
--- /dev/null
+++ b/lib/isc/include/isc/assertions.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.
+ */
+
+/*! \file isc/assertions.h
+ */
+
+#ifndef ISC_ASSERTIONS_H
+#define ISC_ASSERTIONS_H 1
+
+#include <isc/lang.h>
+#include <isc/likely.h>
+#include <isc/platform.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] */
+ISC_PLATFORM_NORETURN_PRE
+void
+isc_assertion_failed(const char *, int, isc_assertiontype_t,
+ const char *) ISC_PLATFORM_NORETURN_POST;
+
+void isc_assertion_setcallback(isc_assertioncallback_t);
+
+const char *
+isc_assertion_typetotext(isc_assertiontype_t type);
+
+#define ISC_REQUIRE(cond) \
+ ((void)(ISC_LIKELY(cond) || \
+ ((isc_assertion_failed)(__FILE__, __LINE__, \
+ isc_assertiontype_require, #cond), \
+ 0)))
+
+#define ISC_ENSURE(cond) \
+ ((void)(ISC_LIKELY(cond) || \
+ ((isc_assertion_failed)(__FILE__, __LINE__, \
+ isc_assertiontype_ensure, #cond), \
+ 0)))
+
+#define ISC_INSIST(cond) \
+ ((void)(ISC_LIKELY(cond) || \
+ ((isc_assertion_failed)(__FILE__, __LINE__, \
+ isc_assertiontype_insist, #cond), \
+ 0)))
+
+#define ISC_INVARIANT(cond) \
+ ((void)(ISC_LIKELY(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
+
+#endif /* ISC_ASSERTIONS_H */
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..fd29202
--- /dev/null
+++ b/lib/isc/include/isc/atomic.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.
+ */
+
+#pragma once
+
+#if HAVE_STDATOMIC_H
+#include <stdatomic.h>
+#else /* if HAVE_STDATOMIC_H */
+#include <isc/stdatomic.h>
+#endif /* if HAVE_STDATOMIC_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)
diff --git a/lib/isc/include/isc/backtrace.h b/lib/isc/include/isc/backtrace.h
new file mode 100644
index 0000000..d32748a
--- /dev/null
+++ b/lib/isc/include/isc/backtrace.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.
+ */
+
+/*! \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). These functions still perform minimal checks and return
+ * ISC_R_FAILURE if they detect an error, but the caller should therefore be
+ * very careful about the use of these functions, and generally discouraged to
+ * use them except in an exit path. The exception is
+ * isc_backtrace_getsymbolfromindex(), which is expected to be used in a
+ * non-error-handling context and validates arguments with assertion checks.
+ */
+
+#ifndef ISC_BACKTRACE_H
+#define ISC_BACKTRACE_H 1
+
+/***
+ *** Imports
+ ***/
+
+#include <isc/types.h>
+
+/***
+ *** Types
+ ***/
+struct isc_backtrace_symmap {
+ void *addr;
+ const char *symbol;
+};
+
+LIBISC_EXTERNAL_DATA extern const int isc__backtrace_nsymbols;
+LIBISC_EXTERNAL_DATA extern const isc_backtrace_symmap_t
+ isc__backtrace_symtable[];
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+isc_result_t
+isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes);
+/*%<
+ * 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
+ */
+
+isc_result_t
+isc_backtrace_getsymbolfromindex(int index, const void **addrp,
+ const char **symbolp);
+/*%<
+ * Returns the content of the internal symbol table of the given index.
+ * On success, *addrsp and *symbolp point to the address and the symbol of
+ * the 'index'th entry of the table, respectively. If 'index' is not in the
+ * range of the symbol table, ISC_R_RANGE will be returned.
+ *
+ * Requires
+ *
+ *\li 'addrp' must be non NULL && '*addrp' == NULL.
+ *
+ *\li 'symbolp' must be non NULL && '*symbolp' == NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_RANGE
+ */
+
+isc_result_t
+isc_backtrace_getsymbol(const void *addr, const char **symbolp,
+ unsigned long *offsetp);
+/*%<
+ * Searches the internal symbol table for the symbol that most matches the
+ * given 'addr'. On success, '*symbolp' will point to the name of function
+ * to which the address 'addr' belong, and '*offsetp' will store the offset
+ * from the function's entry address to 'addr'.
+ *
+ * Requires (note that these are not ensured by assertion checks, see above):
+ *
+ *\li 'symbolp' must be non NULL && '*symbolp' == NULL.
+ *
+ *\li 'offsetp' must be non NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_FAILURE
+ *\li #ISC_R_NOTFOUND
+ */
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_BACKTRACE_H */
diff --git a/lib/isc/include/isc/barrier.h b/lib/isc/include/isc/barrier.h
new file mode 100644
index 0000000..1a2a90b
--- /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 __SANITIZE_THREAD__ && !defined(WIN32)
+
+#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..befe047
--- /dev/null
+++ b/lib/isc/include/isc/base32.h
@@ -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 ISC_BASE32_H
+#define ISC_BASE32_H 1
+
+/*! \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
+
+#endif /* ISC_BASE32_H */
diff --git a/lib/isc/include/isc/base64.h b/lib/isc/include/isc/base64.h
new file mode 100644
index 0000000..057eabb
--- /dev/null
+++ b/lib/isc/include/isc/base64.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.
+ */
+
+#ifndef ISC_BASE64_H
+#define ISC_BASE64_H 1
+
+/*! \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
+
+#endif /* ISC_BASE64_H */
diff --git a/lib/isc/include/isc/bind9.h b/lib/isc/include/isc/bind9.h
new file mode 100644
index 0000000..3c25a76
--- /dev/null
+++ b/lib/isc/include/isc/bind9.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_BIND9_H
+#define ISC_BIND9_H 1
+
+#include <stdbool.h>
+
+#include <isc/platform.h>
+
+/*
+ * This determines whether we are using the libisc/libdns libraries
+ * in BIND9 or in some other application. For BIND9 (named and related
+ * tools) it must be set to true at runtime. Export library clients
+ * will call isc_lib_register(), which will set it to false.
+ */
+LIBISC_EXTERNAL_DATA extern bool isc_bind9;
+
+#endif /* ISC_BIND9_H */
diff --git a/lib/isc/include/isc/buffer.h b/lib/isc/include/isc/buffer.h
new file mode 100644
index 0000000..f3becae
--- /dev/null
+++ b/lib/isc/include/isc/buffer.h
@@ -0,0 +1,1103 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_BUFFER_H
+#define ISC_BUFFER_H 1
+
+/*****
+***** 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/likely.h>
+#include <isc/list.h>
+#include <isc/magic.h>
+#include <isc/types.h>
+
+/*!
+ * To make many functions be inline macros (via \#define) define this.
+ * If it is undefined, a function will be used.
+ */
+/* #define ISC_BUFFER_USEINLINE */
+
+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_init(isc_buffer_t *b, void *base, unsigned int length);
+/*!<
+ * \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.
+ *
+ */
+
+void
+isc__buffer_initnull(isc_buffer_t *b);
+/*!<
+ *\brief Initialize a buffer 'b' with a null data and zero length/
+ */
+
+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_invalidate(isc_buffer_t *b);
+/*!<
+ * \brief Make 'b' an invalid buffer.
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ * Ensures:
+ *\li If assertion checking is enabled, future attempts to use 'b' without
+ * calling isc_buffer_init() on it will cause an assertion failure.
+ */
+
+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_region(isc_buffer_t *b, isc_region_t *r);
+/*!<
+ * \brief Make 'r' refer to the region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+
+void
+isc__buffer_usedregion(const isc_buffer_t *b, isc_region_t *r);
+/*!<
+ * \brief Make 'r' refer to the used region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+
+void
+isc__buffer_availableregion(isc_buffer_t *b, isc_region_t *r);
+/*!<
+ * \brief Make 'r' refer to the available region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+
+void
+isc__buffer_add(isc_buffer_t *b, unsigned int n);
+/*!<
+ * \brief Increase the 'used' region of 'b' by 'n' bytes.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ *\li used + n <= length
+ *
+ */
+
+void
+isc__buffer_subtract(isc_buffer_t *b, unsigned int n);
+/*!<
+ * \brief Decrease the 'used' region of 'b' by 'n' bytes.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ *\li used >= n
+ *
+ */
+
+void
+isc__buffer_clear(isc_buffer_t *b);
+/*!<
+ * \brief Make the used region empty.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ * Ensures:
+ *
+ *\li used = 0
+ *
+ */
+
+void
+isc__buffer_consumedregion(isc_buffer_t *b, isc_region_t *r);
+/*!<
+ * \brief Make 'r' refer to the consumed region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+
+void
+isc__buffer_remainingregion(isc_buffer_t *b, isc_region_t *r);
+/*!<
+ * \brief Make 'r' refer to the remaining region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+
+void
+isc__buffer_activeregion(isc_buffer_t *b, isc_region_t *r);
+/*!<
+ * \brief Make 'r' refer to the active region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+
+void
+isc__buffer_setactive(isc_buffer_t *b, unsigned int n);
+/*!<
+ * \brief Sets the end of the active region 'n' bytes after current.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li current + n <= used
+ */
+
+void
+isc__buffer_first(isc_buffer_t *b);
+/*!<
+ * \brief Make the consumed region empty.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ * Ensures:
+ *
+ *\li current == 0
+ *
+ */
+
+void
+isc__buffer_forward(isc_buffer_t *b, unsigned int n);
+/*!<
+ * \brief Increase the 'consumed' region of 'b' by 'n' bytes.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ *\li current + n <= used
+ *
+ */
+
+void
+isc__buffer_back(isc_buffer_t *b, unsigned int n);
+/*!<
+ * \brief Decrease the 'consumed' region of 'b' by 'n' bytes.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ *\li n <= current
+ *
+ */
+
+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.
+ */
+
+void
+isc__buffer_putuint8(isc_buffer_t *b, uint8_t val);
+/*!<
+ * \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.
+ */
+
+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.
+ */
+
+void
+isc__buffer_putuint16(isc_buffer_t *b, uint16_t 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.
+ */
+
+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.
+ */
+
+void
+isc__buffer_putuint32(isc_buffer_t *b, uint32_t 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.
+ */
+
+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_putuint48(isc_buffer_t *b, uint64_t 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.
+ */
+
+void
+isc__buffer_putuint24(isc_buffer_t *b, uint32_t 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.
+ */
+
+void
+isc__buffer_putmem(isc_buffer_t *b, const unsigned char *base,
+ unsigned int length);
+/*!<
+ * \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'.
+ */
+
+void
+isc__buffer_putstr(isc_buffer_t *b, const char *source);
+/*!<
+ * \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').
+ */
+
+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.
+ */
+
+ISC_LANG_ENDDECLS
+
+/*
+ * Inline macro versions of the functions. These should never be called
+ * directly by an application, but will be used by the functions within
+ * buffer.c. The callers should always use "isc_buffer_*()" names, never
+ * ones beginning with "isc__"
+ */
+
+/*! \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 (macros) 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.
+ */
+#define ISC__BUFFER_INIT(_b, _base, _length) \
+ do { \
+ ISC_REQUIRE((_b) != NULL); \
+ (_b)->base = _base; \
+ (_b)->length = (_length); \
+ (_b)->used = 0; \
+ (_b)->current = 0; \
+ (_b)->active = 0; \
+ (_b)->mctx = NULL; \
+ ISC_LINK_INIT(_b, link); \
+ (_b)->magic = ISC_BUFFER_MAGIC; \
+ (_b)->autore = false; \
+ } while (0)
+
+#define ISC__BUFFER_INITNULL(_b) ISC__BUFFER_INIT(_b, NULL, 0)
+
+#define ISC__BUFFER_INVALIDATE(_b) \
+ do { \
+ 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; \
+ } while (0)
+
+#define ISC__BUFFER_REGION(_b, _r) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ ISC_REQUIRE((_r) != NULL); \
+ (_r)->base = (_b)->base; \
+ (_r)->length = (_b)->length; \
+ } while (0)
+
+#define ISC__BUFFER_USEDREGION(_b, _r) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ ISC_REQUIRE((_r) != NULL); \
+ (_r)->base = (_b)->base; \
+ (_r)->length = (_b)->used; \
+ } while (0)
+
+#define ISC__BUFFER_AVAILABLEREGION(_b, _r) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ ISC_REQUIRE((_r) != NULL); \
+ (_r)->base = isc_buffer_used(_b); \
+ (_r)->length = isc_buffer_availablelength(_b); \
+ } while (0)
+
+#define ISC__BUFFER_ADD(_b, _n) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ ISC_REQUIRE((_b)->used + (_n) <= (_b)->length); \
+ (_b)->used += (_n); \
+ } while (0)
+
+#define ISC__BUFFER_SUBTRACT(_b, _n) \
+ do { \
+ 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; \
+ } while (0)
+
+#define ISC__BUFFER_CLEAR(_b) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ (_b)->used = 0; \
+ (_b)->current = 0; \
+ (_b)->active = 0; \
+ } while (0)
+
+#define ISC__BUFFER_CONSUMEDREGION(_b, _r) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ ISC_REQUIRE((_r) != NULL); \
+ (_r)->base = (_b)->base; \
+ (_r)->length = (_b)->current; \
+ } while (0)
+
+#define ISC__BUFFER_REMAININGREGION(_b, _r) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ ISC_REQUIRE((_r) != NULL); \
+ (_r)->base = isc_buffer_current(_b); \
+ (_r)->length = isc_buffer_remaininglength(_b); \
+ } while (0)
+
+#define ISC__BUFFER_ACTIVEREGION(_b, _r) \
+ do { \
+ 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; \
+ } \
+ } while (0)
+
+#define ISC__BUFFER_SETACTIVE(_b, _n) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ ISC_REQUIRE((_b)->current + (_n) <= (_b)->used); \
+ (_b)->active = (_b)->current + (_n); \
+ } while (0)
+
+#define ISC__BUFFER_FIRST(_b) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ (_b)->current = 0; \
+ } while (0)
+
+#define ISC__BUFFER_FORWARD(_b, _n) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ ISC_REQUIRE((_b)->current + (_n) <= (_b)->used); \
+ (_b)->current += (_n); \
+ } while (0)
+
+#define ISC__BUFFER_BACK(_b, _n) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ ISC_REQUIRE((_n) <= (_b)->current); \
+ (_b)->current -= (_n); \
+ } while (0)
+
+#define ISC__BUFFER_PUTMEM(_b, _base, _length) \
+ do { \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ if (ISC_UNLIKELY((_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); \
+ } \
+ } while (0)
+
+#define ISC__BUFFER_PUTSTR(_b, _source) \
+ do { \
+ unsigned int _length; \
+ unsigned char *_cp; \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ ISC_REQUIRE((_source) != NULL); \
+ _length = (unsigned int)strlen(_source); \
+ if (ISC_UNLIKELY((_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); \
+ } while (0)
+
+#define ISC__BUFFER_PUTUINT8(_b, _val) \
+ do { \
+ unsigned char *_cp; \
+ /* evaluate (_val) only once */ \
+ uint8_t _val2 = (_val); \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ if (ISC_UNLIKELY((_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] = _val2; \
+ } while (0)
+
+#define ISC__BUFFER_PUTUINT16(_b, _val) \
+ do { \
+ unsigned char *_cp; \
+ /* evaluate (_val) only once */ \
+ uint16_t _val2 = (_val); \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ if (ISC_UNLIKELY((_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)(_val2 >> 8); \
+ _cp[1] = (unsigned char)_val2; \
+ } while (0)
+
+#define ISC__BUFFER_PUTUINT24(_b, _val) \
+ do { \
+ unsigned char *_cp; \
+ /* evaluate (_val) only once */ \
+ uint32_t _val2 = (_val); \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ if (ISC_UNLIKELY((_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)(_val2 >> 16); \
+ _cp[1] = (unsigned char)(_val2 >> 8); \
+ _cp[2] = (unsigned char)_val2; \
+ } while (0)
+
+#define ISC__BUFFER_PUTUINT32(_b, _val) \
+ do { \
+ unsigned char *_cp; \
+ /* evaluate (_val) only once */ \
+ uint32_t _val2 = (_val); \
+ ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \
+ if (ISC_UNLIKELY((_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)(_val2 >> 24); \
+ _cp[1] = (unsigned char)(_val2 >> 16); \
+ _cp[2] = (unsigned char)(_val2 >> 8); \
+ _cp[3] = (unsigned char)_val2; \
+ } while (0)
+
+#if defined(ISC_BUFFER_USEINLINE)
+#define isc_buffer_init ISC__BUFFER_INIT
+#define isc_buffer_initnull ISC__BUFFER_INITNULL
+#define isc_buffer_invalidate ISC__BUFFER_INVALIDATE
+#define isc_buffer_region ISC__BUFFER_REGION
+#define isc_buffer_usedregion ISC__BUFFER_USEDREGION
+#define isc_buffer_availableregion ISC__BUFFER_AVAILABLEREGION
+#define isc_buffer_add ISC__BUFFER_ADD
+#define isc_buffer_subtract ISC__BUFFER_SUBTRACT
+#define isc_buffer_clear ISC__BUFFER_CLEAR
+#define isc_buffer_consumedregion ISC__BUFFER_CONSUMEDREGION
+#define isc_buffer_remainingregion ISC__BUFFER_REMAININGREGION
+#define isc_buffer_activeregion ISC__BUFFER_ACTIVEREGION
+#define isc_buffer_setactive ISC__BUFFER_SETACTIVE
+#define isc_buffer_first ISC__BUFFER_FIRST
+#define isc_buffer_forward ISC__BUFFER_FORWARD
+#define isc_buffer_back ISC__BUFFER_BACK
+#define isc_buffer_putmem ISC__BUFFER_PUTMEM
+#define isc_buffer_putstr ISC__BUFFER_PUTSTR
+#define isc_buffer_putuint8 ISC__BUFFER_PUTUINT8
+#define isc_buffer_putuint16 ISC__BUFFER_PUTUINT16
+#define isc_buffer_putuint24 ISC__BUFFER_PUTUINT24
+#define isc_buffer_putuint32 ISC__BUFFER_PUTUINT32
+#else /* if defined(ISC_BUFFER_USEINLINE) */
+#define isc_buffer_init isc__buffer_init
+#define isc_buffer_initnull isc__buffer_initnull
+#define isc_buffer_invalidate isc__buffer_invalidate
+#define isc_buffer_region isc__buffer_region
+#define isc_buffer_usedregion isc__buffer_usedregion
+#define isc_buffer_availableregion isc__buffer_availableregion
+#define isc_buffer_add isc__buffer_add
+#define isc_buffer_subtract isc__buffer_subtract
+#define isc_buffer_clear isc__buffer_clear
+#define isc_buffer_consumedregion isc__buffer_consumedregion
+#define isc_buffer_remainingregion isc__buffer_remainingregion
+#define isc_buffer_activeregion isc__buffer_activeregion
+#define isc_buffer_setactive isc__buffer_setactive
+#define isc_buffer_first isc__buffer_first
+#define isc_buffer_forward isc__buffer_forward
+#define isc_buffer_back isc__buffer_back
+#define isc_buffer_putmem isc__buffer_putmem
+#define isc_buffer_putstr isc__buffer_putstr
+#define isc_buffer_putuint8 isc__buffer_putuint8
+#define isc_buffer_putuint16 isc__buffer_putuint16
+#define isc_buffer_putuint24 isc__buffer_putuint24
+#define isc_buffer_putuint32 isc__buffer_putuint32
+#endif /* if defined(ISC_BUFFER_USEINLINE) */
+
+#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)
+
+/*
+ * No inline method for this one (yet).
+ */
+#define isc_buffer_putuint48 isc__buffer_putuint48
+
+#endif /* ISC_BUFFER_H */
diff --git a/lib/isc/include/isc/bufferlist.h b/lib/isc/include/isc/bufferlist.h
new file mode 100644
index 0000000..95ad3ef
--- /dev/null
+++ b/lib/isc/include/isc/bufferlist.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_BUFFERLIST_H
+#define ISC_BUFFERLIST_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file isc/bufferlist.h
+ *
+ *
+ *\brief Buffer lists have no synchronization. Clients must ensure
+ * exclusive * access.
+ *
+ * \li Reliability:
+ * No anticipated impact.
+ *
+ * \li Security:
+ * No anticipated impact.
+ *
+ * \li Standards:
+ * None.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+unsigned int
+isc_bufferlist_usedcount(isc_bufferlist_t *bl);
+/*!<
+ * \brief Return the length of the sum of all used regions of all buffers in
+ * the buffer list 'bl'
+ *
+ * Requires:
+ *
+ *\li 'bl' is not NULL.
+ *
+ * Returns:
+ *\li sum of all used regions' lengths.
+ */
+
+unsigned int
+isc_bufferlist_availablecount(isc_bufferlist_t *bl);
+/*!<
+ * \brief Return the length of the sum of all available regions of all buffers
+ * in the buffer list 'bl'
+ *
+ * Requires:
+ *
+ *\li 'bl' is not NULL.
+ *
+ * Returns:
+ *\li sum of all available regions' lengths.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_BUFFERLIST_H */
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..69143ce
--- /dev/null
+++ b/lib/isc/include/isc/commandline.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_COMMANDLINE_H
+#define ISC_COMMANDLINE_H 1
+
+/*! \file isc/commandline.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/platform.h>
+#include <isc/result.h>
+
+/*% Index into parent argv vector. */
+LIBISC_EXTERNAL_DATA extern int isc_commandline_index;
+/*% Character checked for validity. */
+LIBISC_EXTERNAL_DATA extern int isc_commandline_option;
+/*% Argument associated with option. */
+LIBISC_EXTERNAL_DATA extern char *isc_commandline_argument;
+/*% For printing error messages. */
+LIBISC_EXTERNAL_DATA extern char *isc_commandline_progname;
+/*% Print error message. */
+LIBISC_EXTERNAL_DATA extern bool isc_commandline_errprint;
+/*% Reset getopt. */
+LIBISC_EXTERNAL_DATA 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
+
+#endif /* ISC_COMMANDLINE_H */
diff --git a/lib/isc/include/isc/counter.h b/lib/isc/include/isc/counter.h
new file mode 100644
index 0000000..f61a55b
--- /dev/null
+++ b/lib/isc/include/isc/counter.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.
+ */
+
+#ifndef ISC_COUNTER_H
+#define ISC_COUNTER_H 1
+
+/*****
+***** 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
+
+#endif /* ISC_COUNTER_H */
diff --git a/lib/isc/include/isc/crc64.h b/lib/isc/include/isc/crc64.h
new file mode 100644
index 0000000..a3fdfb4
--- /dev/null
+++ b/lib/isc/include/isc/crc64.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_CRC64_H
+#define ISC_CRC64_H 1
+
+/*! \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
+
+#endif /* ISC_CRC64_H */
diff --git a/lib/isc/include/isc/deprecated.h b/lib/isc/include/isc/deprecated.h
new file mode 100644
index 0000000..73ce584
--- /dev/null
+++ b/lib/isc/include/isc/deprecated.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_DEPRECATED_H
+#define ISC_DEPRECATED_H
+
+#if (__GNUC__ + 0) > 3
+#define ISC_DEPRECATED __attribute__((deprecated))
+#else /* if (__GNUC__ + 0) > 3 */
+#define ISC_DEPRECATED /* none */
+#endif /* __GNUC__ > 3*/
+
+#endif /* ifndef ISC_DEPRECATED_H */
diff --git a/lib/isc/include/isc/endian.h b/lib/isc/include/isc/endian.h
new file mode 100644
index 0000000..e598a7b
--- /dev/null
+++ b/lib/isc/include/isc/endian.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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(_WIN32)
+
+/*
+ * Windows is always little-endian and has its own byte-swapping routines, so
+ * use these.
+ */
+
+#include <stdlib.h>
+
+#define htobe16(x) _byteswap_ushort(x)
+#define htole16(x) (x)
+#define be16toh(x) _byteswap_ushort(x)
+#define le16toh(x) (x)
+
+#define htobe32(x) _byteswap_ulong(x)
+#define htole32(x) (x)
+#define be32toh(x) _byteswap_ulong(x)
+#define le32toh(x) (x)
+
+#define htobe64(x) _byteswap_uint64(x)
+#define htole64(x) (x)
+#define be64toh(x) _byteswap_uint64(x)
+#define le64toh(x) (x)
+
+#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..1b058dd
--- /dev/null
+++ b/lib/isc/include/isc/errno.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_ERRNO_H
+#define ISC_ERRNO_H 1
+
+/*! \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
+
+#endif /* ISC_ERRNO_H */
diff --git a/lib/isc/include/isc/error.h b/lib/isc/include/isc/error.h
new file mode 100644
index 0000000..4f6fba3
--- /dev/null
+++ b/lib/isc/include/isc/error.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.
+ */
+
+#ifndef ISC_ERROR_H
+#define ISC_ERROR_H 1
+
+/*! \file isc/error.h */
+
+#include <stdarg.h>
+
+#include <isc/formatcheck.h>
+#include <isc/lang.h>
+#include <isc/likely.h>
+#include <isc/platform.h>
+
+ISC_LANG_BEGINDECLS
+
+typedef void (*isc_errorcallback_t)(const char *, int, 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 *, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+/*% fatal error */
+ISC_PLATFORM_NORETURN_PRE void
+isc_error_fatal(const char *, int, const char *, ...)
+ ISC_FORMAT_PRINTF(3, 4) ISC_PLATFORM_NORETURN_POST;
+
+/*% runtimecheck error */
+ISC_PLATFORM_NORETURN_PRE void
+isc_error_runtimecheck(const char *, int,
+ const char *) ISC_PLATFORM_NORETURN_POST;
+
+#define ISC_ERROR_RUNTIMECHECK(cond) \
+ ((void)(ISC_LIKELY(cond) || \
+ ((isc_error_runtimecheck)(__FILE__, __LINE__, #cond), 0)))
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_ERROR_H */
diff --git a/lib/isc/include/isc/event.h b/lib/isc/include/isc/event.h
new file mode 100644
index 0000000..e79a8d8
--- /dev/null
+++ b/lib/isc/include/isc/event.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.
+ */
+
+#ifndef ISC_EVENT_H
+#define ISC_EVENT_H 1
+
+/*! \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
+
+#endif /* ISC_EVENT_H */
diff --git a/lib/isc/include/isc/eventclass.h b/lib/isc/include/isc/eventclass.h
new file mode 100644
index 0000000..300848a
--- /dev/null
+++ b/lib/isc/include/isc/eventclass.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_EVENTCLASS_H
+#define ISC_EVENTCLASS_H 1
+
+/*! \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)
+/*@}*/
+
+#endif /* ISC_EVENTCLASS_H */
diff --git a/lib/isc/include/isc/file.h b/lib/isc/include/isc/file.h
new file mode 100644
index 0000000..6ef2a39
--- /dev/null
+++ b/lib/isc/include/isc/file.h
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_FILE_H
+#define ISC_FILE_H 1
+
+/*! \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
+ */
+
+void *
+isc_file_mmap(void *addr, size_t len, int prot, int flags, int fd,
+ off_t offset);
+/*%<
+ * Portable front-end to mmap(). If mmap() is not defined on this
+ * platform, then we simulate it by calling malloc() and read().
+ * (In this event, the addr, prot, and flags parameters are ignored).
+ */
+
+int
+isc_file_munmap(void *addr, size_t len);
+/*%<
+ * Portable front-end to munmap(). If munmap() is not defined on
+ * this platform, then we simply free the memory.
+ */
+
+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
+
+#endif /* ISC_FILE_H */
diff --git a/lib/isc/include/isc/formatcheck.h b/lib/isc/include/isc/formatcheck.h
new file mode 100644
index 0000000..28bd573
--- /dev/null
+++ b/lib/isc/include/isc/formatcheck.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_FORMATCHECK_H
+#define ISC_FORMATCHECK_H 1
+
+/*! \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__ */
+
+#endif /* ISC_FORMATCHECK_H */
diff --git a/lib/isc/include/isc/fsaccess.h b/lib/isc/include/isc/fsaccess.h
new file mode 100644
index 0000000..46c8774
--- /dev/null
+++ b/lib/isc/include/isc/fsaccess.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_FSACCESS_H
+#define ISC_FSACCESS_H 1
+
+/*! \file isc/fsaccess.h
+ * \brief The ISC filesystem access module encapsulates the setting of file
+ * and directory access permissions into one API that is meant to be
+ * portable to multiple operating systems.
+ *
+ * The two primary operating system flavors that are initially accommodated
+ * are POSIX and Windows NT 4.0 and later. The Windows NT access model is
+ * considerable more flexible than POSIX's model (as much as I am loathe to
+ * admit it), and so the ISC API has a higher degree of complexity than would
+ * be needed to simply address POSIX's needs.
+ *
+ * The full breadth of NT's flexibility is not available either, for the
+ * present time. Much of it is to provide compatibility with what Unix
+ * programmers are expecting. This is also due to not yet really needing all
+ * of the functionality of an NT system (or, for that matter, a POSIX system)
+ * in BIND9, and so resolving how to handle the various incompatibilities has
+ * been a purely theoretical exercise with no operational experience to
+ * indicate how flawed the thinking may be.
+ *
+ * Some of the more notable dumbing down of NT for this API includes:
+ *
+ *\li Each of FILE_READ_DATA and FILE_READ_EA are set with #ISC_FSACCESS_READ.
+ *
+ * \li All of FILE_WRITE_DATA, FILE_WRITE_EA and FILE_APPEND_DATA are
+ * set with #ISC_FSACCESS_WRITE. FILE_WRITE_ATTRIBUTES is not set
+ * so as to be consistent with Unix, where only the owner of the file
+ * or the superuser can change the attributes/mode of a file.
+ *
+ * \li Both of FILE_ADD_FILE and FILE_ADD_SUBDIRECTORY are set with
+ * #ISC_FSACCESS_CREATECHILD. This is similar to setting the WRITE
+ * permission on a Unix directory.
+ *
+ * \li SYNCHRONIZE is always set for files and directories, unless someone
+ * can give me a reason why this is a bad idea.
+ *
+ * \li READ_CONTROL and FILE_READ_ATTRIBUTES are always set; this is
+ * consistent with Unix, where any file or directory can be stat()'d
+ * unless the directory path disallows complete access somewhere along
+ * the way.
+ *
+ * \li WRITE_DAC is only set for the owner. This too is consistent with
+ * Unix, and is tighter security than allowing anyone else to be
+ * able to set permissions.
+ *
+ * \li DELETE is only set for the owner. On Unix the ability to delete
+ * a file is controlled by the directory permissions, but it isn't
+ * currently clear to me what happens on NT if the directory has
+ * FILE_DELETE_CHILD set but a file within it does not have DELETE
+ * set. Always setting DELETE on the file/directory for the owner
+ * gives maximum flexibility to the owner without exposing the
+ * file to deletion by others.
+ *
+ * \li WRITE_OWNER is never set. This too is consistent with Unix,
+ * and is also tighter security than allowing anyone to change the
+ * ownership of the file apart from the superu..ahem, Administrator.
+ *
+ * \li Inheritance is set to NO_INHERITANCE.
+ *
+ * Unix's dumbing down includes:
+ *
+ * \li The sticky bit cannot be set.
+ *
+ * \li setuid and setgid cannot be set.
+ *
+ * \li Only regular files and directories can be set.
+ *
+ * The rest of this comment discusses a few of the incompatibilities
+ * between the two systems that need more thought if this API is to
+ * be extended to accommodate them.
+ *
+ * The Windows standard access right "DELETE" doesn't have a direct
+ * equivalent in the Unix world, so it isn't clear what should be done
+ * with it.
+ *
+ * The Unix sticky bit is not supported. While NT does have a concept
+ * of allowing users to create files in a directory but not delete or
+ * rename them, it does not have a concept of allowing them to be deleted
+ * if they are owned by the user trying to delete/rename. While it is
+ * probable that something could be cobbled together in NT 5 with inheritance,
+ * it can't really be done in NT 4 as a single property that you could
+ * set on a directory. You'd need to coordinate something with file creation
+ * so that every file created had DELETE set for the owner but no one else.
+ *
+ * On Unix systems, setting #ISC_FSACCESS_LISTDIRECTORY sets READ.
+ * ... setting either #ISC_FSACCESS_CREATECHILD or #ISC_FSACCESS_DELETECHILD
+ * sets WRITE.
+ * ... setting #ISC_FSACCESS_ACCESSCHILD sets EXECUTE.
+ *
+ * On NT systems, setting #ISC_FSACCESS_LISTDIRECTORY sets FILE_LIST_DIRECTORY.
+ * ... setting #ISC_FSACCESS_CREATECHILD sets FILE_CREATE_CHILD independently.
+ * ... setting #ISC_FSACCESS_DELETECHILD sets FILE_DELETE_CHILD independently.
+ * ... setting #ISC_FSACCESS_ACCESSCHILD sets FILE_TRAVERSE.
+ *
+ * Unresolved: XXXDCL
+ * \li What NT access right controls the ability to rename a file?
+ * \li How does DELETE work? If a directory has FILE_DELETE_CHILD but a
+ * file or directory within it does not have DELETE, is that file
+ * or directory deletable?
+ * \li To implement isc_fsaccess_get(), mapping an existing Unix permission
+ * mode_t back to an isc_fsaccess_t is pretty trivial; however, mapping
+ * an NT DACL could be impossible to do in a responsible way.
+ * \li Similarly, trying to implement the functionality of being able to
+ * say "add group writability to whatever permissions already exist"
+ * could be tricky on NT because of the order-of-entry issue combined
+ * with possibly having one or more matching ACEs already explicitly
+ * granting or denying access. Because this functionality is
+ * not yet needed by the ISC, no code has been written to try to
+ * solve this problem.
+ */
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+/*
+ * Trustees.
+ */
+#define ISC_FSACCESS_OWNER 0x1 /*%< User account. */
+#define ISC_FSACCESS_GROUP 0x2 /*%< Primary group owner. */
+#define ISC_FSACCESS_OTHER 0x4 /*%< Not the owner or the group owner. */
+#define ISC_FSACCESS_WORLD 0x7 /*%< User, Group, Other. */
+
+/*
+ * Types of permission.
+ */
+#define ISC_FSACCESS_READ 0x00000001 /*%< File only. */
+#define ISC_FSACCESS_WRITE 0x00000002 /*%< File only. */
+#define ISC_FSACCESS_EXECUTE 0x00000004 /*%< File only. */
+#define ISC_FSACCESS_CREATECHILD 0x00000008 /*%< Dir only. */
+#define ISC_FSACCESS_DELETECHILD 0x00000010 /*%< Dir only. */
+#define ISC_FSACCESS_LISTDIRECTORY 0x00000020 /*%< Dir only. */
+#define ISC_FSACCESS_ACCESSCHILD 0x00000040 /*%< Dir only. */
+
+/*%
+ * Adding any permission bits beyond 0x200 would mean typedef'ing
+ * isc_fsaccess_t as uint64_t, and redefining this value to
+ * reflect the new range of permission types, Probably to 21 for
+ * maximum flexibility. The number of bits has to accommodate all of
+ * the permission types, and three full sets of them have to fit
+ * within an isc_fsaccess_t.
+ */
+#define ISC__FSACCESS_PERMISSIONBITS 10
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_fsaccess_add(int trustee, int permission, isc_fsaccess_t *access);
+
+void
+isc_fsaccess_remove(int trustee, int permission, isc_fsaccess_t *access);
+
+isc_result_t
+isc_fsaccess_set(const char *path, isc_fsaccess_t access);
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_FSACCESS_H */
diff --git a/lib/isc/include/isc/fuzz.h b/lib/isc/include/isc/fuzz.h
new file mode 100644
index 0000000..207728e
--- /dev/null
+++ b/lib/isc/include/isc/fuzz.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_FUZZ_H
+#define ISC_FUZZ_H
+
+typedef enum {
+ isc_fuzz_none,
+ isc_fuzz_client,
+ isc_fuzz_tcpclient,
+ isc_fuzz_resolver,
+ isc_fuzz_http,
+ isc_fuzz_rndc
+} isc_fuzztype_t;
+
+#endif /* ISC_FUZZ_H */
diff --git a/lib/isc/include/isc/hash.h b/lib/isc/include/isc/hash.h
new file mode 100644
index 0000000..62ba186
--- /dev/null
+++ b/lib/isc/include/isc/hash.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.
+ */
+
+#ifndef ISC_HASH_H
+#define ISC_HASH_H 1
+
+#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
+
+#endif /* ISC_HASH_H */
diff --git a/lib/isc/include/isc/heap.h b/lib/isc/include/isc/heap.h
new file mode 100644
index 0000000..d492c60
--- /dev/null
+++ b/lib/isc/include/isc/heap.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_HEAP_H
+#define ISC_HEAP_H 1
+
+/*! \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
+
+#endif /* ISC_HEAP_H */
diff --git a/lib/isc/include/isc/hex.h b/lib/isc/include/isc/hex.h
new file mode 100644
index 0000000..a0e5e39
--- /dev/null
+++ b/lib/isc/include/isc/hex.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.
+ */
+
+#ifndef ISC_HEX_H
+#define ISC_HEX_H 1
+
+/*! \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
+
+#endif /* ISC_HEX_H */
diff --git a/lib/isc/include/isc/hmac.h b/lib/isc/include/isc/hmac.h
new file mode 100644
index 0000000..3d0e741
--- /dev/null
+++ b/lib/isc/include/isc/hmac.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can 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/platform.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: 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). 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 @digestlen.
+ */
+isc_result_t
+isc_hmac(const isc_md_type_t *type, const void *key, const int 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, 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: 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. 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 @digestlen. 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..f1386bb
--- /dev/null
+++ b/lib/isc/include/isc/ht.h
@@ -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.
+ */
+
+/* ! \file */
+
+#ifndef ISC_HT_H
+#define ISC_HT_H 1
+
+#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;
+
+/*%
+ * Initialize hashtable at *htp, using memory context and size of (1<<bits)
+ *
+ * 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);
+
+/*%
+ * 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
+ *
+ * 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, 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
+ *
+ * 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, uint32_t keysize,
+ void **valuep);
+
+/*%
+ * Delete node from hashtable
+ *
+ * Requires:
+ *\li ht is a valid hashtable
+ *
+ * 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, 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
+ */
+unsigned int
+isc_ht_count(isc_ht_t *ht);
+#endif /* ifndef ISC_HT_H */
diff --git a/lib/isc/include/isc/httpd.h b/lib/isc/include/isc/httpd.h
new file mode 100644
index 0000000..7b66049
--- /dev/null
+++ b/lib/isc/include/isc/httpd.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_HTTPD_H
+#define ISC_HTTPD_H 1
+
+/*! \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>
+
+/*%
+ * 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;
+};
+
+#define HTTPD_EVENTCLASS ISC_EVENTCLASS(4300)
+#define HTTPD_SHUTDOWN (HTTPD_EVENTCLASS + 0x0001)
+
+#define ISC_HTTPDMGR_FLAGSHUTTINGDOWN 0x00000001
+
+/*
+ * Create a new http daemon which will send, once every time period,
+ * a http-like header followed by HTTP data.
+ */
+isc_result_t
+isc_httpdmgr_create(isc_mem_t *mctx, isc_socket_t *sock, isc_task_t *task,
+ isc_httpdclientok_t *client_ok,
+ isc_httpdondestroy_t *ondestory, void *cb_arg,
+ isc_timermgr_t *tmgr, isc_httpdmgr_t **httpdp);
+
+void
+isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdp);
+
+isc_result_t
+isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url,
+ isc_httpdaction_t *func, void *arg);
+
+isc_result_t
+isc_httpdmgr_addurl2(isc_httpdmgr_t *httpdmgr, const char *url, bool isstatic,
+ isc_httpdaction_t *func, void *arg);
+
+isc_result_t
+isc_httpd_response(isc_httpd_t *httpd);
+
+isc_result_t
+isc_httpd_addheader(isc_httpd_t *httpd, const char *name, const char *val);
+
+isc_result_t
+isc_httpd_addheaderuint(isc_httpd_t *httpd, const char *name, int val);
+
+isc_result_t
+isc_httpd_endheaders(isc_httpd_t *httpd);
+
+void
+isc_httpd_setfinishhook(void (*fn)(void));
+
+#endif /* ISC_HTTPD_H */
diff --git a/lib/isc/include/isc/interfaceiter.h b/lib/isc/include/isc/interfaceiter.h
new file mode 100644
index 0000000..5f6cbe2
--- /dev/null
+++ b/lib/isc/include/isc/interfaceiter.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_INTERFACEITER_H
+#define ISC_INTERFACEITER_H 1
+
+/*****
+***** 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
+
+#endif /* ISC_INTERFACEITER_H */
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..35346db
--- /dev/null
+++ b/lib/isc/include/isc/lang.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_LANG_H
+#define ISC_LANG_H 1
+
+/*! \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 */
+
+#endif /* ISC_LANG_H */
diff --git a/lib/isc/include/isc/lex.h b/lib/isc/include/isc/lex.h
new file mode 100644
index 0000000..0209cbe
--- /dev/null
+++ b/lib/isc/include/isc/lex.h
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_LEX_H
+#define ISC_LEX_H 1
+
+/*****
+***** 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
+
+#endif /* ISC_LEX_H */
diff --git a/lib/isc/include/isc/lfsr.h b/lib/isc/include/isc/lfsr.h
new file mode 100644
index 0000000..0e4f7f2
--- /dev/null
+++ b/lib/isc/include/isc/lfsr.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_LFSR_H
+#define ISC_LFSR_H 1
+
+/*! \file isc/lfsr.h */
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+typedef struct isc_lfsr isc_lfsr_t;
+
+/*%
+ * This function is called when reseeding is needed. It is allowed to
+ * modify any state in the LFSR in any way it sees fit OTHER THAN "bits".
+ *
+ * It MUST set "count" to a new value or the lfsr will never reseed again.
+ *
+ * Also, a reseed will never occur in the middle of an extraction. This
+ * is purely an optimization, and is probably what one would want.
+ */
+typedef void (*isc_lfsrreseed_t)(isc_lfsr_t *, void *);
+
+/*%
+ * The members of this structure can be used by the application, but care
+ * needs to be taken to not change state once the lfsr is in operation.
+ */
+struct isc_lfsr {
+ uint32_t state; /*%< previous state */
+ unsigned int bits; /*%< length */
+ uint32_t tap; /*%< bit taps */
+ unsigned int count; /*%< reseed count (in BITS!) */
+ isc_lfsrreseed_t reseed; /*%< reseed function */
+ void *arg; /*%< reseed function argument */
+};
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_lfsr_init(isc_lfsr_t *lfsr, uint32_t state, unsigned int bits, uint32_t tap,
+ unsigned int count, isc_lfsrreseed_t reseed, void *arg);
+/*%<
+ * Initialize an LFSR.
+ *
+ * Note:
+ *
+ *\li Putting untrusted values into this function will cause the LFSR to
+ * generate (perhaps) non-maximal length sequences.
+ *
+ * Requires:
+ *
+ *\li lfsr != NULL
+ *
+ *\li 8 <= bits <= 32
+ *
+ *\li tap != 0
+ */
+
+void
+isc_lfsr_generate(isc_lfsr_t *lfsr, void *data, unsigned int count);
+/*%<
+ * Returns "count" bytes of data from the LFSR.
+ *
+ * Requires:
+ *
+ *\li lfsr be valid.
+ *
+ *\li data != NULL.
+ *
+ *\li count > 0.
+ */
+
+void
+isc_lfsr_skip(isc_lfsr_t *lfsr, unsigned int skip);
+/*%<
+ * Skip "skip" states.
+ *
+ * Requires:
+ *
+ *\li lfsr be valid.
+ */
+
+uint32_t
+isc_lfsr_generate32(isc_lfsr_t *lfsr1, isc_lfsr_t *lfsr2);
+/*%<
+ * Given two LFSRs, use the current state from each to skip entries in the
+ * other. The next states are then xor'd together and returned.
+ *
+ * WARNING:
+ *
+ *\li This function is used only for very, very low security data, such
+ * as DNS message IDs where it is desired to have an unpredictable
+ * stream of bytes that are harder to predict than a simple flooding
+ * attack.
+ *
+ * Notes:
+ *
+ *\li Since the current state from each of the LFSRs is used to skip
+ * state in the other, it is important that no state be leaked
+ * from either LFSR.
+ *
+ * Requires:
+ *
+ *\li lfsr1 and lfsr2 be valid.
+ *
+ *\li 1 <= skipbits <= 31
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_LFSR_H */
diff --git a/lib/isc/include/isc/lib.h b/lib/isc/include/isc/lib.h
new file mode 100644
index 0000000..60d638d
--- /dev/null
+++ b/lib/isc/include/isc/lib.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/lib.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_lib_register(void);
+/*!<
+ * \brief Register the ISC library implementations for some base services
+ * such as memory or event management and handling socket or timer events.
+ * An external application that wants to use the ISC library must call this
+ * function very early in main().
+ */
+
+#ifdef WIN32
+int
+isc_lib_ntservice(int(WINAPI *mainfunc)(int argc, char *argv[]), int argc,
+ char *argv[]);
+/*!<
+ * \brief Execute a special routine needed when running as a Windows Service.
+ */
+#endif /* ifdef WIN32 */
+
+extern void
+isc_enable_constructors(void);
+/*!<
+ * \brief Enable constructor linkage in non-libtool static builds.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/likely.h b/lib/isc/include/isc/likely.h
new file mode 100644
index 0000000..360c383
--- /dev/null
+++ b/lib/isc/include/isc/likely.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.
+ */
+
+#ifndef ISC_LIKELY_H
+#define ISC_LIKELY_H 1
+
+/*%
+ * Performance
+ */
+#ifdef CPPCHECK
+#define ISC_LIKELY(x) (x)
+#define ISC_UNLIKELY(x) (x)
+#else /* ifdef CPPCHECK */
+#ifdef HAVE_BUILTIN_EXPECT
+#define ISC_LIKELY(x) __builtin_expect(!!(x), 1)
+#define ISC_UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else /* ifdef HAVE_BUILTIN_EXPECT */
+#define ISC_LIKELY(x) (x)
+#define ISC_UNLIKELY(x) (x)
+#endif /* ifdef HAVE_BUILTIN_EXPECT */
+#endif /* ifdef CPPCHECK */
+
+#endif /* ISC_LIKELY_H */
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..289cbd7
--- /dev/null
+++ b/lib/isc/include/isc/log.h
@@ -0,0 +1,848 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_LOG_H
+#define ISC_LOG_H 1
+
+/*! \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/platform.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.
+ */
+LIBISC_EXTERNAL_DATA extern isc_logcategory_t isc_categories[];
+LIBISC_EXTERNAL_DATA extern isc_log_t *isc_lctx;
+LIBISC_EXTERNAL_DATA 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_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.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_LOG_H */
diff --git a/lib/isc/include/isc/magic.h b/lib/isc/include/isc/magic.h
new file mode 100644
index 0000000..1dc9fc2
--- /dev/null
+++ b/lib/isc/include/isc/magic.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_MAGIC_H
+#define ISC_MAGIC_H 1
+
+#include <isc/likely.h>
+
+/*! \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) \
+ (ISC_LIKELY((a) != NULL) && \
+ ISC_LIKELY(((const isc__magic_t *)(a))->magic == (b)))
+
+#define ISC_MAGIC(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
+
+#endif /* ISC_MAGIC_H */
diff --git a/lib/isc/include/isc/managers.h b/lib/isc/include/isc/managers.h
new file mode 100644
index 0000000..0ed17ff
--- /dev/null
+++ b/lib/isc/include/isc/managers.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
+
+#include <isc/netmgr.h>
+#include <isc/result.h>
+#include <isc/socket.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);
+
+void
+isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp);
diff --git a/lib/isc/include/isc/md.h b/lib/isc/include/isc/md.h
new file mode 100644
index 0000000..eb9c863
--- /dev/null
+++ b/lib/isc/include/isc/md.h
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can 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/platform.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..1542edd
--- /dev/null
+++ b/lib/isc/include/isc/mem.h
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_MEM_H
+#define ISC_MEM_H 1
+
+/*! \file isc/mem.h */
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/lang.h>
+#include <isc/mutex.h>
+#include <isc/platform.h>
+#include <isc/types.h>
+#include <isc/util.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 1
+#endif /* ifndef ISC_MEM_TRACKLINES */
+
+/*%
+ * Define ISC_MEM_CHECKOVERRUN=1 to turn on checks for using memory outside
+ * the requested space. This will increase the size of each allocation.
+ *
+ * If we are performing a Coverity static analysis then ISC_MEM_CHECKOVERRUN
+ * can hide bugs that would otherwise discovered so force to zero.
+ */
+#ifdef __COVERITY__
+#undef ISC_MEM_CHECKOVERRUN
+#define ISC_MEM_CHECKOVERRUN 0
+#endif /* ifdef __COVERITY__ */
+#ifndef ISC_MEM_CHECKOVERRUN
+#define ISC_MEM_CHECKOVERRUN 1
+#endif /* ifndef ISC_MEM_CHECKOVERRUN */
+
+/*%
+ * Define ISC_MEMPOOL_NAMES=1 to make memory pools store a symbolic
+ * name so that the leaking pool can be more readily identified in
+ * case of a memory leak.
+ */
+#ifndef ISC_MEMPOOL_NAMES
+#define ISC_MEMPOOL_NAMES 1
+#endif /* ifndef ISC_MEMPOOL_NAMES */
+
+LIBISC_EXTERNAL_DATA extern unsigned int isc_mem_debugging;
+LIBISC_EXTERNAL_DATA extern unsigned int isc_mem_defaultflags;
+
+/*@{*/
+#define ISC_MEM_DEBUGTRACE 0x00000001U
+#define ISC_MEM_DEBUGRECORD 0x00000002U
+#define ISC_MEM_DEBUGUSAGE 0x00000004U
+#define ISC_MEM_DEBUGSIZE 0x00000008U
+#define ISC_MEM_DEBUGCTX 0x00000010U
+#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.
+ *
+ * \li #ISC_MEM_DEBUGSIZE
+ * Check the size argument being passed to isc_mem_put() matches
+ * that passed to isc_mem_get().
+ *
+ * \li #ISC_MEM_DEBUGCTX
+ * Check the mctx argument being passed to isc_mem_put() matches
+ * that passed to isc_mem_get().
+ */
+/*@}*/
+
+#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 */
+
+/*!
+ * Define ISC_MEM_USE_INTERNAL_MALLOC=1 to use the internal malloc()
+ * implementation in preference to the system one. The internal malloc()
+ * is very space-efficient, and quite fast on uniprocessor systems. It
+ * performs poorly on multiprocessor machines.
+ * JT: we can overcome the performance issue on multiprocessor machines
+ * by carefully separating memory contexts.
+ */
+
+#if !defined(ISC_MEM_USE_INTERNAL_MALLOC) && !__SANITIZE_ADDRESS__
+#define ISC_MEM_USE_INTERNAL_MALLOC 0
+#endif /* ifndef ISC_MEM_USE_INTERNAL_MALLOC */
+
+/*
+ * Flags for isc_mem_create() calls.
+ */
+#define ISC_MEMFLAG_RESERVED 0x00000001 /* reserved, obsoleted, don't use */
+#define ISC_MEMFLAG_INTERNAL 0x00000002 /* use internal malloc */
+#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_USE_INTERNAL_MALLOC
+
+#if ISC_MEM_DEFAULTFILL
+#define ISC_MEMFLAG_DEFAULT ISC_MEMFLAG_FILL
+#else /* if ISC_MEM_DEFAULTFILL */
+#define ISC_MEMFLAG_DEFAULT 0
+#endif /* if ISC_MEM_DEFAULTFILL */
+
+#else /* if !ISC_MEM_USE_INTERNAL_MALLOC */
+
+#if ISC_MEM_DEFAULTFILL
+#define ISC_MEMFLAG_DEFAULT ISC_MEMFLAG_INTERNAL | ISC_MEMFLAG_FILL
+#else /* if ISC_MEM_DEFAULTFILL */
+#define ISC_MEMFLAG_DEFAULT ISC_MEMFLAG_INTERNAL
+#endif /* if ISC_MEM_DEFAULTFILL */
+
+#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
+ */
+
+/*% memory and memory pool methods */
+typedef struct isc_memmethods {
+ void *(*memget)(isc_mem_t *mctx, size_t size _ISC_MEM_FLARG);
+ void (*memput)(isc_mem_t *mctx, void *ptr, size_t size _ISC_MEM_FLARG);
+ void (*memputanddetach)(isc_mem_t **mctxp, void *ptr,
+ size_t size _ISC_MEM_FLARG);
+ void *(*memallocate)(isc_mem_t *mctx, size_t size _ISC_MEM_FLARG);
+ void *(*memreallocate)(isc_mem_t *mctx, void *ptr,
+ size_t size _ISC_MEM_FLARG);
+ char *(*memstrdup)(isc_mem_t *mctx, const char *s _ISC_MEM_FLARG);
+ char *(*memstrndup)(isc_mem_t *mctx, const char *s,
+ size_t size _ISC_MEM_FLARG);
+ void (*memfree)(isc_mem_t *mctx, void *ptr _ISC_MEM_FLARG);
+} isc_memmethods_t;
+
+/*%
+ * This structure is actually just the common prefix of a memory context
+ * implementation's version of an isc_mem_t.
+ * \brief
+ * Direct use of this structure by clients is forbidden. mctx implementations
+ * may change the structure. 'magic' must be ISCAPI_MCTX_MAGIC for any of the
+ * isc_mem_ routines to work. mctx implementations must maintain all mctx
+ * invariants.
+ */
+struct isc_mem {
+ unsigned int impmagic;
+ unsigned int magic;
+ isc_memmethods_t *methods;
+};
+
+#define ISCAPI_MCTX_MAGIC ISC_MAGIC('A', 'm', 'c', 'x')
+#define ISCAPI_MCTX_VALID(m) ((m) != NULL && (m)->magic == ISCAPI_MCTX_MAGIC)
+
+/*%
+ * This is the common prefix of a memory pool context. The same note as
+ * that for the mem structure applies.
+ */
+struct isc_mempool {
+ unsigned int impmagic;
+ unsigned int magic;
+};
+
+#define ISCAPI_MPOOL_MAGIC ISC_MAGIC('A', 'm', 'p', 'l')
+#define ISCAPI_MPOOL_VALID(mp) \
+ ((mp) != NULL && (mp)->magic == ISCAPI_MPOOL_MAGIC)
+
+/*%
+ * 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)_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, s) \
+ ISCMEMFUNC(strndup)((c), (p), (s)_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)_ISC_MEM_FILELINE); \
+ (p) = NULL; \
+ } while (0)
+#define isc_mem_putanddetach(c, p, s) \
+ do { \
+ ISCMEMFUNC(putanddetach)((c), (p), (s)_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)
+
+/*@{*/
+void
+isc_mem_create(isc_mem_t **mctxp);
+
+/*!<
+ * \brief Create a memory context.
+ *
+ * Requires:
+ * mctxp != NULL && *mctxp == NULL */
+/*@}*/
+
+/*@{*/
+void
+isc_mem_attach(isc_mem_t *, isc_mem_t **);
+void
+isc_mem_detach(isc_mem_t **);
+/*!<
+ * \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.
+ */
+/*@}*/
+
+void
+isc_mem_destroy(isc_mem_t **);
+/*%<
+ * 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.
+ */
+
+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_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 NULL then 'water_arg', 'hi_water' and 'lo_water' are
+ * ignored and the state is reset.
+ *
+ * Requires:
+ *
+ * 'water' is not NULL.
+ * hi_water >= lo_water
+ */
+
+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, void *tag);
+/*%<
+ * Name 'ctx'.
+ *
+ * Notes:
+ *
+ *\li Only the first 15 characters of 'name' will be copied.
+ *
+ *\li 'tag' is for debugging purposes only.
+ *
+ * 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.
+ */
+
+void *
+isc_mem_gettag(isc_mem_t *ctx);
+/*%<
+ * Get the tag value for 'task', as previously set using isc_mem_setname().
+ *
+ * Requires:
+ *\li 'ctx' is a valid ctx.
+ *
+ * Notes:
+ *\li This function is for debugging purposes only.
+ *
+ * Requires:
+ *\li 'ctx' is a valid task.
+ */
+
+#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
+ */
+
+void
+isc_mempool_create(isc_mem_t *mctx, size_t size, isc_mempool_t **mpctxp);
+/*%<
+ * Create a memory pool.
+ *
+ * Requires:
+ *\li mctx is a valid memory context.
+ *\li size > 0
+ *\li mpctxp != NULL and *mpctxp == NULL
+ *
+ * Defaults:
+ *\li maxalloc = UINT_MAX
+ *\li freemax = 1
+ *\li fillcount = 1
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY -- not enough memory to create pool
+ *\li #ISC_R_SUCCESS -- all is well.
+ */
+
+void
+isc_mempool_destroy(isc_mempool_t **mpctxp);
+/*%<
+ * 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 *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.
+ * For instance, setting "maxalloc" to a number smaller than the currently
+ * allocated count is permitted. New allocations will be refused until
+ * the count drops below this threshold.
+ *
+ * All functions require (in addition to other requirements):
+ * mpctx is a valid memory pool
+ */
+
+unsigned int
+isc_mempool_getfreemax(isc_mempool_t *mpctx);
+/*%<
+ * Returns the maximum allowed size of the free list.
+ */
+
+void
+isc_mempool_setfreemax(isc_mempool_t *mpctx, unsigned int limit);
+/*%<
+ * Sets the maximum allowed size of the free list.
+ */
+
+unsigned int
+isc_mempool_getfreecount(isc_mempool_t *mpctx);
+/*%<
+ * Returns current size of the free list.
+ */
+
+unsigned int
+isc_mempool_getmaxalloc(isc_mempool_t *mpctx);
+/*!<
+ * Returns the maximum allowed number of allocations.
+ */
+
+void
+isc_mempool_setmaxalloc(isc_mempool_t *mpctx, unsigned int limit);
+/*%<
+ * Sets the maximum allowed number of allocations.
+ *
+ * Additional requirements:
+ *\li limit > 0
+ */
+
+unsigned int
+isc_mempool_getallocated(isc_mempool_t *mpctx);
+/*%<
+ * Returns the number of items allocated from this pool.
+ */
+
+unsigned int
+isc_mempool_getfillcount(isc_mempool_t *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 *mpctx, 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 _ISC_MEM_FLARG);
+void ISCMEMFUNC(put)(isc_mem_t *, void *, size_t _ISC_MEM_FLARG);
+void ISCMEMFUNC(free)(isc_mem_t *, void *_ISC_MEM_FLARG);
+
+ISC_ATTR_RETURNS_NONNULL
+ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(put), 2)
+void *ISCMEMFUNC(get)(isc_mem_t *, size_t _ISC_MEM_FLARG);
+
+ISC_ATTR_RETURNS_NONNULL
+ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(free), 2)
+void *ISCMEMFUNC(allocate)(isc_mem_t *, size_t _ISC_MEM_FLARG);
+
+ISC_ATTR_RETURNS_NONNULL
+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);
+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
+
+#endif /* ISC_MEM_H */
diff --git a/lib/isc/include/isc/meminfo.h b/lib/isc/include/isc/meminfo.h
new file mode 100644
index 0000000..3cda5b2
--- /dev/null
+++ b/lib/isc/include/isc/meminfo.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.
+ */
+
+#ifndef ISC_MEMINFO_H
+#define ISC_MEMINFO_H 1
+
+#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
+
+#endif /* ISC_MEMINFO_H */
diff --git a/lib/isc/include/isc/mutexblock.h b/lib/isc/include/isc/mutexblock.h
new file mode 100644
index 0000000..d2144e1
--- /dev/null
+++ b/lib/isc/include/isc/mutexblock.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.
+ */
+
+#ifndef ISC_MUTEXBLOCK_H
+#define ISC_MUTEXBLOCK_H 1
+
+/*! \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
+
+#endif /* ISC_MUTEXBLOCK_H */
diff --git a/lib/isc/include/isc/netaddr.h b/lib/isc/include/isc/netaddr.h
new file mode 100644
index 0000000..519aec7
--- /dev/null
+++ b/lib/isc/include/isc/netaddr.h
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_NETADDR_H
+#define ISC_NETADDR_H 1
+
+/*! \file isc/netaddr.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/net.h>
+#include <isc/types.h>
+
+#ifdef ISC_PLATFORM_HAVESYSUNH
+#include <sys/types.h>
+#include <sys/un.h>
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+
+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;
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ char un[sizeof(((struct sockaddr_un *)0)->sun_path)];
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ } type;
+ uint32_t zone;
+};
+
+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
+
+#endif /* ISC_NETADDR_H */
diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h
new file mode 100644
index 0000000..f1747be
--- /dev/null
+++ b/lib/isc/include/isc/netmgr.h
@@ -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.
+ */
+
+#pragma once
+
+#include <unistd.h>
+
+#include <isc/mem.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/types.h>
+
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <sys/types.h>
+#endif
+
+#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.
+ */
+
+#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.
+ */
+
+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).
+ * 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'.
+ */
+
+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.
+ */
+
+void
+isc_nm_tcpdns_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_tcpdns_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.
+ */
+
+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.
+ */
+
+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.
+ */
+
+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);
+/*%<
+ * Establish a DNS client connection via a TCP 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'.
+ */
+
+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..0622548
--- /dev/null
+++ b/lib/isc/include/isc/netscope.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_NETSCOPE_H
+#define ISC_NETSCOPE_H 1
+
+/*! \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
+
+#endif /* ISC_NETSCOPE_H */
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/os.h b/lib/isc/include/isc/os.h
new file mode 100644
index 0000000..585abc0
--- /dev/null
+++ b/lib/isc/include/isc/os.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_OS_H
+#define ISC_OS_H 1
+
+/*! \file isc/os.h */
+
+#include <isc/lang.h>
+
+ISC_LANG_BEGINDECLS
+
+unsigned int
+isc_os_ncpus(void);
+/*%<
+ * Return the number of CPUs available on the system, or 1 if this cannot
+ * be determined.
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_OS_H */
diff --git a/lib/isc/include/isc/parseint.h b/lib/isc/include/isc/parseint.h
new file mode 100644
index 0000000..d41c57e
--- /dev/null
+++ b/lib/isc/include/isc/parseint.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.
+ */
+
+#ifndef ISC_PARSEINT_H
+#define ISC_PARSEINT_H 1
+
+#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
+
+#endif /* ISC_PARSEINT_H */
diff --git a/lib/isc/include/isc/platform.h.in b/lib/isc/include/isc/platform.h.in
new file mode 100644
index 0000000..85b468c
--- /dev/null
+++ b/lib/isc/include/isc/platform.h.in
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_PLATFORM_H
+#define ISC_PLATFORM_H 1
+
+/*! \file */
+
+/*****
+ ***** Platform-dependent defines.
+ *****/
+
+/***
+ *** Default strerror_r buffer size
+ ***/
+
+#define ISC_STRERRORSIZE 128
+
+/***
+ *** System limitations
+ ***/
+
+#include <limits.h>
+
+#ifndef NAME_MAX
+#define NAME_MAX 256
+#endif
+
+#ifndef PATH_MAX
+#define PATH_MAX 1024
+#endif
+
+#ifndef IOV_MAX
+#define IOV_MAX 1024
+#endif
+
+/***
+ *** Miscellaneous.
+ ***/
+
+/*
+ * Defined to <gssapi.h> or <gssapi/gssapi.h> for how to include
+ * the GSSAPI header.
+ */
+@ISC_PLATFORM_GSSAPIHEADER@
+
+/*
+ * Defined to <gssapi_krb5.h> or <gssapi/gssapi_krb5.h> for how to
+ * include the GSSAPI KRB5 header.
+ */
+@ISC_PLATFORM_GSSAPI_KRB5_HEADER@
+
+/*
+ * Defined to <krb5.h> or <krb5/krb5.h> for how to include
+ * the KRB5 header.
+ */
+@ISC_PLATFORM_KRB5HEADER@
+
+/*
+ * Define if the platform has <sys/un.h>.
+ */
+@ISC_PLATFORM_HAVESYSUNH@
+
+/*
+ * Defines for the noreturn attribute.
+ */
+@ISC_PLATFORM_NORETURN_PRE@
+@ISC_PLATFORM_NORETURN_POST@
+
+/***
+ *** Windows dll support.
+ ***/
+
+#define LIBISC_EXTERNAL_DATA
+#define LIBDNS_EXTERNAL_DATA
+#define LIBISCCC_EXTERNAL_DATA
+#define LIBISCCFG_EXTERNAL_DATA
+#define LIBNS_EXTERNAL_DATA
+#define LIBBIND9_EXTERNAL_DATA
+#define LIBTESTS_EXTERNAL_DATA
+
+/*
+ * Tell emacs to use C mode for this file.
+ *
+ * Local Variables:
+ * mode: c
+ * End:
+ */
+
+#endif /* ISC_PLATFORM_H */
diff --git a/lib/isc/include/isc/pool.h b/lib/isc/include/isc/pool.h
new file mode 100644
index 0000000..b354c18
--- /dev/null
+++ b/lib/isc/include/isc/pool.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_OBJPOOL_H
+#define ISC_OBJPOOL_H 1
+
+/*****
+***** 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
+
+#endif /* ISC_OBJPOOL_H */
diff --git a/lib/isc/include/isc/portset.h b/lib/isc/include/isc/portset.h
new file mode 100644
index 0000000..fbdde82
--- /dev/null
+++ b/lib/isc/include/isc/portset.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.
+ */
+
+/*! \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.
+ */
+
+#ifndef ISC_PORTSET_H
+#define ISC_PORTSET_H 1
+
+/***
+ *** 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
+
+#endif /* ISC_PORTSET_H */
diff --git a/lib/isc/include/isc/print.h b/lib/isc/include/isc/print.h
new file mode 100644
index 0000000..b3ae7fd
--- /dev/null
+++ b/lib/isc/include/isc/print.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.
+ */
+
+#ifndef ISC_PRINT_H
+#define ISC_PRINT_H 1
+
+/*! \file isc/print.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <isc/formatcheck.h> /* Required for ISC_FORMAT_PRINTF() macro. */
+#include <isc/lang.h>
+#include <isc/platform.h>
+
+/***
+ *** Functions
+ ***/
+
+#include <stdio.h>
+
+#endif /* ISC_PRINT_H */
diff --git a/lib/isc/include/isc/quota.h b/lib/isc/include/isc/quota.h
new file mode 100644
index 0000000..b9f9a24
--- /dev/null
+++ b/lib/isc/include/isc/quota.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.
+ */
+
+#ifndef ISC_QUOTA_H
+#define ISC_QUOTA_H 1
+
+/*****
+***** 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;
+};
+
+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
+
+#endif /* ISC_QUOTA_H */
diff --git a/lib/isc/include/isc/radix.h b/lib/isc/include/isc/radix.h
new file mode 100644
index 0000000..7c004e9
--- /dev/null
+++ b/lib/isc/include/isc/radix.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef _RADIX_H
+#define _RADIX_H
+
+#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)
+
+#endif /* _RADIX_H */
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..08862dc
--- /dev/null
+++ b/lib/isc/include/isc/ratelimiter.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.
+ */
+
+#ifndef ISC_RATELIMITER_H
+#define ISC_RATELIMITER_H 1
+
+/*****
+***** 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
+
+#endif /* ISC_RATELIMITER_H */
diff --git a/lib/isc/include/isc/refcount.h b/lib/isc/include/isc/refcount.h
new file mode 100644
index 0000000..68e512c
--- /dev/null
+++ b/lib/isc/include/isc/refcount.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.
+ */
+
+#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/platform.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) \
+ ({ \
+ /* cppcheck-suppress shadowVariable */ \
+ 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) \
+ ({ \
+ /* cppcheck-suppress shadowVariable */ \
+ 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) \
+ ({ \
+ /* cppcheck-suppress shadowVariable */ \
+ 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)
+
+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..f288d86
--- /dev/null
+++ b/lib/isc/include/isc/regex.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_REGEX_H
+#define ISC_REGEX_H 1
+
+/*! \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
+
+#endif /* ISC_REGEX_H */
diff --git a/lib/isc/include/isc/region.h b/lib/isc/include/isc/region.h
new file mode 100644
index 0000000..c386617
--- /dev/null
+++ b/lib/isc/include/isc/region.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_REGION_H
+#define ISC_REGION_H 1
+
+/*! \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
+
+#endif /* ISC_REGION_H */
diff --git a/lib/isc/include/isc/resource.h b/lib/isc/include/isc/resource.h
new file mode 100644
index 0000000..f9e6e20
--- /dev/null
+++ b/lib/isc/include/isc/resource.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_RESOURCE_H
+#define ISC_RESOURCE_H 1
+
+/*! \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
+
+#endif /* ISC_RESOURCE_H */
diff --git a/lib/isc/include/isc/result.h b/lib/isc/include/isc/result.h
new file mode 100644
index 0000000..11ff7f6
--- /dev/null
+++ b/lib/isc/include/isc/result.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_RESULT_H
+#define ISC_RESULT_H 1
+
+/*! \file isc/result.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#define ISC_R_SUCCESS 0 /*%< success */
+#define ISC_R_NOMEMORY 1 /*%< out of memory */
+#define ISC_R_TIMEDOUT 2 /*%< timed out */
+#define ISC_R_NOTHREADS 3 /*%< no available threads */
+#define ISC_R_ADDRNOTAVAIL 4 /*%< address not available */
+#define ISC_R_ADDRINUSE 5 /*%< address in use */
+#define ISC_R_NOPERM 6 /*%< permission denied */
+#define ISC_R_NOCONN 7 /*%< no pending connections */
+#define ISC_R_NETUNREACH 8 /*%< network unreachable */
+#define ISC_R_HOSTUNREACH 9 /*%< host unreachable */
+#define ISC_R_NETDOWN 10 /*%< network down */
+#define ISC_R_HOSTDOWN 11 /*%< host down */
+#define ISC_R_CONNREFUSED 12 /*%< connection refused */
+#define ISC_R_NORESOURCES 13 /*%< not enough free resources */
+#define ISC_R_EOF 14 /*%< end of file */
+#define ISC_R_BOUND 15 /*%< socket already bound */
+#define ISC_R_RELOAD 16 /*%< reload */
+#define ISC_R_SUSPEND ISC_R_RELOAD /*%< alias of 'reload' */
+#define ISC_R_LOCKBUSY 17 /*%< lock busy */
+#define ISC_R_EXISTS 18 /*%< already exists */
+#define ISC_R_NOSPACE 19 /*%< ran out of space */
+#define ISC_R_CANCELED 20 /*%< operation canceled */
+#define ISC_R_NOTBOUND 21 /*%< socket is not bound */
+#define ISC_R_SHUTTINGDOWN 22 /*%< shutting down */
+#define ISC_R_NOTFOUND 23 /*%< not found */
+#define ISC_R_UNEXPECTEDEND 24 /*%< unexpected end of input */
+#define ISC_R_FAILURE 25 /*%< generic failure */
+#define ISC_R_IOERROR 26 /*%< I/O error */
+#define ISC_R_NOTIMPLEMENTED 27 /*%< not implemented */
+#define ISC_R_UNBALANCED 28 /*%< unbalanced parentheses */
+#define ISC_R_NOMORE 29 /*%< no more */
+#define ISC_R_INVALIDFILE 30 /*%< invalid file */
+#define ISC_R_BADBASE64 31 /*%< bad base64 encoding */
+#define ISC_R_UNEXPECTEDTOKEN 32 /*%< unexpected token */
+#define ISC_R_QUOTA 33 /*%< quota reached */
+#define ISC_R_UNEXPECTED 34 /*%< unexpected error */
+#define ISC_R_ALREADYRUNNING 35 /*%< already running */
+#define ISC_R_IGNORE 36 /*%< ignore */
+#define ISC_R_MASKNONCONTIG 37 /*%< addr mask not contiguous */
+#define ISC_R_FILENOTFOUND 38 /*%< file not found */
+#define ISC_R_FILEEXISTS 39 /*%< file already exists */
+#define ISC_R_NOTCONNECTED 40 /*%< socket is not connected */
+#define ISC_R_RANGE 41 /*%< out of range */
+#define ISC_R_NOENTROPY 42 /*%< out of entropy */
+#define ISC_R_MULTICAST 43 /*%< invalid use of multicast */
+#define ISC_R_NOTFILE 44 /*%< not a file */
+#define ISC_R_NOTDIRECTORY 45 /*%< not a directory */
+#define ISC_R_EMPTY 46 /*%< queue is empty */
+#define ISC_R_FAMILYMISMATCH 47 /*%< address family mismatch */
+#define ISC_R_FAMILYNOSUPPORT 48 /*%< AF not supported */
+#define ISC_R_BADHEX 49 /*%< bad hex encoding */
+#define ISC_R_TOOMANYOPENFILES 50 /*%< too many open files */
+#define ISC_R_NOTBLOCKING 51 /*%< not blocking */
+#define ISC_R_UNBALANCEDQUOTES 52 /*%< unbalanced quotes */
+#define ISC_R_INPROGRESS 53 /*%< operation in progress */
+#define ISC_R_CONNECTIONRESET 54 /*%< connection reset */
+#define ISC_R_SOFTQUOTA 55 /*%< soft quota reached */
+#define ISC_R_BADNUMBER 56 /*%< not a valid number */
+#define ISC_R_DISABLED 57 /*%< disabled */
+#define ISC_R_MAXSIZE 58 /*%< max size */
+#define ISC_R_BADADDRESSFORM 59 /*%< invalid address format */
+#define ISC_R_BADBASE32 60 /*%< bad base32 encoding */
+#define ISC_R_UNSET 61 /*%< unset */
+#define ISC_R_MULTIPLE 62 /*%< multiple */
+#define ISC_R_WOULDBLOCK 63 /*%< would block */
+#define ISC_R_COMPLETE 64 /*%< complete */
+#define ISC_R_CRYPTOFAILURE 65 /*%< cryptography library failure */
+#define ISC_R_DISCQUOTA 66 /*%< disc quota */
+#define ISC_R_DISCFULL 67 /*%< disc full */
+#define ISC_R_DEFAULT 68 /*%< default */
+#define ISC_R_IPV4PREFIX 69 /*%< IPv4 prefix */
+#define ISC_R_TLSERROR 70 /*%< TLS error */
+#define ISC_R_HTTP2ALPNERROR 71 /*%< ALPN for HTTP/2 failed */
+
+/*% Not a result code: the number of results. */
+#define ISC_R_NRESULTS 72
+
+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_result_t
+isc_result_register(unsigned int base, unsigned int nresults, const char **text,
+ int set);
+
+isc_result_t
+isc_result_registerids(unsigned int base, unsigned int nresults,
+ const char **ids, int set);
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_RESULT_H */
diff --git a/lib/isc/include/isc/resultclass.h b/lib/isc/include/isc/resultclass.h
new file mode 100644
index 0000000..a3a5079
--- /dev/null
+++ b/lib/isc/include/isc/resultclass.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_RESULTCLASS_H
+#define ISC_RESULTCLASS_H 1
+
+/*! \file isc/resultclass.h
+ * \brief Registry of Predefined Result Type Classes
+ *
+ * A result class number is an unsigned 16 bit number. Each class may
+ * contain up to 65536 results. A result code is formed by adding the
+ * result number within the class to the class number multiplied by 65536.
+ *
+ * Classes < 1024 are reserved for ISC use.
+ * Result classes >= 1024 and <= 65535 are reserved for application use.
+ */
+
+#define ISC_RESULTCLASS_FROMNUM(num) ((num) << 16)
+#define ISC_RESULTCLASS_TONUM(rclass) ((rclass) >> 16)
+#define ISC_RESULTCLASS_SIZE 65536
+#define ISC_RESULTCLASS_INCLASS(rclass, result) \
+ ((rclass) == ((result)&0xFFFF0000))
+
+#define ISC_RESULTCLASS_ISC ISC_RESULTCLASS_FROMNUM(0)
+#define ISC_RESULTCLASS_DNS ISC_RESULTCLASS_FROMNUM(1)
+#define ISC_RESULTCLASS_DST ISC_RESULTCLASS_FROMNUM(2)
+#define ISC_RESULTCLASS_DNSRCODE ISC_RESULTCLASS_FROMNUM(3)
+#define ISC_RESULTCLASS_OMAPI ISC_RESULTCLASS_FROMNUM(4)
+#define ISC_RESULTCLASS_ISCCC ISC_RESULTCLASS_FROMNUM(5)
+#define ISC_RESULTCLASS_DHCP ISC_RESULTCLASS_FROMNUM(6)
+#define ISC_RESULTCLASS_PK11 ISC_RESULTCLASS_FROMNUM(7)
+
+#endif /* ISC_RESULTCLASS_H */
diff --git a/lib/isc/include/isc/rwlock.h b/lib/isc/include/isc/rwlock.h
new file mode 100644
index 0000000..3309d71
--- /dev/null
+++ b/lib/isc/include/isc/rwlock.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_RWLOCK_H
+#define ISC_RWLOCK_H 1
+
+#include <inttypes.h>
+
+/*! \file isc/rwlock.h */
+
+#include <isc/atomic.h>
+#include <isc/condition.h>
+#include <isc/lang.h>
+#include <isc/platform.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
+
+#endif /* ISC_RWLOCK_H */
diff --git a/lib/isc/include/isc/safe.h b/lib/isc/include/isc/safe.h
new file mode 100644
index 0000000..f5bad62
--- /dev/null
+++ b/lib/isc/include/isc/safe.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_SAFE_H
+#define ISC_SAFE_H 1
+
+/*! \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
+
+#endif /* ISC_SAFE_H */
diff --git a/lib/isc/include/isc/serial.h b/lib/isc/include/isc/serial.h
new file mode 100644
index 0000000..e2d5225
--- /dev/null
+++ b/lib/isc/include/isc/serial.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_SERIAL_H
+#define ISC_SERIAL_H 1
+
+#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
+
+#endif /* ISC_SERIAL_H */
diff --git a/lib/isc/include/isc/siphash.h b/lib/isc/include/isc/siphash.h
new file mode 100644
index 0000000..3974c6f
--- /dev/null
+++ b/lib/isc/include/isc/siphash.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.
+ */
+
+/*! \file isc/siphash.h */
+
+#pragma once
+
+#include <isc/lang.h>
+#include <isc/platform.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..b776bb3
--- /dev/null
+++ b/lib/isc/include/isc/sockaddr.h
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_SOCKADDR_H
+#define ISC_SOCKADDR_H 1
+
+/*! \file isc/sockaddr.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/net.h>
+#include <isc/types.h>
+#ifdef ISC_PLATFORM_HAVESYSUNH
+#include <sys/un.h>
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+
+/*
+ * 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;
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ struct sockaddr_un sunix;
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ } 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
+
+#endif /* ISC_SOCKADDR_H */
diff --git a/lib/isc/include/isc/socket.h b/lib/isc/include/isc/socket.h
new file mode 100644
index 0000000..dc39a3f
--- /dev/null
+++ b/lib/isc/include/isc/socket.h
@@ -0,0 +1,914 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_SOCKET_H
+#define ISC_SOCKET_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file isc/socket.h
+ * \brief Provides TCP and UDP sockets for network I/O. The sockets are event
+ * sources in the task system.
+ *
+ * When I/O completes, a completion event for the socket is posted to the
+ * event queue of the task which requested the I/O.
+ *
+ * \li MP:
+ * The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ * Clients of this module must not be holding a socket's task's lock when
+ * making a call that affects that socket. Failure to follow this rule
+ * can result in deadlock.
+ * The caller must ensure that isc_socketmgr_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 <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/event.h>
+#include <isc/eventclass.h>
+#include <isc/lang.h>
+#include <isc/region.h>
+#include <isc/sockaddr.h>
+#include <isc/time.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Constants
+ ***/
+
+/*%
+ * Maximum number of buffers in a scatter/gather read/write. The operating
+ * system in use must support at least this number (plus one on some.)
+ */
+#define ISC_SOCKET_MAXSCATTERGATHER 8
+
+/*@{*/
+/*!
+ * Socket options:
+ *
+ * _REUSEADDRESS: Set SO_REUSEADDR prior to calling bind(),
+ * if a non-zero port is specified (applies to
+ * AF_INET and AF_INET6).
+ */
+typedef enum {
+ ISC_SOCKET_REUSEADDRESS = 0x01U,
+} isc_socket_options_t;
+/*@}*/
+
+/*@{*/
+/*!
+ * _ATTACHED: Internal use only.
+ * _TRUNC: Packet was truncated on receive.
+ * _CTRUNC: Packet control information was truncated. This can
+ * indicate that the packet is not complete, even though
+ * all the data is valid.
+ * _TIMESTAMP: The timestamp member is valid.
+ * _PKTINFO: The pktinfo member is valid.
+ * _MULTICAST: The UDP packet was received via a multicast transmission.
+ * _DSCP: The UDP DSCP value is valid.
+ * _USEMINMTU: Set the per packet IPV6_USE_MIN_MTU flag.
+ */
+typedef enum {
+ ISC_SOCKEVENTATTR_ATTACHED = 0x10000000U, /* internal */
+ ISC_SOCKEVENTATTR_TRUNC = 0x00800000U, /* public */
+ ISC_SOCKEVENTATTR_CTRUNC = 0x00400000U, /* public */
+ ISC_SOCKEVENTATTR_TIMESTAMP = 0x00200000U, /* public */
+ ISC_SOCKEVENTATTR_PKTINFO = 0x00100000U, /* public */
+ ISC_SOCKEVENTATTR_MULTICAST = 0x00080000U, /* public */
+ ISC_SOCKEVENTATTR_DSCP = 0x00040000U, /* public */
+ ISC_SOCKEVENTATTR_USEMINMTU = 0x00020000U /* public */
+} isc_sockeventattr_t;
+/*@}*/
+
+/***
+ *** Types
+ ***/
+
+struct isc_socketevent {
+ ISC_EVENT_COMMON(isc_socketevent_t);
+ isc_result_t result; /*%< OK, EOF, whatever else */
+ unsigned int minimum; /*%< minimum i/o for event */
+ unsigned int n; /*%< bytes read or written */
+ unsigned int offset; /*%< offset into buffer list */
+ isc_region_t region; /*%< for single-buffer i/o */
+ isc_sockaddr_t address; /*%< source address */
+ isc_time_t timestamp; /*%< timestamp of packet recv */
+ struct in6_pktinfo pktinfo; /*%< ipv6 pktinfo */
+ isc_sockeventattr_t attributes; /*%< see isc_sockeventattr_t
+ * enum */
+ isc_eventdestructor_t destroy; /*%< original destructor */
+ unsigned int dscp; /*%< UDP dscp value */
+};
+
+typedef struct isc_socket_newconnev isc_socket_newconnev_t;
+struct isc_socket_newconnev {
+ ISC_EVENT_COMMON(isc_socket_newconnev_t);
+ isc_socket_t *newsocket;
+ isc_result_t result; /*%< OK, EOF, whatever else */
+ isc_sockaddr_t address; /*%< source address */
+};
+
+typedef struct isc_socket_connev isc_socket_connev_t;
+struct isc_socket_connev {
+ ISC_EVENT_COMMON(isc_socket_connev_t);
+ isc_result_t result; /*%< OK, EOF, whatever else */
+};
+
+#define ISC_SOCKEVENT_ANYEVENT (0)
+#define ISC_SOCKEVENT_RECVDONE (ISC_EVENTCLASS_SOCKET + 1)
+#define ISC_SOCKEVENT_SENDDONE (ISC_EVENTCLASS_SOCKET + 2)
+#define ISC_SOCKEVENT_NEWCONN (ISC_EVENTCLASS_SOCKET + 3)
+#define ISC_SOCKEVENT_CONNECT (ISC_EVENTCLASS_SOCKET + 4)
+
+/*
+ * Internal events.
+ */
+#define ISC_SOCKEVENT_INTR (ISC_EVENTCLASS_SOCKET + 256)
+#define ISC_SOCKEVENT_INTW (ISC_EVENTCLASS_SOCKET + 257)
+
+typedef enum {
+ isc_sockettype_udp = 1,
+ isc_sockettype_tcp = 2,
+ isc_sockettype_unix = 3,
+ isc_sockettype_raw = 4
+} isc_sockettype_t;
+
+/*@{*/
+/*!
+ * How a socket should be shutdown in isc_socket_shutdown() calls.
+ */
+#define ISC_SOCKSHUT_RECV 0x00000001 /*%< close read side */
+#define ISC_SOCKSHUT_SEND 0x00000002 /*%< close write side */
+#define ISC_SOCKSHUT_ALL 0x00000003 /*%< close them all */
+/*@}*/
+
+/*@{*/
+/*!
+ * What I/O events to cancel in isc_socket_cancel() calls.
+ */
+#define ISC_SOCKCANCEL_RECV 0x00000001 /*%< cancel recv */
+#define ISC_SOCKCANCEL_SEND 0x00000002 /*%< cancel send */
+#define ISC_SOCKCANCEL_ACCEPT 0x00000004 /*%< cancel accept */
+#define ISC_SOCKCANCEL_CONNECT 0x00000008 /*%< cancel connect */
+#define ISC_SOCKCANCEL_ALL 0x0000000f /*%< cancel everything */
+/*@}*/
+
+/*@{*/
+/*!
+ * Flags for isc_socket_send() and isc_socket_recv() calls.
+ */
+#define ISC_SOCKFLAG_IMMEDIATE 0x00000001 /*%< send event only if needed */
+#define ISC_SOCKFLAG_NORETRY 0x00000002 /*%< drop failed UDP sends */
+/*@}*/
+
+/***
+ *** Socket and Socket Manager Functions
+ ***
+ *** Note: all Ensures conditions apply only if the result is success for
+ *** those functions which return an isc_result.
+ ***/
+
+isc_result_t
+isc_socket_create(isc_socketmgr_t *manager, int pf, isc_sockettype_t type,
+ isc_socket_t **socketp);
+/*%<
+ * Create a new 'type' socket managed by 'manager'.
+ *
+ * Note:
+ *
+ *\li 'pf' is the desired protocol family, e.g. PF_INET or PF_INET6.
+ *
+ * Requires:
+ *
+ *\li 'manager' is a valid manager
+ *
+ *\li 'socketp' is a valid pointer, and *socketp == NULL
+ *
+ * Ensures:
+ *
+ * '*socketp' is attached to the newly created socket
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_NORESOURCES
+ *\li #ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_socket_dup(isc_socket_t *sock0, isc_socket_t **socketp);
+/*%<
+ * Duplicate an existing socket, reusing its file descriptor.
+ */
+
+void
+isc_socket_cancel(isc_socket_t *sock, isc_task_t *task, unsigned int how);
+/*%<
+ * Cancel pending I/O of the type specified by "how".
+ *
+ * Note: if "task" is NULL, then the cancel applies to all tasks using the
+ * socket.
+ *
+ * Requires:
+ *
+ * \li "socket" is a valid socket
+ *
+ * \li "task" is NULL or a valid task
+ *
+ * "how" is a bitmask describing the type of cancellation to perform.
+ * The type ISC_SOCKCANCEL_ALL will cancel all pending I/O on this
+ * socket.
+ *
+ * \li ISC_SOCKCANCEL_RECV:
+ * Cancel pending isc_socket_recv() calls.
+ *
+ * \li ISC_SOCKCANCEL_SEND:
+ * Cancel pending isc_socket_send() and isc_socket_sendto() calls.
+ *
+ * \li ISC_SOCKCANCEL_ACCEPT:
+ * Cancel pending isc_socket_accept() calls.
+ *
+ * \li ISC_SOCKCANCEL_CONNECT:
+ * Cancel pending isc_socket_connect() call.
+ */
+
+void
+isc_socket_shutdown(isc_socket_t *sock, unsigned int how);
+/*%<
+ * Shutdown 'socket' according to 'how'.
+ *
+ * Requires:
+ *
+ * \li 'socket' is a valid socket.
+ *
+ * \li 'task' is NULL or is a valid task.
+ *
+ * \li If 'how' is 'ISC_SOCKSHUT_RECV' or 'ISC_SOCKSHUT_ALL' then
+ *
+ * The read queue must be empty.
+ *
+ * No further read requests may be made.
+ *
+ * \li If 'how' is 'ISC_SOCKSHUT_SEND' or 'ISC_SOCKSHUT_ALL' then
+ *
+ * The write queue must be empty.
+ *
+ * No further write requests may be made.
+ */
+
+void
+isc_socket_attach(isc_socket_t *sock, isc_socket_t **socketp);
+/*%<
+ * Attach *socketp to socket.
+ *
+ * Requires:
+ *
+ * \li 'socket' is a valid socket.
+ *
+ * \li 'socketp' points to a NULL socket.
+ *
+ * Ensures:
+ *
+ * \li *socketp is attached to socket.
+ */
+
+void
+isc_socket_detach(isc_socket_t **socketp);
+/*%<
+ * Detach *socketp from its socket.
+ *
+ * Requires:
+ *
+ * \li 'socketp' points to a valid socket.
+ *
+ * \li If '*socketp' is the last reference to the socket,
+ * then:
+ *
+ * There must be no pending I/O requests.
+ *
+ * Ensures:
+ *
+ * \li *socketp is NULL.
+ *
+ * \li If '*socketp' is the last reference to the socket,
+ * then:
+ *
+ * The socket will be shutdown (both reading and writing)
+ * for all tasks.
+ *
+ * All resources used by the socket have been freed
+ */
+
+isc_result_t
+isc_socket_open(isc_socket_t *sock);
+/*%<
+ * Open a new socket file descriptor of the given socket structure. It simply
+ * opens a new descriptor; all of the other parameters including the socket
+ * type are inherited from the existing socket. This function is provided to
+ * avoid overhead of destroying and creating sockets when many short-lived
+ * sockets are frequently opened and closed. When the efficiency is not an
+ * issue, it should be safer to detach the unused socket and re-create a new
+ * one. This optimization may not be available for some systems, in which
+ * case this function will return ISC_R_NOTIMPLEMENTED and must not be used.
+ *
+ * Requires:
+ *
+ * \li there must be no other reference to this socket.
+ *
+ * \li 'socket' is a valid and previously closed by isc_socket_close()
+ *
+ * Returns:
+ * Same as isc_socket_create().
+ * \li ISC_R_NOTIMPLEMENTED
+ */
+
+isc_result_t
+isc_socket_close(isc_socket_t *sock);
+/*%<
+ * Close a socket file descriptor of the given socket structure. This function
+ * is provided as an alternative to destroying an unused socket when overhead
+ * destroying/re-creating sockets can be significant, and is expected to be
+ * used with isc_socket_open(). This optimization may not be available for some
+ * systems, in which case this function will return ISC_R_NOTIMPLEMENTED and
+ * must not be used.
+ *
+ * Requires:
+ *
+ * \li The socket must have a valid descriptor.
+ *
+ * \li There must be no other reference to this socket.
+ *
+ * \li There must be no pending I/O requests.
+ *
+ * Returns:
+ * \li #ISC_R_NOTIMPLEMENTED
+ */
+
+isc_result_t
+isc_socket_bind(isc_socket_t *sock, const isc_sockaddr_t *addressp,
+ isc_socket_options_t options);
+/*%<
+ * Bind 'socket' to '*addressp'.
+ *
+ * Requires:
+ *
+ * \li 'socket' is a valid socket
+ *
+ * \li 'addressp' points to a valid isc_sockaddr.
+ *
+ * Returns:
+ *
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOPERM
+ * \li ISC_R_ADDRNOTAVAIL
+ * \li ISC_R_ADDRINUSE
+ * \li ISC_R_BOUND
+ * \li ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_socket_filter(isc_socket_t *sock, const char *filter);
+/*%<
+ * Inform the kernel that it should perform accept filtering.
+ * If filter is NULL the current filter will be removed.
+ */
+
+isc_result_t
+isc_socket_listen(isc_socket_t *sock, unsigned int backlog);
+/*%<
+ * Set listen mode on the socket. After this call, the only function that
+ * can be used (other than attach and detach) is isc_socket_accept().
+ *
+ * Notes:
+ *
+ * \li 'backlog' is as in the UNIX system call listen() and may be
+ * ignored by non-UNIX implementations.
+ *
+ * \li If 'backlog' is zero, a reasonable system default is used, usually
+ * SOMAXCONN.
+ *
+ * Requires:
+ *
+ * \li 'socket' is a valid, bound TCP socket or a valid, bound UNIX socket.
+ *
+ * Returns:
+ *
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_socket_accept(isc_socket_t *sock, isc_task_t *task, isc_taskaction_t action,
+ void *arg);
+/*%<
+ * Queue accept event. When a new connection is received, the task will
+ * get an ISC_SOCKEVENT_NEWCONN event with the sender set to the listen
+ * socket. The new socket structure is sent inside the isc_socket_newconnev_t
+ * event type, and is attached to the task 'task'.
+ *
+ * REQUIRES:
+ * \li 'socket' is a valid TCP socket that isc_socket_listen() was called
+ * on.
+ *
+ * \li 'task' is a valid task
+ *
+ * \li 'action' is a valid action
+ *
+ * RETURNS:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOMEMORY
+ * \li ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_socket_connect(isc_socket_t *sock, const isc_sockaddr_t *addressp,
+ isc_task_t *task, isc_taskaction_t action, void *arg);
+/*%<
+ * Connect 'socket' to peer with address *saddr. When the connection
+ * succeeds, or when an error occurs, a CONNECT event with action 'action'
+ * and arg 'arg' will be posted to the event queue for 'task'.
+ *
+ * Requires:
+ *
+ * \li 'socket' is a valid TCP socket
+ *
+ * \li 'addressp' points to a valid isc_sockaddr
+ *
+ * \li 'task' is a valid task
+ *
+ * \li 'action' is a valid action
+ *
+ * Returns:
+ *
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOMEMORY
+ * \li ISC_R_UNEXPECTED
+ *
+ * Posted event's result code:
+ *
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_TIMEDOUT
+ * \li ISC_R_CONNREFUSED
+ * \li ISC_R_NETUNREACH
+ * \li ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_socket_getpeername(isc_socket_t *sock, isc_sockaddr_t *addressp);
+/*%<
+ * Get the name of the peer connected to 'socket'.
+ *
+ * Requires:
+ *
+ * \li 'socket' is a valid TCP socket.
+ *
+ * Returns:
+ *
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_TOOSMALL
+ * \li ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_socket_getsockname(isc_socket_t *sock, isc_sockaddr_t *addressp);
+/*%<
+ * Get the name of 'socket'.
+ *
+ * Requires:
+ *
+ * \li 'socket' is a valid socket.
+ *
+ * Returns:
+ *
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_TOOSMALL
+ * \li ISC_R_UNEXPECTED
+ */
+
+/*@{*/
+isc_result_t
+isc_socket_recv(isc_socket_t *sock, isc_region_t *region, unsigned int minimum,
+ isc_task_t *task, isc_taskaction_t action, void *arg);
+
+isc_result_t
+isc_socket_recv2(isc_socket_t *sock, isc_region_t *region, unsigned int minimum,
+ isc_task_t *task, isc_socketevent_t *event,
+ unsigned int flags);
+
+/*!
+ * Receive from 'socket', storing the results in region.
+ *
+ * Notes:
+ *
+ *\li Let 'length' refer to the length of 'region' or to the sum of all
+ * available regions in the list of buffers '*buflist'.
+ *
+ *\li If 'minimum' is non-zero and at least that many bytes are read,
+ * the completion event will be posted to the task 'task.' If minimum
+ * is zero, the exact number of bytes requested in the region must
+ * be read for an event to be posted. This only makes sense for TCP
+ * connections, and is always set to 1 byte for UDP.
+ *
+ *\li The read will complete when the desired number of bytes have been
+ * read, if end-of-input occurs, or if an error occurs. A read done
+ * event with the given 'action' and 'arg' will be posted to the
+ * event queue of 'task'.
+ *
+ *\li The caller may not modify 'region', the buffers which are passed
+ * into this function, or any data they refer to until the completion
+ * event is received.
+ *
+ *\li For isc_socket_recv2():
+ * 'event' is not NULL, and the non-socket specific fields are
+ * expected to be initialized.
+ *
+ *\li For isc_socket_recv2():
+ * The only defined value for 'flags' is ISC_SOCKFLAG_IMMEDIATE. If
+ * set and the operation completes, the return value will be
+ * ISC_R_SUCCESS and the event will be filled in and not sent. If the
+ * operation does not complete, the return value will be
+ * ISC_R_INPROGRESS and the event will be sent when the operation
+ * completes.
+ *
+ * Requires:
+ *
+ *\li 'socket' is a valid, bound socket.
+ *
+ *\li For isc_socket_recv():
+ * 'region' is a valid region
+ *
+ *\li For isc_socket_recvv():
+ * 'buflist' is non-NULL, and '*buflist' contain at least one buffer.
+ *
+ *\li 'task' is a valid task
+ *
+ *\li For isc_socket_recv() and isc_socket_recvv():
+ * action != NULL and is a valid action
+ *
+ *\li For isc_socket_recv2():
+ * event != NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_INPROGRESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_UNEXPECTED
+ *
+ * Event results:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_UNEXPECTED
+ *\li XXX needs other net-type errors
+ */
+/*@}*/
+
+/*@{*/
+isc_result_t
+isc_socket_send(isc_socket_t *sock, isc_region_t *region, isc_task_t *task,
+ isc_taskaction_t action, void *arg);
+isc_result_t
+isc_socket_sendto(isc_socket_t *sock, isc_region_t *region, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo);
+isc_result_t
+isc_socket_sendto2(isc_socket_t *sock, isc_region_t *region, isc_task_t *task,
+ const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo,
+ isc_socketevent_t *event, unsigned int flags);
+
+/*!
+ * Send the contents of 'region' to the socket's peer.
+ *
+ * Notes:
+ *
+ *\li Shutting down the requestor's task *may* result in any
+ * still pending writes being dropped or completed, depending on the
+ * underlying OS implementation.
+ *
+ *\li If 'action' is NULL, then no completion event will be posted.
+ *
+ *\li The caller may not modify 'region', the buffers which are passed
+ * into this function, or any data they refer to until the completion
+ * event is received.
+ *
+ *\li For isc_socket_sendto2():
+ * 'event' is not NULL, and the non-socket specific fields are
+ * expected to be initialized.
+ *
+ *\li For isc_socket_sendto2():
+ * The only defined values for 'flags' are ISC_SOCKFLAG_IMMEDIATE
+ * and ISC_SOCKFLAG_NORETRY.
+ *
+ *\li If ISC_SOCKFLAG_IMMEDIATE is set and the operation completes, the
+ * return value will be ISC_R_SUCCESS and the event will be filled
+ * in and not sent. If the operation does not complete, the return
+ * value will be ISC_R_INPROGRESS and the event will be sent when
+ * the operation completes.
+ *
+ *\li ISC_SOCKFLAG_NORETRY can only be set for UDP sockets. If set
+ * and the send operation fails due to a transient error, the send
+ * will not be retried and the error will be indicated in the event.
+ * Using this option along with ISC_SOCKFLAG_IMMEDIATE allows the caller
+ * to specify a region that is allocated on the stack.
+ *
+ * Requires:
+ *
+ *\li 'socket' is a valid, bound socket.
+ *
+ *\li For isc_socket_send():
+ * 'region' is a valid region
+ *
+ *\li For isc_socket_sendv() and isc_socket_sendtov():
+ * 'buflist' is non-NULL, and '*buflist' contain at least one buffer.
+ *
+ *\li 'task' is a valid task
+ *
+ *\li For isc_socket_sendv(), isc_socket_sendtov(), isc_socket_send(), and
+ * isc_socket_sendto():
+ * action == NULL or is a valid action
+ *
+ *\li For isc_socket_sendto2():
+ * event != NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_INPROGRESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_UNEXPECTED
+ *
+ * Event results:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_UNEXPECTED
+ *\li XXX needs other net-type errors
+ */
+/*@}*/
+
+isc_result_t
+isc_socketmgr_create(isc_mem_t *mctx, isc_socketmgr_t **managerp);
+
+isc_result_t
+isc_socketmgr_create2(isc_mem_t *mctx, isc_socketmgr_t **managerp,
+ unsigned int maxsocks, int nthreads);
+/*%<
+ * Create a socket manager. If "maxsocks" is non-zero, it specifies the
+ * maximum number of sockets that the created manager should handle.
+ * isc_socketmgr_create() is equivalent of isc_socketmgr_create2() with
+ * "maxsocks" being zero.
+ *
+ * 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_socketmgr_t.
+ *
+ *\li 'actx' is a valid application context (for createinctx()).
+ *
+ * Ensures:
+ *
+ *\li '*managerp' is a valid isc_socketmgr_t.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_UNEXPECTED
+ *\li #ISC_R_NOTIMPLEMENTED
+ */
+
+isc_result_t
+isc_socketmgr_getmaxsockets(isc_socketmgr_t *manager, unsigned int *nsockp);
+/*%<
+ * Returns in "*nsockp" the maximum number of sockets this manager may open.
+ *
+ * Requires:
+ *
+ *\li '*manager' is a valid isc_socketmgr_t.
+ *\li 'nsockp' is not NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTIMPLEMENTED
+ */
+
+void
+isc_socketmgr_setstats(isc_socketmgr_t *manager, isc_stats_t *stats);
+/*%<
+ * Set a general socket statistics counter set 'stats' for 'manager'.
+ *
+ * Requires:
+ * \li 'manager' is valid, hasn't opened any socket, and doesn't have
+ * stats already set.
+ *
+ *\li stats is a valid statistics supporting socket statistics counters
+ * (see above).
+ */
+
+void
+isc_socketmgr_destroy(isc_socketmgr_t **managerp);
+/*%<
+ * Destroy a socket manager.
+ *
+ * Notes:
+ *
+ *\li This routine blocks until there are no sockets left in the manager,
+ * so if the caller holds any socket references using the manager, it
+ * must detach them before calling isc_socketmgr_destroy() or it will
+ * block forever.
+ *
+ * Requires:
+ *
+ *\li '*managerp' is a valid isc_socketmgr_t.
+ *
+ *\li All sockets managed by this manager are fully detached.
+ *
+ * Ensures:
+ *
+ *\li *managerp == NULL
+ *
+ *\li All resources used by the manager have been freed.
+ */
+
+isc_sockettype_t
+isc_socket_gettype(isc_socket_t *sock);
+/*%<
+ * Returns the socket type for "sock."
+ *
+ * Requires:
+ *
+ *\li "sock" is a valid socket.
+ */
+
+/*@{*/
+void
+isc_socket_ipv6only(isc_socket_t *sock, bool yes);
+/*%<
+ * If the socket is an IPv6 socket set/clear the IPV6_IPV6ONLY socket
+ * option if the host OS supports this option.
+ *
+ * Requires:
+ *\li 'sock' is a valid socket.
+ */
+/*@}*/
+
+void
+isc_socket_dscp(isc_socket_t *sock, isc_dscp_t dscp);
+/*%<
+ * Sets the Differentiated Services Code Point (DSCP) field for packets
+ * transmitted on this socket. If 'dscp' is -1, return immediately.
+ *
+ * Requires:
+ *\li 'sock' is a valid socket.
+ */
+
+isc_socketevent_t *
+isc_socket_socketevent(isc_mem_t *mctx, void *sender, isc_eventtype_t eventtype,
+ isc_taskaction_t action, void *arg);
+/*%<
+ * Get a isc_socketevent_t to be used with isc_socket_sendto2(), etc.
+ */
+
+void
+isc_socket_cleanunix(const isc_sockaddr_t *addr, bool active);
+
+/*%<
+ * Cleanup UNIX domain sockets in the file-system. If 'active' is true
+ * then just unlink the socket. If 'active' is false try to determine
+ * if there is a listener of the socket or not. If no listener is found
+ * then unlink socket.
+ *
+ * Prior to unlinking the path is tested to see if it a socket.
+ *
+ * Note: there are a number of race conditions which cannot be avoided
+ * both in the filesystem and any application using UNIX domain
+ * sockets (e.g. socket is tested between bind() and listen(),
+ * the socket is deleted and replaced in the file-system between
+ * stat() and unlink()).
+ */
+
+isc_result_t
+isc_socket_permunix(const isc_sockaddr_t *sockaddr, uint32_t perm,
+ uint32_t owner, uint32_t group);
+/*%<
+ * Set ownership and file permissions on the UNIX domain socket.
+ *
+ * Note: On Solaris this secures the directory containing
+ * the socket as Solaris do not honour the filesystem
+ * permissions on the socket.
+ *
+ * Requires:
+ * \li 'sockaddr' to be a valid UNIX domain sockaddr.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_FAILURE
+ */
+
+void
+isc_socket_setname(isc_socket_t *socket, const char *name, void *tag);
+/*%<
+ * Set the name and optional tag for a socket. This allows tracking of the
+ * owner or purpose for this socket, and is useful for tracing and statistics
+ * reporting.
+ */
+
+const char *
+isc_socket_getname(isc_socket_t *socket);
+/*%<
+ * Get the name associated with a socket, if any.
+ */
+
+void *
+isc_socket_gettag(isc_socket_t *socket);
+/*%<
+ * Get the tag associated with a socket, if any.
+ */
+
+int
+isc_socket_getfd(isc_socket_t *socket);
+/*%<
+ * Get the file descriptor associated with a socket
+ */
+
+void
+isc_socketmgr_setreserved(isc_socketmgr_t *mgr, uint32_t);
+/*%<
+ * Temporary. For use by named only.
+ */
+
+void
+isc_socketmgr_maxudp(isc_socketmgr_t *mgr, unsigned int maxudp);
+/*%<
+ * Test interface. Drop UDP packet > 'maxudp'.
+ */
+
+bool
+isc_socket_hasreuseport(void);
+/*%<
+ * Return true if there is SO_REUSEPORT support
+ */
+
+#ifdef HAVE_LIBXML2
+int
+isc_socketmgr_renderxml(isc_socketmgr_t *mgr, void *writer0);
+/*%<
+ * Render internal statistics and other state into the XML document.
+ */
+#endif /* HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+isc_result_t
+isc_socketmgr_renderjson(isc_socketmgr_t *mgr, void *stats0);
+/*%<
+ * Render internal statistics and other state into JSON format.
+ */
+#endif /* HAVE_JSON_C */
+
+/*%<
+ * See isc_socketmgr_create() above.
+ */
+typedef isc_result_t (*isc_socketmgrcreatefunc_t)(isc_mem_t *mctx,
+ isc_socketmgr_t **managerp);
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_SOCKET_H */
diff --git a/lib/isc/include/isc/stats.h b/lib/isc/include/isc/stats.h
new file mode 100644
index 0000000..5e25bac
--- /dev/null
+++ b/lib/isc/include/isc/stats.h
@@ -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 ISC_STATS_H
+#define ISC_STATS_H 1
+
+/*! \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
+
+#endif /* ISC_STATS_H */
diff --git a/lib/isc/include/isc/stdio.h b/lib/isc/include/isc/stdio.h
new file mode 100644
index 0000000..2c391d7
--- /dev/null
+++ b/lib/isc/include/isc/stdio.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_STDIO_H
+#define ISC_STDIO_H 1
+
+/*! \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
+
+#endif /* ISC_STDIO_H */
diff --git a/lib/isc/include/isc/strerr.h b/lib/isc/include/isc/strerr.h
new file mode 100644
index 0000000..ce093b0
--- /dev/null
+++ b/lib/isc/include/isc/strerr.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
+
+/*! \file isc/strerr.h */
+
+#include <isc/string.h>
+
+#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..1bc5693
--- /dev/null
+++ b/lib/isc/include/isc/string.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 isc/string.h */
+
+#include <string.h>
+
+#include "isc/lang.h"
+#include "isc/platform.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..9ec8564
--- /dev/null
+++ b/lib/isc/include/isc/symtab.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.
+ */
+
+#ifndef ISC_SYMTAB_H
+#define ISC_SYMTAB_H 1
+
+/*****
+***** 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
+
+#endif /* ISC_SYMTAB_H */
diff --git a/lib/isc/include/isc/task.h b/lib/isc/include/isc/task.h
new file mode 100644
index 0000000..810552d
--- /dev/null
+++ b/lib/isc/include/isc/task.h
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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.
+ *
+ * Unsending returns a list of events that matched the pattern.
+ * The caller is then responsible for them.
+ *
+ * Consumers of events should purge, not unsend.
+ *
+ * Producers of events often want to remove events when the caller indicates
+ * it is no longer interested in the object, e.g. by canceling a timer.
+ * Sometimes this can be done by purging, but for some event types, the
+ * calls to isc_event_free() cause deadlock because the event free routine
+ * wants to acquire a lock the caller is already holding. Unsending instead
+ * of purging solves this problem. As a general rule, producers should only
+ * unsend events which they have sent.
+ */
+
+/***
+ *** 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_unsendrange(isc_task_t *task, void *sender, isc_eventtype_t first,
+ isc_eventtype_t last, void *tag, isc_eventlist_t *events);
+/*%<
+ * Remove events from a task's event queue.
+ *
+ * Requires:
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li last >= first.
+ *
+ *\li *events is a valid list.
+ *
+ * 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 dequeued
+ * and appended to *events.
+ *
+ *\li A sender of NULL will match any sender. A NULL tag matches any
+ * tag.
+ *
+ * Returns:
+ *
+ *\li The number of events unsent.
+ */
+
+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 safe 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().
+ */
+
+void
+isc_task_pause(isc_task_t *task0);
+void
+isc_task_unpause(isc_task_t *task0);
+/*%<
+ * Pause/unpause this task. Pausing a task removes it from the ready
+ * queue if it is present there; this ensures that the task will not
+ * run again until unpaused. This is necessary when the libuv network
+ * thread executes a function which schedules task manager events; this
+ * prevents the task manager from executing the next event in a task
+ * before the network thread has finished.
+ *
+ * Requires:
+ *\li 'task' is a valid task, and is not already paused or shutting down.
+ */
+
+void
+isc_task_getcurrenttime(isc_task_t *task, isc_stdtime_t *t);
+void
+isc_task_getcurrenttimex(isc_task_t *task, isc_time_t *t);
+/*%<
+ * Provide the most recent timestamp on the task. The timestamp is considered
+ * as the "current time".
+ *
+ * isc_task_getcurrentime() returns the time in one-second granularity;
+ * isc_task_getcurrentimex() returns it in nanosecond granularity.
+ *
+ * Requires:
+ *\li 'task' is a valid task.
+ *\li 't' is a valid non NULL pointer.
+ *
+ * Ensures:
+ *\li '*t' has the "current time".
+ */
+
+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..ea4c569
--- /dev/null
+++ b/lib/isc/include/isc/taskpool.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.
+ */
+
+#ifndef ISC_TASKPOOL_H
+#define ISC_TASKPOOL_H 1
+
+/*****
+***** 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
+
+#endif /* ISC_TASKPOOL_H */
diff --git a/lib/isc/include/isc/timer.h b/lib/isc/include/isc/timer.h
new file mode 100644
index 0000000..4814a65
--- /dev/null
+++ b/lib/isc/include/isc/timer.h
@@ -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 ISC_TIMER_H
+#define ISC_TIMER_H 1
+
+/*****
+***** 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.
+ */
+
+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.
+ */
+
+void
+isc_timermgr_poke(isc_timermgr_t *m);
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_TIMER_H */
diff --git a/lib/isc/include/isc/tm.h b/lib/isc/include/isc/tm.h
new file mode 100644
index 0000000..dd13448
--- /dev/null
+++ b/lib/isc/include/isc/tm.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_TM_H
+#define ISC_TM_H 1
+
+/*! \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
+
+#endif /* ISC_TIMER_H */
diff --git a/lib/isc/include/isc/types.h b/lib/isc/include/isc/types.h
new file mode 100644
index 0000000..60e7114
--- /dev/null
+++ b/lib/isc/include/isc/types.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_TYPES_H
+#define ISC_TYPES_H 1
+
+#include <isc/bind9.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_backtrace_symmap isc_backtrace_symmap_t; /*%< Symbol Table
+ * Entry */
+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 int16_t isc_dscp_t; /*%< Diffserv code point */
+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 uint32_t isc_fsaccess_t; /*%< FS Access */
+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_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 unsigned int isc_result_t; /*%< Result */
+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_socket isc_socket_t; /*%< Socket */
+typedef struct isc_socketevent isc_socketevent_t; /*%< Socket Event */
+typedef struct isc_socketmgr isc_socketmgr_t; /*%< Socket Manager */
+typedef struct isc_stats isc_stats_t; /*%< Statistics */
+#if defined(_WIN32) && !defined(_WIN64)
+typedef int_fast32_t isc_statscounter_t; /*%< Statistics Counter */
+#else /* if defined(_WIN32) && !defined(_WIN64) */
+typedef int_fast64_t isc_statscounter_t;
+#endif /* if defined(_WIN32) && !defined(_WIN64) */
+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 */
+
+typedef void (*isc_taskaction_t)(isc_task_t *, isc_event_t *);
+typedef int (*isc_sockfdwatch_t)(isc_task_t *, isc_socket_t *, void *, int);
+
+/* The following cannot be listed alphabetically due to forward reference */
+typedef isc_result_t(isc_httpdaction_t)(
+ const char *url, isc_httpdurl_t *urlinfo, const char *querystring,
+ const char *headers, 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 *);
+
+/*% 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;
+
+#endif /* ISC_TYPES_H */
diff --git a/lib/isc/include/isc/url.h b/lib/isc/include/isc/url.h
new file mode 100644
index 0000000..4894a45
--- /dev/null
+++ b/lib/isc/include/isc/url.h
@@ -0,0 +1,82 @@
+/*
+ * 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 <stdbool.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..3396559
--- /dev/null
+++ b/lib/isc/include/isc/util.h
@@ -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.
+ */
+
+#ifndef ISC_UTIL_H
+#define ISC_UTIL_H 1
+
+#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))
+#elif WIN32
+#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
+ */
+#include <isc/likely.h>
+
+/* 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) */
+#define STATIC_ASSERT(cond, msg) INSIST(cond)
+#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 */
+
+#ifndef CPPCHECK
+
+/*
+ * 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()
+
+#else /* CPPCHECK */
+
+/*% Require Assertion */
+#define REQUIRE(e) \
+ if (!(e)) \
+ abort()
+/*% Ensure Assertion */
+#define ENSURE(e) \
+ if (!(e)) \
+ abort()
+/*% Insist Assertion */
+#define INSIST(e) \
+ if (!(e)) \
+ abort()
+/*% Invariant Assertion */
+#define INVARIANT(e) \
+ if (!(e)) \
+ abort()
+
+#define UNREACHABLE() abort()
+
+#endif /* CPPCHECK */
+
+#endif /* UNIT_TESTING */
+
+/*
+ * Errors
+ */
+#include <isc/error.h> /* Contractual promise. */
+
+/*% Unexpected Error */
+#define UNEXPECTED_ERROR isc_error_unexpected
+/*% Fatal Error */
+#define FATAL_ERROR isc_error_fatal
+
+#ifdef UNIT_TESTING
+
+#define RUNTIME_CHECK(expression) \
+ ((!(expression)) \
+ ? (mock_assert(0, #expression, __FILE__, __LINE__), abort()) \
+ : (void)0)
+
+#else /* UNIT_TESTING */
+
+#ifndef CPPCHECK
+/*% Runtime Check */
+#define RUNTIME_CHECK(cond) ISC_ERROR_RUNTIMECHECK(cond)
+#else /* ifndef CPPCHECK */
+#define RUNTIME_CHECK(e) \
+ if (!(e)) \
+ abort()
+#endif /* ifndef CPPCHECK */
+
+#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__ */
+
+#if HAVE_FUNC_ATTRIBUTE_RETURNS_NONNULL
+#define ISC_ATTR_RETURNS_NONNULL __attribute__((returns_nonnull))
+#else
+#define ISC_ATTR_RETURNS_NONNULL
+#endif
+
+/*
+ * Support for malloc attributes.
+ */
+#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 */
+
+/*%
+ * Misc
+ */
+#include <isc/deprecated.h>
+
+#endif /* ISC_UTIL_H */
diff --git a/lib/isc/include/isc/version.h b/lib/isc/include/isc/version.h
new file mode 100644
index 0000000..4b81e44
--- /dev/null
+++ b/lib/isc/include/isc/version.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can 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/version.h */
+
+#include <isc/platform.h>
+
+LIBISC_EXTERNAL_DATA extern const char isc_version[];
diff --git a/lib/isc/include/pk11/Makefile.in b/lib/isc/include/pk11/Makefile.in
new file mode 100644
index 0000000..3ba0c1f
--- /dev/null
+++ b/lib/isc/include/pk11/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+#
+# Only list headers that are to be installed and are not
+# machine generated. The latter are handled specially in the
+# install target below.
+#
+HEADERS = constants.h internal.h pk11.h result.h site.h
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/pk11
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/pk11 || exit 1; \
+ done
+
+uninstall::
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/pk11/$$i || exit 1; \
+ done
diff --git a/lib/isc/include/pk11/constants.h b/lib/isc/include/pk11/constants.h
new file mode 100644
index 0000000..c682053
--- /dev/null
+++ b/lib/isc/include/pk11/constants.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 <inttypes.h>
+
+/*! \file pk11/constants.h */
+
+/*%
+ * Static arrays of data used for key template initialization
+ */
+#define PK11_ECC_PRIME256V1 \
+ (uint8_t[]) { \
+ 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 \
+ }
+#define PK11_ECC_SECP384R1 \
+ (uint8_t[]) { 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22 }
+#define PK11_ECX_ED25519 \
+ (uint8_t[]) { \
+ 0x13, 0xc, 'e', 'd', 'w', 'a', 'r', 'd', 's', '2', '5', '5', \
+ '1', '9' \
+ }
+#define PK11_ECX_ED448 \
+ (uint8_t[]) { \
+ 0x13, 0xa, 'e', 'd', 'w', 'a', 'r', 'd', 's', '4', '4', '8' \
+ }
diff --git a/lib/isc/include/pk11/internal.h b/lib/isc/include/pk11/internal.h
new file mode 100644
index 0000000..38bd751
--- /dev/null
+++ b/lib/isc/include/pk11/internal.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef PK11_INTERNAL_H
+#define PK11_INTERNAL_H 1
+
+/*! \file pk11/internal.h */
+
+#include <pk11/pk11.h>
+
+ISC_LANG_BEGINDECLS
+
+const char *
+pk11_get_lib_name(void);
+
+void *
+pk11_mem_get(size_t size);
+
+void
+pk11_mem_put(void *ptr, size_t size);
+
+CK_SLOT_ID
+pk11_get_best_token(pk11_optype_t optype);
+
+isc_result_t
+pk11_numbits(CK_BYTE_PTR data, unsigned int bytecnt, unsigned int *bits);
+
+CK_ATTRIBUTE *
+pk11_attribute_first(const pk11_object_t *obj);
+
+CK_ATTRIBUTE *
+pk11_attribute_next(const pk11_object_t *obj, CK_ATTRIBUTE *attr);
+
+CK_ATTRIBUTE *
+pk11_attribute_bytype(const pk11_object_t *obj, CK_ATTRIBUTE_TYPE type);
+
+ISC_LANG_ENDDECLS
+
+#endif /* PK11_INTERNAL_H */
diff --git a/lib/isc/include/pk11/pk11.h b/lib/isc/include/pk11/pk11.h
new file mode 100644
index 0000000..076c119
--- /dev/null
+++ b/lib/isc/include/pk11/pk11.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.
+ */
+
+#ifndef PK11_PK11_H
+#define PK11_PK11_H 1
+
+/*! \file pk11/pk11.h */
+
+#include <stdbool.h>
+#include <unistd.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/types.h>
+
+#define PK11_FATALCHECK(func, args) \
+ ((void)(((rv = (func)args) == CKR_OK) || \
+ ((pk11_error_fatalcheck)(__FILE__, __LINE__, #func, rv), 0)))
+
+#include <pk11/site.h>
+#include <pkcs11/pkcs11.h>
+
+ISC_LANG_BEGINDECLS
+
+#define SES_MAGIC ISC_MAGIC('P', 'K', 'S', 'S')
+#define TOK_MAGIC ISC_MAGIC('P', 'K', 'T', 'K')
+
+#define VALID_SES(x) ISC_MAGIC_VALID(x, SES_MAGIC)
+#define VALID_TOK(x) ISC_MAGIC_VALID(x, TOK_MAGIC)
+
+typedef struct pk11_context pk11_context_t;
+
+struct pk11_object {
+ CK_OBJECT_HANDLE object;
+ CK_SLOT_ID slot;
+ CK_BBOOL ontoken;
+ CK_BBOOL reqlogon;
+ CK_BYTE attrcnt;
+ CK_ATTRIBUTE *repr;
+};
+
+struct pk11_context {
+ void *handle;
+ CK_SESSION_HANDLE session;
+ CK_BBOOL ontoken;
+ CK_OBJECT_HANDLE object;
+};
+
+typedef struct pk11_object pk11_object_t;
+
+typedef enum {
+ OP_ANY = 0,
+ OP_RSA = 1,
+ OP_DH = 3,
+ OP_ECDSA = 4,
+ OP_EDDSA = 5,
+ OP_MAX = 6
+} pk11_optype_t;
+
+/*%
+ * Global flag to make choose_slots() verbose
+ */
+LIBISC_EXTERNAL_DATA extern bool pk11_verbose_init;
+
+/*%
+ * Function prototypes
+ */
+
+void
+pk11_set_lib_name(const char *lib_name);
+/*%<
+ * Set the PKCS#11 provider (aka library) path/name.
+ */
+
+isc_result_t
+pk11_initialize(isc_mem_t *mctx, const char *engine);
+/*%<
+ * Initialize PKCS#11 device
+ *
+ * mctx: memory context to attach to pk11_mctx.
+ * engine: PKCS#11 provider (aka library) path/name.
+ *
+ * returns:
+ * ISC_R_SUCCESS
+ * PK11_R_NOPROVIDER: can't load the provider
+ * PK11_R_INITFAILED: C_Initialize() failed
+ * PK11_R_NORANDOMSERVICE: can't find required random service
+ * PK11_R_NODIGESTSERVICE: can't find required digest service
+ * PK11_R_NOAESSERVICE: can't find required AES service
+ */
+
+isc_result_t
+pk11_get_session(pk11_context_t *ctx, pk11_optype_t optype, bool need_services,
+ bool rw, bool logon, const char *pin, CK_SLOT_ID slot);
+/*%<
+ * Initialize PKCS#11 device and acquire a session.
+ *
+ * need_services:
+ * if true, this session requires full PKCS#11 API
+ * support including random and digest services, and
+ * the lack of these services will cause the session not
+ * to be initialized. If false, the function will return
+ * an error code indicating the missing service, but the
+ * session will be usable for other purposes.
+ * rw: if true, session will be read/write (useful for
+ * generating or destroying keys); otherwise read-only.
+ * login: indicates whether to log in to the device
+ * pin: optional PIN, overriding any PIN currently associated
+ * with the
+ * slot: device slot ID
+ */
+
+void
+pk11_return_session(pk11_context_t *ctx);
+/*%<
+ * Release an active PKCS#11 session for reuse.
+ */
+
+isc_result_t
+pk11_finalize(void);
+/*%<
+ * Shut down PKCS#11 device and free all sessions.
+ */
+
+isc_result_t
+pk11_parse_uri(pk11_object_t *obj, const char *label, isc_mem_t *mctx,
+ pk11_optype_t optype);
+
+ISC_PLATFORM_NORETURN_PRE void
+pk11_error_fatalcheck(const char *file, int line, const char *funcname,
+ CK_RV rv) ISC_PLATFORM_NORETURN_POST;
+
+void
+pk11_dump_tokens(void);
+
+CK_RV
+pkcs_C_Initialize(CK_VOID_PTR pReserved);
+
+char *
+pk11_get_load_error_message(void);
+
+CK_RV
+pkcs_C_Finalize(CK_VOID_PTR pReserved);
+
+CK_RV
+pkcs_C_GetSlotList(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR pSlotList,
+ CK_ULONG_PTR pulCount);
+
+CK_RV
+pkcs_C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo);
+
+CK_RV
+pkcs_C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type,
+ CK_MECHANISM_INFO_PTR pInfo);
+
+CK_RV
+pkcs_C_OpenSession(CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication,
+ CK_RV (*Notify)(CK_SESSION_HANDLE hSession,
+ CK_NOTIFICATION event,
+ CK_VOID_PTR pApplication),
+ CK_SESSION_HANDLE_PTR phSession);
+
+CK_RV
+pkcs_C_CloseSession(CK_SESSION_HANDLE hSession);
+
+CK_RV
+pkcs_C_Login(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType,
+ CK_CHAR_PTR pPin, CK_ULONG usPinLen);
+
+CK_RV
+pkcs_C_Logout(CK_SESSION_HANDLE hSession);
+
+CK_RV
+pkcs_C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
+ CK_ULONG usCount, CK_OBJECT_HANDLE_PTR phObject);
+
+CK_RV
+pkcs_C_DestroyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject);
+
+CK_RV
+pkcs_C_GetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
+ CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount);
+
+CK_RV
+pkcs_C_SetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
+ CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount);
+
+CK_RV
+pkcs_C_FindObjectsInit(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
+ CK_ULONG usCount);
+
+CK_RV
+pkcs_C_FindObjects(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE_PTR phObject,
+ CK_ULONG usMaxObjectCount, CK_ULONG_PTR pusObjectCount);
+
+CK_RV
+pkcs_C_FindObjectsFinal(CK_SESSION_HANDLE hSession);
+
+CK_RV
+pkcs_C_EncryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hKey);
+
+CK_RV
+pkcs_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData,
+ CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData,
+ CK_ULONG_PTR pulEncryptedDataLen);
+
+CK_RV
+pkcs_C_DigestInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism);
+
+CK_RV
+pkcs_C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
+ CK_ULONG ulPartLen);
+
+CK_RV
+pkcs_C_DigestFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pDigest,
+ CK_ULONG_PTR pulDigestLen);
+
+CK_RV
+pkcs_C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hKey);
+
+CK_RV
+pkcs_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen,
+ CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen);
+
+CK_RV
+pkcs_C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
+ CK_ULONG ulPartLen);
+
+CK_RV
+pkcs_C_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature,
+ CK_ULONG_PTR pulSignatureLen);
+
+CK_RV
+pkcs_C_VerifyInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hKey);
+
+CK_RV
+pkcs_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen,
+ CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen);
+
+CK_RV
+pkcs_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
+ CK_ULONG ulPartLen);
+
+CK_RV
+pkcs_C_VerifyFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature,
+ CK_ULONG ulSignatureLen);
+
+CK_RV
+pkcs_C_GenerateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount,
+ CK_OBJECT_HANDLE_PTR phKey);
+
+CK_RV
+pkcs_C_GenerateKeyPair(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_ATTRIBUTE_PTR pPublicKeyTemplate,
+ CK_ULONG usPublicKeyAttributeCount,
+ CK_ATTRIBUTE_PTR pPrivateKeyTemplate,
+ CK_ULONG usPrivateKeyAttributeCount,
+ CK_OBJECT_HANDLE_PTR phPrivateKey,
+ CK_OBJECT_HANDLE_PTR phPublicKey);
+
+CK_RV
+pkcs_C_DeriveKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hBaseKey, CK_ATTRIBUTE_PTR pTemplate,
+ CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey);
+
+CK_RV
+pkcs_C_SeedRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSeed,
+ CK_ULONG ulSeedLen);
+
+CK_RV
+pkcs_C_GenerateRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR RandomData,
+ CK_ULONG ulRandomLen);
+
+ISC_LANG_ENDDECLS
+
+#endif /* PK11_PK11_H */
diff --git a/lib/isc/include/pk11/result.h b/lib/isc/include/pk11/result.h
new file mode 100644
index 0000000..d92b15d
--- /dev/null
+++ b/lib/isc/include/pk11/result.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef PK11_RESULT_H
+#define PK11_RESULT_H 1
+
+/*! \file pk11/result.h */
+
+#include <isc/lang.h>
+#include <isc/resultclass.h>
+#include <isc/types.h>
+
+/*
+ * Nothing in this file truly depends on <isc/result.h>, but the
+ * PK11 result codes are considered to be publicly derived from
+ * the ISC result codes, so including this file buys you the ISC_R_
+ * namespace too.
+ */
+#include <isc/result.h> /* Contractual promise. */
+
+#define PK11_R_INITFAILED (ISC_RESULTCLASS_PK11 + 0)
+#define PK11_R_NOPROVIDER (ISC_RESULTCLASS_PK11 + 1)
+#define PK11_R_NORANDOMSERVICE (ISC_RESULTCLASS_PK11 + 2)
+#define PK11_R_NODIGESTSERVICE (ISC_RESULTCLASS_PK11 + 3)
+#define PK11_R_NOAESSERVICE (ISC_RESULTCLASS_PK11 + 4)
+
+#define PK11_R_NRESULTS 5 /* Number of results */
+
+ISC_LANG_BEGINDECLS
+
+const char *pk11_result_totext(isc_result_t);
+
+void
+pk11_result_register(void);
+
+ISC_LANG_ENDDECLS
+
+#endif /* PK11_RESULT_H */
diff --git a/lib/isc/include/pk11/site.h b/lib/isc/include/pk11/site.h
new file mode 100644
index 0000000..ceebb59
--- /dev/null
+++ b/lib/isc/include/pk11/site.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
+
+/*! \file pk11/site.h */
diff --git a/lib/isc/include/pkcs11/Makefile.in b/lib/isc/include/pkcs11/Makefile.in
new file mode 100644
index 0000000..8822f5a
--- /dev/null
+++ b/lib/isc/include/pkcs11/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+#
+# Only list headers that are to be installed and are not
+# machine generated. The latter are handled specially in the
+# install target below.
+#
+HEADERS = pkcs11.h
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/pkcs11
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/pkcs11 || exit 1; \
+ done
+
+uninstall::
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/pkcs11/$$i || exit 1; \
+ done
diff --git a/lib/isc/include/pkcs11/pkcs11.h b/lib/isc/include/pkcs11/pkcs11.h
new file mode 100644
index 0000000..30228d2
--- /dev/null
+++ b/lib/isc/include/pkcs11/pkcs11.h
@@ -0,0 +1,1655 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ * Copyright 2006 Andreas Jellinghaus
+ * Copyright 2006, 2007 g10 Code GmbH
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* Please submit any changes back to the p11-kit project at
+ * https://github.com/p11-glue/p11-kit/, so that
+ * they can be picked up by other projects from there as well. */
+
+/* This file is a modified implementation of the PKCS #11 standard by
+ * OASIS group. It is mostly a drop-in replacement, with the
+ * following change:
+ *
+ * This header file does not require any macro definitions by the user
+ * (like CK_DEFINE_FUNCTION etc). In fact, it defines those macros
+ * for you (if useful, some are missing, let me know if you need
+ * more).
+ *
+ * There is an additional API available that does comply better to the
+ * GNU coding standard. It can be switched on by defining
+ * CRYPTOKI_GNU before including this header file. For this, the
+ * following changes are made to the specification:
+ *
+ * All structure types are changed to a "struct ck_foo" where CK_FOO
+ * is the type name in PKCS #11.
+ *
+ * All non-structure types are changed to ck_foo_t where CK_FOO is the
+ * lowercase version of the type name in PKCS #11. The basic types
+ * (CK_ULONG et al.) are removed without substitute.
+ *
+ * All members of structures are modified in the following way: Type
+ * indication prefixes are removed, and underscore characters are
+ * inserted before words. Then the result is lowercased.
+ *
+ * Note that function names are still in the original case, as they
+ * need for ABI compatibility.
+ *
+ * CK_FALSE, CK_TRUE and NULL_PTR are removed without substitute. Use
+ * <stdbool.h>.
+ *
+ * If CRYPTOKI_COMPAT is defined before including this header file,
+ * then none of the API changes above take place, and the API is the
+ * one defined by the PKCS #11 standard. */
+
+#ifndef PKCS11_H
+#define PKCS11_H 1
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* if defined(__cplusplus) */
+
+/* The version of cryptoki we implement. The revision is changed with
+ * each modification of this file. */
+#define CRYPTOKI_VERSION_MAJOR 2
+#define CRYPTOKI_VERSION_MINOR 40
+#define P11_KIT_CRYPTOKI_VERSION_REVISION 0
+
+/* Compatibility interface is default, unless CRYPTOKI_GNU is
+ * given. */
+#ifndef CRYPTOKI_GNU
+#ifndef CRYPTOKI_COMPAT
+#define CRYPTOKI_COMPAT 1
+#endif /* ifndef CRYPTOKI_COMPAT */
+#endif /* ifndef CRYPTOKI_GNU */
+
+/* System dependencies. */
+
+#if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32)
+
+/* There is a matching pop below. */
+#pragma pack(push, cryptoki, 1)
+
+#ifdef CRYPTOKI_EXPORTS
+#define CK_SPEC __declspec(dllexport)
+#else /* ifdef CRYPTOKI_EXPORTS */
+#define CK_SPEC __declspec(dllimport)
+#endif /* ifdef CRYPTOKI_EXPORTS */
+
+#else /* if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) */
+
+#define CK_SPEC
+
+#endif /* if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) */
+
+#ifdef CRYPTOKI_COMPAT
+/* If we are in compatibility mode, switch all exposed names to the
+ * PKCS #11 variant. There are corresponding #undefs below. */
+
+#define ck_flags_t CK_FLAGS
+#define ck_version _CK_VERSION
+
+#define ck_info _CK_INFO
+#define cryptoki_version cryptokiVersion
+#define manufacturer_id manufacturerID
+#define library_description libraryDescription
+#define library_version libraryVersion
+
+#define ck_notification_t CK_NOTIFICATION
+#define ck_slot_id_t CK_SLOT_ID
+
+#define ck_slot_info _CK_SLOT_INFO
+#define slot_description slotDescription
+#define hardware_version hardwareVersion
+#define firmware_version firmwareVersion
+
+#define ck_token_info _CK_TOKEN_INFO
+#define serial_number serialNumber
+#define max_session_count ulMaxSessionCount
+#define session_count ulSessionCount
+#define max_rw_session_count ulMaxRwSessionCount
+#define rw_session_count ulRwSessionCount
+#define max_pin_len ulMaxPinLen
+#define min_pin_len ulMinPinLen
+#define total_public_memory ulTotalPublicMemory
+#define free_public_memory ulFreePublicMemory
+#define total_private_memory ulTotalPrivateMemory
+#define free_private_memory ulFreePrivateMemory
+#define utc_time utcTime
+
+#define ck_session_handle_t CK_SESSION_HANDLE
+#define ck_user_type_t CK_USER_TYPE
+#define ck_state_t CK_STATE
+
+#define ck_session_info _CK_SESSION_INFO
+#define slot_id slotID
+#define device_error ulDeviceError
+
+#define ck_object_handle_t CK_OBJECT_HANDLE
+#define ck_object_class_t CK_OBJECT_CLASS
+#define ck_hw_feature_type_t CK_HW_FEATURE_TYPE
+#define ck_key_type_t CK_KEY_TYPE
+#define ck_certificate_type_t CK_CERTIFICATE_TYPE
+#define ck_attribute_type_t CK_ATTRIBUTE_TYPE
+
+#define ck_attribute _CK_ATTRIBUTE
+#define value pValue
+#define value_len ulValueLen
+
+#define count ulCount
+
+#define ck_date _CK_DATE
+
+#define ck_mechanism_type_t CK_MECHANISM_TYPE
+
+#define ck_mechanism _CK_MECHANISM
+#define parameter pParameter
+#define parameter_len ulParameterLen
+
+#define params pParams
+
+#define ck_mechanism_info _CK_MECHANISM_INFO
+#define min_key_size ulMinKeySize
+#define max_key_size ulMaxKeySize
+
+#define ck_param_type CK_PARAM_TYPE
+#define ck_otp_param CK_OTP_PARAM
+#define ck_otp_params CK_OTP_PARAMS
+#define ck_otp_signature_info CK_OTP_SIGNATURE_INFO
+
+#define ck_rv_t CK_RV
+#define ck_notify_t CK_NOTIFY
+
+#define ck_function_list _CK_FUNCTION_LIST
+
+#define ck_createmutex_t CK_CREATEMUTEX
+#define ck_destroymutex_t CK_DESTROYMUTEX
+#define ck_lockmutex_t CK_LOCKMUTEX
+#define ck_unlockmutex_t CK_UNLOCKMUTEX
+
+#define ck_c_initialize_args _CK_C_INITIALIZE_ARGS
+#define create_mutex CreateMutex
+#define destroy_mutex DestroyMutex
+#define lock_mutex LockMutex
+#define unlock_mutex UnlockMutex
+#define reserved pReserved
+
+#define ck_rsa_pkcs_mgf_type_t CK_RSA_PKCS_MGF_TYPE
+#define ck_rsa_pkcs_oaep_source_type_t CK_RSA_PKCS_OAEP_SOURCE_TYPE
+#define hash_alg hashAlg
+#define s_len sLen
+#define source_data pSourceData
+#define source_data_len ulSourceDataLen
+
+#define counter_bits ulCounterBits
+#define iv_ptr pIv
+#define iv_len ulIvLen
+#define iv_bits ulIvBits
+#define aad_ptr pAAD
+#define aad_len ulAADLen
+#define tag_bits ulTagBits
+#define shared_data_len ulSharedDataLen
+#define shared_data pSharedData
+#define public_data_len ulPublicDataLen
+#define public_data pPublicData
+#define string_data pData
+#define string_data_len ulLen
+#define data_params pData
+#endif /* CRYPTOKI_COMPAT */
+
+typedef unsigned long ck_flags_t;
+
+struct ck_version {
+ unsigned char major;
+ unsigned char minor;
+};
+
+struct ck_info {
+ struct ck_version cryptoki_version;
+ unsigned char manufacturer_id[32];
+ ck_flags_t flags;
+ unsigned char library_description[32];
+ struct ck_version library_version;
+};
+
+typedef unsigned long ck_notification_t;
+
+#define CKN_SURRENDER (0UL)
+
+typedef unsigned long ck_slot_id_t;
+
+struct ck_slot_info {
+ unsigned char slot_description[64];
+ unsigned char manufacturer_id[32];
+ ck_flags_t flags;
+ struct ck_version hardware_version;
+ struct ck_version firmware_version;
+};
+
+#define CKF_TOKEN_PRESENT (1UL << 0)
+#define CKF_REMOVABLE_DEVICE (1UL << 1)
+#define CKF_HW_SLOT (1UL << 2)
+#define CKF_ARRAY_ATTRIBUTE (1UL << 30)
+
+struct ck_token_info {
+ unsigned char label[32];
+ unsigned char manufacturer_id[32];
+ unsigned char model[16];
+ unsigned char serial_number[16];
+ ck_flags_t flags;
+ unsigned long max_session_count;
+ unsigned long session_count;
+ unsigned long max_rw_session_count;
+ unsigned long rw_session_count;
+ unsigned long max_pin_len;
+ unsigned long min_pin_len;
+ unsigned long total_public_memory;
+ unsigned long free_public_memory;
+ unsigned long total_private_memory;
+ unsigned long free_private_memory;
+ struct ck_version hardware_version;
+ struct ck_version firmware_version;
+ unsigned char utc_time[16];
+};
+
+#define CKF_RNG (1UL << 0)
+#define CKF_WRITE_PROTECTED (1UL << 1)
+#define CKF_LOGIN_REQUIRED (1UL << 2)
+#define CKF_USER_PIN_INITIALIZED (1UL << 3)
+#define CKF_RESTORE_KEY_NOT_NEEDED (1UL << 5)
+#define CKF_CLOCK_ON_TOKEN (1UL << 6)
+#define CKF_PROTECTED_AUTHENTICATION_PATH (1UL << 8)
+#define CKF_DUAL_CRYPTO_OPERATIONS (1UL << 9)
+#define CKF_TOKEN_INITIALIZED (1UL << 10)
+#define CKF_SECONDARY_AUTHENTICATION (1UL << 11)
+#define CKF_USER_PIN_COUNT_LOW (1UL << 16)
+#define CKF_USER_PIN_FINAL_TRY (1UL << 17)
+#define CKF_USER_PIN_LOCKED (1UL << 18)
+#define CKF_USER_PIN_TO_BE_CHANGED (1UL << 19)
+#define CKF_SO_PIN_COUNT_LOW (1UL << 20)
+#define CKF_SO_PIN_FINAL_TRY (1UL << 21)
+#define CKF_SO_PIN_LOCKED (1UL << 22)
+#define CKF_SO_PIN_TO_BE_CHANGED (1UL << 23)
+
+#define CK_UNAVAILABLE_INFORMATION ((unsigned long)-1L)
+#define CK_EFFECTIVELY_INFINITE (0UL)
+
+typedef unsigned long ck_session_handle_t;
+
+#define CK_INVALID_HANDLE (0UL)
+
+typedef unsigned long ck_user_type_t;
+
+#define CKU_SO (0UL)
+#define CKU_USER (1UL)
+#define CKU_CONTEXT_SPECIFIC (2UL)
+
+typedef unsigned long ck_state_t;
+
+#define CKS_RO_PUBLIC_SESSION (0UL)
+#define CKS_RO_USER_FUNCTIONS (1UL)
+#define CKS_RW_PUBLIC_SESSION (2UL)
+#define CKS_RW_USER_FUNCTIONS (3UL)
+#define CKS_RW_SO_FUNCTIONS (4UL)
+
+struct ck_session_info {
+ ck_slot_id_t slot_id;
+ ck_state_t state;
+ ck_flags_t flags;
+ unsigned long device_error;
+};
+
+#define CKF_RW_SESSION (1UL << 1)
+#define CKF_SERIAL_SESSION (1UL << 2)
+
+typedef unsigned long ck_object_handle_t;
+
+typedef unsigned long ck_object_class_t;
+
+#define CKO_DATA (0UL)
+#define CKO_CERTIFICATE (1UL)
+#define CKO_PUBLIC_KEY (2UL)
+#define CKO_PRIVATE_KEY (3UL)
+#define CKO_SECRET_KEY (4UL)
+#define CKO_HW_FEATURE (5UL)
+#define CKO_DOMAIN_PARAMETERS (6UL)
+#define CKO_MECHANISM (7UL)
+#define CKO_OTP_KEY (8UL)
+#define CKO_VENDOR_DEFINED ((unsigned long)(1UL << 31))
+
+typedef unsigned long ck_hw_feature_type_t;
+
+#define CKH_MONOTONIC_COUNTER (1UL)
+#define CKH_CLOCK (2UL)
+#define CKH_USER_INTERFACE (3UL)
+#define CKH_VENDOR_DEFINED ((unsigned long)(1UL << 31))
+
+typedef unsigned long ck_key_type_t;
+
+#define CKK_RSA (0UL)
+#define CKK_DSA (1UL)
+#define CKK_DH (2UL)
+#define CKK_ECDSA (3UL)
+#define CKK_EC (3UL)
+#define CKK_X9_42_DH (4UL)
+#define CKK_KEA (5UL)
+#define CKK_GENERIC_SECRET (0x10UL)
+#define CKK_RC2 (0x11UL)
+#define CKK_RC4 (0x12UL)
+#define CKK_DES (0x13UL)
+#define CKK_DES2 (0x14UL)
+#define CKK_DES3 (0x15UL)
+#define CKK_CAST (0x16UL)
+#define CKK_CAST3 (0x17UL)
+#define CKK_CAST128 (0x18UL)
+#define CKK_RC5 (0x19UL)
+#define CKK_IDEA (0x1aUL)
+#define CKK_SKIPJACK (0x1bUL)
+#define CKK_BATON (0x1cUL)
+#define CKK_JUNIPER (0x1dUL)
+#define CKK_CDMF (0x1eUL)
+#define CKK_AES (0x1fUL)
+#define CKK_BLOWFISH (0x20UL)
+#define CKK_TWOFISH (0x21UL)
+#define CKK_SECURID (0x22UL)
+#define CKK_HOTP (0x23UL)
+#define CKK_ACTI (0x24UL)
+#define CKK_CAMELLIA (0x25UL)
+#define CKK_ARIA (0x26UL)
+#define CKK_MD5_HMAC (0x27UL)
+#define CKK_SHA_1_HMAC (0x28UL)
+#define CKK_RIPEMD128_HMAC (0x29UL)
+#define CKK_RIPEMD160_HMAC (0x2aUL)
+#define CKK_SHA256_HMAC (0x2bUL)
+#define CKK_SHA384_HMAC (0x2cUL)
+#define CKK_SHA512_HMAC (0x2dUL)
+#define CKK_SHA224_HMAC (0x2eUL)
+#define CKK_SEED (0x2fUL)
+#define CKK_GOSTR3410 (0x30UL)
+#define CKK_GOSTR3411 (0x31UL)
+#define CKK_GOST28147 (0x32UL)
+#define CKK_EC_EDWARDS (0x40UL)
+#define CKK_VENDOR_DEFINED ((unsigned long)(1UL << 31))
+
+typedef unsigned long ck_certificate_type_t;
+
+#define CKC_X_509 (0UL)
+#define CKC_X_509_ATTR_CERT (1UL)
+#define CKC_WTLS (2UL)
+#define CKC_VENDOR_DEFINED ((unsigned long)(1UL << 31))
+
+#define CKC_OPENPGP (CKC_VENDOR_DEFINED | 0x504750UL)
+
+typedef unsigned long ck_attribute_type_t;
+
+#define CKA_CLASS (0UL)
+#define CKA_TOKEN (1UL)
+#define CKA_PRIVATE (2UL)
+#define CKA_LABEL (3UL)
+#define CKA_APPLICATION (0x10UL)
+#define CKA_VALUE (0x11UL)
+#define CKA_OBJECT_ID (0x12UL)
+#define CKA_CERTIFICATE_TYPE (0x80UL)
+#define CKA_ISSUER (0x81UL)
+#define CKA_SERIAL_NUMBER (0x82UL)
+#define CKA_AC_ISSUER (0x83UL)
+#define CKA_OWNER (0x84UL)
+#define CKA_ATTR_TYPES (0x85UL)
+#define CKA_TRUSTED (0x86UL)
+#define CKA_CERTIFICATE_CATEGORY (0x87UL)
+#define CKA_JAVA_MIDP_SECURITY_DOMAIN (0x88UL)
+#define CKA_URL (0x89UL)
+#define CKA_HASH_OF_SUBJECT_PUBLIC_KEY (0x8aUL)
+#define CKA_HASH_OF_ISSUER_PUBLIC_KEY (0x8bUL)
+#define CKA_NAME_HASH_ALGORITHM (0x8cUL)
+#define CKA_CHECK_VALUE (0x90UL)
+#define CKA_KEY_TYPE (0x100UL)
+#define CKA_SUBJECT (0x101UL)
+#define CKA_ID (0x102UL)
+#define CKA_SENSITIVE (0x103UL)
+#define CKA_ENCRYPT (0x104UL)
+#define CKA_DECRYPT (0x105UL)
+#define CKA_WRAP (0x106UL)
+#define CKA_UNWRAP (0x107UL)
+#define CKA_SIGN (0x108UL)
+#define CKA_SIGN_RECOVER (0x109UL)
+#define CKA_VERIFY (0x10aUL)
+#define CKA_VERIFY_RECOVER (0x10bUL)
+#define CKA_DERIVE (0x10cUL)
+#define CKA_START_DATE (0x110UL)
+#define CKA_END_DATE (0x111UL)
+#define CKA_MODULUS (0x120UL)
+#define CKA_MODULUS_BITS (0x121UL)
+#define CKA_PUBLIC_EXPONENT (0x122UL)
+#define CKA_PRIVATE_EXPONENT (0x123UL)
+#define CKA_PRIME_1 (0x124UL)
+#define CKA_PRIME_2 (0x125UL)
+#define CKA_EXPONENT_1 (0x126UL)
+#define CKA_EXPONENT_2 (0x127UL)
+#define CKA_COEFFICIENT (0x128UL)
+#define CKA_PUBLIC_KEY_INFO (0x129UL)
+#define CKA_PRIME (0x130UL)
+#define CKA_SUBPRIME (0x131UL)
+#define CKA_BASE (0x132UL)
+#define CKA_PRIME_BITS (0x133UL)
+#define CKA_SUB_PRIME_BITS (0x134UL)
+#define CKA_VALUE_BITS (0x160UL)
+#define CKA_VALUE_LEN (0x161UL)
+#define CKA_EXTRACTABLE (0x162UL)
+#define CKA_LOCAL (0x163UL)
+#define CKA_NEVER_EXTRACTABLE (0x164UL)
+#define CKA_ALWAYS_SENSITIVE (0x165UL)
+#define CKA_KEY_GEN_MECHANISM (0x166UL)
+#define CKA_MODIFIABLE (0x170UL)
+#define CKA_COPYABLE (0x171UL)
+#define CKA_DESTROYABLE (0x172UL)
+#define CKA_ECDSA_PARAMS (0x180UL)
+#define CKA_EC_PARAMS (0x180UL)
+#define CKA_EC_POINT (0x181UL)
+#define CKA_SECONDARY_AUTH (0x200UL)
+#define CKA_AUTH_PIN_FLAGS (0x201UL)
+#define CKA_ALWAYS_AUTHENTICATE (0x202UL)
+#define CKA_WRAP_WITH_TRUSTED (0x210UL)
+#define CKA_OTP_FORMAT (0x220UL)
+#define CKA_OTP_LENGTH (0x221UL)
+#define CKA_OTP_TIME_INTERVAL (0x222UL)
+#define CKA_OTP_USER_FRIENDLY_MODE (0x223UL)
+#define CKA_OTP_CHALLENGE_REQUIREMENT (0x224UL)
+#define CKA_OTP_TIME_REQUIREMENT (0x225UL)
+#define CKA_OTP_COUNTER_REQUIREMENT (0x226UL)
+#define CKA_OTP_PIN_REQUIREMENT (0x227UL)
+#define CKA_OTP_USER_IDENTIFIER (0x22AUL)
+#define CKA_OTP_SERVICE_IDENTIFIER (0x22BUL)
+#define CKA_OTP_SERVICE_LOGO (0x22CUL)
+#define CKA_OTP_SERVICE_LOGO_TYPE (0x22DUL)
+#define CKA_OTP_COUNTER (0x22EUL)
+#define CKA_OTP_TIME (0x22FUL)
+#define CKA_GOSTR3410_PARAMS (0x250UL)
+#define CKA_GOSTR3411_PARAMS (0x251UL)
+#define CKA_GOST28147_PARAMS (0x252UL)
+#define CKA_HW_FEATURE_TYPE (0x300UL)
+#define CKA_RESET_ON_INIT (0x301UL)
+#define CKA_HAS_RESET (0x302UL)
+#define CKA_PIXEL_X (0x400UL)
+#define CKA_PIXEL_Y (0x401UL)
+#define CKA_RESOLUTION (0x402UL)
+#define CKA_CHAR_ROWS (0x403UL)
+#define CKA_CHAR_COLUMNS (0x404UL)
+#define CKA_COLOR (0x405UL)
+#define CKA_BITS_PER_PIXEL (0x406UL)
+#define CKA_CHAR_SETS (0x480UL)
+#define CKA_ENCODING_METHODS (0x481UL)
+#define CKA_MIME_TYPES (0x482UL)
+#define CKA_MECHANISM_TYPE (0x500UL)
+#define CKA_REQUIRED_CMS_ATTRIBUTES (0x501UL)
+#define CKA_DEFAULT_CMS_ATTRIBUTES (0x502UL)
+#define CKA_SUPPORTED_CMS_ATTRIBUTES (0x503UL)
+#define CKA_WRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x211UL)
+#define CKA_UNWRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x212UL)
+#define CKA_DERIVE_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x213UL)
+#define CKA_ALLOWED_MECHANISMS (CKF_ARRAY_ATTRIBUTE | 0x600UL)
+#define CKA_VENDOR_DEFINED ((unsigned long)(1UL << 31))
+
+struct ck_attribute {
+ ck_attribute_type_t type;
+ void *value;
+ unsigned long value_len;
+};
+
+struct ck_date {
+ unsigned char year[4];
+ unsigned char month[2];
+ unsigned char day[2];
+};
+
+typedef unsigned long ck_mechanism_type_t;
+
+#define CKM_RSA_PKCS_KEY_PAIR_GEN (0UL)
+#define CKM_RSA_PKCS (1UL)
+#define CKM_RSA_9796 (2UL)
+#define CKM_RSA_X_509 (3UL)
+#define CKM_MD2_RSA_PKCS (4UL)
+#define CKM_MD5_RSA_PKCS (5UL)
+#define CKM_SHA1_RSA_PKCS (6UL)
+#define CKM_RIPEMD128_RSA_PKCS (7UL)
+#define CKM_RIPEMD160_RSA_PKCS (8UL)
+#define CKM_RSA_PKCS_OAEP (9UL)
+#define CKM_RSA_X9_31_KEY_PAIR_GEN (0xaUL)
+#define CKM_RSA_X9_31 (0xbUL)
+#define CKM_SHA1_RSA_X9_31 (0xcUL)
+#define CKM_RSA_PKCS_PSS (0xdUL)
+#define CKM_SHA1_RSA_PKCS_PSS (0xeUL)
+#define CKM_DSA_KEY_PAIR_GEN (0x10UL)
+#define CKM_DSA (0x11UL)
+#define CKM_DSA_SHA1 (0x12UL)
+#define CKM_DSA_SHA224 (0x13UL)
+#define CKM_DSA_SHA256 (0x14UL)
+#define CKM_DSA_SHA384 (0x15UL)
+#define CKM_DSA_SHA512 (0x16UL)
+#define CKM_DH_PKCS_KEY_PAIR_GEN (0x20UL)
+#define CKM_DH_PKCS_DERIVE (0x21UL)
+#define CKM_X9_42_DH_KEY_PAIR_GEN (0x30UL)
+#define CKM_X9_42_DH_DERIVE (0x31UL)
+#define CKM_X9_42_DH_HYBRID_DERIVE (0x32UL)
+#define CKM_X9_42_MQV_DERIVE (0x33UL)
+#define CKM_SHA256_RSA_PKCS (0x40UL)
+#define CKM_SHA384_RSA_PKCS (0x41UL)
+#define CKM_SHA512_RSA_PKCS (0x42UL)
+#define CKM_SHA256_RSA_PKCS_PSS (0x43UL)
+#define CKM_SHA384_RSA_PKCS_PSS (0x44UL)
+#define CKM_SHA512_RSA_PKCS_PSS (0x45UL)
+#define CKM_SHA512_224 (0x48UL)
+#define CKM_SHA512_224_HMAC (0x49UL)
+#define CKM_SHA512_224_HMAC_GENERAL (0x4aUL)
+#define CKM_SHA512_224_KEY_DERIVATION (0x4bUL)
+#define CKM_SHA512_256 (0x4cUL)
+#define CKM_SHA512_256_HMAC (0x4dUL)
+#define CKM_SHA512_256_HMAC_GENERAL (0x4eUL)
+#define CKM_SHA512_256_KEY_DERIVATION (0x4fUL)
+#define CKM_SHA512_T (0x50UL)
+#define CKM_SHA512_T_HMAC (0x51UL)
+#define CKM_SHA512_T_HMAC_GENERAL (0x52UL)
+#define CKM_SHA512_T_KEY_DERIVATION (0x53UL)
+#define CKM_RC2_KEY_GEN (0x100UL)
+#define CKM_RC2_ECB (0x101UL)
+#define CKM_RC2_CBC (0x102UL)
+#define CKM_RC2_MAC (0x103UL)
+#define CKM_RC2_MAC_GENERAL (0x104UL)
+#define CKM_RC2_CBC_PAD (0x105UL)
+#define CKM_RC4_KEY_GEN (0x110UL)
+#define CKM_RC4 (0x111UL)
+#define CKM_DES_KEY_GEN (0x120UL)
+#define CKM_DES_ECB (0x121UL)
+#define CKM_DES_CBC (0x122UL)
+#define CKM_DES_MAC (0x123UL)
+#define CKM_DES_MAC_GENERAL (0x124UL)
+#define CKM_DES_CBC_PAD (0x125UL)
+#define CKM_DES2_KEY_GEN (0x130UL)
+#define CKM_DES3_KEY_GEN (0x131UL)
+#define CKM_DES3_ECB (0x132UL)
+#define CKM_DES3_CBC (0x133UL)
+#define CKM_DES3_MAC (0x134UL)
+#define CKM_DES3_MAC_GENERAL (0x135UL)
+#define CKM_DES3_CBC_PAD (0x136UL)
+#define CKM_DES3_CMAC_GENERAL (0x137UL)
+#define CKM_DES3_CMAC (0x138UL)
+#define CKM_CDMF_KEY_GEN (0x140UL)
+#define CKM_CDMF_ECB (0x141UL)
+#define CKM_CDMF_CBC (0x142UL)
+#define CKM_CDMF_MAC (0x143UL)
+#define CKM_CDMF_MAC_GENERAL (0x144UL)
+#define CKM_CDMF_CBC_PAD (0x145UL)
+#define CKM_DES_OFB64 (0x150UL)
+#define CKM_DES_OFB8 (0x151UL)
+#define CKM_DES_CFB64 (0x152UL)
+#define CKM_DES_CFB8 (0x153UL)
+#define CKM_MD2 (0x200UL)
+#define CKM_MD2_HMAC (0x201UL)
+#define CKM_MD2_HMAC_GENERAL (0x202UL)
+#define CKM_MD5 (0x210UL)
+#define CKM_MD5_HMAC (0x211UL)
+#define CKM_MD5_HMAC_GENERAL (0x212UL)
+#define CKM_SHA_1 (0x220UL)
+#define CKM_SHA_1_HMAC (0x221UL)
+#define CKM_SHA_1_HMAC_GENERAL (0x222UL)
+#define CKM_RIPEMD128 (0x230UL)
+#define CKM_RIPEMD128_HMAC (0x231UL)
+#define CKM_RIPEMD128_HMAC_GENERAL (0x232UL)
+#define CKM_RIPEMD160 (0x240UL)
+#define CKM_RIPEMD160_HMAC (0x241UL)
+#define CKM_RIPEMD160_HMAC_GENERAL (0x242UL)
+#define CKM_SHA256 (0x250UL)
+#define CKM_SHA256_HMAC (0x251UL)
+#define CKM_SHA256_HMAC_GENERAL (0x252UL)
+#define CKM_SHA384 (0x260UL)
+#define CKM_SHA384_HMAC (0x261UL)
+#define CKM_SHA384_HMAC_GENERAL (0x262UL)
+#define CKM_SHA512 (0x270UL)
+#define CKM_SHA512_HMAC (0x271UL)
+#define CKM_SHA512_HMAC_GENERAL (0x272UL)
+#define CKM_SECURID_KEY_GEN (0x280UL)
+#define CKM_SECURID (0x282UL)
+#define CKM_HOTP_KEY_GEN (0x290UL)
+#define CKM_HOTP (0x291UL)
+#define CKM_ACTI (0x2a0UL)
+#define CKM_ACTI_KEY_GEN (0x2a1UL)
+#define CKM_CAST_KEY_GEN (0x300UL)
+#define CKM_CAST_ECB (0x301UL)
+#define CKM_CAST_CBC (0x302UL)
+#define CKM_CAST_MAC (0x303UL)
+#define CKM_CAST_MAC_GENERAL (0x304UL)
+#define CKM_CAST_CBC_PAD (0x305UL)
+#define CKM_CAST3_KEY_GEN (0x310UL)
+#define CKM_CAST3_ECB (0x311UL)
+#define CKM_CAST3_CBC (0x312UL)
+#define CKM_CAST3_MAC (0x313UL)
+#define CKM_CAST3_MAC_GENERAL (0x314UL)
+#define CKM_CAST3_CBC_PAD (0x315UL)
+#define CKM_CAST5_KEY_GEN (0x320UL)
+#define CKM_CAST128_KEY_GEN (0x320UL)
+#define CKM_CAST5_ECB (0x321UL)
+#define CKM_CAST128_ECB (0x321UL)
+#define CKM_CAST5_CBC (0x322UL)
+#define CKM_CAST128_CBC (0x322UL)
+#define CKM_CAST5_MAC (0x323UL)
+#define CKM_CAST128_MAC (0x323UL)
+#define CKM_CAST5_MAC_GENERAL (0x324UL)
+#define CKM_CAST128_MAC_GENERAL (0x324UL)
+#define CKM_CAST5_CBC_PAD (0x325UL)
+#define CKM_CAST128_CBC_PAD (0x325UL)
+#define CKM_RC5_KEY_GEN (0x330UL)
+#define CKM_RC5_ECB (0x331UL)
+#define CKM_RC5_CBC (0x332UL)
+#define CKM_RC5_MAC (0x333UL)
+#define CKM_RC5_MAC_GENERAL (0x334UL)
+#define CKM_RC5_CBC_PAD (0x335UL)
+#define CKM_IDEA_KEY_GEN (0x340UL)
+#define CKM_IDEA_ECB (0x341UL)
+#define CKM_IDEA_CBC (0x342UL)
+#define CKM_IDEA_MAC (0x343UL)
+#define CKM_IDEA_MAC_GENERAL (0x344UL)
+#define CKM_IDEA_CBC_PAD (0x345UL)
+#define CKM_GENERIC_SECRET_KEY_GEN (0x350UL)
+#define CKM_CONCATENATE_BASE_AND_KEY (0x360UL)
+#define CKM_CONCATENATE_BASE_AND_DATA (0x362UL)
+#define CKM_CONCATENATE_DATA_AND_BASE (0x363UL)
+#define CKM_XOR_BASE_AND_DATA (0x364UL)
+#define CKM_EXTRACT_KEY_FROM_KEY (0x365UL)
+#define CKM_SSL3_PRE_MASTER_KEY_GEN (0x370UL)
+#define CKM_SSL3_MASTER_KEY_DERIVE (0x371UL)
+#define CKM_SSL3_KEY_AND_MAC_DERIVE (0x372UL)
+#define CKM_SSL3_MASTER_KEY_DERIVE_DH (0x373UL)
+#define CKM_TLS_PRE_MASTER_KEY_GEN (0x374UL)
+#define CKM_TLS_MASTER_KEY_DERIVE (0x375UL)
+#define CKM_TLS_KEY_AND_MAC_DERIVE (0x376UL)
+#define CKM_TLS_MASTER_KEY_DERIVE_DH (0x377UL)
+#define CKM_TLS_PRF (0x378UL)
+#define CKM_SSL3_MD5_MAC (0x380UL)
+#define CKM_SSL3_SHA1_MAC (0x381UL)
+#define CKM_MD5_KEY_DERIVATION (0x390UL)
+#define CKM_MD2_KEY_DERIVATION (0x391UL)
+#define CKM_SHA1_KEY_DERIVATION (0x392UL)
+#define CKM_SHA256_KEY_DERIVATION (0x393UL)
+#define CKM_SHA384_KEY_DERIVATION (0x394UL)
+#define CKM_SHA512_KEY_DERIVATION (0x395UL)
+#define CKM_PBE_MD2_DES_CBC (0x3a0UL)
+#define CKM_PBE_MD5_DES_CBC (0x3a1UL)
+#define CKM_PBE_MD5_CAST_CBC (0x3a2UL)
+#define CKM_PBE_MD5_CAST3_CBC (0x3a3UL)
+#define CKM_PBE_MD5_CAST5_CBC (0x3a4UL)
+#define CKM_PBE_MD5_CAST128_CBC (0x3a4UL)
+#define CKM_PBE_SHA1_CAST5_CBC (0x3a5UL)
+#define CKM_PBE_SHA1_CAST128_CBC (0x3a5UL)
+#define CKM_PBE_SHA1_RC4_128 (0x3a6UL)
+#define CKM_PBE_SHA1_RC4_40 (0x3a7UL)
+#define CKM_PBE_SHA1_DES3_EDE_CBC (0x3a8UL)
+#define CKM_PBE_SHA1_DES2_EDE_CBC (0x3a9UL)
+#define CKM_PBE_SHA1_RC2_128_CBC (0x3aaUL)
+#define CKM_PBE_SHA1_RC2_40_CBC (0x3abUL)
+#define CKM_PKCS5_PBKD2 (0x3b0UL)
+#define CKM_PBA_SHA1_WITH_SHA1_HMAC (0x3c0UL)
+#define CKM_WTLS_PRE_MASTER_KEY_GEN (0x3d0UL)
+#define CKM_WTLS_MASTER_KEY_DERIVE (0x3d1UL)
+#define CKM_WTLS_MASTER_KEY_DERIVE_DH_ECC (0x3d2UL)
+#define CKM_WTLS_PRF (0x3d3UL)
+#define CKM_WTLS_SERVER_KEY_AND_MAC_DERIVE (0x3d4UL)
+#define CKM_WTLS_CLIENT_KEY_AND_MAC_DERIVE (0x3d5UL)
+#define CKM_TLS10_MAC_SERVER (0x3d6UL)
+#define CKM_TLS10_MAC_CLIENT (0x3d7UL)
+#define CKM_TLS12_MAC (0x3d8UL)
+#define CKM_TLS12_KDF (0x3d9UL)
+#define CKM_TLS12_MASTER_KEY_DERIVE (0x3e0UL)
+#define CKM_TLS12_KEY_AND_MAC_DERIVE (0x3e1UL)
+#define CKM_TLS12_MASTER_KEY_DERIVE_DH (0x3e2UL)
+#define CKM_TLS12_KEY_SAFE_DERIVE (0x3e3UL)
+#define CKM_TLS_MAC (0x3e4UL)
+#define CKM_TLS_KDF (0x3e5UL)
+#define CKM_KEY_WRAP_LYNKS (0x400UL)
+#define CKM_KEY_WRAP_SET_OAEP (0x401UL)
+#define CKM_CMS_SIG (0x500UL)
+#define CKM_KIP_DERIVE (0x510UL)
+#define CKM_KIP_WRAP (0x511UL)
+#define CKM_KIP_MAC (0x512UL)
+#define CKM_ARIA_KEY_GEN (0x560UL)
+#define CKM_ARIA_ECB (0x561UL)
+#define CKM_ARIA_CBC (0x562UL)
+#define CKM_ARIA_MAC (0x563UL)
+#define CKM_ARIA_MAC_GENERAL (0x564UL)
+#define CKM_ARIA_CBC_PAD (0x565UL)
+#define CKM_ARIA_ECB_ENCRYPT_DATA (0x566UL)
+#define CKM_ARIA_CBC_ENCRYPT_DATA (0x567UL)
+#define CKM_SEED_KEY_GEN (0x650UL)
+#define CKM_SEED_ECB (0x651UL)
+#define CKM_SEED_CBC (0x652UL)
+#define CKM_SEED_MAC (0x653UL)
+#define CKM_SEED_MAC_GENERAL (0x654UL)
+#define CKM_SEED_CBC_PAD (0x655UL)
+#define CKM_SEED_ECB_ENCRYPT_DATA (0x656UL)
+#define CKM_SEED_CBC_ENCRYPT_DATA (0x657UL)
+#define CKM_SKIPJACK_KEY_GEN (0x1000UL)
+#define CKM_SKIPJACK_ECB64 (0x1001UL)
+#define CKM_SKIPJACK_CBC64 (0x1002UL)
+#define CKM_SKIPJACK_OFB64 (0x1003UL)
+#define CKM_SKIPJACK_CFB64 (0x1004UL)
+#define CKM_SKIPJACK_CFB32 (0x1005UL)
+#define CKM_SKIPJACK_CFB16 (0x1006UL)
+#define CKM_SKIPJACK_CFB8 (0x1007UL)
+#define CKM_SKIPJACK_WRAP (0x1008UL)
+#define CKM_SKIPJACK_PRIVATE_WRAP (0x1009UL)
+#define CKM_SKIPJACK_RELAYX (0x100aUL)
+#define CKM_KEA_KEY_PAIR_GEN (0x1010UL)
+#define CKM_KEA_KEY_DERIVE (0x1011UL)
+#define CKM_FORTEZZA_TIMESTAMP (0x1020UL)
+#define CKM_BATON_KEY_GEN (0x1030UL)
+#define CKM_BATON_ECB128 (0x1031UL)
+#define CKM_BATON_ECB96 (0x1032UL)
+#define CKM_BATON_CBC128 (0x1033UL)
+#define CKM_BATON_COUNTER (0x1034UL)
+#define CKM_BATON_SHUFFLE (0x1035UL)
+#define CKM_BATON_WRAP (0x1036UL)
+#define CKM_ECDSA_KEY_PAIR_GEN (0x1040UL)
+#define CKM_EC_KEY_PAIR_GEN (0x1040UL)
+#define CKM_ECDSA (0x1041UL)
+#define CKM_ECDSA_SHA1 (0x1042UL)
+#define CKM_ECDSA_SHA224 (0x1043UL)
+#define CKM_ECDSA_SHA256 (0x1044UL)
+#define CKM_ECDSA_SHA384 (0x1045UL)
+#define CKM_ECDSA_SHA512 (0x1046UL)
+#define CKM_ECDH1_DERIVE (0x1050UL)
+#define CKM_ECDH1_COFACTOR_DERIVE (0x1051UL)
+#define CKM_ECMQV_DERIVE (0x1052UL)
+#define CKM_ECDH_AES_KEY_WRAP (0x1053UL)
+#define CKM_RSA_AES_KEY_WRAP (0x1054UL)
+#define CKM_JUNIPER_KEY_GEN (0x1060UL)
+#define CKM_JUNIPER_ECB128 (0x1061UL)
+#define CKM_JUNIPER_CBC128 (0x1062UL)
+#define CKM_JUNIPER_COUNTER (0x1063UL)
+#define CKM_JUNIPER_SHUFFLE (0x1064UL)
+#define CKM_JUNIPER_WRAP (0x1065UL)
+#define CKM_FASTHASH (0x1070UL)
+#define CKM_AES_KEY_GEN (0x1080UL)
+#define CKM_AES_ECB (0x1081UL)
+#define CKM_AES_CBC (0x1082UL)
+#define CKM_AES_MAC (0x1083UL)
+#define CKM_AES_MAC_GENERAL (0x1084UL)
+#define CKM_AES_CBC_PAD (0x1085UL)
+#define CKM_AES_CTR (0x1086UL)
+#define CKM_AES_GCM (0x1087UL)
+#define CKM_AES_CCM (0x1088UL)
+#define CKM_AES_CTS (0x1089UL)
+#define CKM_AES_CMAC (0x108aUL)
+#define CKM_AES_CMAC_GENERAL (0x108bUL)
+#define CKM_AES_XCBC_MAC (0x108cUL)
+#define CKM_AES_XCBC_MAC_96 (0x108dUL)
+#define CKM_AES_GMAC (0x108eUL)
+#define CKM_BLOWFISH_KEY_GEN (0x1090UL)
+#define CKM_BLOWFISH_CBC (0x1091UL)
+#define CKM_TWOFISH_KEY_GEN (0x1092UL)
+#define CKM_TWOFISH_CBC (0x1093UL)
+#define CKM_BLOWFISH_CBC_PAD (0x1094UL)
+#define CKM_TWOFISH_CBC_PAD (0x1095UL)
+#define CKM_DES_ECB_ENCRYPT_DATA (0x1100UL)
+#define CKM_DES_CBC_ENCRYPT_DATA (0x1101UL)
+#define CKM_DES3_ECB_ENCRYPT_DATA (0x1102UL)
+#define CKM_DES3_CBC_ENCRYPT_DATA (0x1103UL)
+#define CKM_AES_ECB_ENCRYPT_DATA (0x1104UL)
+#define CKM_AES_CBC_ENCRYPT_DATA (0x1105UL)
+#define CKM_GOSTR3410_KEY_PAIR_GEN (0x1200UL)
+#define CKM_GOSTR3410 (0x1201UL)
+#define CKM_GOSTR3410_WITH_GOSTR3411 (0x1202UL)
+#define CKM_GOSTR3410_KEY_WRAP (0x1203UL)
+#define CKM_GOSTR3410_DERIVE (0x1204UL)
+#define CKM_GOSTR3411 (0x1210UL)
+#define CKM_GOSTR3411_HMAC (0x1211UL)
+#define CKM_GOST28147_KEY_GEN (0x1220UL)
+#define CKM_GOST28147_ECB (0x1221UL)
+#define CKM_GOST28147 (0x1222UL)
+#define CKM_GOST28147_MAC (0x1223UL)
+#define CKM_GOST28147_KEY_WRAP (0x1224UL)
+#define CKM_DSA_PARAMETER_GEN (0x2000UL)
+#define CKM_DH_PKCS_PARAMETER_GEN (0x2001UL)
+#define CKM_X9_42_DH_PARAMETER_GEN (0x2002UL)
+#define CKM_DSA_PROBABLISTIC_PARAMETER_GEN (0x2003UL)
+#define CKM_DSA_SHAWE_TAYLOR_PARAMETER_GEN (0x2004UL)
+#define CKM_AES_OFB (0x2104UL)
+#define CKM_AES_CFB64 (0x2105UL)
+#define CKM_AES_CFB8 (0x2106UL)
+#define CKM_AES_CFB128 (0x2107UL)
+#define CKM_AES_CFB1 (0x2108UL)
+
+#define CKM_VENDOR_DEFINED ((unsigned long)(1UL << 31))
+
+/* Amendments */
+#define CKM_SHA224 (0x255UL)
+#define CKM_SHA224_HMAC (0x256UL)
+#define CKM_SHA224_HMAC_GENERAL (0x257UL)
+#define CKM_SHA224_RSA_PKCS (0x46UL)
+#define CKM_SHA224_RSA_PKCS_PSS (0x47UL)
+#define CKM_SHA224_KEY_DERIVATION (0x396UL)
+
+#define CKM_CAMELLIA_KEY_GEN (0x550UL)
+#define CKM_CAMELLIA_ECB (0x551UL)
+#define CKM_CAMELLIA_CBC (0x552UL)
+#define CKM_CAMELLIA_MAC (0x553UL)
+#define CKM_CAMELLIA_MAC_GENERAL (0x554UL)
+#define CKM_CAMELLIA_CBC_PAD (0x555UL)
+#define CKM_CAMELLIA_ECB_ENCRYPT_DATA (0x556UL)
+#define CKM_CAMELLIA_CBC_ENCRYPT_DATA (0x557UL)
+#define CKM_CAMELLIA_CTR (0x558UL)
+
+#define CKM_AES_KEY_WRAP (0x2109UL)
+#define CKM_AES_KEY_WRAP_PAD (0x210aUL)
+
+#define CKM_RSA_PKCS_TPM_1_1 (0x4001UL)
+#define CKM_RSA_PKCS_OAEP_TPM_1_1 (0x4002UL)
+
+/* From version 3.0 */
+#define CKM_EC_EDWARDS_KEY_PAIR_GEN (0x1055UL)
+#define CKM_EDDSA (0x1057UL)
+
+/* Attribute and other constants related to OTP */
+#define CK_OTP_FORMAT_DECIMAL (0UL)
+#define CK_OTP_FORMAT_HEXADECIMAL (1UL)
+#define CK_OTP_FORMAT_ALPHANUMERIC (2UL)
+#define CK_OTP_FORMAT_BINARY (3UL)
+#define CK_OTP_PARAM_IGNORED (0UL)
+#define CK_OTP_PARAM_OPTIONAL (1UL)
+#define CK_OTP_PARAM_MANDATORY (2UL)
+
+#define CK_OTP_VALUE (0UL)
+#define CK_OTP_PIN (1UL)
+#define CK_OTP_CHALLENGE (2UL)
+#define CK_OTP_TIME (3UL)
+#define CK_OTP_COUNTER (4UL)
+#define CK_OTP_FLAGS (5UL)
+#define CK_OTP_OUTPUT_LENGTH (6UL)
+#define CK_OTP_FORMAT (7UL)
+
+/* OTP mechanism flags */
+#define CKF_NEXT_OTP (0x01UL)
+#define CKF_EXCLUDE_TIME (0x02UL)
+#define CKF_EXCLUDE_COUNTER (0x04UL)
+#define CKF_EXCLUDE_CHALLENGE (0x08UL)
+#define CKF_EXCLUDE_PIN (0x10UL)
+#define CKF_USER_FRIENDLY_OTP (0x20UL)
+
+#define CKN_OTP_CHANGED (0x01UL)
+
+struct ck_mechanism {
+ ck_mechanism_type_t mechanism;
+ void *parameter;
+ unsigned long parameter_len;
+};
+
+struct ck_mechanism_info {
+ unsigned long min_key_size;
+ unsigned long max_key_size;
+ ck_flags_t flags;
+};
+
+typedef unsigned long ck_param_type;
+
+typedef struct ck_otp_param {
+ ck_param_type type;
+ void *value;
+ unsigned long value_len;
+} ck_otp_param;
+
+typedef struct ck_otp_params {
+ struct ck_otp_param *params;
+ unsigned long count;
+} ck_otp_params;
+
+typedef struct ck_otp_signature_info {
+ struct ck_otp_param *params;
+ unsigned long count;
+} ck_otp_signature_info;
+
+#define CKG_MGF1_SHA1 0x00000001UL
+#define CKG_MGF1_SHA224 0x00000005UL
+#define CKG_MGF1_SHA256 0x00000002UL
+#define CKG_MGF1_SHA384 0x00000003UL
+#define CKG_MGF1_SHA512 0x00000004UL
+
+typedef unsigned long ck_rsa_pkcs_mgf_type_t;
+
+struct ck_rsa_pkcs_pss_params {
+ ck_mechanism_type_t hash_alg;
+ ck_rsa_pkcs_mgf_type_t mgf;
+ unsigned long s_len;
+};
+
+typedef unsigned long ck_rsa_pkcs_oaep_source_type_t;
+
+struct ck_rsa_pkcs_oaep_params {
+ ck_mechanism_type_t hash_alg;
+ ck_rsa_pkcs_mgf_type_t mgf;
+ ck_rsa_pkcs_oaep_source_type_t source;
+ void *source_data;
+ unsigned long source_data_len;
+};
+
+struct ck_aes_ctr_params {
+ unsigned long counter_bits;
+ unsigned char cb[16];
+};
+
+struct ck_gcm_params {
+ unsigned char *iv_ptr;
+ unsigned long iv_len;
+ unsigned long iv_bits;
+ unsigned char *aad_ptr;
+ unsigned long aad_len;
+ unsigned long tag_bits;
+};
+
+/* The following EC Key Derivation Functions are defined */
+#define CKD_NULL (0x01UL)
+#define CKD_SHA1_KDF (0x02UL)
+
+/* The following X9.42 DH key derivation functions are defined */
+#define CKD_SHA1_KDF_ASN1 (0x03UL)
+#define CKD_SHA1_KDF_CONCATENATE (0x04UL)
+#define CKD_SHA224_KDF (0x05UL)
+#define CKD_SHA256_KDF (0x06UL)
+#define CKD_SHA384_KDF (0x07UL)
+#define CKD_SHA512_KDF (0x08UL)
+#define CKD_CPDIVERSIFY_KDF (0x09UL)
+
+typedef unsigned long ck_ec_kdf_t;
+
+struct ck_ecdh1_derive_params {
+ ck_ec_kdf_t kdf;
+ unsigned long shared_data_len;
+ unsigned char *shared_data;
+ unsigned long public_data_len;
+ unsigned char *public_data;
+};
+
+struct ck_key_derivation_string_data {
+ unsigned char *string_data;
+ unsigned long string_data_len;
+};
+
+struct ck_des_cbc_encrypt_data_params {
+ unsigned char iv[8];
+ unsigned char *data_params;
+ unsigned long length;
+};
+
+struct ck_aes_cbc_encrypt_data_params {
+ unsigned char iv[16];
+ unsigned char *data_params;
+ unsigned long length;
+};
+
+#define CKF_HW (1UL << 0)
+#define CKF_ENCRYPT (1UL << 8)
+#define CKF_DECRYPT (1UL << 9)
+#define CKF_DIGEST (1UL << 10)
+#define CKF_SIGN (1UL << 11)
+#define CKF_SIGN_RECOVER (1UL << 12)
+#define CKF_VERIFY (1UL << 13)
+#define CKF_VERIFY_RECOVER (1UL << 14)
+#define CKF_GENERATE (1UL << 15)
+#define CKF_GENERATE_KEY_PAIR (1UL << 16)
+#define CKF_WRAP (1UL << 17)
+#define CKF_UNWRAP (1UL << 18)
+#define CKF_DERIVE (1UL << 19)
+#define CKF_EXTENSION ((unsigned long)(1UL << 31))
+
+#define CKF_EC_F_P (1UL << 20)
+#define CKF_EC_NAMEDCURVE (1UL << 23)
+#define CKF_EC_UNCOMPRESS (1UL << 24)
+#define CKF_EC_COMPRESS (1UL << 25)
+
+/* Flags for C_WaitForSlotEvent. */
+#define CKF_DONT_BLOCK (1UL)
+
+typedef unsigned long ck_rv_t;
+
+typedef ck_rv_t (*ck_notify_t)(ck_session_handle_t session,
+ ck_notification_t event, void *application);
+
+/* Forward reference. */
+struct ck_function_list;
+
+#define _CK_DECLARE_FUNCTION(name, args) \
+ typedef ck_rv_t(*CK_##name) args; \
+ ck_rv_t CK_SPEC name args
+
+_CK_DECLARE_FUNCTION(C_Initialize, (void *init_args));
+_CK_DECLARE_FUNCTION(C_Finalize, (void *reserved));
+_CK_DECLARE_FUNCTION(C_GetInfo, (struct ck_info * info));
+_CK_DECLARE_FUNCTION(C_GetFunctionList,
+ (struct ck_function_list * *function_list));
+
+_CK_DECLARE_FUNCTION(C_GetSlotList,
+ (unsigned char token_present, ck_slot_id_t *slot_list,
+ unsigned long *count));
+_CK_DECLARE_FUNCTION(C_GetSlotInfo,
+ (ck_slot_id_t slot_id, struct ck_slot_info *info));
+_CK_DECLARE_FUNCTION(C_GetTokenInfo,
+ (ck_slot_id_t slot_id, struct ck_token_info *info));
+_CK_DECLARE_FUNCTION(C_WaitForSlotEvent,
+ (ck_flags_t flags, ck_slot_id_t *slot, void *reserved));
+_CK_DECLARE_FUNCTION(C_GetMechanismList,
+ (ck_slot_id_t slot_id, ck_mechanism_type_t *mechanism_list,
+ unsigned long *count));
+_CK_DECLARE_FUNCTION(C_GetMechanismInfo,
+ (ck_slot_id_t slot_id, ck_mechanism_type_t type,
+ struct ck_mechanism_info *info));
+_CK_DECLARE_FUNCTION(C_InitToken,
+ (ck_slot_id_t slot_id, unsigned char *pin,
+ unsigned long pin_len, unsigned char *label));
+_CK_DECLARE_FUNCTION(C_InitPIN, (ck_session_handle_t session,
+ unsigned char *pin, unsigned long pin_len));
+_CK_DECLARE_FUNCTION(C_SetPIN, (ck_session_handle_t session,
+ unsigned char *old_pin, unsigned long old_len,
+ unsigned char *new_pin, unsigned long new_len));
+
+_CK_DECLARE_FUNCTION(C_OpenSession,
+ (ck_slot_id_t slot_id, ck_flags_t flags, void *application,
+ ck_notify_t notify, ck_session_handle_t *session));
+_CK_DECLARE_FUNCTION(C_CloseSession, (ck_session_handle_t session));
+_CK_DECLARE_FUNCTION(C_CloseAllSessions, (ck_slot_id_t slot_id));
+_CK_DECLARE_FUNCTION(C_GetSessionInfo, (ck_session_handle_t session,
+ struct ck_session_info *info));
+_CK_DECLARE_FUNCTION(C_GetOperationState, (ck_session_handle_t session,
+ unsigned char *operation_state,
+ unsigned long *operation_state_len));
+_CK_DECLARE_FUNCTION(C_SetOperationState,
+ (ck_session_handle_t session,
+ unsigned char *operation_state,
+ unsigned long operation_state_len,
+ ck_object_handle_t encryption_key,
+ ck_object_handle_t authentiation_key));
+_CK_DECLARE_FUNCTION(C_Login,
+ (ck_session_handle_t session, ck_user_type_t user_type,
+ unsigned char *pin, unsigned long pin_len));
+_CK_DECLARE_FUNCTION(C_Logout, (ck_session_handle_t session));
+
+_CK_DECLARE_FUNCTION(C_CreateObject,
+ (ck_session_handle_t session, struct ck_attribute *templ,
+ unsigned long count, ck_object_handle_t *object));
+_CK_DECLARE_FUNCTION(C_CopyObject,
+ (ck_session_handle_t session, ck_object_handle_t object,
+ struct ck_attribute *templ, unsigned long count,
+ ck_object_handle_t *new_object));
+_CK_DECLARE_FUNCTION(C_DestroyObject,
+ (ck_session_handle_t session, ck_object_handle_t object));
+_CK_DECLARE_FUNCTION(C_GetObjectSize,
+ (ck_session_handle_t session, ck_object_handle_t object,
+ unsigned long *size));
+_CK_DECLARE_FUNCTION(C_GetAttributeValue,
+ (ck_session_handle_t session, ck_object_handle_t object,
+ struct ck_attribute *templ, unsigned long count));
+_CK_DECLARE_FUNCTION(C_SetAttributeValue,
+ (ck_session_handle_t session, ck_object_handle_t object,
+ struct ck_attribute *templ, unsigned long count));
+_CK_DECLARE_FUNCTION(C_FindObjectsInit,
+ (ck_session_handle_t session, struct ck_attribute *templ,
+ unsigned long count));
+_CK_DECLARE_FUNCTION(C_FindObjects,
+ (ck_session_handle_t session, ck_object_handle_t *object,
+ unsigned long max_object_count,
+ unsigned long *object_count));
+_CK_DECLARE_FUNCTION(C_FindObjectsFinal, (ck_session_handle_t session));
+
+_CK_DECLARE_FUNCTION(C_EncryptInit,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism, ck_object_handle_t key));
+_CK_DECLARE_FUNCTION(C_Encrypt,
+ (ck_session_handle_t session, unsigned char *data,
+ unsigned long data_len, unsigned char *encrypted_data,
+ unsigned long *encrypted_data_len));
+_CK_DECLARE_FUNCTION(C_EncryptUpdate,
+ (ck_session_handle_t session, unsigned char *part,
+ unsigned long part_len, unsigned char *encrypted_part,
+ unsigned long *encrypted_part_len));
+_CK_DECLARE_FUNCTION(C_EncryptFinal, (ck_session_handle_t session,
+ unsigned char *last_encrypted_part,
+ unsigned long *last_encrypted_part_len));
+
+_CK_DECLARE_FUNCTION(C_DecryptInit,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism, ck_object_handle_t key));
+_CK_DECLARE_FUNCTION(C_Decrypt, (ck_session_handle_t session,
+ unsigned char *encrypted_data,
+ unsigned long encrypted_data_len,
+ unsigned char *data, unsigned long *data_len));
+_CK_DECLARE_FUNCTION(C_DecryptUpdate,
+ (ck_session_handle_t session,
+ unsigned char *encrypted_part,
+ unsigned long encrypted_part_len, unsigned char *part,
+ unsigned long *part_len));
+_CK_DECLARE_FUNCTION(C_DecryptFinal,
+ (ck_session_handle_t session, unsigned char *last_part,
+ unsigned long *last_part_len));
+
+_CK_DECLARE_FUNCTION(C_DigestInit, (ck_session_handle_t session,
+ struct ck_mechanism *mechanism));
+_CK_DECLARE_FUNCTION(C_Digest,
+ (ck_session_handle_t session, unsigned char *data,
+ unsigned long data_len, unsigned char *digest,
+ unsigned long *digest_len));
+_CK_DECLARE_FUNCTION(C_DigestUpdate,
+ (ck_session_handle_t session, unsigned char *part,
+ unsigned long part_len));
+_CK_DECLARE_FUNCTION(C_DigestKey,
+ (ck_session_handle_t session, ck_object_handle_t key));
+_CK_DECLARE_FUNCTION(C_DigestFinal,
+ (ck_session_handle_t session, unsigned char *digest,
+ unsigned long *digest_len));
+
+_CK_DECLARE_FUNCTION(C_SignInit,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism, ck_object_handle_t key));
+_CK_DECLARE_FUNCTION(C_Sign, (ck_session_handle_t session, unsigned char *data,
+ unsigned long data_len, unsigned char *signature,
+ unsigned long *signature_len));
+_CK_DECLARE_FUNCTION(C_SignUpdate,
+ (ck_session_handle_t session, unsigned char *part,
+ unsigned long part_len));
+_CK_DECLARE_FUNCTION(C_SignFinal,
+ (ck_session_handle_t session, unsigned char *signature,
+ unsigned long *signature_len));
+_CK_DECLARE_FUNCTION(C_SignRecoverInit,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism, ck_object_handle_t key));
+_CK_DECLARE_FUNCTION(C_SignRecover,
+ (ck_session_handle_t session, unsigned char *data,
+ unsigned long data_len, unsigned char *signature,
+ unsigned long *signature_len));
+
+_CK_DECLARE_FUNCTION(C_VerifyInit,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism, ck_object_handle_t key));
+_CK_DECLARE_FUNCTION(C_Verify,
+ (ck_session_handle_t session, unsigned char *data,
+ unsigned long data_len, unsigned char *signature,
+ unsigned long signature_len));
+_CK_DECLARE_FUNCTION(C_VerifyUpdate,
+ (ck_session_handle_t session, unsigned char *part,
+ unsigned long part_len));
+_CK_DECLARE_FUNCTION(C_VerifyFinal,
+ (ck_session_handle_t session, unsigned char *signature,
+ unsigned long signature_len));
+_CK_DECLARE_FUNCTION(C_VerifyRecoverInit,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism, ck_object_handle_t key));
+_CK_DECLARE_FUNCTION(C_VerifyRecover,
+ (ck_session_handle_t session, unsigned char *signature,
+ unsigned long signature_len, unsigned char *data,
+ unsigned long *data_len));
+
+_CK_DECLARE_FUNCTION(C_DigestEncryptUpdate,
+ (ck_session_handle_t session, unsigned char *part,
+ unsigned long part_len, unsigned char *encrypted_part,
+ unsigned long *encrypted_part_len));
+_CK_DECLARE_FUNCTION(C_DecryptDigestUpdate,
+ (ck_session_handle_t session,
+ unsigned char *encrypted_part,
+ unsigned long encrypted_part_len, unsigned char *part,
+ unsigned long *part_len));
+_CK_DECLARE_FUNCTION(C_SignEncryptUpdate,
+ (ck_session_handle_t session, unsigned char *part,
+ unsigned long part_len, unsigned char *encrypted_part,
+ unsigned long *encrypted_part_len));
+_CK_DECLARE_FUNCTION(C_DecryptVerifyUpdate,
+ (ck_session_handle_t session,
+ unsigned char *encrypted_part,
+ unsigned long encrypted_part_len, unsigned char *part,
+ unsigned long *part_len));
+
+_CK_DECLARE_FUNCTION(C_GenerateKey,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism,
+ struct ck_attribute *templ, unsigned long count,
+ ck_object_handle_t *key));
+_CK_DECLARE_FUNCTION(C_GenerateKeyPair,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism,
+ struct ck_attribute *public_key_template,
+ unsigned long public_key_attribute_count,
+ struct ck_attribute *private_key_template,
+ unsigned long private_key_attribute_count,
+ ck_object_handle_t *public_key,
+ ck_object_handle_t *private_key));
+_CK_DECLARE_FUNCTION(C_WrapKey,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism,
+ ck_object_handle_t wrapping_key, ck_object_handle_t key,
+ unsigned char *wrapped_key,
+ unsigned long *wrapped_key_len));
+_CK_DECLARE_FUNCTION(C_UnwrapKey,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism,
+ ck_object_handle_t unwrapping_key,
+ unsigned char *wrapped_key, unsigned long wrapped_key_len,
+ struct ck_attribute *templ, unsigned long attribute_count,
+ ck_object_handle_t *key));
+_CK_DECLARE_FUNCTION(C_DeriveKey,
+ (ck_session_handle_t session,
+ struct ck_mechanism *mechanism,
+ ck_object_handle_t base_key, struct ck_attribute *templ,
+ unsigned long attribute_count, ck_object_handle_t *key));
+
+_CK_DECLARE_FUNCTION(C_SeedRandom,
+ (ck_session_handle_t session, unsigned char *seed,
+ unsigned long seed_len));
+_CK_DECLARE_FUNCTION(C_GenerateRandom,
+ (ck_session_handle_t session, unsigned char *random_data,
+ unsigned long random_len));
+
+_CK_DECLARE_FUNCTION(C_GetFunctionStatus, (ck_session_handle_t session));
+_CK_DECLARE_FUNCTION(C_CancelFunction, (ck_session_handle_t session));
+
+struct ck_function_list {
+ struct ck_version version;
+ CK_C_Initialize C_Initialize;
+ CK_C_Finalize C_Finalize;
+ CK_C_GetInfo C_GetInfo;
+ CK_C_GetFunctionList C_GetFunctionList;
+ CK_C_GetSlotList C_GetSlotList;
+ CK_C_GetSlotInfo C_GetSlotInfo;
+ CK_C_GetTokenInfo C_GetTokenInfo;
+ CK_C_GetMechanismList C_GetMechanismList;
+ CK_C_GetMechanismInfo C_GetMechanismInfo;
+ CK_C_InitToken C_InitToken;
+ CK_C_InitPIN C_InitPIN;
+ CK_C_SetPIN C_SetPIN;
+ CK_C_OpenSession C_OpenSession;
+ CK_C_CloseSession C_CloseSession;
+ CK_C_CloseAllSessions C_CloseAllSessions;
+ CK_C_GetSessionInfo C_GetSessionInfo;
+ CK_C_GetOperationState C_GetOperationState;
+ CK_C_SetOperationState C_SetOperationState;
+ CK_C_Login C_Login;
+ CK_C_Logout C_Logout;
+ CK_C_CreateObject C_CreateObject;
+ CK_C_CopyObject C_CopyObject;
+ CK_C_DestroyObject C_DestroyObject;
+ CK_C_GetObjectSize C_GetObjectSize;
+ CK_C_GetAttributeValue C_GetAttributeValue;
+ CK_C_SetAttributeValue C_SetAttributeValue;
+ CK_C_FindObjectsInit C_FindObjectsInit;
+ CK_C_FindObjects C_FindObjects;
+ CK_C_FindObjectsFinal C_FindObjectsFinal;
+ CK_C_EncryptInit C_EncryptInit;
+ CK_C_Encrypt C_Encrypt;
+ CK_C_EncryptUpdate C_EncryptUpdate;
+ CK_C_EncryptFinal C_EncryptFinal;
+ CK_C_DecryptInit C_DecryptInit;
+ CK_C_Decrypt C_Decrypt;
+ CK_C_DecryptUpdate C_DecryptUpdate;
+ CK_C_DecryptFinal C_DecryptFinal;
+ CK_C_DigestInit C_DigestInit;
+ CK_C_Digest C_Digest;
+ CK_C_DigestUpdate C_DigestUpdate;
+ CK_C_DigestKey C_DigestKey;
+ CK_C_DigestFinal C_DigestFinal;
+ CK_C_SignInit C_SignInit;
+ CK_C_Sign C_Sign;
+ CK_C_SignUpdate C_SignUpdate;
+ CK_C_SignFinal C_SignFinal;
+ CK_C_SignRecoverInit C_SignRecoverInit;
+ CK_C_SignRecover C_SignRecover;
+ CK_C_VerifyInit C_VerifyInit;
+ CK_C_Verify C_Verify;
+ CK_C_VerifyUpdate C_VerifyUpdate;
+ CK_C_VerifyFinal C_VerifyFinal;
+ CK_C_VerifyRecoverInit C_VerifyRecoverInit;
+ CK_C_VerifyRecover C_VerifyRecover;
+ CK_C_DigestEncryptUpdate C_DigestEncryptUpdate;
+ CK_C_DecryptDigestUpdate C_DecryptDigestUpdate;
+ CK_C_SignEncryptUpdate C_SignEncryptUpdate;
+ CK_C_DecryptVerifyUpdate C_DecryptVerifyUpdate;
+ CK_C_GenerateKey C_GenerateKey;
+ CK_C_GenerateKeyPair C_GenerateKeyPair;
+ CK_C_WrapKey C_WrapKey;
+ CK_C_UnwrapKey C_UnwrapKey;
+ CK_C_DeriveKey C_DeriveKey;
+ CK_C_SeedRandom C_SeedRandom;
+ CK_C_GenerateRandom C_GenerateRandom;
+ CK_C_GetFunctionStatus C_GetFunctionStatus;
+ CK_C_CancelFunction C_CancelFunction;
+ CK_C_WaitForSlotEvent C_WaitForSlotEvent;
+};
+
+typedef ck_rv_t (*ck_createmutex_t)(void **mutex);
+typedef ck_rv_t (*ck_destroymutex_t)(void *mutex);
+typedef ck_rv_t (*ck_lockmutex_t)(void *mutex);
+typedef ck_rv_t (*ck_unlockmutex_t)(void *mutex);
+
+struct ck_c_initialize_args {
+ ck_createmutex_t create_mutex;
+ ck_destroymutex_t destroy_mutex;
+ ck_lockmutex_t lock_mutex;
+ ck_unlockmutex_t unlock_mutex;
+ ck_flags_t flags;
+ void *reserved;
+};
+
+#define CKF_LIBRARY_CANT_CREATE_OS_THREADS (1UL << 0)
+#define CKF_OS_LOCKING_OK (1UL << 1)
+
+#define CKR_OK (0UL)
+#define CKR_CANCEL (1UL)
+#define CKR_HOST_MEMORY (2UL)
+#define CKR_SLOT_ID_INVALID (3UL)
+#define CKR_GENERAL_ERROR (5UL)
+#define CKR_FUNCTION_FAILED (6UL)
+#define CKR_ARGUMENTS_BAD (7UL)
+#define CKR_NO_EVENT (8UL)
+#define CKR_NEED_TO_CREATE_THREADS (9UL)
+#define CKR_CANT_LOCK (0xaUL)
+#define CKR_ATTRIBUTE_READ_ONLY (0x10UL)
+#define CKR_ATTRIBUTE_SENSITIVE (0x11UL)
+#define CKR_ATTRIBUTE_TYPE_INVALID (0x12UL)
+#define CKR_ATTRIBUTE_VALUE_INVALID (0x13UL)
+#define CKR_ACTION_PROHIBITED (0x1BUL)
+#define CKR_DATA_INVALID (0x20UL)
+#define CKR_DATA_LEN_RANGE (0x21UL)
+#define CKR_DEVICE_ERROR (0x30UL)
+#define CKR_DEVICE_MEMORY (0x31UL)
+#define CKR_DEVICE_REMOVED (0x32UL)
+#define CKR_ENCRYPTED_DATA_INVALID (0x40UL)
+#define CKR_ENCRYPTED_DATA_LEN_RANGE (0x41UL)
+#define CKR_FUNCTION_CANCELED (0x50UL)
+#define CKR_FUNCTION_NOT_PARALLEL (0x51UL)
+#define CKR_FUNCTION_NOT_SUPPORTED (0x54UL)
+#define CKR_KEY_HANDLE_INVALID (0x60UL)
+#define CKR_KEY_SIZE_RANGE (0x62UL)
+#define CKR_KEY_TYPE_INCONSISTENT (0x63UL)
+#define CKR_KEY_NOT_NEEDED (0x64UL)
+#define CKR_KEY_CHANGED (0x65UL)
+#define CKR_KEY_NEEDED (0x66UL)
+#define CKR_KEY_INDIGESTIBLE (0x67UL)
+#define CKR_KEY_FUNCTION_NOT_PERMITTED (0x68UL)
+#define CKR_KEY_NOT_WRAPPABLE (0x69UL)
+#define CKR_KEY_UNEXTRACTABLE (0x6aUL)
+#define CKR_MECHANISM_INVALID (0x70UL)
+#define CKR_MECHANISM_PARAM_INVALID (0x71UL)
+#define CKR_OBJECT_HANDLE_INVALID (0x82UL)
+#define CKR_OPERATION_ACTIVE (0x90UL)
+#define CKR_OPERATION_NOT_INITIALIZED (0x91UL)
+#define CKR_PIN_INCORRECT (0xa0UL)
+#define CKR_PIN_INVALID (0xa1UL)
+#define CKR_PIN_LEN_RANGE (0xa2UL)
+#define CKR_PIN_EXPIRED (0xa3UL)
+#define CKR_PIN_LOCKED (0xa4UL)
+#define CKR_SESSION_CLOSED (0xb0UL)
+#define CKR_SESSION_COUNT (0xb1UL)
+#define CKR_SESSION_HANDLE_INVALID (0xb3UL)
+#define CKR_SESSION_PARALLEL_NOT_SUPPORTED (0xb4UL)
+#define CKR_SESSION_READ_ONLY (0xb5UL)
+#define CKR_SESSION_EXISTS (0xb6UL)
+#define CKR_SESSION_READ_ONLY_EXISTS (0xb7UL)
+#define CKR_SESSION_READ_WRITE_SO_EXISTS (0xb8UL)
+#define CKR_SIGNATURE_INVALID (0xc0UL)
+#define CKR_SIGNATURE_LEN_RANGE (0xc1UL)
+#define CKR_TEMPLATE_INCOMPLETE (0xd0UL)
+#define CKR_TEMPLATE_INCONSISTENT (0xd1UL)
+#define CKR_TOKEN_NOT_PRESENT (0xe0UL)
+#define CKR_TOKEN_NOT_RECOGNIZED (0xe1UL)
+#define CKR_TOKEN_WRITE_PROTECTED (0xe2UL)
+#define CKR_UNWRAPPING_KEY_HANDLE_INVALID (0xf0UL)
+#define CKR_UNWRAPPING_KEY_SIZE_RANGE (0xf1UL)
+#define CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT (0xf2UL)
+#define CKR_USER_ALREADY_LOGGED_IN (0x100UL)
+#define CKR_USER_NOT_LOGGED_IN (0x101UL)
+#define CKR_USER_PIN_NOT_INITIALIZED (0x102UL)
+#define CKR_USER_TYPE_INVALID (0x103UL)
+#define CKR_USER_ANOTHER_ALREADY_LOGGED_IN (0x104UL)
+#define CKR_USER_TOO_MANY_TYPES (0x105UL)
+#define CKR_WRAPPED_KEY_INVALID (0x110UL)
+#define CKR_WRAPPED_KEY_LEN_RANGE (0x112UL)
+#define CKR_WRAPPING_KEY_HANDLE_INVALID (0x113UL)
+#define CKR_WRAPPING_KEY_SIZE_RANGE (0x114UL)
+#define CKR_WRAPPING_KEY_TYPE_INCONSISTENT (0x115UL)
+#define CKR_RANDOM_SEED_NOT_SUPPORTED (0x120UL)
+#define CKR_RANDOM_NO_RNG (0x121UL)
+#define CKR_DOMAIN_PARAMS_INVALID (0x130UL)
+#define CKR_BUFFER_TOO_SMALL (0x150UL)
+#define CKR_SAVED_STATE_INVALID (0x160UL)
+#define CKR_INFORMATION_SENSITIVE (0x170UL)
+#define CKR_STATE_UNSAVEABLE (0x180UL)
+#define CKR_CRYPTOKI_NOT_INITIALIZED (0x190UL)
+#define CKR_CRYPTOKI_ALREADY_INITIALIZED (0x191UL)
+#define CKR_MUTEX_BAD (0x1a0UL)
+#define CKR_MUTEX_NOT_LOCKED (0x1a1UL)
+#define CKR_NEW_PIN_MODE (0x1b0UL)
+#define CKR_NEXT_OTP (0x1b1UL)
+#define CKR_EXCEEDED_MAX_ITERATIONS (0x1c0UL)
+#define CKR_FIPS_SELF_TEST_FAILED (0x1c1UL)
+#define CKR_LIBRARY_LOAD_FAILED (0x1c2UL)
+#define CKR_PIN_TOO_WEAK (0x1c3UL)
+#define CKR_PUBLIC_KEY_INVALID (0x1c4UL)
+#define CKR_FUNCTION_REJECTED (0x200UL)
+#define CKR_VENDOR_DEFINED ((unsigned long)(1UL << 31))
+
+#define CKZ_DATA_SPECIFIED (0x01UL)
+
+/* Compatibility layer. */
+
+#ifdef CRYPTOKI_COMPAT
+
+#undef CK_DEFINE_FUNCTION
+#define CK_DEFINE_FUNCTION(retval, name) retval CK_SPEC name
+
+/* For NULL. */
+#include <stddef.h>
+
+typedef unsigned char CK_BYTE;
+typedef unsigned char CK_CHAR;
+typedef unsigned char CK_UTF8CHAR;
+typedef unsigned char CK_BBOOL;
+typedef unsigned long int CK_ULONG;
+typedef long int CK_LONG;
+typedef CK_BYTE *CK_BYTE_PTR;
+typedef CK_CHAR *CK_CHAR_PTR;
+typedef CK_UTF8CHAR *CK_UTF8CHAR_PTR;
+typedef CK_ULONG *CK_ULONG_PTR;
+typedef void *CK_VOID_PTR;
+typedef void **CK_VOID_PTR_PTR;
+#define CK_FALSE 0
+#define CK_TRUE 1
+#ifndef CK_DISABLE_TRUE_FALSE
+#ifndef FALSE
+#define FALSE 0
+#endif /* ifndef FALSE */
+#ifndef TRUE
+#define TRUE 1
+#endif /* ifndef TRUE */
+#endif /* ifndef CK_DISABLE_TRUE_FALSE */
+
+typedef struct ck_version CK_VERSION;
+typedef struct ck_version *CK_VERSION_PTR;
+
+typedef struct ck_info CK_INFO;
+typedef struct ck_info *CK_INFO_PTR;
+
+typedef ck_slot_id_t *CK_SLOT_ID_PTR;
+
+typedef struct ck_slot_info CK_SLOT_INFO;
+typedef struct ck_slot_info *CK_SLOT_INFO_PTR;
+
+typedef struct ck_token_info CK_TOKEN_INFO;
+typedef struct ck_token_info *CK_TOKEN_INFO_PTR;
+
+typedef ck_session_handle_t *CK_SESSION_HANDLE_PTR;
+
+typedef struct ck_session_info CK_SESSION_INFO;
+typedef struct ck_session_info *CK_SESSION_INFO_PTR;
+
+typedef ck_object_handle_t *CK_OBJECT_HANDLE_PTR;
+
+typedef ck_object_class_t *CK_OBJECT_CLASS_PTR;
+
+typedef struct ck_attribute CK_ATTRIBUTE;
+typedef struct ck_attribute *CK_ATTRIBUTE_PTR;
+
+typedef struct ck_date CK_DATE;
+typedef struct ck_date *CK_DATE_PTR;
+
+typedef ck_mechanism_type_t *CK_MECHANISM_TYPE_PTR;
+
+typedef struct ck_mechanism CK_MECHANISM;
+typedef struct ck_mechanism *CK_MECHANISM_PTR;
+
+typedef struct ck_mechanism_info CK_MECHANISM_INFO;
+typedef struct ck_mechanism_info *CK_MECHANISM_INFO_PTR;
+
+typedef struct ck_otp_mechanism_info CK_OTP_MECHANISM_INFO;
+typedef struct ck_otp_mechanism_info *CK_OTP_MECHANISM_INFO_PTR;
+
+typedef struct ck_function_list CK_FUNCTION_LIST;
+typedef struct ck_function_list *CK_FUNCTION_LIST_PTR;
+typedef struct ck_function_list **CK_FUNCTION_LIST_PTR_PTR;
+
+typedef struct ck_c_initialize_args CK_C_INITIALIZE_ARGS;
+typedef struct ck_c_initialize_args *CK_C_INITIALIZE_ARGS_PTR;
+
+typedef struct ck_rsa_pkcs_pss_params CK_RSA_PKCS_PSS_PARAMS;
+typedef struct ck_rsa_pkcs_pss_params *CK_RSA_PKCS_PSS_PARAMS_PTR;
+
+typedef struct ck_rsa_pkcs_oaep_params CK_RSA_PKCS_OAEP_PARAMS;
+typedef struct ck_rsa_pkcs_oaep_params *CK_RSA_PKCS_OAEP_PARAMS_PTR;
+
+typedef struct ck_aes_ctr_params CK_AES_CTR_PARAMS;
+typedef struct ck_aes_ctr_params *CK_AES_CTR_PARAMS_PTR;
+
+typedef struct ck_gcm_params CK_GCM_PARAMS;
+typedef struct ck_gcm_params *CK_GCM_PARAMS_PTR;
+
+typedef struct ck_ecdh1_derive_params CK_ECDH1_DERIVE_PARAMS;
+typedef struct ck_ecdh1_derive_params *CK_ECDH1_DERIVE_PARAMS_PTR;
+
+typedef struct ck_key_derivation_string_data CK_KEY_DERIVATION_STRING_DATA;
+typedef struct ck_key_derivation_string_data *CK_KEY_DERIVATION_STRING_DATA_PTR;
+
+typedef struct ck_des_cbc_encrypt_data_params CK_DES_CBC_ENCRYPT_DATA_PARAMS;
+typedef struct ck_des_cbc_encrypt_data_params
+ *CK_DES_CBC_ENCRYPT_DATA_PARAMS_PTR;
+
+typedef struct ck_aes_cbc_encrypt_data_params CK_AES_CBC_ENCRYPT_DATA_PARAMS;
+typedef struct ck_aes_cbc_encrypt_data_params
+ *CK_AES_CBC_ENCRYPT_DATA_PARAMS_PTR;
+
+#ifndef NULL_PTR
+#define NULL_PTR NULL
+#endif /* ifndef NULL_PTR */
+
+/* Delete the helper macros defined at the top of the file. */
+#undef ck_flags_t
+#undef ck_version
+
+#undef ck_info
+#undef cryptoki_version
+#undef manufacturer_id
+#undef library_description
+#undef library_version
+
+#undef ck_notification_t
+#undef ck_slot_id_t
+
+#undef ck_slot_info
+#undef slot_description
+#undef hardware_version
+#undef firmware_version
+
+#undef ck_token_info
+#undef serial_number
+#undef max_session_count
+#undef session_count
+#undef max_rw_session_count
+#undef rw_session_count
+#undef max_pin_len
+#undef min_pin_len
+#undef total_public_memory
+#undef free_public_memory
+#undef total_private_memory
+#undef free_private_memory
+#undef utc_time
+
+#undef ck_session_handle_t
+#undef ck_user_type_t
+#undef ck_state_t
+
+#undef ck_session_info
+#undef slot_id
+#undef device_error
+
+#undef ck_object_handle_t
+#undef ck_object_class_t
+#undef ck_hw_feature_type_t
+#undef ck_key_type_t
+#undef ck_certificate_type_t
+#undef ck_attribute_type_t
+
+#undef ck_attribute
+#undef value
+#undef value_len
+
+#undef params
+#undef count
+
+#undef ck_date
+
+#undef ck_mechanism_type_t
+
+#undef ck_mechanism
+#undef parameter
+#undef parameter_len
+
+#undef ck_mechanism_info
+
+#undef ck_param_type
+#undef ck_otp_param
+#undef ck_otp_params
+#undef ck_otp_signature_info
+
+#undef min_key_size
+#undef max_key_size
+
+#undef ck_rv_t
+#undef ck_notify_t
+
+#undef ck_function_list
+
+#undef ck_createmutex_t
+#undef ck_destroymutex_t
+#undef ck_lockmutex_t
+#undef ck_unlockmutex_t
+
+#undef ck_c_initialize_args
+#undef create_mutex
+#undef destroy_mutex
+#undef lock_mutex
+#undef unlock_mutex
+#undef reserved
+
+#endif /* CRYPTOKI_COMPAT */
+
+/* System dependencies. */
+#if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32)
+#pragma pack(pop, cryptoki)
+#endif /* if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) */
+
+#if defined(__cplusplus)
+}
+#endif /* if defined(__cplusplus) */
+
+#endif /* PKCS11_H */
diff --git a/lib/isc/iterated_hash.c b/lib/isc/iterated_hash.c
new file mode 100644
index 0000000..1f95353
--- /dev/null
+++ b/lib/isc/iterated_hash.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.
+ */
+
+#include <stdio.h>
+
+#include <openssl/opensslv.h>
+#include <openssl/sha.h>
+
+#include <isc/iterated_hash.h>
+#include <isc/thread.h>
+#include <isc/util.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) {
+ return (0);
+ }
+
+ if (SHA1_Update(&ctx, buf, len) != 1) {
+ return (0);
+ }
+
+ if (SHA1_Update(&ctx, salt, saltlength) != 1) {
+ return (0);
+ }
+
+ if (SHA1_Final(out, &ctx) != 1) {
+ return (0);
+ }
+
+ buf = out;
+ len = SHA_DIGEST_LENGTH;
+ } while (n++ < iterations);
+
+ return (SHA_DIGEST_LENGTH);
+}
diff --git a/lib/isc/lex.c b/lib/isc/lex.c
new file mode 100644
index 0000000..5878d53
--- /dev/null
+++ b/lib/isc/lex.c
@@ -0,0 +1,1133 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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>
+
+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_R_IOERROR;
+ 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(__FILE__, __LINE__, "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/lfsr.c b/lib/isc/lfsr.c
new file mode 100644
index 0000000..633b4a9
--- /dev/null
+++ b/lib/isc/lfsr.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.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include <isc/assertions.h>
+#include <isc/lfsr.h>
+#include <isc/util.h>
+
+#define VALID_LFSR(x) (x != NULL)
+
+void
+isc_lfsr_init(isc_lfsr_t *lfsr, uint32_t state, unsigned int bits, uint32_t tap,
+ unsigned int count, isc_lfsrreseed_t reseed, void *arg) {
+ REQUIRE(VALID_LFSR(lfsr));
+ REQUIRE(8 <= bits && bits <= 32);
+ REQUIRE(tap != 0);
+
+ lfsr->state = state;
+ lfsr->bits = bits;
+ lfsr->tap = tap;
+ lfsr->count = count;
+ lfsr->reseed = reseed;
+ lfsr->arg = arg;
+
+ if (count == 0 && reseed != NULL) {
+ reseed(lfsr, arg);
+ }
+ if (lfsr->state == 0) {
+ lfsr->state = 0xffffffffU >> (32 - lfsr->bits);
+ }
+}
+
+/*!
+ * Return the next state of the lfsr.
+ */
+static uint32_t
+lfsr_generate(isc_lfsr_t *lfsr) {
+ /*
+ * If the previous state is zero, we must fill it with something
+ * here, or we will begin to generate an extremely predictable output.
+ *
+ * First, give the reseed function a crack at it. If the state is
+ * still 0, set it to all ones.
+ */
+ if (lfsr->state == 0) {
+ if (lfsr->reseed != NULL) {
+ lfsr->reseed(lfsr, lfsr->arg);
+ }
+ if (lfsr->state == 0) {
+ lfsr->state = 0xffffffffU >> (32 - lfsr->bits);
+ }
+ }
+
+ if (lfsr->state & 0x01) {
+ lfsr->state = (lfsr->state >> 1) ^ lfsr->tap;
+ return (1);
+ } else {
+ lfsr->state >>= 1;
+ return (0);
+ }
+}
+
+void
+isc_lfsr_generate(isc_lfsr_t *lfsr, void *data, unsigned int count) {
+ unsigned char *p;
+ unsigned int bit;
+ unsigned int byte;
+
+ REQUIRE(VALID_LFSR(lfsr));
+ REQUIRE(data != NULL);
+ REQUIRE(count > 0);
+
+ p = data;
+ byte = count;
+
+ while (byte--) {
+ *p = 0;
+ for (bit = 0; bit < 7; bit++) {
+ *p |= lfsr_generate(lfsr);
+ *p <<= 1;
+ }
+ *p |= lfsr_generate(lfsr);
+ p++;
+ }
+
+ if (lfsr->count != 0 && lfsr->reseed != NULL) {
+ if (lfsr->count <= count * 8) {
+ lfsr->reseed(lfsr, lfsr->arg);
+ } else {
+ lfsr->count -= (count * 8);
+ }
+ }
+}
+
+static uint32_t
+lfsr_skipgenerate(isc_lfsr_t *lfsr, unsigned int skip) {
+ while (skip--) {
+ (void)lfsr_generate(lfsr);
+ }
+
+ (void)lfsr_generate(lfsr);
+
+ return (lfsr->state);
+}
+
+/*
+ * Skip "skip" states in "lfsr".
+ */
+void
+isc_lfsr_skip(isc_lfsr_t *lfsr, unsigned int skip) {
+ REQUIRE(VALID_LFSR(lfsr));
+
+ while (skip--) {
+ (void)lfsr_generate(lfsr);
+ }
+}
+
+/*
+ * Skip states in lfsr1 and lfsr2 using the other's current state.
+ * Return the final state of lfsr1 ^ lfsr2.
+ */
+uint32_t
+isc_lfsr_generate32(isc_lfsr_t *lfsr1, isc_lfsr_t *lfsr2) {
+ uint32_t state1, state2;
+ uint32_t skip1, skip2;
+
+ REQUIRE(VALID_LFSR(lfsr1));
+ REQUIRE(VALID_LFSR(lfsr2));
+
+ skip1 = lfsr1->state & 0x01;
+ skip2 = lfsr2->state & 0x01;
+
+ /* cross-skip. */
+ state1 = lfsr_skipgenerate(lfsr1, skip2);
+ state2 = lfsr_skipgenerate(lfsr2, skip1);
+
+ return (state1 ^ state2);
+}
diff --git a/lib/isc/lib.c b/lib/isc/lib.c
new file mode 100644
index 0000000..848ed58
--- /dev/null
+++ b/lib/isc/lib.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.
+ */
+
+/*! \file */
+
+#include <isc/bind9.h>
+#include <isc/iterated_hash.h>
+#include <isc/lib.h>
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include "config.h"
+#include "mem_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_lib_register(void) {
+ isc_bind9 = false;
+}
+
+#ifdef WIN32
+int
+isc_lib_ntservice(int(WINAPI *mainfunc)(int argc, char *argv[]), int argc,
+ char *argv[]) {
+ isc__trampoline_t *trampoline = isc__trampoline_get(NULL, NULL);
+ int r;
+
+ isc__trampoline_attach(trampoline);
+
+ r = mainfunc(argc, argv);
+
+ isc__trampoline_detach(trampoline);
+
+ return (r);
+}
+#endif /* ifdef WIN32 */
+
+void
+isc__initialize(void) ISC_CONSTRUCTOR;
+void
+isc__shutdown(void) ISC_DESTRUCTOR;
+
+void
+isc__initialize(void) {
+ isc__mem_initialize();
+ isc__tls_initialize();
+ isc__trampoline_initialize();
+}
+
+void
+isc__shutdown(void) {
+ isc__trampoline_shutdown();
+ isc__tls_shutdown();
+ isc__mem_shutdown();
+}
+
+/*
+ * This is a workaround for situation when libisc is statically linked. Under
+ * normal situation, the linker throws out all symbols from compilation unit
+ * when no symbols are used in the final binary. This empty function must be
+ * called at least once from different compilation unit (mem.c in this case).
+ */
+void
+isc_enable_constructors() {
+ /* do nothing */
+}
diff --git a/lib/isc/lib_p.h b/lib/isc/lib_p.h
new file mode 100644
index 0000000..56bbbe3
--- /dev/null
+++ b/lib/isc/lib_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__initialize(void);
+
+void
+isc__shutdown(void);
diff --git a/lib/isc/log.c b/lib/isc/log.c
new file mode 100644
index 0000000..b15d66a
--- /dev/null
+++ b/lib/isc/log.c
@@ -0,0 +1,1896 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 <isc/atomic.h>
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/rwlock.h>
+#include <isc/stat.h>
+#include <isc/stdio.h>
+#include <isc/string.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);
+
+/*
+ * 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[].
+ */
+LIBISC_EXTERNAL_DATA isc_logcategory_t isc_categories[] = { { "default",
+ 0 }, /* "default
+ must come
+ first. */
+ { "general", 0 },
+ { NULL, 0 } };
+
+/*!
+ * See above comment for categories on LIBISC_EXTERNAL_DATA, and apply it to
+ * modules.
+ */
+LIBISC_EXTERNAL_DATA 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.
+ */
+LIBISC_EXTERNAL_DATA 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 *bname, *digit_end;
+ const char *dirname;
+ int version, greatest = -1;
+ size_t bnamelen;
+ isc_dir_t dir;
+ isc_result_t result;
+ char sep = '/';
+#ifdef _WIN32
+ char *bname2;
+#endif /* ifdef _WIN32 */
+
+ /*
+ * It is safe to DE_CONST the file.name because it was copied
+ * with isc_mem_strdup().
+ */
+ bname = strrchr(file->name, sep);
+#ifdef _WIN32
+ bname2 = strrchr(file->name, '\\');
+ if ((bname != NULL && bname2 != NULL && bname2 > bname) ||
+ (bname == NULL && bname2 != NULL))
+ {
+ bname = bname2;
+ sep = '\\';
+ }
+#endif /* ifdef _WIN32 */
+ if (bname != NULL) {
+ *bname++ = '\0';
+ dirname = file->name;
+ } else {
+ DE_CONST(file->name, bname);
+ dirname = ".";
+ }
+ bnamelen = strlen(bname);
+
+ isc_dir_init(&dir);
+ result = isc_dir_open(&dir, dirname);
+
+ /*
+ * Replace the file separator if it was taken out.
+ */
+ if (bname != file->name) {
+ *(bname - 1) = sep;
+ }
+
+ /*
+ * 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) {
+ result = isc_file_remove(dir.entry.name);
+ if (result != ISC_R_SUCCESS &&
+ result != ISC_R_FILENOTFOUND)
+ {
+ syslog(LOG_ERR,
+ "unable to remove "
+ "log file '%s': %s",
+ 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, 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) {
+ isc_result_t result;
+ char *bname = NULL, *digit_end = NULL;
+ const char *dirname = NULL;
+ int64_t version, last = INT64_MAX;
+ size_t bnamelen;
+ isc_dir_t dir;
+ char sep = '/';
+#ifdef _WIN32
+ char *bname2;
+#endif /* ifdef _WIN32 */
+ /*
+ * It is safe to DE_CONST the file.name because it was copied
+ * with isc_mem_strdup().
+ */
+ bname = strrchr(file->name, sep);
+#ifdef _WIN32
+ bname2 = strrchr(file->name, '\\');
+ if ((bname != NULL && bname2 != NULL && bname2 > bname) ||
+ (bname == NULL && bname2 != NULL))
+ {
+ bname = bname2;
+ sep = '\\';
+ }
+#endif /* ifdef _WIN32 */
+ if (bname != NULL) {
+ *bname++ = '\0';
+ dirname = file->name;
+ } else {
+ DE_CONST(file->name, bname);
+ dirname = ".";
+ }
+ bnamelen = strlen(bname);
+
+ isc_dir_init(&dir);
+ result = isc_dir_open(&dir, dirname);
+
+ /*
+ * Replace the file separator if it was taken out.
+ */
+ if (bname != file->name) {
+ *(bname - 1) = sep;
+ }
+
+ /*
+ * Return if the directory open failed.
+ */
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ last = last_to_keep(versions, &dir, bname, bnamelen);
+
+ /*
+ * Then we remove all files that we don't want to_keep
+ */
+ while (isc_dir_read(&dir) == ISC_R_SUCCESS) {
+ if (dir.entry.length > bnamelen &&
+ strncmp(dir.entry.name, bname, bnamelen) == 0 &&
+ dir.entry.name[bnamelen] == '.')
+ {
+ char *ename = &dir.entry.name[bnamelen + 1];
+ version = strtoull(ename, &digit_end, 10);
+ /*
+ * Remove any backup files that exceed versions.
+ */
+ if (*digit_end == '\0' && version < last) {
+ result = isc_file_remove(dir.entry.name);
+ if (result != ISC_R_SUCCESS &&
+ result != ISC_R_FILENOTFOUND)
+ {
+ syslog(LOG_ERR,
+ "unable to remove "
+ "log file '%s': %s",
+ 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);
+ }
+
+ 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;
+ 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);
+
+ int_fast32_t 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);
+}
diff --git a/lib/isc/managers.c b/lib/isc/managers.c
new file mode 100644
index 0000000..ba4b21d
--- /dev/null
+++ b/lib/isc/managers.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.
+ */
+
+#include <isc/managers.h>
+#include <isc/util.h>
+
+#include "netmgr_p.h"
+#include "task_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_result_t result;
+ isc_taskmgr_t *taskmgr = NULL;
+ isc_nm_t *netmgr = 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(__FILE__, __LINE__,
+ "isc_managers_create() failed: %s",
+ isc_result_totext(result));
+ goto fail;
+ }
+ *taskmgrp = taskmgr;
+ }
+
+ return (ISC_R_SUCCESS);
+fail:
+ isc_managers_destroy(netmgrp, taskmgrp);
+
+ return (result);
+}
+
+void
+isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp) {
+ /*
+ * 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);
+ }
+}
diff --git a/lib/isc/md.c b/lib/isc/md.c
new file mode 100644
index 0000000..0c6a70d
--- /dev/null
+++ b/lib/isc/md.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#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 (ISC_UNLIKELY(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) {
+ 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) {
+ 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 (ISC_UNLIKELY(buf == NULL || len == 0)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (EVP_DigestUpdate(md, buf, len) != 1) {
+ 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) {
+ 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_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) { return (EVP_##alg()); }
+
+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..21b2d86
--- /dev/null
+++ b/lib/isc/mem.c
@@ -0,0 +1,2450 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/bind9.h>
+#include <isc/hash.h>
+#include <isc/lib.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/strerr.h>
+#include <isc/string.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 "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 */
+LIBISC_EXTERNAL_DATA unsigned int isc_mem_debugging = ISC_MEM_DEBUGGING;
+LIBISC_EXTERNAL_DATA unsigned int isc_mem_defaultflags = ISC_MEMFLAG_DEFAULT;
+
+/*
+ * Constants.
+ */
+
+#define DEF_MAX_SIZE 1100
+#define DEF_MEM_TARGET 4096
+#define ALIGNMENT_SIZE \
+ 8U /*%< must be a power of 2, also update lib/dns/rbt.c */
+#define NUM_BASIC_BLOCKS 64 /*%< must be > 1 */
+#define TABLE_INCREMENT 1024
+#define DEBUG_TABLE_COUNT 512U
+
+/*
+ * Types.
+ */
+typedef struct isc__mem isc__mem_t;
+typedef struct isc__mempool isc__mempool_t;
+
+#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;
+};
+
+typedef struct {
+ /*!
+ * This structure must be ALIGNMENT_SIZE bytes.
+ */
+ union {
+ size_t size;
+ isc__mem_t *ctx;
+ char bytes[ALIGNMENT_SIZE];
+ } u;
+} size_info;
+
+struct stats {
+ unsigned long gets;
+ unsigned long totalgets;
+ unsigned long blocks;
+ unsigned long freefrags;
+};
+
+#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;
+
+/*%
+ * Memory allocation and free function definitions.
+ * isc__memalloc_t must deal with memory allocation failure
+ * and must never return NULL.
+ */
+typedef void *(*isc__memalloc_t)(size_t);
+typedef void (*isc__memfree_t)(void *);
+
+struct isc__mem {
+ isc_mem_t common;
+ unsigned int flags;
+ isc_mutex_t lock;
+ isc__memalloc_t memalloc;
+ isc__memfree_t memfree;
+ size_t max_size;
+ bool checkfree;
+ struct stats *stats;
+ isc_refcount_t references;
+ char name[16];
+ void *tag;
+ size_t total;
+ size_t inuse;
+ size_t maxinuse;
+ size_t malloced;
+ size_t maxmalloced;
+ size_t hi_water;
+ size_t lo_water;
+ bool hi_called;
+ bool is_overmem;
+ isc_mem_water_t water;
+ void *water_arg;
+ ISC_LIST(isc__mempool_t) pools;
+ unsigned int poolcnt;
+
+ /* ISC_MEMFLAG_INTERNAL */
+ size_t mem_target;
+ element **freelists;
+ element *basic_blocks;
+ unsigned char **basic_table;
+ unsigned int basic_table_count;
+ unsigned int basic_table_size;
+ unsigned char *lowest;
+ unsigned char *highest;
+
+#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 */
+ isc_mempool_t common; /*%< common header of mempool's */
+ 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 */
+ unsigned int maxalloc; /*%< max number of items allowed */
+ unsigned int allocated; /*%< # of items currently given out */
+ unsigned int freecount; /*%< # of items on reserved list */
+ unsigned int freemax; /*%< # of items allowed on free list */
+ unsigned int fillcount; /*%< # of items to fetch on each fill */
+ /*%< Stats only. */
+ unsigned int gets; /*%< # of requests to this pool */
+ /*%< Debugging only. */
+#if ISC_MEMPOOL_NAMES
+ char name[16]; /*%< printed name in stats reports */
+#endif /* if ISC_MEMPOOL_NAMES */
+};
+
+/*
+ * 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 ADD_TRACE(a, b, c, d, e) \
+ do { \
+ if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0 && \
+ b != NULL)) \
+ add_trace_entry(a, b, c, d, e); \
+ } while (0)
+#define DELETE_TRACE(a, b, c, d, e) \
+ do { \
+ if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0 && \
+ b != NULL)) \
+ delete_trace_entry(a, b, c, d, e); \
+ } while (0)
+
+static void
+print_active(isc__mem_t *ctx, FILE *out);
+
+#endif /* ISC_MEM_TRACKLINES */
+
+static void *
+isc___mem_get(isc_mem_t *ctx, size_t size FLARG);
+static void
+isc___mem_put(isc_mem_t *ctx, void *ptr, size_t size FLARG);
+static void
+isc___mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size FLARG);
+static void *
+isc___mem_allocate(isc_mem_t *ctx, size_t size FLARG);
+static void *
+isc___mem_reallocate(isc_mem_t *ctx, void *ptr, size_t size FLARG);
+static char *
+isc___mem_strdup(isc_mem_t *mctx, const char *s FLARG);
+static char *
+isc___mem_strndup(isc_mem_t *mctx0, const char *s, size_t size FLARG);
+static void
+isc___mem_free(isc_mem_t *ctx, void *ptr FLARG);
+
+static isc_memmethods_t memmethods = {
+ isc___mem_get, isc___mem_put, isc___mem_putanddetach,
+ isc___mem_allocate, isc___mem_reallocate, isc___mem_strdup,
+ isc___mem_strndup, isc___mem_free,
+};
+
+#if ISC_MEM_TRACKLINES
+/*!
+ * mctx must be locked.
+ */
+static void
+add_trace_entry(isc__mem_t *mctx, const void *ptr, size_t size FLARG) {
+ debuglink_t *dl;
+ uint32_t hash;
+ uint32_t idx;
+
+ 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) {
+ return;
+ }
+
+#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 = malloc(sizeof(debuglink_t));
+ INSIST(dl != NULL);
+ mctx->malloced += sizeof(debuglink_t);
+ if (mctx->malloced > mctx->maxmalloced) {
+ mctx->maxmalloced = mctx->malloced;
+ }
+
+ 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++;
+}
+
+static void
+delete_trace_entry(isc__mem_t *mctx, const void *ptr, size_t size,
+ const char *file, unsigned int line) {
+ debuglink_t *dl;
+ uint32_t hash;
+ uint32_t idx;
+
+ 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) {
+ return;
+ }
+
+#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 (ISC_LIKELY(dl != NULL)) {
+ if (ISC_UNLIKELY(dl->ptr == ptr)) {
+ ISC_LIST_UNLINK(mctx->debuglist[idx], dl, link);
+ mctx->malloced -= sizeof(*dl);
+ free(dl);
+ return;
+ }
+ dl = ISC_LIST_NEXT(dl, link);
+ }
+
+ /*
+ * If we get here, we didn't find the item on the list. We're
+ * screwed.
+ */
+ UNREACHABLE();
+}
+#endif /* ISC_MEM_TRACKLINES */
+
+static size_t
+rmsize(size_t size) {
+ /*
+ * round down to ALIGNMENT_SIZE
+ */
+ return (size & (~(ALIGNMENT_SIZE - 1)));
+}
+
+static size_t
+quantize(size_t size) {
+ /*!
+ * Round up the result in order to get a size big
+ * enough to satisfy the request and be aligned on ALIGNMENT_SIZE
+ * byte boundaries.
+ */
+
+ if (size == 0U) {
+ return (ALIGNMENT_SIZE);
+ }
+ return ((size + ALIGNMENT_SIZE - 1) & (~(ALIGNMENT_SIZE - 1)));
+}
+
+static void
+more_basic_blocks(isc__mem_t *ctx) {
+ void *tmp;
+ unsigned char *curr, *next;
+ unsigned char *first, *last;
+ unsigned char **table;
+ unsigned int table_size;
+
+ /* Require: we hold the context lock. */
+
+ INSIST(ctx->basic_table_count <= ctx->basic_table_size);
+ if (ctx->basic_table_count == ctx->basic_table_size) {
+ table_size = ctx->basic_table_size + TABLE_INCREMENT;
+ table = (ctx->memalloc)(table_size * sizeof(unsigned char *));
+ ctx->malloced += table_size * sizeof(unsigned char *);
+ if (ctx->malloced > ctx->maxmalloced) {
+ ctx->maxmalloced = ctx->malloced;
+ }
+ if (ctx->basic_table_size != 0) {
+ memmove(table, ctx->basic_table,
+ ctx->basic_table_size *
+ sizeof(unsigned char *));
+ (ctx->memfree)(ctx->basic_table);
+ ctx->malloced -= ctx->basic_table_size *
+ sizeof(unsigned char *);
+ }
+ ctx->basic_table = table;
+ ctx->basic_table_size = table_size;
+ }
+
+ tmp = (ctx->memalloc)(NUM_BASIC_BLOCKS * ctx->mem_target);
+ ctx->total += NUM_BASIC_BLOCKS * ctx->mem_target;
+ ctx->basic_table[ctx->basic_table_count] = tmp;
+ ctx->basic_table_count++;
+ ctx->malloced += NUM_BASIC_BLOCKS * ctx->mem_target;
+ if (ctx->malloced > ctx->maxmalloced) {
+ ctx->maxmalloced = ctx->malloced;
+ }
+
+ curr = tmp;
+ next = curr + ctx->mem_target;
+ for (int i = 0; i < (NUM_BASIC_BLOCKS - 1); i++) {
+ ((element *)curr)->next = (element *)next;
+ curr = next;
+ next += ctx->mem_target;
+ }
+ /*
+ * curr is now pointing at the last block in the
+ * array.
+ */
+ ((element *)curr)->next = NULL;
+ first = tmp;
+ last = first + NUM_BASIC_BLOCKS * ctx->mem_target - 1;
+ if (first < ctx->lowest || ctx->lowest == NULL) {
+ ctx->lowest = first;
+ }
+ if (last > ctx->highest) {
+ ctx->highest = last;
+ }
+ ctx->basic_blocks = tmp;
+}
+
+static void
+more_frags(isc__mem_t *ctx, size_t new_size) {
+ int frags;
+ size_t total_size;
+ void *tmp;
+ unsigned char *curr, *next;
+
+ /*!
+ * Try to get more fragments by chopping up a basic block.
+ */
+
+ if (ctx->basic_blocks == NULL) {
+ more_basic_blocks(ctx);
+ }
+ INSIST(ctx->basic_blocks != NULL);
+
+ total_size = ctx->mem_target;
+ tmp = ctx->basic_blocks;
+ ctx->basic_blocks = ctx->basic_blocks->next;
+ frags = (int)(total_size / new_size);
+ ctx->stats[new_size].blocks++;
+ ctx->stats[new_size].freefrags += frags;
+ /*
+ * Set up a linked-list of blocks of size
+ * "new_size".
+ */
+ curr = tmp;
+ next = curr + new_size;
+ total_size -= new_size;
+ for (int i = 0; i < (frags - 1); i++) {
+ ((element *)curr)->next = (element *)next;
+ curr = next;
+ next += new_size;
+ total_size -= new_size;
+ }
+ /*
+ * Add the remaining fragment of the basic block to a free list.
+ */
+ total_size = rmsize(total_size);
+ if (total_size > 0U) {
+ ((element *)next)->next = ctx->freelists[total_size];
+ ctx->freelists[total_size] = (element *)next;
+ ctx->stats[total_size].freefrags++;
+ }
+ /*
+ * curr is now pointing at the last block in the
+ * array.
+ */
+ ((element *)curr)->next = NULL;
+ ctx->freelists[new_size] = tmp;
+}
+
+static void *
+mem_getunlocked(isc__mem_t *ctx, size_t size) {
+ size_t new_size = quantize(size);
+ void *ret;
+
+ if (new_size >= ctx->max_size) {
+ /*
+ * memget() was called on something beyond our upper limit.
+ */
+ ret = (ctx->memalloc)(size);
+ ctx->total += size;
+ ctx->inuse += size;
+ ctx->stats[ctx->max_size].gets++;
+ ctx->stats[ctx->max_size].totalgets++;
+ ctx->malloced += size;
+ if (ctx->malloced > ctx->maxmalloced) {
+ ctx->maxmalloced = ctx->malloced;
+ }
+ /*
+ * If we don't set new_size to size, then the
+ * ISC_MEMFLAG_FILL code might write over bytes we don't
+ * own.
+ */
+ new_size = size;
+ goto done;
+ }
+ /*
+ * If there are no blocks in the free list for this size, get a chunk
+ * of memory and then break it up into "new_size"-sized blocks, adding
+ * them to the free list.
+ */
+ if (ctx->freelists[new_size] == NULL) {
+ more_frags(ctx, new_size);
+ }
+ INSIST(ctx->freelists[new_size] != NULL);
+
+ /*
+ * The free list uses the "rounded-up" size "new_size".
+ */
+
+ ret = ctx->freelists[new_size];
+ ctx->freelists[new_size] = ctx->freelists[new_size]->next;
+
+ /*
+ * The stats[] uses the _actual_ "size" requested by the
+ * caller, with the caveat (in the code above) that "size" >= the
+ * max. size (max_size) ends up getting recorded as a call to
+ * max_size.
+ */
+ ctx->stats[size].gets++;
+ ctx->stats[size].totalgets++;
+ ctx->stats[new_size].freefrags--;
+ ctx->inuse += new_size;
+
+done:
+ if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0) &&
+ ISC_LIKELY(ret != NULL))
+ {
+ memset(ret, 0xbe, new_size); /* Mnemonic for "beef". */
+ }
+
+ return (ret);
+}
+
+#if ISC_MEM_CHECKOVERRUN
+static void
+check_overrun(void *mem, size_t size, size_t new_size) {
+ unsigned char *cp;
+
+ cp = (unsigned char *)mem;
+ cp += size;
+ while (size < new_size) {
+ INSIST(*cp == 0xbe);
+ cp++;
+ size++;
+ }
+}
+#endif /* if ISC_MEM_CHECKOVERRUN */
+
+/* coverity[+free : arg-1] */
+static void
+mem_putunlocked(isc__mem_t *ctx, void *mem, size_t size) {
+ size_t new_size = quantize(size);
+
+ if (new_size >= ctx->max_size) {
+ /*
+ * memput() called on something beyond our upper limit.
+ */
+ if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) {
+ memset(mem, 0xde, size); /* Mnemonic for "dead". */
+ }
+
+ (ctx->memfree)(mem);
+ INSIST(ctx->stats[ctx->max_size].gets != 0U);
+ ctx->stats[ctx->max_size].gets--;
+ INSIST(size <= ctx->inuse);
+ ctx->inuse -= size;
+ ctx->malloced -= size;
+ return;
+ }
+
+ if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) {
+#if ISC_MEM_CHECKOVERRUN
+ check_overrun(mem, size, new_size);
+#endif /* if ISC_MEM_CHECKOVERRUN */
+ memset(mem, 0xde, new_size); /* Mnemonic for "dead". */
+ }
+
+ /*
+ * The free list uses the "rounded-up" size "new_size".
+ */
+ ((element *)mem)->next = ctx->freelists[new_size];
+ ctx->freelists[new_size] = (element *)mem;
+
+ /*
+ * The stats[] uses the _actual_ "size" requested by the
+ * caller, with the caveat (in the code above) that "size" >= the
+ * max. size (max_size) ends up getting recorded as a call to
+ * max_size.
+ */
+ INSIST(ctx->stats[size].gets != 0U);
+ ctx->stats[size].gets--;
+ ctx->stats[new_size].freefrags++;
+ ctx->inuse -= new_size;
+}
+
+/*!
+ * Perform a malloc, doing memory filling and overrun detection as necessary.
+ */
+static void *
+mem_get(isc__mem_t *ctx, size_t size) {
+ char *ret;
+
+#if ISC_MEM_CHECKOVERRUN
+ size += 1;
+#endif /* if ISC_MEM_CHECKOVERRUN */
+ ret = (ctx->memalloc)(size);
+
+ if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) {
+ if (ISC_LIKELY(ret != NULL)) {
+ memset(ret, 0xbe, size); /* Mnemonic for "beef". */
+ }
+ }
+#if ISC_MEM_CHECKOVERRUN
+ else
+ {
+ if (ISC_LIKELY(ret != NULL)) {
+ ret[size - 1] = 0xbe;
+ }
+ }
+#endif /* if ISC_MEM_CHECKOVERRUN */
+
+ 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) {
+#if ISC_MEM_CHECKOVERRUN
+ INSIST(((unsigned char *)mem)[size] == 0xbe);
+ size += 1;
+#endif /* if ISC_MEM_CHECKOVERRUN */
+ if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) {
+ memset(mem, 0xde, size); /* Mnemonic for "dead". */
+ }
+ (ctx->memfree)(mem);
+}
+
+/*!
+ * Update internal counters after a memory get.
+ */
+static void
+mem_getstats(isc__mem_t *ctx, size_t size) {
+ ctx->total += size;
+ ctx->inuse += size;
+
+ if (size > ctx->max_size) {
+ ctx->stats[ctx->max_size].gets++;
+ ctx->stats[ctx->max_size].totalgets++;
+ } else {
+ ctx->stats[size].gets++;
+ ctx->stats[size].totalgets++;
+ }
+
+#if ISC_MEM_CHECKOVERRUN
+ size += 1;
+#endif /* if ISC_MEM_CHECKOVERRUN */
+ ctx->malloced += size;
+ if (ctx->malloced > ctx->maxmalloced) {
+ ctx->maxmalloced = ctx->malloced;
+ }
+}
+
+/*!
+ * Update internal counters after a memory put.
+ */
+static void
+mem_putstats(isc__mem_t *ctx, void *ptr, size_t size) {
+ UNUSED(ptr);
+
+ INSIST(ctx->inuse >= size);
+ ctx->inuse -= size;
+
+ if (size > ctx->max_size) {
+ INSIST(ctx->stats[ctx->max_size].gets > 0U);
+ ctx->stats[ctx->max_size].gets--;
+ } else {
+ INSIST(ctx->stats[size].gets > 0U);
+ ctx->stats[size].gets--;
+ }
+#if ISC_MEM_CHECKOVERRUN
+ size += 1;
+#endif /* if ISC_MEM_CHECKOVERRUN */
+ ctx->malloced -= size;
+}
+
+/*
+ * Private.
+ */
+
+static void *
+default_memalloc(size_t size) {
+ void *ptr;
+
+ ptr = malloc(size);
+
+ /*
+ * If the space cannot be allocated, a null pointer is returned. If the
+ * size of the space requested is zero, the behavior is
+ * implementation-defined: either a null pointer is returned, or the
+ * behavior is as if the size were some nonzero value, except that the
+ * returned pointer shall not be used to access an object.
+ * [ISO9899 § 7.22.3]
+ *
+ * [ISO9899]
+ * ISO/IEC WG 9899:2011: Programming languages - C.
+ * International Organization for Standardization, Geneva,
+ * Switzerland.
+ * http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1570.pdf
+ */
+
+ if (ptr == NULL && size != 0) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__, "malloc failed: %s",
+ strbuf);
+ }
+
+ return (ptr);
+}
+
+static void
+default_memfree(void *ptr) {
+ free(ptr);
+}
+
+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) {
+ REQUIRE(ctxp != NULL && *ctxp == NULL);
+#if __SANITIZE_ADDRESS__
+ REQUIRE((flags & ISC_MEMFLAG_INTERNAL) == 0);
+#endif
+
+ isc__mem_t *ctx;
+
+ isc_enable_constructors();
+
+ STATIC_ASSERT((ALIGNMENT_SIZE & (ALIGNMENT_SIZE - 1)) == 0,
+ "wrong alignment size");
+
+ ctx = (default_memalloc)(sizeof(*ctx));
+
+ isc_mutex_init(&ctx->lock);
+
+ ctx->max_size = DEF_MAX_SIZE;
+ ctx->flags = flags;
+ isc_refcount_init(&ctx->references, 1);
+ memset(ctx->name, 0, sizeof(ctx->name));
+ ctx->tag = NULL;
+ ctx->total = 0;
+ ctx->inuse = 0;
+ ctx->maxinuse = 0;
+ ctx->malloced = sizeof(*ctx);
+ ctx->maxmalloced = sizeof(*ctx);
+ ctx->hi_water = 0;
+ ctx->lo_water = 0;
+ ctx->hi_called = false;
+ ctx->is_overmem = false;
+ ctx->water = NULL;
+ ctx->water_arg = NULL;
+ ctx->common.impmagic = MEM_MAGIC;
+ ctx->common.magic = ISCAPI_MCTX_MAGIC;
+ ctx->common.methods = (isc_memmethods_t *)&memmethods;
+ ctx->memalloc = default_memalloc;
+ ctx->memfree = default_memfree;
+ ctx->stats = NULL;
+ ctx->checkfree = true;
+#if ISC_MEM_TRACKLINES
+ ctx->debuglist = NULL;
+ ctx->debuglistcnt = 0;
+#endif /* if ISC_MEM_TRACKLINES */
+ ISC_LIST_INIT(ctx->pools);
+ ctx->poolcnt = 0;
+ ctx->freelists = NULL;
+ ctx->basic_blocks = NULL;
+ ctx->basic_table = NULL;
+ ctx->basic_table_count = 0;
+ ctx->basic_table_size = 0;
+ ctx->lowest = NULL;
+ ctx->highest = NULL;
+
+ ctx->stats =
+ (ctx->memalloc)((ctx->max_size + 1) * sizeof(struct stats));
+
+ memset(ctx->stats, 0, (ctx->max_size + 1) * sizeof(struct stats));
+ ctx->malloced += (ctx->max_size + 1) * sizeof(struct stats);
+ ctx->maxmalloced += (ctx->max_size + 1) * sizeof(struct stats);
+
+ if ((flags & ISC_MEMFLAG_INTERNAL) != 0) {
+ ctx->mem_target = DEF_MEM_TARGET;
+ ctx->freelists =
+ (ctx->memalloc)(ctx->max_size * sizeof(element *));
+ memset(ctx->freelists, 0, ctx->max_size * sizeof(element *));
+ ctx->malloced += ctx->max_size * sizeof(element *);
+ ctx->maxmalloced += ctx->max_size * sizeof(element *);
+ }
+
+#if ISC_MEM_TRACKLINES
+ if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGRECORD) != 0)) {
+ unsigned int i;
+
+ ctx->debuglist = (ctx->memalloc)(
+ (DEBUG_TABLE_COUNT * sizeof(debuglist_t)));
+ for (i = 0; i < DEBUG_TABLE_COUNT; i++) {
+ ISC_LIST_INIT(ctx->debuglist[i]);
+ }
+ ctx->malloced += DEBUG_TABLE_COUNT * sizeof(debuglist_t);
+ ctx->maxmalloced += DEBUG_TABLE_COUNT * sizeof(debuglist_t);
+ }
+#endif /* if ISC_MEM_TRACKLINES */
+
+ LOCK(&contextslock);
+ ISC_LIST_INITANDAPPEND(contexts, ctx, link);
+ UNLOCK(&contextslock);
+
+ *ctxp = (isc_mem_t *)ctx;
+}
+
+/*
+ * Public.
+ */
+
+static void
+destroy(isc__mem_t *ctx) {
+ unsigned int i;
+
+ LOCK(&contextslock);
+ ISC_LIST_UNLINK(contexts, ctx, link);
+ totallost += ctx->inuse;
+ UNLOCK(&contextslock);
+
+ ctx->common.impmagic = 0;
+ ctx->common.magic = 0;
+
+ INSIST(ISC_LIST_EMPTY(ctx->pools));
+
+#if ISC_MEM_TRACKLINES
+ if (ISC_UNLIKELY(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);
+ free(dl);
+ ctx->malloced -= sizeof(*dl);
+ }
+ }
+
+ (ctx->memfree)(ctx->debuglist);
+ ctx->malloced -= DEBUG_TABLE_COUNT * sizeof(debuglist_t);
+ }
+#endif /* if ISC_MEM_TRACKLINES */
+
+ if (ctx->checkfree) {
+ for (i = 0; i <= ctx->max_size; i++) {
+ if (ctx->stats[i].gets != 0U) {
+ fprintf(stderr,
+ "Failing assertion due to probable "
+ "leaked memory in context %p (\"%s\") "
+ "(stats[%u].gets == %lu).\n",
+ ctx, ctx->name, i, ctx->stats[i].gets);
+#if ISC_MEM_TRACKLINES
+ print_active(ctx, stderr);
+#endif /* if ISC_MEM_TRACKLINES */
+ INSIST(ctx->stats[i].gets == 0U);
+ }
+ }
+ }
+
+ (ctx->memfree)(ctx->stats);
+ ctx->malloced -= (ctx->max_size + 1) * sizeof(struct stats);
+
+ if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) {
+ for (i = 0; i < ctx->basic_table_count; i++) {
+ (ctx->memfree)(ctx->basic_table[i]);
+ ctx->malloced -= NUM_BASIC_BLOCKS * ctx->mem_target;
+ }
+ (ctx->memfree)(ctx->freelists);
+ ctx->malloced -= ctx->max_size * sizeof(element *);
+ if (ctx->basic_table != NULL) {
+ (ctx->memfree)(ctx->basic_table);
+ ctx->malloced -= ctx->basic_table_size *
+ sizeof(unsigned char *);
+ }
+ }
+
+ isc_mutex_destroy(&ctx->lock);
+
+ ctx->malloced -= sizeof(*ctx);
+ if (ctx->checkfree) {
+ INSIST(ctx->malloced == 0);
+ }
+ (ctx->memfree)(ctx);
+}
+
+void
+isc_mem_attach(isc_mem_t *source0, isc_mem_t **targetp) {
+ REQUIRE(VALID_CONTEXT(source0));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc__mem_t *source = (isc__mem_t *)source0;
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = (isc_mem_t *)source;
+}
+
+void
+isc_mem_detach(isc_mem_t **ctxp) {
+ REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp));
+
+ isc__mem_t *ctx = (isc__mem_t *)*ctxp;
+ *ctxp = NULL;
+
+ if (isc_refcount_decrement(&ctx->references) == 1) {
+ isc_refcount_destroy(&ctx->references);
+ 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 FLARG) {
+ REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp));
+ REQUIRE(ptr != NULL);
+
+ isc__mem_t *ctx = (isc__mem_t *)*ctxp;
+ *ctxp = NULL;
+
+ if (ISC_UNLIKELY((isc_mem_debugging &
+ (ISC_MEM_DEBUGSIZE | ISC_MEM_DEBUGCTX)) != 0))
+ {
+ if ((isc_mem_debugging & ISC_MEM_DEBUGSIZE) != 0) {
+ size_info *si = &(((size_info *)ptr)[-1]);
+ size_t oldsize = si->u.size - ALIGNMENT_SIZE;
+ if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) {
+ oldsize -= ALIGNMENT_SIZE;
+ }
+ INSIST(oldsize == size);
+ }
+ isc__mem_free((isc_mem_t *)ctx, ptr FLARG_PASS);
+
+ goto destroy;
+ }
+
+ MCTXLOCK(ctx);
+
+ DELETE_TRACE(ctx, ptr, size, file, line);
+
+ if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) {
+ mem_putunlocked(ctx, ptr, size);
+ } else {
+ mem_putstats(ctx, ptr, size);
+ mem_put(ctx, ptr, size);
+ }
+ MCTXUNLOCK(ctx);
+
+destroy:
+ if (isc_refcount_decrement(&ctx->references) == 1) {
+ isc_refcount_destroy(&ctx->references);
+ destroy(ctx);
+ }
+}
+
+void
+isc_mem_destroy(isc_mem_t **ctxp) {
+ /*
+ * This routine provides legacy support for callers who use mctxs
+ * without attaching/detaching.
+ */
+
+ REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp));
+
+ isc__mem_t *ctx = (isc__mem_t *)*ctxp;
+
+#if ISC_MEM_TRACKLINES
+ 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;
+}
+
+void *
+isc___mem_get(isc_mem_t *ctx0, size_t size FLARG) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ void *ptr;
+ bool call_water = false;
+
+ if (ISC_UNLIKELY((isc_mem_debugging &
+ (ISC_MEM_DEBUGSIZE | ISC_MEM_DEBUGCTX)) != 0))
+ {
+ return (isc__mem_allocate(ctx0, size FLARG_PASS));
+ }
+
+ if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) {
+ MCTXLOCK(ctx);
+ ptr = mem_getunlocked(ctx, size);
+ } else {
+ ptr = mem_get(ctx, size);
+ MCTXLOCK(ctx);
+ if (ptr != NULL) {
+ mem_getstats(ctx, size);
+ }
+ }
+
+ ADD_TRACE(ctx, ptr, size, file, line);
+
+ if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water) {
+ ctx->is_overmem = true;
+ if (!ctx->hi_called) {
+ call_water = true;
+ }
+ }
+ if (ctx->inuse > ctx->maxinuse) {
+ ctx->maxinuse = ctx->inuse;
+ if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water &&
+ (isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0)
+ {
+ fprintf(stderr, "maxinuse = %lu\n",
+ (unsigned long)ctx->inuse);
+ }
+ }
+ MCTXUNLOCK(ctx);
+
+ if (call_water && (ctx->water != NULL)) {
+ (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER);
+ }
+
+ return (ptr);
+}
+
+void
+isc___mem_put(isc_mem_t *ctx0, void *ptr, size_t size FLARG) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+ REQUIRE(ptr != NULL);
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ bool call_water = false;
+ size_info *si;
+ size_t oldsize;
+
+ if (ISC_UNLIKELY((isc_mem_debugging &
+ (ISC_MEM_DEBUGSIZE | ISC_MEM_DEBUGCTX)) != 0))
+ {
+ if ((isc_mem_debugging & ISC_MEM_DEBUGSIZE) != 0) {
+ si = &(((size_info *)ptr)[-1]);
+ oldsize = si->u.size - ALIGNMENT_SIZE;
+ if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) {
+ oldsize -= ALIGNMENT_SIZE;
+ }
+ INSIST(oldsize == size);
+ }
+ isc__mem_free((isc_mem_t *)ctx, ptr FLARG_PASS);
+ return;
+ }
+
+ MCTXLOCK(ctx);
+
+ DELETE_TRACE(ctx, ptr, size, file, line);
+
+ if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) {
+ mem_putunlocked(ctx, ptr, size);
+ } else {
+ mem_putstats(ctx, ptr, size);
+ mem_put(ctx, ptr, size);
+ }
+
+ /*
+ * The check against ctx->lo_water == 0 is for the condition
+ * when the context was pushed over hi_water but then had
+ * isc_mem_setwater() called with 0 for hi_water and lo_water.
+ */
+ if ((ctx->inuse < ctx->lo_water) || (ctx->lo_water == 0U)) {
+ ctx->is_overmem = false;
+ if (ctx->hi_called) {
+ call_water = true;
+ }
+ }
+
+ MCTXUNLOCK(ctx);
+
+ if (call_water && (ctx->water != NULL)) {
+ (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER);
+ }
+}
+
+void
+isc_mem_waterack(isc_mem_t *ctx0, int flag) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+
+ MCTXLOCK(ctx);
+ if (flag == ISC_MEM_LOWATER) {
+ ctx->hi_called = false;
+ } else if (flag == ISC_MEM_HIWATER) {
+ ctx->hi_called = true;
+ }
+ MCTXUNLOCK(ctx);
+}
+
+#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;
+
+ fputs("Dump of all outstanding memory allocations:\n", out);
+ 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) {
+ fputs("\tNone.\n", out);
+ }
+ }
+}
+#endif /* if ISC_MEM_TRACKLINES */
+
+/*
+ * Print the stats[] on the stream "out" with suitable formatting.
+ */
+void
+isc_mem_stats(isc_mem_t *ctx0, FILE *out) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ size_t i;
+ const struct stats *s;
+ const isc__mempool_t *pool;
+
+ MCTXLOCK(ctx);
+
+ for (i = 0; i <= ctx->max_size; i++) {
+ s = &ctx->stats[i];
+
+ if (s->totalgets == 0U && s->gets == 0U) {
+ continue;
+ }
+ fprintf(out, "%s%5lu: %11lu gets, %11lu rem",
+ (i == ctx->max_size) ? ">=" : " ", (unsigned long)i,
+ s->totalgets, s->gets);
+ if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0 &&
+ (s->blocks != 0U || s->freefrags != 0U))
+ {
+ fprintf(out, " (%lu bl, %lu ff)", s->blocks,
+ s->freefrags);
+ }
+ 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) {
+ fputs("[Pool statistics]\n", out);
+ fprintf(out, "%15s %10s %10s %10s %10s %10s %10s %10s %1s\n",
+ "name", "size", "maxalloc", "allocated", "freecount",
+ "freemax", "fillcount", "gets", "L");
+ }
+ while (pool != NULL) {
+ fprintf(out, "%15s %10lu %10u %10u %10u %10u %10u %10u %s\n",
+#if ISC_MEMPOOL_NAMES
+ pool->name,
+#else /* if ISC_MEMPOOL_NAMES */
+ "(not tracked)",
+#endif /* if ISC_MEMPOOL_NAMES */
+ (unsigned long)pool->size, pool->maxalloc,
+ 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);
+}
+
+/*
+ * Replacements for malloc() and free() -- they implicitly remember the
+ * size of the object allocated (with some additional overhead).
+ */
+
+static void *
+mem_allocateunlocked(isc_mem_t *ctx0, size_t size) {
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ size_info *si;
+
+ size += ALIGNMENT_SIZE;
+ if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0)) {
+ size += ALIGNMENT_SIZE;
+ }
+
+ if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) {
+ si = mem_getunlocked(ctx, size);
+ } else {
+ si = mem_get(ctx, size);
+ }
+
+ if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0)) {
+ si->u.ctx = ctx;
+ si++;
+ }
+ si->u.size = size;
+ return (&si[1]);
+}
+
+void *
+isc___mem_allocate(isc_mem_t *ctx0, size_t size FLARG) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ size_info *si;
+ bool call_water = false;
+
+ MCTXLOCK(ctx);
+ si = mem_allocateunlocked((isc_mem_t *)ctx, size);
+ if (((ctx->flags & ISC_MEMFLAG_INTERNAL) == 0)) {
+ mem_getstats(ctx, si[-1].u.size);
+ }
+
+ ADD_TRACE(ctx, si, si[-1].u.size, file, line);
+ if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water &&
+ !ctx->is_overmem)
+ {
+ ctx->is_overmem = true;
+ }
+
+ if (ctx->hi_water != 0U && !ctx->hi_called &&
+ ctx->inuse > ctx->hi_water)
+ {
+ ctx->hi_called = true;
+ call_water = true;
+ }
+ if (ctx->inuse > ctx->maxinuse) {
+ ctx->maxinuse = ctx->inuse;
+ if (ISC_UNLIKELY(ctx->hi_water != 0U &&
+ ctx->inuse > ctx->hi_water &&
+ (isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0))
+ {
+ fprintf(stderr, "maxinuse = %lu\n",
+ (unsigned long)ctx->inuse);
+ }
+ }
+ MCTXUNLOCK(ctx);
+
+ if (call_water) {
+ (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER);
+ }
+
+ return (si);
+}
+
+void *
+isc___mem_reallocate(isc_mem_t *ctx0, void *ptr, size_t size FLARG) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ void *new_ptr = NULL;
+ size_t oldsize, copysize;
+
+ /*
+ * This function emulates the realloc(3) standard library function:
+ * - if size > 0, allocate new memory; and if ptr is non NULL, copy
+ * as much of the old contents to the new buffer and free the old one.
+ * Note that when allocation fails the original pointer is intact;
+ * the caller must free it.
+ * - if size is 0 and ptr is non NULL, simply free the given ptr.
+ * - this function returns:
+ * pointer to the newly allocated memory, or
+ * NULL if allocation fails or doesn't happen.
+ */
+ if (size > 0U) {
+ new_ptr = isc__mem_allocate(ctx0, size FLARG_PASS);
+ if (new_ptr != NULL && ptr != NULL) {
+ oldsize = (((size_info *)ptr)[-1]).u.size;
+ INSIST(oldsize >= ALIGNMENT_SIZE);
+ oldsize -= ALIGNMENT_SIZE;
+ if (ISC_UNLIKELY((isc_mem_debugging &
+ ISC_MEM_DEBUGCTX) != 0))
+ {
+ INSIST(oldsize >= ALIGNMENT_SIZE);
+ oldsize -= ALIGNMENT_SIZE;
+ }
+ copysize = (oldsize > size) ? size : oldsize;
+ memmove(new_ptr, ptr, copysize);
+ isc__mem_free(ctx0, ptr FLARG_PASS);
+ }
+ } else if (ptr != NULL) {
+ isc__mem_free(ctx0, ptr FLARG_PASS);
+ }
+
+ return (new_ptr);
+}
+
+void
+isc___mem_free(isc_mem_t *ctx0, void *ptr FLARG) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+ REQUIRE(ptr != NULL);
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ size_info *si;
+ size_t size;
+ bool call_water = false;
+
+ if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0)) {
+ si = &(((size_info *)ptr)[-2]);
+ REQUIRE(si->u.ctx == ctx);
+ size = si[1].u.size;
+ } else {
+ si = &(((size_info *)ptr)[-1]);
+ size = si->u.size;
+ }
+
+ MCTXLOCK(ctx);
+
+ DELETE_TRACE(ctx, ptr, size, file, line);
+
+ if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) {
+ mem_putunlocked(ctx, si, size);
+ } else {
+ mem_putstats(ctx, si, size);
+ mem_put(ctx, si, size);
+ }
+
+ /*
+ * The check against ctx->lo_water == 0 is for the condition
+ * when the context was pushed over hi_water but then had
+ * isc_mem_setwater() called with 0 for hi_water and lo_water.
+ */
+ if (ctx->is_overmem &&
+ (ctx->inuse < ctx->lo_water || ctx->lo_water == 0U))
+ {
+ ctx->is_overmem = false;
+ }
+
+ if (ctx->hi_called &&
+ (ctx->inuse < ctx->lo_water || ctx->lo_water == 0U))
+ {
+ ctx->hi_called = false;
+
+ if (ctx->water != NULL) {
+ call_water = true;
+ }
+ }
+ MCTXUNLOCK(ctx);
+
+ if (call_water) {
+ (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER);
+ }
+}
+
+/*
+ * Other useful things.
+ */
+
+char *
+isc___mem_strdup(isc_mem_t *mctx0, const char *s FLARG) {
+ REQUIRE(VALID_CONTEXT(mctx0));
+ REQUIRE(s != NULL);
+
+ isc__mem_t *mctx = (isc__mem_t *)mctx0;
+ size_t len;
+ char *ns;
+
+ len = strlen(s) + 1;
+
+ ns = isc__mem_allocate((isc_mem_t *)mctx, len FLARG_PASS);
+
+ if (ns != NULL) {
+ strlcpy(ns, s, len);
+ }
+
+ return (ns);
+}
+
+char *
+isc___mem_strndup(isc_mem_t *mctx0, const char *s, size_t size FLARG) {
+ REQUIRE(VALID_CONTEXT(mctx0));
+ REQUIRE(s != NULL);
+
+ isc__mem_t *mctx = (isc__mem_t *)mctx0;
+ size_t len;
+ char *ns;
+
+ len = strlen(s) + 1;
+ if (len > size) {
+ len = size;
+ }
+
+ ns = isc__mem_allocate((isc_mem_t *)mctx, len FLARG_PASS);
+
+ if (ns != NULL) {
+ strlcpy(ns, s, len);
+ }
+
+ return (ns);
+}
+
+void
+isc_mem_setdestroycheck(isc_mem_t *ctx0, bool flag) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+
+ MCTXLOCK(ctx);
+
+ ctx->checkfree = flag;
+
+ MCTXUNLOCK(ctx);
+}
+
+size_t
+isc_mem_inuse(isc_mem_t *ctx0) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ size_t inuse;
+
+ MCTXLOCK(ctx);
+
+ inuse = ctx->inuse;
+
+ MCTXUNLOCK(ctx);
+
+ return (inuse);
+}
+
+size_t
+isc_mem_maxinuse(isc_mem_t *ctx0) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ size_t maxinuse;
+
+ MCTXLOCK(ctx);
+
+ maxinuse = ctx->maxinuse;
+
+ MCTXUNLOCK(ctx);
+
+ return (maxinuse);
+}
+
+size_t
+isc_mem_total(isc_mem_t *ctx0) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ size_t total;
+
+ MCTXLOCK(ctx);
+
+ total = ctx->total;
+
+ MCTXUNLOCK(ctx);
+
+ return (total);
+}
+
+void
+isc_mem_setwater(isc_mem_t *ctx0, isc_mem_water_t water, void *water_arg,
+ size_t hiwater, size_t lowater) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+ REQUIRE(hiwater >= lowater);
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ bool callwater = false;
+ isc_mem_water_t oldwater;
+ void *oldwater_arg;
+
+ MCTXLOCK(ctx);
+ oldwater = ctx->water;
+ oldwater_arg = ctx->water_arg;
+ if (water == NULL) {
+ callwater = ctx->hi_called;
+ ctx->water = NULL;
+ ctx->water_arg = NULL;
+ ctx->hi_water = 0;
+ ctx->lo_water = 0;
+ } else {
+ if (ctx->hi_called &&
+ (ctx->water != water || ctx->water_arg != water_arg ||
+ ctx->inuse < lowater || lowater == 0U))
+ {
+ callwater = true;
+ }
+ ctx->water = water;
+ ctx->water_arg = water_arg;
+ ctx->hi_water = hiwater;
+ ctx->lo_water = lowater;
+ }
+ MCTXUNLOCK(ctx);
+
+ if (callwater && oldwater != NULL) {
+ (oldwater)(oldwater_arg, ISC_MEM_LOWATER);
+ }
+}
+
+ISC_NO_SANITIZE_THREAD bool
+isc_mem_isovermem(isc_mem_t *ctx0) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+
+ /*
+ * We don't bother to lock the context because 100% accuracy isn't
+ * necessary (and even if we locked the context the returned value
+ * could be different from the actual state when it's used anyway)
+ */
+ return (ctx->is_overmem);
+}
+
+void
+isc_mem_setname(isc_mem_t *ctx0, const char *name, void *tag) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+
+ LOCK(&ctx->lock);
+ strlcpy(ctx->name, name, sizeof(ctx->name));
+ ctx->tag = tag;
+ UNLOCK(&ctx->lock);
+}
+
+const char *
+isc_mem_getname(isc_mem_t *ctx0) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+
+ if (ctx->name[0] == 0) {
+ return ("");
+ }
+
+ return (ctx->name);
+}
+
+void *
+isc_mem_gettag(isc_mem_t *ctx0) {
+ REQUIRE(VALID_CONTEXT(ctx0));
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+
+ return (ctx->tag);
+}
+
+/*
+ * Memory pool stuff
+ */
+
+void
+isc_mempool_create(isc_mem_t *mctx0, size_t size, isc_mempool_t **mpctxp) {
+ REQUIRE(VALID_CONTEXT(mctx0));
+ REQUIRE(size > 0U);
+ REQUIRE(mpctxp != NULL && *mpctxp == NULL);
+
+ isc__mem_t *mctx = (isc__mem_t *)mctx0;
+ isc__mempool_t *mpctx;
+
+ /*
+ * Allocate space for this pool, initialize values, and if all works
+ * well, attach to the memory context.
+ */
+ mpctx = isc_mem_get((isc_mem_t *)mctx, sizeof(isc__mempool_t));
+
+ mpctx->common.impmagic = MEMPOOL_MAGIC;
+ mpctx->common.magic = ISCAPI_MPOOL_MAGIC;
+ mpctx->mctx = NULL;
+ isc_mem_attach((isc_mem_t *)mctx, (isc_mem_t **)&mpctx->mctx);
+ /*
+ * Mempools are stored as a linked list of element.
+ */
+ if (size < sizeof(element)) {
+ size = sizeof(element);
+ }
+ mpctx->size = size;
+ mpctx->maxalloc = UINT_MAX;
+ mpctx->allocated = 0;
+ mpctx->freecount = 0;
+ mpctx->freemax = 1;
+ mpctx->fillcount = 1;
+ mpctx->gets = 0;
+#if ISC_MEMPOOL_NAMES
+ mpctx->name[0] = 0;
+#endif /* if ISC_MEMPOOL_NAMES */
+ mpctx->items = NULL;
+
+ *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 *mpctx0, const char *name) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+ REQUIRE(name != NULL);
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+
+#if ISC_MEMPOOL_NAMES
+ strlcpy(mpctx->name, name, sizeof(mpctx->name));
+#else /* if ISC_MEMPOOL_NAMES */
+ UNUSED(mpctx);
+ UNUSED(name);
+#endif /* if ISC_MEMPOOL_NAMES */
+}
+
+void
+isc_mempool_destroy(isc_mempool_t **mpctxp) {
+ REQUIRE(mpctxp != NULL);
+ REQUIRE(VALID_MEMPOOL(*mpctxp));
+
+ isc__mempool_t *mpctx;
+ isc__mem_t *mctx;
+ element *item;
+
+ mpctx = (isc__mempool_t *)*mpctxp;
+#if ISC_MEMPOOL_NAMES
+ if (mpctx->allocated > 0) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_mempool_destroy(): mempool %s "
+ "leaked memory",
+ mpctx->name);
+ }
+#endif /* if ISC_MEMPOOL_NAMES */
+ REQUIRE(mpctx->allocated == 0);
+
+ mctx = mpctx->mctx;
+
+ /*
+ * Return any items on the free list
+ */
+ MCTXLOCK(mctx);
+ 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);
+ }
+ MCTXUNLOCK(mctx);
+
+ /*
+ * Remove our linked list entry from the memory context.
+ */
+ MCTXLOCK(mctx);
+ ISC_LIST_UNLINK(mctx->pools, mpctx, link);
+ mctx->poolcnt--;
+ MCTXUNLOCK(mctx);
+
+ mpctx->common.impmagic = 0;
+ mpctx->common.magic = 0;
+
+ isc_mem_putanddetach((isc_mem_t **)&mpctx->mctx, mpctx,
+ sizeof(isc__mempool_t));
+
+ *mpctxp = NULL;
+}
+
+#if __SANITIZE_ADDRESS__
+void *
+isc__mempool_get(isc_mempool_t *mpctx0 FLARG) {
+ void *item = NULL;
+
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+ isc_mem_t *mctx = (isc_mem_t *)mpctx->mctx;
+
+ /*
+ * Don't let the caller go over quota
+ */
+ if (ISC_UNLIKELY(mpctx->allocated >= mpctx->maxalloc)) {
+ goto out;
+ }
+
+ item = isc__mem_get(mctx, mpctx->size FLARG_PASS);
+ mpctx->gets++;
+ mpctx->allocated++;
+
+out:
+ return (item);
+}
+
+void
+isc__mempool_put(isc_mempool_t *mpctx0, void *mem FLARG) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+ isc_mem_t *mctx = (isc_mem_t *)mpctx->mctx;
+
+ REQUIRE(mem != NULL);
+
+ INSIST(mpctx->allocated > 0);
+ mpctx->allocated--;
+
+ isc__mem_put(mctx, mem, mpctx->size FLARG_PASS);
+}
+
+#else /* __SANITIZE_ADDRESS__ */
+void *
+isc__mempool_get(isc_mempool_t *mpctx0 FLARG) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+ element *item;
+ isc__mem_t *mctx;
+ unsigned int i;
+
+ mctx = mpctx->mctx;
+
+ /*
+ * Don't let the caller go over quota
+ */
+ if (ISC_UNLIKELY(mpctx->allocated >= mpctx->maxalloc)) {
+ item = NULL;
+ goto out;
+ }
+
+ if (ISC_UNLIKELY(mpctx->items == NULL)) {
+ /*
+ * We need to dip into the well. Lock the memory context
+ * here and fill up our free list.
+ */
+ MCTXLOCK(mctx);
+ for (i = 0; i < mpctx->fillcount; i++) {
+ item = mem_get(mctx, mpctx->size);
+ mem_getstats(mctx, mpctx->size);
+ item->next = mpctx->items;
+ mpctx->items = item;
+ mpctx->freecount++;
+ }
+ MCTXUNLOCK(mctx);
+ }
+
+ /*
+ * If we didn't get any items, return NULL.
+ */
+ item = mpctx->items;
+ if (ISC_UNLIKELY(item == NULL)) {
+ goto out;
+ }
+
+ mpctx->items = item->next;
+ INSIST(mpctx->freecount > 0);
+ mpctx->freecount--;
+ mpctx->gets++;
+ mpctx->allocated++;
+
+out:
+#if ISC_MEM_TRACKLINES
+ if (ISC_UNLIKELY(((isc_mem_debugging & TRACE_OR_RECORD) != 0) &&
+ item != NULL))
+ {
+ MCTXLOCK(mctx);
+ ADD_TRACE(mctx, item, mpctx->size, file, line);
+ MCTXUNLOCK(mctx);
+ }
+#endif /* ISC_MEM_TRACKLINES */
+
+ return (item);
+}
+
+/* coverity[+free : arg-1] */
+void
+isc__mempool_put(isc_mempool_t *mpctx0, void *mem FLARG) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+ REQUIRE(mem != NULL);
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+ isc__mem_t *mctx = mpctx->mctx;
+ element *item;
+
+ INSIST(mpctx->allocated > 0);
+ mpctx->allocated--;
+
+#if ISC_MEM_TRACKLINES
+ if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0)) {
+ MCTXLOCK(mctx);
+ DELETE_TRACE(mctx, mem, mpctx->size, file, line);
+ MCTXUNLOCK(mctx);
+ }
+#endif /* ISC_MEM_TRACKLINES */
+
+ /*
+ * If our free list is full, return this to the mctx directly.
+ */
+ if (mpctx->freecount >= mpctx->freemax) {
+ MCTXLOCK(mctx);
+ mem_putstats(mctx, mem, mpctx->size);
+ mem_put(mctx, mem, mpctx->size);
+ MCTXUNLOCK(mctx);
+ return;
+ }
+
+ /*
+ * Otherwise, attach it to our free list and bump the counter.
+ */
+ mpctx->freecount++;
+ item = (element *)mem;
+ item->next = mpctx->items;
+ mpctx->items = item;
+}
+
+#endif /* __SANITIZE_ADDRESS__ */
+
+/*
+ * Quotas
+ */
+
+void
+isc_mempool_setfreemax(isc_mempool_t *mpctx0, unsigned int limit) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+
+ mpctx->freemax = limit;
+}
+
+unsigned int
+isc_mempool_getfreemax(isc_mempool_t *mpctx0) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+
+ return (mpctx->freemax);
+}
+
+unsigned int
+isc_mempool_getfreecount(isc_mempool_t *mpctx0) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+
+ return (mpctx->freecount);
+}
+
+void
+isc_mempool_setmaxalloc(isc_mempool_t *mpctx0, unsigned int limit) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+ REQUIRE(limit > 0);
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+
+ mpctx->maxalloc = limit;
+}
+
+unsigned int
+isc_mempool_getmaxalloc(isc_mempool_t *mpctx0) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+
+ return (mpctx->maxalloc);
+}
+
+unsigned int
+isc_mempool_getallocated(isc_mempool_t *mpctx0) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+
+ return (mpctx->allocated);
+}
+
+void
+isc_mempool_setfillcount(isc_mempool_t *mpctx0, unsigned int limit) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+ REQUIRE(limit > 0);
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+
+ mpctx->fillcount = limit;
+}
+
+unsigned int
+isc_mempool_getfillcount(isc_mempool_t *mpctx0) {
+ REQUIRE(VALID_MEMPOOL(mpctx0));
+
+ isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0;
+
+ return (mpctx->fillcount);
+}
+
+/*
+ * Requires contextslock to be held by caller.
+ */
+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);
+}
+
+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_UNLIKELY((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 *ctx0) {
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+ return (isc_refcount_current(&ctx->references));
+}
+
+typedef struct summarystat {
+ uint64_t total;
+ uint64_t inuse;
+ uint64_t malloced;
+ uint64_t blocksize;
+ 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) +
+ (ctx->max_size + 1) * sizeof(struct stats) +
+ ctx->max_size * sizeof(element *) +
+ ctx->basic_table_count * sizeof(char *);
+#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 += ctx->total;
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "total"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ (uint64_t)ctx->total));
+ TRY0(xmlTextWriterEndElement(writer)); /* total */
+
+ summary->inuse += ctx->inuse;
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "inuse"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ (uint64_t)ctx->inuse));
+ TRY0(xmlTextWriterEndElement(writer)); /* inuse */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxinuse"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ (uint64_t)ctx->maxinuse));
+ TRY0(xmlTextWriterEndElement(writer)); /* maxinuse */
+
+ summary->malloced += ctx->malloced;
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "malloced"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ (uint64_t)ctx->malloced));
+ TRY0(xmlTextWriterEndElement(writer)); /* malloced */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxmalloced"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ (uint64_t)ctx->maxmalloced));
+ TRY0(xmlTextWriterEndElement(writer)); /* maxmalloced */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "blocksize"));
+ if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) {
+ summary->blocksize += ctx->basic_table_count *
+ NUM_BASIC_BLOCKS * ctx->mem_target;
+ TRY0(xmlTextWriterWriteFormatString(
+ writer, "%" PRIu64 "",
+ (uint64_t)ctx->basic_table_count * NUM_BASIC_BLOCKS *
+ ctx->mem_target));
+ } else {
+ TRY0(xmlTextWriterWriteFormatString(writer, "%s", "-"));
+ }
+ TRY0(xmlTextWriterEndElement(writer)); /* blocksize */
+
+ 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)ctx->hi_water));
+ TRY0(xmlTextWriterEndElement(writer)); /* hiwater */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "lowater"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ (uint64_t)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;
+ uint64_t lost;
+ int xmlrc;
+ xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0;
+
+ memset(&summary, 0, sizeof(summary));
+
+ 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 "BlockSize"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ summary.blocksize));
+ TRY0(xmlTextWriterEndElement(writer)); /* BlockSize */
+
+ 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) +
+ (ctx->max_size + 1) * sizeof(struct stats) +
+ ctx->max_size * sizeof(element *) +
+ ctx->basic_table_count * sizeof(char *);
+ summary->total += ctx->total;
+ summary->inuse += ctx->inuse;
+ summary->malloced += ctx->malloced;
+ if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) {
+ summary->blocksize += ctx->basic_table_count *
+ NUM_BASIC_BLOCKS * ctx->mem_target;
+ }
+#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(ctx->total);
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "total", obj);
+
+ obj = json_object_new_int64(ctx->inuse);
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "inuse", obj);
+
+ obj = json_object_new_int64(ctx->maxinuse);
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "maxinuse", obj);
+
+ obj = json_object_new_int64(ctx->malloced);
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "malloced", obj);
+
+ obj = json_object_new_int64(ctx->maxmalloced);
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "maxmalloced", obj);
+
+ if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) {
+ uint64_t blocksize;
+ blocksize = ctx->basic_table_count * NUM_BASIC_BLOCKS *
+ ctx->mem_target;
+ obj = json_object_new_int64(blocksize);
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "blocksize", 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(ctx->hi_water);
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "hiwater", obj);
+
+ obj = json_object_new_int64(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;
+ uint64_t lost;
+ json_object *ctxarray, *obj;
+ json_object *memobj = (json_object *)memobj0;
+
+ memset(&summary, 0, sizeof(summary));
+
+ 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.blocksize);
+ CHECKMEM(obj);
+ json_object_object_add(memobj, "BlockSize", 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) {
+ mem_create(mctxp, isc_mem_defaultflags);
+}
+
+void *
+isc__mem_get(isc_mem_t *mctx, size_t size FLARG) {
+ REQUIRE(ISCAPI_MCTX_VALID(mctx));
+
+ return (mctx->methods->memget(mctx, size FLARG_PASS));
+}
+
+void
+isc__mem_put(isc_mem_t *mctx, void *ptr, size_t size FLARG) {
+ REQUIRE(ISCAPI_MCTX_VALID(mctx));
+
+ mctx->methods->memput(mctx, ptr, size FLARG_PASS);
+}
+
+void
+isc__mem_putanddetach(isc_mem_t **mctxp, void *ptr, size_t size FLARG) {
+ REQUIRE(mctxp != NULL && ISCAPI_MCTX_VALID(*mctxp));
+
+ (*mctxp)->methods->memputanddetach(mctxp, ptr, size FLARG_PASS);
+}
+
+void *
+isc__mem_allocate(isc_mem_t *mctx, size_t size FLARG) {
+ REQUIRE(ISCAPI_MCTX_VALID(mctx));
+
+ return (mctx->methods->memallocate(mctx, size FLARG_PASS));
+}
+
+void *
+isc__mem_reallocate(isc_mem_t *mctx, void *ptr, size_t size FLARG) {
+ REQUIRE(ISCAPI_MCTX_VALID(mctx));
+
+ return (mctx->methods->memreallocate(mctx, ptr, size FLARG_PASS));
+}
+
+char *
+isc__mem_strdup(isc_mem_t *mctx, const char *s FLARG) {
+ REQUIRE(ISCAPI_MCTX_VALID(mctx));
+
+ return (mctx->methods->memstrdup(mctx, s FLARG_PASS));
+}
+
+char *
+isc__mem_strndup(isc_mem_t *mctx, const char *s, size_t size FLARG) {
+ REQUIRE(ISCAPI_MCTX_VALID(mctx));
+
+ return (mctx->methods->memstrndup(mctx, s, size FLARG_PASS));
+}
+
+void
+isc__mem_free(isc_mem_t *mctx, void *ptr FLARG) {
+ REQUIRE(ISCAPI_MCTX_VALID(mctx));
+
+ mctx->methods->memfree(mctx, ptr FLARG_PASS);
+}
+
+void
+isc__mem_printactive(isc_mem_t *ctx0, FILE *file) {
+#if ISC_MEM_TRACKLINES
+ REQUIRE(VALID_CONTEXT(ctx0));
+ REQUIRE(file != NULL);
+
+ isc__mem_t *ctx = (isc__mem_t *)ctx0;
+
+ print_active(ctx, file);
+#else /* if ISC_MEM_TRACKLINES */
+ UNUSED(ctx0);
+ 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/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/netaddr.c b/lib/isc/netaddr.c
new file mode 100644
index 0000000..39aaae6
--- /dev/null
+++ b/lib/isc/netaddr.c
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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;
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ case AF_UNIX:
+ if (strcmp(a->type.un, b->type.un) != 0) {
+ return (false);
+ }
+ break;
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ 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;
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ 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);
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ 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) {
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ 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);
+#else /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ UNUSED(netaddr);
+ UNUSED(path);
+ return (ISC_R_NOTIMPLEMENTED);
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+}
+
+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;
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ case AF_UNIX:
+ memmove(t->type.un, s->type.sunix.sun_path, sizeof(t->type.un));
+ t->zone = 0;
+ break;
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ 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/Makefile.in b/lib/isc/netmgr/Makefile.in
new file mode 100644
index 0000000..1806cb4
--- /dev/null
+++ b/lib/isc/netmgr/Makefile.in
@@ -0,0 +1,41 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+CINCLUDES = -I${srcdir}/include \
+ -I${srcdir}/../unix/include \
+ -I${srcdir}/../pthreads/include \
+ -I../include \
+ -I${srcdir}/../include \
+ -I${srcdir}/.. \
+ ${OPENSSL_CFLAGS} \
+ ${JSON_C_CFLAGS} \
+ ${LIBUV_CFLAGS} \
+ ${LIBXML2_CFLAGS}
+
+CDEFINES =
+CWARNINGS =
+
+# Alphabetically
+OBJS = netmgr.@O@ tcp.@O@ udp.@O@ \
+ tcpdns.@O@ \
+ uverr2result.@O@ uv-compat.@O@
+
+# Alphabetically
+SRCS = netmgr.c tcp.c udp.c tcpdns.c \
+ uverr2result.c uv-compat.c
+
+TARGETS = ${OBJS}
+
+@BIND9_MAKE_RULES@
diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h
new file mode 100644
index 0000000..05fde1a
--- /dev/null
+++ b/lib/isc/netmgr/netmgr-int.h
@@ -0,0 +1,1615 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/rwlock.h>
+#include <isc/sockaddr.h>
+#include <isc/stats.h>
+#include <isc/thread.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)
+
+/*%
+ * 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 connections, so there's no risk of overrun
+ * when using a buffer this size.
+ */
+#define NM_BIG_BUF ISC_NETMGR_TCP_RECVBUF_SIZE * 2
+
+/*
+ * 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)
+#elif defined(_WIN32)
+#define gettid() (uint32_t) GetCurrentThreadId()
+#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 *);
+
+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_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_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_shutdown,
+ netievent_stop,
+ netievent_pause,
+
+ netievent_connectcb,
+ netievent_readcb,
+ netievent_sendcb,
+
+ netievent_detach,
+ netievent_close,
+
+ netievent_task,
+ netievent_privilegedtask,
+
+ /*
+ * 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_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 */
+
+ 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;
+};
+
+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_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 {
+ 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 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_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;
+
+#ifdef NETMGR_TRACE
+ ISC_LIST(isc_nmsocket_t) active_sockets;
+#endif
+};
+
+typedef enum isc_nmsocket_type {
+ isc_nm_udpsocket,
+ isc_nm_udplistener, /* Aggregate of nm_udpsocks */
+ isc_nm_tcpsocket,
+ isc_nm_tcplistener,
+ isc_nm_tcpdnslistener,
+ isc_nm_tcpdnssocket,
+} isc_nmsocket_type;
+
+/*%
+ * 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.
+ */
+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
+};
+
+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;
+
+ /*%
+ * 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;
+
+ /*%
+ * 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;
+ bool accepting;
+ 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;
+
+#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.
+ */
+
+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_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_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_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.
+ */
+
+#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_incstats(isc_nm_t *mgr, isc_statscounter_t counterid);
+/*%<
+ * Increment socket-related statistics counters.
+ */
+
+void
+isc__nm_decstats(isc_nm_t *mgr, isc_statscounter_t counterid);
+/*%<
+ * 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_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).
+ */
+
+/*
+ * 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(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_REQ_TYPE(tcpconnect);
+NETIEVENT_SOCKET_REQ_TYPE(tcpsend);
+NETIEVENT_SOCKET_TYPE(tcpstartread);
+NETIEVENT_SOCKET_REQ_TYPE(udpconnect);
+
+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);
+
+/* 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(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_REQ_DECL(tcpconnect);
+NETIEVENT_SOCKET_REQ_DECL(tcpsend);
+NETIEVENT_SOCKET_REQ_DECL(udpconnect);
+
+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);
+
+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);
+
+isc_result_t
+isc__nm_tcpdns_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);
+
+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);
+
+/*%<
+ *
+ * Maximum number of simultaneous handles in flight supported for a single
+ * connected TCPDNS socket. This value was chosen arbitrarily, and may be
+ * changed in the future.
+ */
+#define STREAM_CLIENTS_PER_CONN 23
+
+#define UV_RUNTIME_CHECK(func, ret) \
+ if (ret != 0) { \
+ isc_error_fatal(__FILE__, __LINE__, "%s failed: %s\n", #func, \
+ uv_strerror(ret)); \
+ }
diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c
new file mode 100644
index 0000000..6f42ec9
--- /dev/null
+++ b/lib/isc/netmgr/netmgr.c
@@ -0,0 +1,3396 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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>
+#ifdef HAVE_LIBCTRACE
+#include <execinfo.h>
+#endif /* ifdef HAVE_LIBCTRACE */
+
+#include <isc/atomic.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/strerr.h>
+#include <isc/task.h>
+#include <isc/thread.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.
+ */
+
+#if defined(HAVE_THREAD_LOCAL)
+#include <threads.h>
+static thread_local int isc__nm_tid_v = ISC_NETMGR_TID_UNKNOWN;
+#elif defined(HAVE___THREAD)
+static __thread int isc__nm_tid_v = ISC_NETMGR_TID_UNKNOWN;
+#elif HAVE___DECLSPEC_THREAD
+__declspec(thread) int isc__nm_tid_v = ISC_NETMGR_TID_UNKNOWN;
+#endif /* if defined(HAVE_THREAD_LOCAL) */
+
+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);
+
+void
+isc__nmsocket_reset(isc_nmsocket_t *sock);
+
+/*%<
+ * 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);
+}
+
+#ifdef WIN32
+static void
+isc__nm_winsock_initialize(void) {
+ WORD wVersionRequested = MAKEWORD(2, 2);
+ WSADATA wsaData;
+ int result;
+
+ result = WSAStartup(wVersionRequested, &wsaData);
+ if (result != 0) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(result, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "WSAStartup() failed with error code %lu: %s",
+ result, strbuf);
+ }
+
+ /*
+ * Confirm that the WinSock DLL supports version 2.2. Note that if the
+ * DLL supports versions greater than 2.2 in addition to 2.2, it will
+ * still return 2.2 in wVersion since that is the version we requested.
+ */
+ if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "Unusable WinSock DLL version: %u.%u",
+ LOBYTE(wsaData.wVersion),
+ HIBYTE(wsaData.wVersion));
+ }
+}
+
+static void
+isc__nm_winsock_destroy(void) {
+ WSACleanup();
+}
+#endif /* WIN32 */
+
+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_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)
+#elif _WIN32
+#define MINIMAL_UV_VERSION UV_VERSION(1, 0, 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) {
+ isc_error_fatal(__FILE__, __LINE__,
+ "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) {
+ isc_error_fatal(__FILE__, __LINE__,
+ "libuv version too old: running with libuv %s "
+ "when compiled with libuv %s will lead to "
+ "libuv failures",
+ uv_version_string(), UV_VERSION_STRING);
+ }
+
+#ifdef WIN32
+ isc__nm_winsock_initialize();
+#endif /* WIN32 */
+
+ 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);
+#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));
+
+#ifdef WIN32
+ isc__nm_winsock_destroy();
+#endif /* WIN32 */
+}
+
+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);
+
+ REQUIRE(atomic_compare_exchange_strong(&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);
+
+ REQUIRE(atomic_compare_exchange_strong(&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));
+
+ handle->sock->write_timeout = write_timeout;
+}
+
+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);
+}
+
+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);
+
+ 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(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(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(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_REQ_DEF(tcpconnect);
+NETIEVENT_SOCKET_REQ_DEF(tcpsend);
+NETIEVENT_SOCKET_REQ_DEF(udpconnect);
+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);
+
+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));
+
+ 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;
+ }
+ if (sock->statsindex != NULL) {
+ isc__nm_decstats(sock->mgr, sock->statsindex[STATID_ACTIVE]);
+ }
+
+ 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_free(sock->mgr->mctx, sock->buf);
+ }
+
+ 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);
+#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;
+ 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);
+
+ 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);
+ REQUIRE(iface != NULL);
+
+ family = iface->type.sa.sa_family;
+
+ *sock = (isc_nmsocket_t){ .type = type,
+ .iface = *iface,
+ .fd = -1,
+ .inactivehandles = isc_astack_new(
+ mgr->mctx, ISC_NM_HANDLES_STACK_SIZE),
+ .inactivereqs = isc_astack_new(
+ mgr->mctx, ISC_NM_REQS_STACK_SIZE) };
+
+#if NETMGR_TRACE
+ sock->backtrace_size = 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:
+ if (family == AF_INET) {
+ sock->statsindex = udp4statsindex;
+ } else {
+ sock->statsindex = udp6statsindex;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_ACTIVE]);
+ break;
+ case isc_nm_tcpsocket:
+ case isc_nm_tcplistener:
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tcpdnslistener:
+ if (family == AF_INET) {
+ sock->statsindex = tcp4statsindex;
+ } else {
+ sock->statsindex = tcp6statsindex;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_ACTIVE]);
+ break;
+ default:
+ break;
+ }
+
+ isc_mutex_init(&sock->lock);
+ isc_condition_init(&sock->cond);
+ isc_condition_init(&sock->scond);
+ isc_refcount_init(&sock->references, 1);
+
+ 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);
+
+ sock->magic = NMSOCK_MAGIC;
+}
+
+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 = 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:
+ if (!atomic_load(&sock->client)) {
+ break;
+ }
+ FALLTHROUGH;
+ case isc_nm_tcpsocket:
+ 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;
+ }
+
+ 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);
+}
+
+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;
+
+ /*
+ * 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
+
+ INSIST(atomic_fetch_sub(&sock->ah, 1) > 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);
+ }
+}
+
+void
+isc__nmsocket_shutdown(isc_nmsocket_t *sock);
+
+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);
+ }
+
+ 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_allocate(sock->mgr->mctx, alloc_len);
+ sock->buf_size = alloc_len;
+ } else {
+ /* We have the buffer but it's too small */
+ sock->buf = isc_mem_reallocate(sock->mgr->mctx, sock->buf,
+ 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(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);
+
+ 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__nmsocket_timer_stop(sock);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ INSIST(atomic_compare_exchange_strong(&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));
+ UNUSED(async);
+
+ 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;
+ 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.
+ */
+
+ INSIST(atomic_compare_exchange_strong(&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(sock->reading);
+
+ if (atomic_load(&sock->client)) {
+ uv_timer_stop(timer);
+
+ 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 (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:
+ isc_nmhandle_attach(sock->statichandle, &req->handle);
+ break;
+ default:
+ if (atomic_load(&sock->client)) {
+ 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:
+ 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 (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;
+ default:
+ UNREACHABLE();
+ }
+
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ } else {
+ sock->reading = true;
+ }
+
+ return (result);
+}
+
+void
+isc__nm_stop_reading(isc_nmsocket_t *sock) {
+ int r;
+
+ if (!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:
+ r = uv_read_stop(&sock->uv_handle.stream);
+ UV_RUNTIME_CHECK(uv_read_stop, r);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ 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) ||
+ atomic_load(&sock->mgr->closing) ||
+ (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));
+ 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, or the number of queries we are
+ * processing simultaneously has reached the clients-per-connection
+ * limit. In this case we'll be called again by resume_processing()
+ * later.
+ */
+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) ||
+ ah >= STREAM_CLIENTS_PER_CONN)
+ {
+ 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) {
+ 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) {
+ 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:
+ 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;
+ default:
+ /*
+ * For any other protocol, this is a no-op.
+ */
+ return;
+ }
+}
+
+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 };
+ 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;
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+isc_nm_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ /*
+ * This is always called via callback (from accept or connect), and
+ * caller must attach to the handle, so the references always need to be
+ * at least 2.
+ */
+ REQUIRE(isc_refcount_current(&handle->references) >= 2);
+
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ default:
+ UNREACHABLE();
+ }
+}
+
+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:
+ /*
+ * This can be called from the TCP write timeout.
+ */
+ 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_udplistener:
+ case isc_nm_tcplistener:
+ case isc_nm_tcpdnslistener:
+ 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:
+ 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_nm_t *mgr, isc_statscounter_t counterid) {
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(counterid != -1);
+
+ if (mgr->stats != NULL) {
+ isc_stats_increment(mgr->stats, counterid);
+ }
+}
+
+void
+isc__nm_decstats(isc_nm_t *mgr, isc_statscounter_t counterid) {
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(counterid != -1);
+
+ if (mgr->stats != NULL) {
+ isc_stats_decrement(mgr->stats, counterid);
+ }
+}
+
+isc_result_t
+isc__nm_socket(int domain, int type, int protocol, uv_os_sock_t *sockp) {
+#ifdef WIN32
+ SOCKET sock;
+ sock = socket(domain, type, protocol);
+ if (sock == INVALID_SOCKET) {
+ char strbuf[ISC_STRERRORSIZE];
+ DWORD socket_errno = WSAGetLastError();
+ switch (socket_errno) {
+ case WSAEMFILE:
+ case WSAENOBUFS:
+ return (ISC_R_NORESOURCES);
+
+ case WSAEPROTONOSUPPORT:
+ case WSAEPFNOSUPPORT:
+ case WSAEAFNOSUPPORT:
+ return (ISC_R_FAMILYNOSUPPORT);
+ default:
+ strerror_r(socket_errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(
+ __FILE__, __LINE__,
+ "socket() failed with error code %lu: %s",
+ socket_errno, strbuf);
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+#else
+ int sock = socket(domain, type, protocol);
+ if (sock < 0) {
+ return (isc_errno_toresult(errno));
+ }
+#endif
+ *sockp = (uv_os_sock_t)sock;
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc__nm_closesocket(uv_os_sock_t sock) {
+#ifdef WIN32
+ closesocket(sock);
+#else
+ close(sock);
+#endif
+}
+
+#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);
+}
+
+#if defined(_WIN32)
+#define TIMEOUT_TYPE DWORD
+#define TIMEOUT_DIV 1000
+#define TIMEOUT_OPTNAME TCP_MAXRT
+#elif 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
+}
+
+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_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()];
+
+ 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);
+}
+
+#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");
+ 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");
+ 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" : "",
+ sock->accepting ? " accepting" : "");
+ fprintf(stderr, "Created by:\n");
+ 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..821d6c4
--- /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(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);
+
+ 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->mgr, sock->statsindex[STATID_OPENFAIL]);
+ goto done;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[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->mgr,
+ sock->statsindex[STATID_BINDFAIL]);
+ goto done;
+ }
+ }
+
+ 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->mgr,
+ sock->statsindex[STATID_CONNECTFAIL]);
+ goto done;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CONNECT]);
+
+ 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__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());
+
+ isc__nmsocket_timer_stop(sock);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ 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;
+ }
+
+ 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__nmsocket_closing(sock)) {
+ /* Socket was closed midflight by isc__nm_tcp_shutdown() */
+ 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 != 0) {
+ result = isc__nm_uverr2result(status);
+ goto error;
+ }
+
+ isc__nm_incstats(sock->mgr, sock->statsindex[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;
+ }
+
+ 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);
+
+ /* FIXME: set mss */
+
+ result = isc__nm_socket_reuse(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+#ifndef _WIN32
+ if (mgr->load_balance_sockets) {
+ result = isc__nm_socket_reuse_lb(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+#endif
+
+ 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);
+
+#ifdef _WIN32
+ UNUSED(fd);
+ csock->fd = isc__nm_tcp_lb_socket(mgr, iface->type.sa.sa_family);
+#else
+ 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);
+ }
+#endif
+ 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);
+#if defined(WIN32)
+ sock->nchildren = 1;
+#else
+ sock->nchildren = mgr->nworkers;
+#endif
+ 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;
+
+#ifndef _WIN32
+ if (!mgr->load_balance_sockets) {
+ fd = isc__nm_tcp_lb_socket(mgr, iface->type.sa.sa_family);
+ }
+#endif
+
+ 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());
+ }
+
+#ifndef _WIN32
+ if (!mgr->load_balance_sockets) {
+ isc__nm_closesocket(fd);
+ }
+#endif
+
+ 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());
+
+ /* TODO: set min mss */
+
+ 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->mgr, sock->statsindex[STATID_OPENFAIL]);
+ goto done;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPEN]);
+
+ if (sa_family == AF_INET6) {
+ flags = UV_TCP_IPV6ONLY;
+ }
+
+#ifdef _WIN32
+ r = isc_uv_tcp_freebind(&sock->uv_handle.tcp, &sock->iface.type.sa,
+ flags);
+ if (r < 0) {
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]);
+ goto done;
+ }
+#else
+ 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->mgr,
+ sock->statsindex[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->mgr, 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;
+ }
+ }
+#endif
+
+ /*
+ * 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->mgr, sock->statsindex[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->mgr,
+ ssock->statsindex[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);
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(!sock->recv_read);
+
+ 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) {
+ 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)) {
+ 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(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->mgr,
+ sock->statsindex[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 (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;
+ 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;
+ }
+
+ csock->accepting = false;
+
+ isc__nm_incstats(csock->mgr, csock->statsindex[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->mgr, sock->statsindex[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->mgr, sock->statsindex[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->mgr, sock->statsindex[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->mgr, sock->statsindex[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 (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) {
+ isc__nm_tcp_failed_read_cb(sock, ISC_R_CANCELED);
+ 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..bd593eb
--- /dev/null
+++ b/lib/isc/netmgr/tcpdns.c
@@ -0,0 +1,1505 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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_CANCELED;
+ goto error;
+ }
+
+ r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd);
+ if (r != 0) {
+ isc__nm_closesocket(sock->fd);
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPENFAIL]);
+ goto done;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[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->mgr,
+ sock->statsindex[STATID_BINDFAIL]);
+ goto done;
+ }
+ }
+
+ 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->mgr,
+ sock->statsindex[STATID_CONNECTFAIL]);
+ goto done;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CONNECT]);
+
+ 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__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());
+
+ isc__nmsocket_timer_stop(sock);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ 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;
+ }
+
+ if (isc__nmsocket_closing(sock)) {
+ /* Socket was closed midflight by isc__nm_tcpdns_shutdown() */
+ 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 != 0) {
+ result = isc__nm_uverr2result(status);
+ goto error;
+ }
+
+ isc__nm_incstats(sock->mgr, sock->statsindex[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;
+ }
+
+ /* 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);
+
+ /* FIXME: set mss */
+
+ result = isc__nm_socket_reuse(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+#ifndef _WIN32
+ if (mgr->load_balance_sockets) {
+ result = isc__nm_socket_reuse_lb(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+#endif
+
+ 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);
+
+#ifdef _WIN32
+ UNUSED(fd);
+ csock->fd = isc__nm_tcpdns_lb_socket(mgr, iface->type.sa.sa_family);
+#else
+ 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);
+ }
+#endif
+ 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);
+#if defined(WIN32)
+ sock->nchildren = 1;
+#else
+ sock->nchildren = mgr->nworkers;
+#endif
+ 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;
+
+#ifndef _WIN32
+ if (!mgr->load_balance_sockets) {
+ fd = isc__nm_tcpdns_lb_socket(mgr, iface->type.sa.sa_family);
+ }
+#endif
+
+ 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());
+ }
+
+#ifndef _WIN32
+ if (!mgr->load_balance_sockets) {
+ isc__nm_closesocket(fd);
+ }
+#endif
+
+ 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());
+
+ /* TODO: set min mss */
+
+ 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->mgr, sock->statsindex[STATID_OPENFAIL]);
+ goto done;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPEN]);
+
+ if (sa_family == AF_INET6) {
+ flags = UV_TCP_IPV6ONLY;
+ }
+
+#ifdef _WIN32
+ r = isc_uv_tcp_freebind(&sock->uv_handle.tcp, &sock->iface.type.sa,
+ flags);
+ if (r < 0) {
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]);
+ goto done;
+ }
+#else
+ 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->mgr,
+ sock->statsindex[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->mgr, 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;
+ }
+ }
+#endif
+
+ /*
+ * 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->mgr, sock->statsindex[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->mgr,
+ ssock->statsindex[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);
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(!sock->recv_read);
+
+ 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) {
+ 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);
+ }
+
+ req = isc__nm_get_read_req(sock, NULL);
+ REQUIRE(VALID_UVREQ(req));
+
+ /*
+ * We need to launch the resume_processing after the buffer has
+ * been consumed, thus we need to delay the 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;
+
+ /*
+ * The assertion failure here means that there's a errnoneous extra
+ * nmhandle detach happening in the callback and resume_processing gets
+ * called while we are still 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(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->mgr,
+ sock->statsindex[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;
+ 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;
+ }
+
+ csock->accepting = false;
+
+ isc__nm_incstats(csock->mgr, csock->statsindex[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) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ isc_nmsocket_t *sock = handle->sock;
+ isc__netievent_tcpdnssend_t *ievent = NULL;
+ isc__nm_uvreq_t *uvreq = NULL;
+
+ 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->mgr, sock->statsindex[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__netievent_tcpdnssend_t *ievent =
+ (isc__netievent_tcpdnssend_t *)ev0;
+
+ REQUIRE(VALID_UVREQ(ievent->req));
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(ievent->sock->type == isc_nm_tcpdnssocket);
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+
+ isc_result_t result;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *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 } };
+ int nbufs = 2;
+ int r;
+
+ UNUSED(worker);
+
+ 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:
+ if (result != ISC_R_SUCCESS) {
+ isc__nm_incstats(sock->mgr, sock->statsindex[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->mgr, sock->statsindex[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->mgr, sock->statsindex[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 (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) {
+ 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);
+}
+
+void
+isc_nm_tcpdns_sequential(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_tcpdnssocket);
+
+ sock = handle->sock;
+
+ /*
+ * 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 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);
+}
diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c
new file mode 100644
index 0000000..00f9d40
--- /dev/null
+++ b/lib/isc/netmgr/udp.c
@@ -0,0 +1,1211 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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"
+
+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);
+
+ result = isc__nm_socket_reuse(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+#ifndef _WIN32
+ if (mgr->load_balance_sockets) {
+ result = isc__nm_socket_reuse_lb(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+#endif
+
+ 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;
+ csock->reading = true;
+ csock->recv_cb = sock->recv_cb;
+ csock->recv_cbarg = sock->recv_cbarg;
+ csock->extrahandlesize = sock->extrahandlesize;
+ csock->tid = tid;
+
+#ifdef _WIN32
+ UNUSED(fd);
+ csock->fd = isc__nm_udp_lb_socket(mgr, iface->type.sa.sa_family);
+#else
+ 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);
+ }
+#endif
+ 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);
+#if defined(WIN32)
+ sock->nchildren = 1;
+#else
+ sock->nchildren = mgr->nworkers;
+#endif
+
+ 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;
+
+#ifndef _WIN32
+ if (!mgr->load_balance_sockets) {
+ fd = isc__nm_udp_lb_socket(mgr, iface->type.sa.sa_family);
+ }
+#endif
+
+ 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());
+ }
+
+#ifndef _WIN32
+ if (!mgr->load_balance_sockets) {
+ isc__nm_closesocket(fd);
+ }
+#endif
+
+ 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);
+}
+
+/*
+ * 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());
+
+#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->mgr, sock->statsindex[STATID_OPENFAIL]);
+ goto done;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPEN]);
+
+ if (sa_family == AF_INET6) {
+ uv_bind_flags |= UV_UDP_IPV6ONLY;
+ }
+
+#ifdef _WIN32
+ r = isc_uv_udp_freebind(&sock->uv_handle.udp,
+ &sock->parent->iface.type.sa, uv_bind_flags);
+ if (r < 0) {
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]);
+ goto done;
+ }
+#else
+ 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->mgr,
+ sock->statsindex[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->mgr, 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;
+ }
+ }
+#endif
+
+#ifdef ISC_RECV_BUFFER_SIZE
+ uv_recv_buffer_size(&sock->uv_handle.handle,
+ &(int){ ISC_RECV_BUFFER_SIZE });
+#endif
+#ifdef ISC_SEND_BUFFER_SIZE
+ uv_send_buffer_size(&sock->uv_handle.handle,
+ &(int){ ISC_SEND_BUFFER_SIZE });
+#endif
+ r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb,
+ udp_recv_cb);
+ if (r != 0) {
+ isc__nm_incstats(sock->mgr, sock->statsindex[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_sockaddr_t sockaddr;
+ isc_result_t result;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(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 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 (nrecv < 0) {
+ isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nrecv),
+ false);
+ goto free;
+ }
+
+ result = isc_sockaddr_fromsockaddr(&sockaddr, addr);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ req = isc__nm_get_read_req(sock, &sockaddr);
+
+ /*
+ * 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 defined(WIN32)
+ /* On Windows, we have only a single listening listener */
+ rsock = sock;
+#else
+ if (isc__nm_in_netthread()) {
+ ntid = isc_nm_tid();
+ } else {
+ ntid = sock->tid;
+ }
+ rsock = &sock->parent->children[ntid];
+#endif
+ }
+
+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->mgr, sock->statsindex[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->mgr, sock->statsindex[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 tries = 3;
+ 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);
+
+ r = uv_udp_open(&sock->uv_handle.udp, sock->fd);
+ if (r != 0) {
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPENFAIL]);
+ goto done;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[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->mgr, sock->statsindex[STATID_BINDFAIL]);
+ goto done;
+ }
+
+#ifdef ISC_RECV_BUFFER_SIZE
+ uv_recv_buffer_size(&sock->uv_handle.handle,
+ &(int){ ISC_RECV_BUFFER_SIZE });
+#endif
+#ifdef ISC_SEND_BUFFER_SIZE
+ uv_send_buffer_size(&sock->uv_handle.handle,
+ &(int){ ISC_SEND_BUFFER_SIZE });
+#endif
+
+ /*
+ * 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 && --tries > 0);
+ if (r != 0) {
+ isc__nm_incstats(sock->mgr,
+ sock->statsindex[STATID_CONNECTFAIL]);
+ goto done;
+ }
+ isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CONNECT]);
+
+ atomic_store(&sock->connecting, false);
+ 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);
+}
+
+/*
+ * 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);
+
+ 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__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__nmsocket_closing(sock)) {
+ result = ISC_R_CANCELED;
+ } else {
+ result = isc__nm_start_reading(sock);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ 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->tid == isc_nm_tid());
+ REQUIRE(!sock->recv_read);
+
+ sock->recv_cb = cb;
+ sock->recv_cbarg = cbarg;
+ sock->recv_read = true;
+
+ if (!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->mgr, sock->statsindex[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->mgr, sock->statsindex[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) {
+ 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..a1fc309
--- /dev/null
+++ b/lib/isc/netmgr/uv-compat.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 "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);
+#ifdef WIN32
+ err = connect(handle->socket, addr, addrlen);
+#else /* WIN32 */
+ err = connect(handle->io_watcher.fd, addr, addrlen);
+#endif /* WIN32 */
+ } while (err == -1 && errno == EINTR);
+
+ if (err) {
+#ifdef WIN32
+ return (uv_translate_sys_error(err));
+#else /* WIN32 */
+#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) */
+#endif /* WIN32 */
+ }
+
+ 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);
+ }
+
+#if defined(WIN32)
+ REQUIRE(fd != INVALID_SOCKET);
+#endif
+
+ 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..5ce953d
--- /dev/null
+++ b/lib/isc/netmgr/uverr2result.c
@@ -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.
+ */
+
+#include <stdbool.h>
+#include <uv.h>
+
+#include <isc/result.h>
+#include <isc/strerr.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);
+ default:
+ if (dolog) {
+ UNEXPECTED_ERROR(
+ file, line,
+ "unable to convert libuv "
+ "error code in %s to isc_result: %d: %s",
+ func, 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..3baa04a
--- /dev/null
+++ b/lib/isc/openssl_shim.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.
+ */
+
+#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_NEW
+EVP_MD_CTX *
+EVP_MD_CTX_new(void) {
+ EVP_MD_CTX *ctx = OPENSSL_malloc(sizeof(*ctx));
+ if (ctx != NULL) {
+ memset(ctx, 0, sizeof(*ctx));
+ }
+ return (ctx);
+}
+#endif /* if !HAVE_EVP_MD_CTX_NEW */
+
+#if !HAVE_EVP_MD_CTX_FREE
+void
+EVP_MD_CTX_free(EVP_MD_CTX *ctx) {
+ if (ctx != NULL) {
+ EVP_MD_CTX_cleanup(ctx);
+ OPENSSL_free(ctx);
+ }
+}
+#endif /* if !HAVE_EVP_MD_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_HMAC_CTX_NEW
+HMAC_CTX *
+HMAC_CTX_new(void) {
+ HMAC_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx));
+ if (ctx != NULL) {
+ if (!HMAC_CTX_reset(ctx)) {
+ HMAC_CTX_free(ctx);
+ return (NULL);
+ }
+ }
+ return (ctx);
+}
+#endif /* if !HAVE_HMAC_CTX_NEW */
+
+#if !HAVE_HMAC_CTX_FREE
+void
+HMAC_CTX_free(HMAC_CTX *ctx) {
+ if (ctx != NULL) {
+ HMAC_CTX_cleanup(ctx);
+ OPENSSL_free(ctx);
+ }
+}
+#endif /* if !HAVE_HMAC_CTX_FREE */
+
+#if !HAVE_HMAC_CTX_RESET
+int
+HMAC_CTX_reset(HMAC_CTX *ctx) {
+ HMAC_CTX_cleanup(ctx);
+ return (1);
+}
+#endif /* if !HAVE_HMAC_CTX_RESET */
+
+#if !HAVE_HMAC_CTX_GET_MD
+const EVP_MD *
+HMAC_CTX_get_md(const HMAC_CTX *ctx) {
+ return (ctx->md);
+}
+#endif /* if !HAVE_HMAC_CTX_GET_MD */
+
+#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
diff --git a/lib/isc/openssl_shim.h b/lib/isc/openssl_shim.h
new file mode 100644
index 0000000..9c8a63a
--- /dev/null
+++ b/lib/isc/openssl_shim.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
+
+#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_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
+EVP_MD_CTX *
+EVP_MD_CTX_new(void);
+#endif /* if !HAVE_EVP_MD_CTX_NEW */
+
+#if !HAVE_EVP_MD_CTX_FREE
+void
+EVP_MD_CTX_free(EVP_MD_CTX *ctx);
+#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_HMAC_CTX_NEW
+HMAC_CTX *
+HMAC_CTX_new(void);
+#endif /* if !HAVE_HMAC_CTX_NEW */
+
+#if !HAVE_HMAC_CTX_FREE
+void
+HMAC_CTX_free(HMAC_CTX *ctx);
+#endif /* if !HAVE_HMAC_CTX_FREE */
+
+#if !HAVE_HMAC_CTX_RESET
+int
+HMAC_CTX_reset(HMAC_CTX *ctx);
+#endif /* if !HAVE_HMAC_CTX_RESET */
+
+#if !HAVE_HMAC_CTX_GET_MD
+const EVP_MD *
+HMAC_CTX_get_md(const HMAC_CTX *ctx);
+#endif /* if !HAVE_HMAC_CTX_GET_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
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/pk11.c b/lib/isc/pk11.c
new file mode 100644
index 0000000..6d77314
--- /dev/null
+++ b/lib/isc/pk11.c
@@ -0,0 +1,1112 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/stdio.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <pk11/internal.h>
+#include <pk11/pk11.h>
+#include <pk11/result.h>
+#include <pk11/site.h>
+#include <pkcs11/pkcs11.h>
+
+#include <dst/result.h>
+
+/* was 32 octets, Petr Spacek suggested 1024, SoftHSMv2 uses 256... */
+#ifndef PINLEN
+#define PINLEN 256
+#endif /* ifndef PINLEN */
+
+#ifndef PK11_NO_LOGERR
+#define PK11_NO_LOGERR 1
+#endif /* ifndef PK11_NO_LOGERR */
+
+LIBISC_EXTERNAL_DATA bool pk11_verbose_init = false;
+
+static isc_once_t once = ISC_ONCE_INIT;
+static isc_mem_t *pk11_mctx = NULL;
+static int32_t allocsize = 0;
+static bool initialized = false;
+
+typedef struct pk11_session pk11_session_t;
+typedef struct pk11_token pk11_token_t;
+typedef ISC_LIST(pk11_session_t) pk11_sessionlist_t;
+
+struct pk11_session {
+ unsigned int magic;
+ CK_SESSION_HANDLE session;
+ ISC_LINK(pk11_session_t) link;
+ pk11_token_t *token;
+};
+
+struct pk11_token {
+ unsigned int magic;
+ unsigned int operations;
+ ISC_LINK(pk11_token_t) link;
+ CK_SLOT_ID slotid;
+ pk11_sessionlist_t sessions;
+ bool logged;
+ char name[32];
+ char manuf[32];
+ char model[16];
+ char serial[16];
+ char pin[PINLEN + 1];
+};
+static ISC_LIST(pk11_token_t) tokens;
+
+static pk11_token_t *best_rsa_token;
+static pk11_token_t *best_ecdsa_token;
+static pk11_token_t *best_eddsa_token;
+
+static isc_result_t
+free_all_sessions(void);
+static isc_result_t
+free_session_list(pk11_sessionlist_t *slist);
+static isc_result_t
+setup_session(pk11_session_t *sp, pk11_token_t *token, bool rw);
+static void
+scan_slots(void);
+static isc_result_t
+token_login(pk11_session_t *sp);
+static char *
+percent_decode(char *x, size_t *len);
+static bool
+pk11strcmp(const char *x, size_t lenx, const char *y, size_t leny);
+static CK_ATTRIBUTE *
+push_attribute(pk11_object_t *obj, isc_mem_t *mctx, size_t len);
+
+static isc_mutex_t alloclock;
+static isc_mutex_t sessionlock;
+
+static pk11_sessionlist_t actives;
+
+static CK_C_INITIALIZE_ARGS pk11_init_args = {
+ NULL_PTR, /* CreateMutex */
+ NULL_PTR, /* DestroyMutex */
+ NULL_PTR, /* LockMutex */
+ NULL_PTR, /* UnlockMutex */
+ CKF_OS_LOCKING_OK, /* flags */
+ NULL_PTR, /* pReserved */
+};
+
+#ifndef PK11_LIB_LOCATION
+#define PK11_LIB_LOCATION "unknown_provider"
+#endif /* ifndef PK11_LIB_LOCATION */
+
+#ifndef WIN32
+static const char *lib_name = PK11_LIB_LOCATION;
+#else /* ifndef WIN32 */
+static const char *lib_name = PK11_LIB_LOCATION ".dll";
+#endif /* ifndef WIN32 */
+
+void
+pk11_set_lib_name(const char *name) {
+ lib_name = name;
+}
+
+const char *
+pk11_get_lib_name(void) {
+ return (lib_name);
+}
+
+static void
+initialize(void) {
+ char *pk11_provider;
+
+ isc_mutex_init(&alloclock);
+ isc_mutex_init(&sessionlock);
+
+ pk11_provider = getenv("PKCS11_PROVIDER");
+ if (pk11_provider != NULL) {
+ lib_name = pk11_provider;
+ }
+}
+
+void *
+pk11_mem_get(size_t size) {
+ void *ptr;
+
+ LOCK(&alloclock);
+ if (pk11_mctx != NULL) {
+ ptr = isc_mem_get(pk11_mctx, size);
+ } else {
+ ptr = malloc(size);
+ if (ptr == NULL && size != 0) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__, "malloc failed: %s",
+ strbuf);
+ }
+ }
+ UNLOCK(&alloclock);
+
+ if (ptr != NULL) {
+ memset(ptr, 0, size);
+ }
+ return (ptr);
+}
+
+void
+pk11_mem_put(void *ptr, size_t size) {
+ if (ptr != NULL) {
+ memset(ptr, 0, size);
+ }
+ LOCK(&alloclock);
+ if (pk11_mctx != NULL) {
+ isc_mem_put(pk11_mctx, ptr, size);
+ } else {
+ if (ptr != NULL) {
+ allocsize -= (int)size;
+ }
+ free(ptr);
+ }
+ UNLOCK(&alloclock);
+}
+
+isc_result_t
+pk11_initialize(isc_mem_t *mctx, const char *engine) {
+ isc_result_t result = ISC_R_SUCCESS;
+ CK_RV rv;
+
+ RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS);
+
+ LOCK(&sessionlock);
+ LOCK(&alloclock);
+ if ((mctx != NULL) && (pk11_mctx == NULL) && (allocsize == 0)) {
+ isc_mem_attach(mctx, &pk11_mctx);
+ }
+ UNLOCK(&alloclock);
+ if (initialized) {
+ goto unlock;
+ } else {
+ initialized = true;
+ }
+
+ ISC_LIST_INIT(tokens);
+ ISC_LIST_INIT(actives);
+
+ if (engine != NULL) {
+ lib_name = engine;
+ }
+
+ /* Initialize the CRYPTOKI library */
+ rv = pkcs_C_Initialize((CK_VOID_PTR)&pk11_init_args);
+
+ if (rv == 0xfe) {
+ result = PK11_R_NOPROVIDER;
+ fprintf(stderr, "Can't load PKCS#11 provider: %s\n",
+ pk11_get_load_error_message());
+ goto unlock;
+ }
+ if (rv != CKR_OK) {
+ result = PK11_R_INITFAILED;
+ goto unlock;
+ }
+
+ scan_slots();
+unlock:
+ UNLOCK(&sessionlock);
+ return (result);
+}
+
+isc_result_t
+pk11_finalize(void) {
+ pk11_token_t *token, *next;
+ isc_result_t ret;
+
+ ret = free_all_sessions();
+ (void)pkcs_C_Finalize(NULL_PTR);
+ token = ISC_LIST_HEAD(tokens);
+ while (token != NULL) {
+ next = ISC_LIST_NEXT(token, link);
+ ISC_LIST_UNLINK(tokens, token, link);
+ if (token == best_rsa_token) {
+ best_rsa_token = NULL;
+ }
+ if (token == best_ecdsa_token) {
+ best_ecdsa_token = NULL;
+ }
+ if (token == best_eddsa_token) {
+ best_eddsa_token = NULL;
+ }
+ pk11_mem_put(token, sizeof(*token));
+ token = next;
+ }
+ if (pk11_mctx != NULL) {
+ isc_mem_detach(&pk11_mctx);
+ }
+ initialized = false;
+ return (ret);
+}
+
+isc_result_t
+pk11_get_session(pk11_context_t *ctx, pk11_optype_t optype, bool need_services,
+ bool rw, bool logon, const char *pin, CK_SLOT_ID slot) {
+ pk11_token_t *token = NULL;
+ pk11_sessionlist_t *freelist;
+ pk11_session_t *sp;
+ isc_result_t ret;
+ UNUSED(need_services);
+
+ memset(ctx, 0, sizeof(pk11_context_t));
+ ctx->handle = NULL;
+ ctx->session = CK_INVALID_HANDLE;
+
+ ret = pk11_initialize(NULL, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ return (ret);
+ }
+
+ LOCK(&sessionlock);
+ /* wait for initialization to finish */
+ UNLOCK(&sessionlock);
+
+ switch (optype) {
+ case OP_ANY:
+ for (token = ISC_LIST_HEAD(tokens); token != NULL;
+ token = ISC_LIST_NEXT(token, link))
+ {
+ if (token->slotid == slot) {
+ break;
+ }
+ }
+ break;
+ default:
+ for (token = ISC_LIST_HEAD(tokens); token != NULL;
+ token = ISC_LIST_NEXT(token, link))
+ {
+ if (token->slotid == slot) {
+ break;
+ }
+ }
+ break;
+ }
+ if (token == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /* Override the token's PIN */
+ if (logon && pin != NULL && *pin != '\0') {
+ if (strlen(pin) > PINLEN) {
+ return (ISC_R_RANGE);
+ }
+ /*
+ * We want to zero out the old pin before
+ * overwriting with a new one.
+ */
+ memset(token->pin, 0, sizeof(token->pin));
+ strlcpy(token->pin, pin, sizeof(token->pin));
+ }
+
+ freelist = &token->sessions;
+
+ LOCK(&sessionlock);
+ sp = ISC_LIST_HEAD(*freelist);
+ if (sp != NULL) {
+ ISC_LIST_UNLINK(*freelist, sp, link);
+ ISC_LIST_APPEND(actives, sp, link);
+ UNLOCK(&sessionlock);
+ if (logon) {
+ ret = token_login(sp);
+ }
+ ctx->handle = sp;
+ ctx->session = sp->session;
+ return (ret);
+ }
+ UNLOCK(&sessionlock);
+
+ sp = pk11_mem_get(sizeof(*sp));
+ sp->magic = SES_MAGIC;
+ sp->token = token;
+ sp->session = CK_INVALID_HANDLE;
+ ISC_LINK_INIT(sp, link);
+ ret = setup_session(sp, token, rw);
+ if ((ret == ISC_R_SUCCESS) && logon) {
+ ret = token_login(sp);
+ }
+ LOCK(&sessionlock);
+ ISC_LIST_APPEND(actives, sp, link);
+ UNLOCK(&sessionlock);
+ ctx->handle = sp;
+ ctx->session = sp->session;
+ return (ret);
+}
+
+void
+pk11_return_session(pk11_context_t *ctx) {
+ pk11_session_t *sp = (pk11_session_t *)ctx->handle;
+
+ if (sp == NULL) {
+ return;
+ }
+ ctx->handle = NULL;
+ ctx->session = CK_INVALID_HANDLE;
+
+ LOCK(&sessionlock);
+ ISC_LIST_UNLINK(actives, sp, link);
+ UNLOCK(&sessionlock);
+ if (sp->session == CK_INVALID_HANDLE) {
+ pk11_mem_put(sp, sizeof(*sp));
+ return;
+ }
+
+ LOCK(&sessionlock);
+ ISC_LIST_APPEND(sp->token->sessions, sp, link);
+ UNLOCK(&sessionlock);
+}
+
+static isc_result_t
+free_all_sessions(void) {
+ pk11_token_t *token;
+ isc_result_t ret = ISC_R_SUCCESS;
+ isc_result_t oret;
+
+ for (token = ISC_LIST_HEAD(tokens); token != NULL;
+ token = ISC_LIST_NEXT(token, link))
+ {
+ oret = free_session_list(&token->sessions);
+ if (oret != ISC_R_SUCCESS) {
+ ret = oret;
+ }
+ }
+ if (!ISC_LIST_EMPTY(actives)) {
+ ret = ISC_R_ADDRINUSE;
+ oret = free_session_list(&actives);
+ if (oret != ISC_R_SUCCESS) {
+ ret = oret;
+ }
+ }
+ return (ret);
+}
+
+static isc_result_t
+free_session_list(pk11_sessionlist_t *slist) {
+ pk11_session_t *sp;
+ CK_RV rv;
+ isc_result_t ret;
+
+ ret = ISC_R_SUCCESS;
+ LOCK(&sessionlock);
+ while (!ISC_LIST_EMPTY(*slist)) {
+ sp = ISC_LIST_HEAD(*slist);
+ ISC_LIST_UNLINK(*slist, sp, link);
+ UNLOCK(&sessionlock);
+ if (sp->session != CK_INVALID_HANDLE) {
+ rv = pkcs_C_CloseSession(sp->session);
+ if (rv != CKR_OK) {
+ ret = DST_R_CRYPTOFAILURE;
+ }
+ }
+ LOCK(&sessionlock);
+ pk11_mem_put(sp, sizeof(*sp));
+ }
+ UNLOCK(&sessionlock);
+
+ return (ret);
+}
+
+static isc_result_t
+setup_session(pk11_session_t *sp, pk11_token_t *token, bool rw) {
+ CK_RV rv;
+ CK_FLAGS flags = CKF_SERIAL_SESSION;
+
+ if (rw) {
+ flags += CKF_RW_SESSION;
+ }
+
+ rv = pkcs_C_OpenSession(token->slotid, flags, NULL_PTR, NULL_PTR,
+ &sp->session);
+ if (rv != CKR_OK) {
+ return (DST_R_CRYPTOFAILURE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+token_login(pk11_session_t *sp) {
+ CK_RV rv;
+ pk11_token_t *token = sp->token;
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ LOCK(&sessionlock);
+ if (!token->logged) {
+ rv = pkcs_C_Login(sp->session, CKU_USER,
+ (CK_UTF8CHAR_PTR)token->pin,
+ (CK_ULONG)strlen(token->pin));
+ if (rv != CKR_OK) {
+#if PK11_NO_LOGERR
+ pk11_error_fatalcheck(__FILE__, __LINE__,
+ "pkcs_C_Login", rv);
+#else /* if PK11_NO_LOGERR */
+ ret = ISC_R_NOPERM;
+#endif /* if PK11_NO_LOGERR */
+ } else {
+ token->logged = true;
+ }
+ }
+ UNLOCK(&sessionlock);
+ return (ret);
+}
+
+#define PK11_TRACE(fmt) \
+ if (pk11_verbose_init) \
+ fprintf(stderr, fmt)
+#define PK11_TRACE1(fmt, arg) \
+ if (pk11_verbose_init) \
+ fprintf(stderr, fmt, arg)
+#define PK11_TRACE2(fmt, arg1, arg2) \
+ if (pk11_verbose_init) \
+ fprintf(stderr, fmt, arg1, arg2)
+#define PK11_TRACEM(mech) \
+ if (pk11_verbose_init) \
+ fprintf(stderr, #mech ": 0x%lx\n", rv)
+
+static void
+scan_slots(void) {
+ CK_MECHANISM_INFO mechInfo;
+ CK_TOKEN_INFO tokenInfo;
+ CK_RV rv;
+ CK_SLOT_ID slot;
+ CK_SLOT_ID_PTR slotList;
+ CK_ULONG slotCount;
+ pk11_token_t *token;
+ unsigned int i;
+ bool bad;
+
+ slotCount = 0;
+ PK11_FATALCHECK(pkcs_C_GetSlotList, (CK_FALSE, NULL_PTR, &slotCount));
+ PK11_TRACE1("slotCount=%lu\n", slotCount);
+ /* it's not an error if we didn't find any providers */
+ if (slotCount == 0) {
+ return;
+ }
+ slotList = pk11_mem_get(sizeof(CK_SLOT_ID) * slotCount);
+ PK11_FATALCHECK(pkcs_C_GetSlotList, (CK_FALSE, slotList, &slotCount));
+
+ for (i = 0; i < slotCount; i++) {
+ slot = slotList[i];
+ PK11_TRACE2("slot#%u=0x%lx\n", i, slot);
+
+ rv = pkcs_C_GetTokenInfo(slot, &tokenInfo);
+ if (rv != CKR_OK) {
+ continue;
+ }
+ token = pk11_mem_get(sizeof(*token));
+ token->magic = TOK_MAGIC;
+ token->slotid = slot;
+ ISC_LINK_INIT(token, link);
+ ISC_LIST_INIT(token->sessions);
+ memmove(token->name, tokenInfo.label, 32);
+ memmove(token->manuf, tokenInfo.manufacturerID, 32);
+ memmove(token->model, tokenInfo.model, 16);
+ memmove(token->serial, tokenInfo.serialNumber, 16);
+ ISC_LIST_APPEND(tokens, token, link);
+
+ /* Check for RSA support */
+ bad = false;
+ rv = pkcs_C_GetMechanismInfo(slot, CKM_RSA_PKCS_KEY_PAIR_GEN,
+ &mechInfo);
+ if ((rv != CKR_OK) ||
+ ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0))
+ {
+ bad = true;
+ PK11_TRACEM(CKM_RSA_PKCS_KEY_PAIR_GEN);
+ }
+ rv = pkcs_C_GetMechanismInfo(slot, CKM_MD5_RSA_PKCS, &mechInfo);
+ if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) ||
+ ((mechInfo.flags & CKF_VERIFY) == 0))
+ {
+ bad = true;
+ PK11_TRACEM(CKM_MD5_RSA_PKCS);
+ }
+ rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA1_RSA_PKCS,
+ &mechInfo);
+ if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) ||
+ ((mechInfo.flags & CKF_VERIFY) == 0))
+ {
+ bad = true;
+ PK11_TRACEM(CKM_SHA1_RSA_PKCS);
+ }
+ rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA256_RSA_PKCS,
+ &mechInfo);
+ if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) ||
+ ((mechInfo.flags & CKF_VERIFY) == 0))
+ {
+ bad = true;
+ PK11_TRACEM(CKM_SHA256_RSA_PKCS);
+ }
+ rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA512_RSA_PKCS,
+ &mechInfo);
+ if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) ||
+ ((mechInfo.flags & CKF_VERIFY) == 0))
+ {
+ bad = true;
+ PK11_TRACEM(CKM_SHA512_RSA_PKCS);
+ }
+ rv = pkcs_C_GetMechanismInfo(slot, CKM_RSA_PKCS, &mechInfo);
+ if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) ||
+ ((mechInfo.flags & CKF_VERIFY) == 0))
+ {
+ bad = true;
+ PK11_TRACEM(CKM_RSA_PKCS);
+ }
+ if (!bad) {
+ token->operations |= 1 << OP_RSA;
+ if (best_rsa_token == NULL) {
+ best_rsa_token = token;
+ }
+ }
+
+ /* Check for ECDSA support */
+ bad = false;
+ rv = pkcs_C_GetMechanismInfo(slot, CKM_EC_KEY_PAIR_GEN,
+ &mechInfo);
+ if ((rv != CKR_OK) ||
+ ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0))
+ {
+ bad = true;
+ PK11_TRACEM(CKM_EC_KEY_PAIR_GEN);
+ }
+ rv = pkcs_C_GetMechanismInfo(slot, CKM_ECDSA, &mechInfo);
+ if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) ||
+ ((mechInfo.flags & CKF_VERIFY) == 0))
+ {
+ bad = true;
+ PK11_TRACEM(CKM_ECDSA);
+ }
+ if (!bad) {
+ token->operations |= 1 << OP_ECDSA;
+ if (best_ecdsa_token == NULL) {
+ best_ecdsa_token = token;
+ }
+ }
+
+ /* Check for EDDSA support */
+ bad = false;
+ rv = pkcs_C_GetMechanismInfo(slot, CKM_EC_EDWARDS_KEY_PAIR_GEN,
+ &mechInfo);
+ if ((rv != CKR_OK) ||
+ ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0))
+ {
+ bad = true;
+ PK11_TRACEM(CKM_EC_EDWARDS_KEY_PAIR_GEN);
+ }
+ rv = pkcs_C_GetMechanismInfo(slot, CKM_EDDSA, &mechInfo);
+ if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) ||
+ ((mechInfo.flags & CKF_VERIFY) == 0))
+ {
+ bad = true;
+ PK11_TRACEM(CKM_EDDSA);
+ }
+ if (!bad) {
+ token->operations |= 1 << OP_EDDSA;
+ if (best_eddsa_token == NULL) {
+ best_eddsa_token = token;
+ }
+ }
+ }
+
+ if (slotList != NULL) {
+ pk11_mem_put(slotList, sizeof(CK_SLOT_ID) * slotCount);
+ }
+}
+
+CK_SLOT_ID
+pk11_get_best_token(pk11_optype_t optype) {
+ pk11_token_t *token = NULL;
+
+ switch (optype) {
+ case OP_RSA:
+ token = best_rsa_token;
+ break;
+ case OP_ECDSA:
+ token = best_ecdsa_token;
+ break;
+ case OP_EDDSA:
+ token = best_eddsa_token;
+ break;
+ default:
+ break;
+ }
+ if (token == NULL) {
+ return (0);
+ }
+ return (token->slotid);
+}
+
+isc_result_t
+pk11_numbits(CK_BYTE_PTR data, unsigned int bytecnt, unsigned int *bits) {
+ unsigned int bitcnt, i;
+ CK_BYTE top;
+
+ if (bytecnt == 0) {
+ *bits = 0;
+ return (ISC_R_SUCCESS);
+ }
+ bitcnt = bytecnt * 8;
+ for (i = 0; i < bytecnt; i++) {
+ top = data[i];
+ if (top == 0) {
+ bitcnt -= 8;
+ continue;
+ }
+ if (top & 0x80) {
+ *bits = bitcnt;
+ return (ISC_R_SUCCESS);
+ }
+ if (top & 0x40) {
+ *bits = bitcnt - 1;
+ return (ISC_R_SUCCESS);
+ }
+ if (top & 0x20) {
+ *bits = bitcnt - 2;
+ return (ISC_R_SUCCESS);
+ }
+ if (top & 0x10) {
+ *bits = bitcnt - 3;
+ return (ISC_R_SUCCESS);
+ }
+ if (top & 0x08) {
+ *bits = bitcnt - 4;
+ return (ISC_R_SUCCESS);
+ }
+ if (top & 0x04) {
+ *bits = bitcnt - 5;
+ return (ISC_R_SUCCESS);
+ }
+ if (top & 0x02) {
+ *bits = bitcnt - 6;
+ return (ISC_R_SUCCESS);
+ }
+ if (top & 0x01) {
+ *bits = bitcnt - 7;
+ return (ISC_R_SUCCESS);
+ }
+ break;
+ }
+ return (ISC_R_RANGE);
+}
+
+CK_ATTRIBUTE *
+pk11_attribute_first(const pk11_object_t *obj) {
+ return (obj->repr);
+}
+
+CK_ATTRIBUTE *
+pk11_attribute_next(const pk11_object_t *obj, CK_ATTRIBUTE *attr) {
+ CK_ATTRIBUTE *next;
+
+ next = attr + 1;
+ if ((next - obj->repr) >= obj->attrcnt) {
+ return (NULL);
+ }
+ return (next);
+}
+
+CK_ATTRIBUTE *
+pk11_attribute_bytype(const pk11_object_t *obj, CK_ATTRIBUTE_TYPE type) {
+ CK_ATTRIBUTE *attr;
+
+ for (attr = pk11_attribute_first(obj); attr != NULL;
+ attr = pk11_attribute_next(obj, attr))
+ {
+ if (attr->type == type) {
+ return (attr);
+ }
+ }
+ return (NULL);
+}
+
+static char *
+percent_decode(char *x, size_t *len) {
+ char *p, *c;
+ unsigned char v = 0;
+
+ INSIST(len != NULL);
+
+ for (p = c = x; p[0] != '\0'; p++, c++) {
+ switch (p[0]) {
+ case '%':
+ switch (p[1]) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ v = (p[1] - '0') << 4;
+ break;
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ v = (p[1] - 'A' + 10) << 4;
+ break;
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ v = (p[1] - 'a' + 10) << 4;
+ break;
+ default:
+ return (NULL);
+ }
+ switch (p[2]) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ v |= (p[2] - '0') & 0x0f;
+ break;
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ v = (p[2] - 'A' + 10) & 0x0f;
+ break;
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ v = (p[2] - 'a' + 10) & 0x0f;
+ break;
+ default:
+ return (NULL);
+ }
+ p += 2;
+ *c = (char)v;
+ (*len)++;
+ break;
+ default:
+ *c = *p;
+ (*len)++;
+ }
+ }
+ return (x);
+}
+
+static bool
+pk11strcmp(const char *x, size_t lenx, const char *y, size_t leny) {
+ char buf[32];
+
+ INSIST((leny == 32) || (leny == 16));
+
+ memset(buf, ' ', 32);
+ if (lenx > leny) {
+ lenx = leny;
+ }
+ memmove(buf, x, lenx);
+ return (memcmp(buf, y, leny) == 0);
+}
+
+static CK_ATTRIBUTE *
+push_attribute(pk11_object_t *obj, isc_mem_t *mctx, size_t len) {
+ CK_ATTRIBUTE *old = obj->repr;
+ CK_ATTRIBUTE *attr;
+ CK_BYTE cnt = obj->attrcnt;
+
+ REQUIRE(old != NULL || cnt == 0);
+
+ obj->repr = isc_mem_get(mctx, (cnt + 1) * sizeof(*attr));
+ memset(obj->repr, 0, (cnt + 1) * sizeof(*attr));
+ if (old != NULL) {
+ memmove(obj->repr, old, cnt * sizeof(*attr));
+ }
+ attr = obj->repr + cnt;
+ attr->ulValueLen = (CK_ULONG)len;
+ attr->pValue = isc_mem_get(mctx, len);
+ memset(attr->pValue, 0, len);
+ if (old != NULL) {
+ memset(old, 0, cnt * sizeof(*attr));
+ isc_mem_put(mctx, old, cnt * sizeof(*attr));
+ }
+ obj->attrcnt++;
+ return (attr);
+}
+
+#define DST_RET(a) \
+ { \
+ ret = a; \
+ goto err; \
+ }
+
+isc_result_t
+pk11_parse_uri(pk11_object_t *obj, const char *label, isc_mem_t *mctx,
+ pk11_optype_t optype) {
+ CK_ATTRIBUTE *attr;
+ pk11_token_t *token = NULL;
+ char *uri, *p, *a, *na, *v;
+ size_t len, l;
+ FILE *stream = NULL;
+ char pin[PINLEN + 1];
+ bool gotpin = false;
+ isc_result_t ret;
+
+ /* get values to work on */
+ len = strlen(label) + 1;
+ uri = isc_mem_get(mctx, len);
+ memmove(uri, label, len);
+
+ /* get the URI scheme */
+ p = strchr(uri, ':');
+ if (p == NULL) {
+ DST_RET(PK11_R_NOPROVIDER);
+ }
+ *p++ = '\0';
+ if (strcmp(uri, "pkcs11") != 0) {
+ DST_RET(PK11_R_NOPROVIDER);
+ }
+
+ /* get attributes */
+ for (na = p; na != NULL;) {
+ a = na;
+ p = strchr(a, ';');
+ if (p == NULL) {
+ /* last attribute */
+ na = NULL;
+ } else {
+ *p++ = '\0';
+ na = p;
+ }
+ p = strchr(a, '=');
+ if (p != NULL) {
+ *p++ = '\0';
+ v = p;
+ } else {
+ v = a;
+ }
+ l = 0;
+ v = percent_decode(v, &l);
+ if (v == NULL) {
+ DST_RET(PK11_R_NOPROVIDER);
+ }
+ if ((a == v) || (strcmp(a, "object") == 0)) {
+ /* object: CKA_LABEL */
+ attr = pk11_attribute_bytype(obj, CKA_LABEL);
+ if (attr != NULL) {
+ DST_RET(PK11_R_NOPROVIDER);
+ }
+ attr = push_attribute(obj, mctx, l);
+ if (attr == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ attr->type = CKA_LABEL;
+ memmove(attr->pValue, v, l);
+ } else if (strcmp(a, "token") == 0) {
+ /* token: CK_TOKEN_INFO label */
+ if (token == NULL) {
+ for (token = ISC_LIST_HEAD(tokens);
+ token != NULL;
+ token = ISC_LIST_NEXT(token, link))
+ {
+ if (pk11strcmp(v, l, token->name, 32)) {
+ break;
+ }
+ }
+ }
+ } else if (strcmp(a, "manufacturer") == 0) {
+ /* manufacturer: CK_TOKEN_INFO manufacturerID */
+ if (token == NULL) {
+ for (token = ISC_LIST_HEAD(tokens);
+ token != NULL;
+ token = ISC_LIST_NEXT(token, link))
+ {
+ if (pk11strcmp(v, l, token->manuf, 32))
+ {
+ break;
+ }
+ }
+ }
+ } else if (strcmp(a, "serial") == 0) {
+ /* serial: CK_TOKEN_INFO serialNumber */
+ if (token == NULL) {
+ for (token = ISC_LIST_HEAD(tokens);
+ token != NULL;
+ token = ISC_LIST_NEXT(token, link))
+ {
+ if (pk11strcmp(v, l, token->serial, 16))
+ {
+ break;
+ }
+ }
+ }
+ } else if (strcmp(a, "model") == 0) {
+ /* model: CK_TOKEN_INFO model */
+ if (token == NULL) {
+ for (token = ISC_LIST_HEAD(tokens);
+ token != NULL;
+ token = ISC_LIST_NEXT(token, link))
+ {
+ if (pk11strcmp(v, l, token->model, 16))
+ {
+ break;
+ }
+ }
+ }
+ } else if (strcmp(a, "library-manufacturer") == 0) {
+ /* ignored */
+ } else if (strcmp(a, "library-description") == 0) {
+ /* ignored */
+ } else if (strcmp(a, "library-version") == 0) {
+ /* ignored */
+ } else if (strcmp(a, "object-type") == 0) {
+ /* object-type: CKA_CLASS */
+ /* only private makes sense */
+ if (strcmp(v, "private") != 0) {
+ DST_RET(PK11_R_NOPROVIDER);
+ }
+ } else if (strcmp(a, "id") == 0) {
+ /* id: CKA_ID */
+ attr = pk11_attribute_bytype(obj, CKA_ID);
+ if (attr != NULL) {
+ DST_RET(PK11_R_NOPROVIDER);
+ }
+ attr = push_attribute(obj, mctx, l);
+ if (attr == NULL) {
+ DST_RET(ISC_R_NOMEMORY);
+ }
+ attr->type = CKA_ID;
+ memmove(attr->pValue, v, l);
+ } else if (strcmp(a, "pin-source") == 0) {
+ /* pin-source: PIN */
+ ret = isc_stdio_open(v, "r", &stream);
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ memset(pin, 0, PINLEN + 1);
+ ret = isc_stdio_read(pin, 1, PINLEN + 1, stream, &l);
+ if ((ret != ISC_R_SUCCESS) && (ret != ISC_R_EOF)) {
+ goto err;
+ }
+ if (l > PINLEN) {
+ DST_RET(ISC_R_RANGE);
+ }
+ ret = isc_stdio_close(stream);
+ stream = NULL;
+ if (ret != ISC_R_SUCCESS) {
+ goto err;
+ }
+ gotpin = true;
+ } else {
+ DST_RET(PK11_R_NOPROVIDER);
+ }
+ }
+
+ if ((pk11_attribute_bytype(obj, CKA_LABEL) == NULL) &&
+ (pk11_attribute_bytype(obj, CKA_ID) == NULL))
+ {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+
+ if (token == NULL) {
+ if (optype == OP_RSA) {
+ token = best_rsa_token;
+ } else if (optype == OP_ECDSA) {
+ token = best_ecdsa_token;
+ } else if (optype == OP_EDDSA) {
+ token = best_eddsa_token;
+ }
+ }
+ if (token == NULL) {
+ DST_RET(ISC_R_NOTFOUND);
+ }
+ obj->slot = token->slotid;
+ if (gotpin) {
+ memmove(token->pin, pin, PINLEN + 1);
+ obj->reqlogon = true;
+ }
+
+ ret = ISC_R_SUCCESS;
+
+err:
+ if (stream != NULL) {
+ (void)isc_stdio_close(stream);
+ }
+ isc_mem_put(mctx, uri, len);
+ return (ret);
+}
+
+void
+pk11_error_fatalcheck(const char *file, int line, const char *funcname,
+ CK_RV rv) {
+ isc_error_fatal(file, line, "%s: Error = 0x%.8lX\n", funcname, rv);
+}
+
+void
+pk11_dump_tokens(void) {
+ pk11_token_t *token;
+ bool first;
+
+ printf("DEFAULTS\n");
+ printf("\tbest_rsa_token=%p\n", best_rsa_token);
+ printf("\tbest_ecdsa_token=%p\n", best_ecdsa_token);
+ printf("\tbest_eddsa_token=%p\n", best_eddsa_token);
+
+ for (token = ISC_LIST_HEAD(tokens); token != NULL;
+ token = ISC_LIST_NEXT(token, link))
+ {
+ printf("\nTOKEN\n");
+ printf("\taddress=%p\n", token);
+ printf("\tslotID=%lu\n", token->slotid);
+ printf("\tlabel=%.32s\n", token->name);
+ printf("\tmanufacturerID=%.32s\n", token->manuf);
+ printf("\tmodel=%.16s\n", token->model);
+ printf("\tserialNumber=%.16s\n", token->serial);
+ printf("\tsupported operations=0x%x (", token->operations);
+ first = true;
+ if (token->operations & (1 << OP_RSA)) {
+ first = false;
+ printf("RSA");
+ }
+ if (token->operations & (1 << OP_ECDSA)) {
+ if (!first) {
+ printf(",");
+ }
+ printf("EC");
+ }
+ printf(")\n");
+ }
+}
diff --git a/lib/isc/pk11_result.c b/lib/isc/pk11_result.c
new file mode 100644
index 0000000..73966fa
--- /dev/null
+++ b/lib/isc/pk11_result.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.
+ */
+
+#include <stddef.h>
+
+#include <isc/once.h>
+#include <isc/util.h>
+
+#include <pk11/result.h>
+
+static const char *text[PK11_R_NRESULTS] = {
+ "PKCS#11 initialization failed", /*%< 0 */
+ "no PKCS#11 provider", /*%< 1 */
+ "PKCS#11 no random service", /*%< 2 */
+ "PKCS#11 no digist service", /*%< 3 */
+ "PKCS#11 no AES service", /*%< 4 */
+};
+
+static const char *ids[PK11_R_NRESULTS] = {
+ "PK11_R_INITFAILED", "PK11_R_NOPROVIDER",
+ "PK11_R_NORANDOMSERVICE", "PK11_R_NODIGESTSERVICE",
+ "PK11_R_NOAESSERVICE",
+};
+
+#define PK11_RESULT_RESULTSET 2
+
+static isc_once_t once = ISC_ONCE_INIT;
+
+static void
+initialize_action(void) {
+ isc_result_t result;
+
+ result = isc_result_register(ISC_RESULTCLASS_PK11, PK11_R_NRESULTS,
+ text, PK11_RESULT_RESULTSET);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_result_register() failed: %u", result);
+ }
+
+ result = isc_result_registerids(ISC_RESULTCLASS_PK11, PK11_R_NRESULTS,
+ ids, PK11_RESULT_RESULTSET);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_result_registerids() failed: %u", result);
+ }
+}
+
+static void
+initialize(void) {
+ RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS);
+}
+
+const char *
+pk11_result_totext(isc_result_t result) {
+ initialize();
+
+ return (isc_result_totext(result));
+}
+
+void
+pk11_result_register(void) {
+ initialize();
+}
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/pthreads/Makefile.in b/lib/isc/pthreads/Makefile.in
new file mode 100644
index 0000000..c79e949
--- /dev/null
+++ b/lib/isc/pthreads/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+CINCLUDES = -I${srcdir}/include \
+ -I${srcdir}/../unix/include \
+ -I../include \
+ -I${srcdir}/../include \
+ -I${srcdir}/..
+
+CDEFINES =
+CWARNINGS =
+
+OBJS = condition.@O@ mutex.@O@ thread.@O@
+
+SRCS = condition.c mutex.c thread.c
+
+SUBDIRS = include
+TARGETS = ${OBJS}
+
+@BIND9_MAKE_RULES@
diff --git a/lib/isc/pthreads/condition.c b/lib/isc/pthreads/condition.c
new file mode 100644
index 0000000..7657b18
--- /dev/null
+++ b/lib/isc/pthreads/condition.c
@@ -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.
+ */
+
+/*! \file */
+
+#include <errno.h>
+
+#include <isc/condition.h>
+#include <isc/strerr.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;
+ char strbuf[ISC_STRERRORSIZE];
+
+ 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 {
+#if ISC_MUTEX_PROFILE
+ presult = pthread_cond_timedwait(c, &m->mutex, &ts);
+#else /* if ISC_MUTEX_PROFILE */
+ presult = pthread_cond_timedwait(c, m, &ts);
+#endif /* if ISC_MUTEX_PROFILE */
+ if (presult == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ if (presult == ETIMEDOUT) {
+ return (ISC_R_TIMEDOUT);
+ }
+ } while (presult == EINTR);
+
+ strerror_r(presult, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "pthread_cond_timedwait() returned %s", strbuf);
+ return (ISC_R_UNEXPECTED);
+}
diff --git a/lib/isc/pthreads/include/.clang-format b/lib/isc/pthreads/include/.clang-format
new file mode 120000
index 0000000..e919bba
--- /dev/null
+++ b/lib/isc/pthreads/include/.clang-format
@@ -0,0 +1 @@
+../../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/isc/pthreads/include/Makefile.in b/lib/isc/pthreads/include/Makefile.in
new file mode 100644
index 0000000..60a0a67
--- /dev/null
+++ b/lib/isc/pthreads/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = isc
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/isc/pthreads/include/isc/Makefile.in b/lib/isc/pthreads/include/isc/Makefile.in
new file mode 100644
index 0000000..e92364d
--- /dev/null
+++ b/lib/isc/pthreads/include/isc/Makefile.in
@@ -0,0 +1,36 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+HEADERS = condition.h mutex.h once.h thread.h
+
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/isc
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} $(srcdir)/$$i ${DESTDIR}${includedir}/isc || exit 1; \
+ done
+
+uninstall::
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/isc/$$i || exit 1; \
+ done
diff --git a/lib/isc/pthreads/include/isc/condition.h b/lib/isc/pthreads/include/isc/condition.h
new file mode 100644
index 0000000..8a79eb4
--- /dev/null
+++ b/lib/isc/pthreads/include/isc/condition.h
@@ -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.
+ */
+
+#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) { \
+ char isc_condition_strbuf[ISC_STRERRORSIZE]; \
+ strerror_r(errno, isc_condition_strbuf, \
+ sizeof(isc_condition_strbuf)); \
+ isc_error_fatal(__FILE__, __LINE__, \
+ "pthread_cond_init failed: %s", \
+ isc_condition_strbuf); \
+ }
+
+#if ISC_MUTEX_PROFILE
+#define isc_condition_wait(cp, mp) \
+ ((pthread_cond_wait((cp), &((mp)->mutex)) == 0) ? ISC_R_SUCCESS \
+ : ISC_R_UNEXPECTED)
+#else /* if ISC_MUTEX_PROFILE */
+#define isc_condition_wait(cp, mp) \
+ ((pthread_cond_wait((cp), (mp)) == 0) ? ISC_R_SUCCESS \
+ : ISC_R_UNEXPECTED)
+#endif /* if ISC_MUTEX_PROFILE */
+
+#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/pthreads/include/isc/mutex.h b/lib/isc/pthreads/include/isc/mutex.h
new file mode 100644
index 0000000..2390cd9
--- /dev/null
+++ b/lib/isc/pthreads/include/isc/mutex.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.
+ */
+
+#ifndef ISC_MUTEX_H
+#define ISC_MUTEX_H 1
+
+/*! \file */
+
+#include <pthread.h>
+#include <stdio.h>
+
+#include <isc/lang.h>
+#include <isc/result.h> /* for ISC_R_ codes */
+
+ISC_LANG_BEGINDECLS
+
+/*!
+ * Supply mutex attributes that enable deadlock detection
+ * (helpful when debugging). This is system dependent and
+ * currently only supported on NetBSD.
+ */
+#if ISC_MUTEX_DEBUG && defined(__NetBSD__) && defined(PTHREAD_MUTEX_ERRORCHECK)
+extern pthread_mutexattr_t isc__mutex_attrs;
+#define ISC__MUTEX_ATTRS &isc__mutex_attrs
+#else /* if ISC_MUTEX_DEBUG && defined(__NetBSD__) && \
+ * defined(PTHREAD_MUTEX_ERRORCHECK) */
+#define ISC__MUTEX_ATTRS NULL
+#endif /* if ISC_MUTEX_DEBUG && defined(__NetBSD__) && \
+ * defined(PTHREAD_MUTEX_ERRORCHECK) */
+
+/* XXX We could do fancier error handling... */
+
+/*!
+ * Define ISC_MUTEX_PROFILE to turn on profiling of mutexes by line. When
+ * enabled, isc_mutex_stats() can be used to print a table showing the
+ * number of times each type of mutex was locked and the amount of time
+ * waiting to obtain the lock.
+ */
+#ifndef ISC_MUTEX_PROFILE
+#define ISC_MUTEX_PROFILE 0
+#endif /* ifndef ISC_MUTEX_PROFILE */
+
+#if ISC_MUTEX_PROFILE
+typedef struct isc_mutexstats isc_mutexstats_t;
+
+typedef struct {
+ pthread_mutex_t mutex; /*%< The actual mutex. */
+ isc_mutexstats_t *stats; /*%< Mutex statistics. */
+} isc_mutex_t;
+#else /* if ISC_MUTEX_PROFILE */
+typedef pthread_mutex_t isc_mutex_t;
+#endif /* if ISC_MUTEX_PROFILE */
+
+#if ISC_MUTEX_PROFILE
+#define isc_mutex_init(mp) isc_mutex_init_profile((mp), __FILE__, __LINE__)
+#else /* if ISC_MUTEX_PROFILE */
+#if ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK)
+#define isc_mutex_init(mp) isc_mutex_init_errcheck((mp))
+#else /* if ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK) */
+#define isc_mutex_init(mp) isc__mutex_init((mp), __FILE__, __LINE__)
+void
+isc__mutex_init(isc_mutex_t *mp, const char *file, unsigned int line);
+#endif /* if ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK) */
+#endif /* if ISC_MUTEX_PROFILE */
+
+#if ISC_MUTEX_PROFILE
+#define isc_mutex_lock(mp) isc_mutex_lock_profile((mp), __FILE__, __LINE__)
+#else /* if ISC_MUTEX_PROFILE */
+#define isc_mutex_lock(mp) \
+ ((pthread_mutex_lock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED)
+#endif /* if ISC_MUTEX_PROFILE */
+
+#if ISC_MUTEX_PROFILE
+#define isc_mutex_unlock(mp) isc_mutex_unlock_profile((mp), __FILE__, __LINE__)
+#else /* if ISC_MUTEX_PROFILE */
+#define isc_mutex_unlock(mp) \
+ ((pthread_mutex_unlock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED)
+#endif /* if ISC_MUTEX_PROFILE */
+
+#if ISC_MUTEX_PROFILE
+#define isc_mutex_trylock(mp) \
+ ((pthread_mutex_trylock((&(mp)->mutex)) == 0) ? ISC_R_SUCCESS \
+ : ISC_R_LOCKBUSY)
+#else /* if ISC_MUTEX_PROFILE */
+#define isc_mutex_trylock(mp) \
+ ((pthread_mutex_trylock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_LOCKBUSY)
+#endif /* if ISC_MUTEX_PROFILE */
+
+#if ISC_MUTEX_PROFILE
+#define isc_mutex_destroy(mp) \
+ RUNTIME_CHECK(pthread_mutex_destroy((&(mp)->mutex)) == 0)
+#else /* if ISC_MUTEX_PROFILE */
+#define isc_mutex_destroy(mp) RUNTIME_CHECK(pthread_mutex_destroy((mp)) == 0)
+#endif /* if ISC_MUTEX_PROFILE */
+
+#if ISC_MUTEX_PROFILE
+#define isc_mutex_stats(fp) isc_mutex_statsprofile(fp);
+#else /* if ISC_MUTEX_PROFILE */
+#define isc_mutex_stats(fp)
+#endif /* if ISC_MUTEX_PROFILE */
+
+#if ISC_MUTEX_PROFILE
+
+void
+isc_mutex_init_profile(isc_mutex_t *mp, const char *_file, int _line);
+isc_result_t
+isc_mutex_lock_profile(isc_mutex_t *mp, const char *_file, int _line);
+isc_result_t
+isc_mutex_unlock_profile(isc_mutex_t *mp, const char *_file, int _line);
+
+void
+isc_mutex_statsprofile(FILE *fp);
+#endif /* ISC_MUTEX_PROFILE */
+
+void
+isc_mutex_init_errcheck(isc_mutex_t *mp);
+
+ISC_LANG_ENDDECLS
+#endif /* ISC_MUTEX_H */
diff --git a/lib/isc/pthreads/include/isc/once.h b/lib/isc/pthreads/include/isc/once.h
new file mode 100644
index 0000000..481dba5
--- /dev/null
+++ b/lib/isc/pthreads/include/isc/once.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.
+ */
+
+#ifndef ISC_ONCE_H
+#define ISC_ONCE_H 1
+
+/*! \file */
+
+#include <pthread.h>
+
+#include <isc/platform.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)
+
+#endif /* ISC_ONCE_H */
diff --git a/lib/isc/pthreads/include/isc/thread.h b/lib/isc/pthreads/include/isc/thread.h
new file mode 100644
index 0000000..6f83191
--- /dev/null
+++ b/lib/isc/pthreads/include/isc/thread.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_THREAD_H
+#define ISC_THREAD_H 1
+
+/*! \file */
+
+#include <pthread.h>
+
+#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>
+
+#if defined(HAVE_THREAD_LOCAL)
+#include <threads.h>
+extern thread_local size_t isc_tid_v;
+#elif defined(HAVE___THREAD)
+extern __thread size_t isc_tid_v;
+#endif /* if defined(HAVE_THREAD_LOCAL) */
+
+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
+
+/***
+ *** Thread-Local Storage
+ ***/
+
+#if defined(HAVE_TLS)
+#if defined(HAVE_THREAD_LOCAL)
+#define ISC_THREAD_LOCAL static thread_local
+#elif defined(HAVE___THREAD)
+#define ISC_THREAD_LOCAL static __thread
+#else /* if defined(HAVE_THREAD_LOCAL) */
+#error "Unknown method for defining a TLS variable!"
+#endif /* if defined(HAVE_THREAD_LOCAL) */
+#else /* if defined(HAVE_TLS) */
+#error "Thread-local storage support is required!"
+#endif /* if defined(HAVE_TLS) */
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_THREAD_H */
diff --git a/lib/isc/pthreads/mutex.c b/lib/isc/pthreads/mutex.c
new file mode 100644
index 0000000..aa3dc53
--- /dev/null
+++ b/lib/isc/pthreads/mutex.c
@@ -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.
+ */
+
+/*! \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/strerr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#if ISC_MUTEX_PROFILE
+
+/*@{*/
+/*% Operations on timevals; adapted from FreeBSD's sys/time.h */
+#define timevalclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0)
+#define timevaladd(vvp, uvp) \
+ do { \
+ (vvp)->tv_sec += (uvp)->tv_sec; \
+ (vvp)->tv_usec += (uvp)->tv_usec; \
+ if ((vvp)->tv_usec >= 1000000) { \
+ (vvp)->tv_sec++; \
+ (vvp)->tv_usec -= 1000000; \
+ } \
+ } while (0)
+#define timevalsub(vvp, uvp) \
+ do { \
+ (vvp)->tv_sec -= (uvp)->tv_sec; \
+ (vvp)->tv_usec -= (uvp)->tv_usec; \
+ if ((vvp)->tv_usec < 0) { \
+ (vvp)->tv_sec--; \
+ (vvp)->tv_usec += 1000000; \
+ } \
+ } while (0)
+
+/*@}*/
+
+#define ISC_MUTEX_MAX_LOCKERS 32
+
+typedef struct {
+ const char *file;
+ int line;
+ unsigned count;
+ struct timeval locked_total;
+ struct timeval wait_total;
+} isc_mutexlocker_t;
+
+struct isc_mutexstats {
+ const char *file; /*%< File mutex was created in. */
+ int line; /*%< Line mutex was created on. */
+ unsigned count;
+ struct timeval lock_t;
+ struct timeval locked_total;
+ struct timeval wait_total;
+ isc_mutexlocker_t *cur_locker;
+ isc_mutexlocker_t lockers[ISC_MUTEX_MAX_LOCKERS];
+};
+
+#ifndef ISC_MUTEX_PROFTABLESIZE
+#define ISC_MUTEX_PROFTABLESIZE (1024 * 1024)
+#endif /* ifndef ISC_MUTEX_PROFTABLESIZE */
+static isc_mutexstats_t stats[ISC_MUTEX_PROFTABLESIZE];
+static int stats_next = 0;
+static bool stats_init = false;
+static pthread_mutex_t statslock = PTHREAD_MUTEX_INITIALIZER;
+
+void
+isc_mutex_init_profile(isc_mutex_t *mp, const char *file, int line) {
+ int i, err;
+
+ err = pthread_mutex_init(&mp->mutex, NULL);
+ if (err != 0) {
+ strerror_r(err, strbuf, sizeof(strbuf));
+ isc_error_fatal(file, line, "pthread_mutex_init failed: %s",
+ strbuf);
+ }
+
+ RUNTIME_CHECK(pthread_mutex_lock(&statslock) == 0);
+
+ if (!stats_init) {
+ stats_init = true;
+ }
+
+ /*
+ * If all statistics entries have been used, give up and trigger an
+ * assertion failure. There would be no other way to deal with this
+ * because we'd like to keep record of all locks for the purpose of
+ * debugging and the number of necessary locks is unpredictable.
+ * If this failure is triggered while debugging, named should be
+ * rebuilt with an increased ISC_MUTEX_PROFTABLESIZE.
+ */
+ RUNTIME_CHECK(stats_next < ISC_MUTEX_PROFTABLESIZE);
+ mp->stats = &stats[stats_next++];
+
+ RUNTIME_CHECK(pthread_mutex_unlock(&statslock) == 0);
+
+ mp->stats->file = file;
+ mp->stats->line = line;
+ mp->stats->count = 0;
+ timevalclear(&mp->stats->locked_total);
+ timevalclear(&mp->stats->wait_total);
+ for (i = 0; i < ISC_MUTEX_MAX_LOCKERS; i++) {
+ mp->stats->lockers[i].file = NULL;
+ mp->stats->lockers[i].line = 0;
+ mp->stats->lockers[i].count = 0;
+ timevalclear(&mp->stats->lockers[i].locked_total);
+ timevalclear(&mp->stats->lockers[i].wait_total);
+ }
+}
+
+isc_result_t
+isc_mutex_lock_profile(isc_mutex_t *mp, const char *file, int line) {
+ struct timeval prelock_t;
+ struct timeval postlock_t;
+ isc_mutexlocker_t *locker = NULL;
+ int i;
+
+ gettimeofday(&prelock_t, NULL);
+
+ if (pthread_mutex_lock(&mp->mutex) != 0) {
+ return (ISC_R_UNEXPECTED);
+ }
+
+ gettimeofday(&postlock_t, NULL);
+ mp->stats->lock_t = postlock_t;
+
+ timevalsub(&postlock_t, &prelock_t);
+
+ mp->stats->count++;
+ timevaladd(&mp->stats->wait_total, &postlock_t);
+
+ for (i = 0; i < ISC_MUTEX_MAX_LOCKERS; i++) {
+ if (mp->stats->lockers[i].file == NULL) {
+ locker = &mp->stats->lockers[i];
+ locker->file = file;
+ locker->line = line;
+ break;
+ } else if (mp->stats->lockers[i].file == file &&
+ mp->stats->lockers[i].line == line)
+ {
+ locker = &mp->stats->lockers[i];
+ break;
+ }
+ }
+
+ if (locker != NULL) {
+ locker->count++;
+ timevaladd(&locker->wait_total, &postlock_t);
+ }
+
+ mp->stats->cur_locker = locker;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_mutex_unlock_profile(isc_mutex_t *mp, const char *file, int line) {
+ struct timeval unlock_t;
+
+ UNUSED(file);
+ UNUSED(line);
+
+ if (mp->stats->cur_locker != NULL) {
+ gettimeofday(&unlock_t, NULL);
+ timevalsub(&unlock_t, &mp->stats->lock_t);
+ timevaladd(&mp->stats->locked_total, &unlock_t);
+ timevaladd(&mp->stats->cur_locker->locked_total, &unlock_t);
+ mp->stats->cur_locker = NULL;
+ }
+
+ return ((pthread_mutex_unlock((&mp->mutex)) == 0) ? ISC_R_SUCCESS
+ : ISC_R_UNEXPECTED);
+}
+
+void
+isc_mutex_statsprofile(FILE *fp) {
+ isc_mutexlocker_t *locker;
+ int i, j;
+
+ fprintf(fp, "Mutex stats (in us)\n");
+ for (i = 0; i < stats_next; i++) {
+ fprintf(fp, "%-12s %4d: %10u %lu.%06lu %lu.%06lu %5d\n",
+ stats[i].file, stats[i].line, stats[i].count,
+ stats[i].locked_total.tv_sec,
+ stats[i].locked_total.tv_usec,
+ stats[i].wait_total.tv_sec, stats[i].wait_total.tv_usec,
+ i);
+ for (j = 0; j < ISC_MUTEX_MAX_LOCKERS; j++) {
+ locker = &stats[i].lockers[j];
+ if (locker->file == NULL) {
+ continue;
+ }
+ fprintf(fp,
+ " %-11s %4d: %10u %lu.%06lu %lu.%06lu %5d\n",
+ locker->file, locker->line, locker->count,
+ locker->locked_total.tv_sec,
+ locker->locked_total.tv_usec,
+ locker->wait_total.tv_sec,
+ locker->wait_total.tv_usec, i);
+ }
+ }
+}
+
+#endif /* ISC_MUTEX_PROFILE */
+
+#if ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK)
+
+static bool errcheck_initialized = false;
+static pthread_mutexattr_t errcheck;
+static isc_once_t once_errcheck = ISC_ONCE_INIT;
+
+static void
+initialize_errcheck(void) {
+ RUNTIME_CHECK(pthread_mutexattr_init(&errcheck) == 0);
+ RUNTIME_CHECK(pthread_mutexattr_settype(&errcheck,
+ PTHREAD_MUTEX_ERRORCHECK) == 0);
+ errcheck_initialized = true;
+}
+
+void
+isc_mutex_init_errcheck(isc_mutex_t *mp) {
+ isc_result_t result;
+ int err;
+
+ result = isc_once_do(&once_errcheck, initialize_errcheck);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ err = pthread_mutex_init(mp, &errcheck);
+ if (err != 0) {
+ strerror_r(err, strbuf, sizeof(strbuf));
+ isc_error_fatal(file, line, "pthread_mutex_init failed: %s",
+ strbuf);
+ }
+}
+#endif /* if ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK) */
+
+#if ISC_MUTEX_DEBUG && defined(__NetBSD__) && defined(PTHREAD_MUTEX_ERRORCHECK)
+pthread_mutexattr_t isc__mutex_attrs = {
+ PTHREAD_MUTEX_ERRORCHECK, /* m_type */
+ 0 /* m_flags, which appears to be unused. */
+};
+#endif /* if ISC_MUTEX_DEBUG && defined(__NetBSD__) && \
+ * defined(PTHREAD_MUTEX_ERRORCHECK) */
+
+#if !(ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK)) && \
+ !ISC_MUTEX_PROFILE
+
+#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;
+#endif /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */
+
+#ifdef HAVE_PTHREAD_MUTEX_ADAPTIVE_NP
+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 */
+
+void
+isc__mutex_init(isc_mutex_t *mp, const char *file, unsigned int line) {
+ int err;
+
+#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);
+
+ err = pthread_mutex_init(mp, &attr);
+#else /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */
+ err = pthread_mutex_init(mp, ISC__MUTEX_ATTRS);
+#endif /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */
+ if (err != 0) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(err, strbuf, sizeof(strbuf));
+ isc_error_fatal(file, line, "pthread_mutex_init failed: %s",
+ strbuf);
+ }
+}
+#endif /* if !(ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK)) && \
+ * !ISC_MUTEX_PROFILE */
diff --git a/lib/isc/pthreads/thread.c b/lib/isc/pthreads/thread.c
new file mode 100644
index 0000000..4c7380c
--- /dev/null
+++ b/lib/isc/pthreads/thread.c
@@ -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.
+ */
+
+/*! \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/strerr.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 */
+
+#define _FATAL(r, f) \
+ { \
+ char strbuf[ISC_STRERRORSIZE]; \
+ strerror_r(r, strbuf, sizeof(strbuf)); \
+ isc_error_fatal(__FILE__, __LINE__, f " failed: %s", strbuf); \
+ }
+
+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(ret, "pthread_attr_getstacksize()");
+ }
+
+ if (stacksize < THREAD_MINSTACKSIZE) {
+ ret = pthread_attr_setstacksize(&attr, THREAD_MINSTACKSIZE);
+ if (ret != 0) {
+ _FATAL(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(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(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/quota.c b/lib/isc/quota.c
new file mode 100644
index 0000000..02c7176
--- /dev/null
+++ b/lib/isc/quota.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 <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);
+ 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) {
+ /*
+ * 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;
+ }
+ }
+
+ INSIST(atomic_fetch_sub_release(&quota->used, 1) > 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..d84e5d0
--- /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;
+ }
+
+ isc_mem_put(radix->mctx, node, sizeof(*node));
+ radix->num_active_node--;
+
+ if (parent->r == node) {
+ parent->r = child;
+ } else {
+ INSIST(parent->l == node);
+ parent->l = child;
+ }
+}
diff --git a/lib/isc/random.c b/lib/isc/random.c
new file mode 100644
index 0000000..b11c39f
--- /dev/null
+++ b/lib/isc/random.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.
+ */
+
+/*
+ * 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/platform.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.
+ */
+#include "xoshiro128starstar.c"
+
+ISC_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..f502bbd
--- /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,
+ false);
+ 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/result.c b/lib/isc/result.c
new file mode 100644
index 0000000..d083000
--- /dev/null
+++ b/lib/isc/result.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/lib.h>
+#include <isc/once.h>
+#include <isc/resultclass.h>
+#include <isc/rwlock.h>
+#include <isc/util.h>
+
+typedef struct resulttable {
+ unsigned int base;
+ unsigned int last;
+ const char **text;
+ int set;
+ ISC_LINK(struct resulttable) link;
+} resulttable;
+
+typedef ISC_LIST(resulttable) resulttable_list_t;
+
+static const char *description[ISC_R_NRESULTS] = {
+ "success", /*%< 0 */
+ "out of memory", /*%< 1 */
+ "timed out", /*%< 2 */
+ "no available threads", /*%< 3 */
+ "address not available", /*%< 4 */
+ "address in use", /*%< 5 */
+ "permission denied", /*%< 6 */
+ "no pending connections", /*%< 7 */
+ "network unreachable", /*%< 8 */
+ "host unreachable", /*%< 9 */
+ "network down", /*%< 10 */
+ "host down", /*%< 11 */
+ "connection refused", /*%< 12 */
+ "not enough free resources", /*%< 13 */
+ "end of file", /*%< 14 */
+ "socket already bound", /*%< 15 */
+ "reload", /*%< 16 */
+ "lock busy", /*%< 17 */
+ "already exists", /*%< 18 */
+ "ran out of space", /*%< 19 */
+ "operation canceled", /*%< 20 */
+ "socket is not bound", /*%< 21 */
+ "shutting down", /*%< 22 */
+ "not found", /*%< 23 */
+ "unexpected end of input", /*%< 24 */
+ "failure", /*%< 25 */
+ "I/O error", /*%< 26 */
+ "not implemented", /*%< 27 */
+ "unbalanced parentheses", /*%< 28 */
+ "no more", /*%< 29 */
+ "invalid file", /*%< 30 */
+ "bad base64 encoding", /*%< 31 */
+ "unexpected token", /*%< 32 */
+ "quota reached", /*%< 33 */
+ "unexpected error", /*%< 34 */
+ "already running", /*%< 35 */
+ "ignore", /*%< 36 */
+ "address mask not contiguous", /*%< 37 */
+ "file not found", /*%< 38 */
+ "file already exists", /*%< 39 */
+ "socket is not connected", /*%< 40 */
+ "out of range", /*%< 41 */
+ "out of entropy", /*%< 42 */
+ "invalid use of multicast address", /*%< 43 */
+ "not a file", /*%< 44 */
+ "not a directory", /*%< 45 */
+ "queue is empty", /*%< 46 */
+ "address family mismatch", /*%< 47 */
+ "address family not supported", /*%< 48 */
+ "bad hex encoding", /*%< 49 */
+ "too many open files", /*%< 50 */
+ "not blocking", /*%< 51 */
+ "unbalanced quotes", /*%< 52 */
+ "operation in progress", /*%< 53 */
+ "connection reset", /*%< 54 */
+ "soft quota reached", /*%< 55 */
+ "not a valid number", /*%< 56 */
+ "disabled", /*%< 57 */
+ "max size", /*%< 58 */
+ "invalid address format", /*%< 59 */
+ "bad base32 encoding", /*%< 60 */
+ "unset", /*%< 61 */
+ "multiple", /*%< 62 */
+ "would block", /*%< 63 */
+ "complete", /*%< 64 */
+ "crypto failure", /*%< 65 */
+ "disc quota", /*%< 66 */
+ "disc full", /*%< 67 */
+ "default", /*%< 68 */
+ "IPv4 prefix", /*%< 69 */
+ "TLS error", /*%< 70 */
+ "ALPN for HTTP/2 failed" /*%< 71 */
+};
+
+static const char *identifier[ISC_R_NRESULTS] = { "ISC_R_SUCCESS",
+ "ISC_R_NOMEMORY",
+ "ISC_R_TIMEDOUT",
+ "ISC_R_NOTHREADS",
+ "ISC_R_ADDRNOTAVAIL",
+ "ISC_R_ADDRINUSE",
+ "ISC_R_NOPERM",
+ "ISC_R_NOCONN",
+ "ISC_R_NETUNREACH",
+ "ISC_R_HOSTUNREACH",
+ "ISC_R_NETDOWN",
+ "ISC_R_HOSTDOWN",
+ "ISC_R_CONNREFUSED",
+ "ISC_R_NORESOURCES",
+ "ISC_R_EOF",
+ "ISC_R_BOUND",
+ "ISC_R_RELOAD",
+ "ISC_R_LOCKBUSY",
+ "ISC_R_EXISTS",
+ "ISC_R_NOSPACE",
+ "ISC_R_CANCELED",
+ "ISC_R_NOTBOUND",
+ "ISC_R_SHUTTINGDOWN",
+ "ISC_R_NOTFOUND",
+ "ISC_R_UNEXPECTEDEND",
+ "ISC_R_FAILURE",
+ "ISC_R_IOERROR",
+ "ISC_R_NOTIMPLEMENTED",
+ "ISC_R_UNBALANCED",
+ "ISC_R_NOMORE",
+ "ISC_R_INVALIDFILE",
+ "ISC_R_BADBASE64",
+ "ISC_R_UNEXPECTEDTOKEN",
+ "ISC_R_QUOTA",
+ "ISC_R_UNEXPECTED",
+ "ISC_R_ALREADYRUNNING",
+ "ISC_R_IGNORE",
+ "ISC_R_MASKNONCONTIG",
+ "ISC_R_FILENOTFOUND",
+ "ISC_R_FILEEXISTS",
+ "ISC_R_NOTCONNECTED",
+ "ISC_R_RANGE",
+ "ISC_R_NOENTROPY",
+ "ISC_R_MULTICAST",
+ "ISC_R_NOTFILE",
+ "ISC_R_NOTDIRECTORY",
+ "ISC_R_EMPTY",
+ "ISC_R_FAMILYMISMATCH",
+ "ISC_R_FAMILYNOSUPPORT",
+ "ISC_R_BADHEX",
+ "ISC_R_TOOMANYOPENFILES",
+ "ISC_R_NOTBLOCKING",
+ "ISC_R_UNBALANCEDQUOTES",
+ "ISC_R_INPROGRESS",
+ "ISC_R_CONNECTIONRESET",
+ "ISC_R_SOFTQUOTA",
+ "ISC_R_BADNUMBER",
+ "ISC_R_DISABLED",
+ "ISC_R_MAXSIZE",
+ "ISC_R_BADADDRESSFORM",
+ "ISC_R_BADBASE32",
+ "ISC_R_UNSET",
+ "ISC_R_MULTIPLE",
+ "ISC_R_WOULDBLOCK",
+ "ISC_R_COMPLETE",
+ "ISC_R_CRYPTOFAILURE",
+ "ISC_R_DISCQUOTA",
+ "ISC_R_DISCFULL",
+ "ISC_R_DEFAULT",
+ "ISC_R_IPV4PREFIX",
+ "ISC_R_TLSERROR",
+ "ISC_R_HTTP2ALPNERROR" };
+
+#define ISC_RESULT_RESULTSET 2
+#define ISC_RESULT_UNAVAILABLESET 3
+
+static isc_once_t once = ISC_ONCE_INIT;
+static resulttable_list_t description_tables;
+static resulttable_list_t identifier_tables;
+static isc_rwlock_t lock;
+
+static isc_result_t
+register_table(resulttable_list_t *tables, unsigned int base,
+ unsigned int nresults, const char **text, int set) {
+ resulttable *table;
+
+ REQUIRE(base % ISC_RESULTCLASS_SIZE == 0);
+ REQUIRE(nresults <= ISC_RESULTCLASS_SIZE);
+ REQUIRE(text != NULL);
+
+ /*
+ * We use malloc() here because we we want to be able to use
+ * isc_result_totext() even if there is no memory context.
+ */
+ table = malloc(sizeof(*table));
+ if (table == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ table->base = base;
+ table->last = base + nresults - 1;
+ table->text = text;
+ table->set = set;
+ ISC_LINK_INIT(table, link);
+
+ RWLOCK(&lock, isc_rwlocktype_write);
+
+ ISC_LIST_APPEND(*tables, table, link);
+
+ RWUNLOCK(&lock, isc_rwlocktype_write);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+initialize_action(void) {
+ isc_result_t result;
+
+ isc_rwlock_init(&lock, 0, 0);
+ ISC_LIST_INIT(description_tables);
+ ISC_LIST_INIT(identifier_tables);
+
+ result = register_table(&description_tables, ISC_RESULTCLASS_ISC,
+ ISC_R_NRESULTS, description,
+ ISC_RESULT_RESULTSET);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "register_table() failed: %u", result);
+ }
+
+ result = register_table(&identifier_tables, ISC_RESULTCLASS_ISC,
+ ISC_R_NRESULTS, identifier,
+ ISC_RESULT_RESULTSET);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "register_table() failed: %u", result);
+ }
+}
+
+static void
+initialize(void) {
+ RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS);
+}
+
+static const char *
+isc_result_tomany_helper(resulttable_list_t *tables, isc_result_t result) {
+ resulttable *table;
+ const char *text;
+ int index;
+
+ initialize();
+
+ RWLOCK(&lock, isc_rwlocktype_read);
+
+ text = NULL;
+ for (table = ISC_LIST_HEAD(*tables); table != NULL;
+ table = ISC_LIST_NEXT(table, link))
+ {
+ if (result >= table->base && result <= table->last) {
+ index = (int)(result - table->base);
+ text = table->text[index];
+ break;
+ }
+ }
+ if (text == NULL) {
+ text = "(result code text not available)";
+ }
+
+ RWUNLOCK(&lock, isc_rwlocktype_read);
+
+ return (text);
+}
+
+const char *
+isc_result_totext(isc_result_t result) {
+ return (isc_result_tomany_helper(&description_tables, result));
+}
+
+const char *
+isc_result_toid(isc_result_t result) {
+ return (isc_result_tomany_helper(&identifier_tables, result));
+}
+
+isc_result_t
+isc_result_register(unsigned int base, unsigned int nresults, const char **text,
+ int set) {
+ initialize();
+
+ return (register_table(&description_tables, base, nresults, text, set));
+}
+
+isc_result_t
+isc_result_registerids(unsigned int base, unsigned int nresults,
+ const char **ids, int set) {
+ initialize();
+
+ return (register_table(&identifier_tables, base, nresults, ids, set));
+}
diff --git a/lib/isc/rwlock.c b/lib/isc/rwlock.c
new file mode 100644
index 0000000..c69b6d7
--- /dev/null
+++ b/lib/isc/rwlock.c
@@ -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.
+ */
+
+/*! \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/platform.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(__FILE__, __LINE__,
+ "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..16afbd5
--- /dev/null
+++ b/lib/isc/sockaddr.c
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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>
+#if defined(WIN32) || defined(WIN64)
+#include <malloc.h>
+#endif /* if defined(WIN32) || defined(WIN64) */
+
+#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;
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ 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);
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ 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(__FILE__, __LINE__,
+ "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(__FILE__, __LINE__, "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(__FILE__, __LINE__, "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(__FILE__, __LINE__, "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) {
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ 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);
+#else /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ UNUSED(sockaddr);
+ UNUSED(path);
+ return (ISC_R_NOTIMPLEMENTED);
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+}
+
+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;
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ case AF_UNIX:
+ length = sizeof(isa->type.sunix);
+ break;
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ 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..aca8074
--- /dev/null
+++ b/lib/isc/stats.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/platform.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)
+
+#if defined(_WIN32) && !defined(_WIN64)
+typedef atomic_int_fast32_t isc__atomic_statcounter_t;
+#else /* if defined(_WIN32) && !defined(_WIN64) */
+typedef atomic_int_fast64_t isc__atomic_statcounter_t;
+#endif /* if defined(_WIN32) && !defined(_WIN64) */
+
+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/string.c b/lib/isc/string.c
new file mode 100644
index 0000000..5de6d88
--- /dev/null
+++ b/lib/isc/string.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.
+ */
+
+/*
+ * 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 */
+#include <isc/util.h>
+
+#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, *r;
+ 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--;
+ }
+ DE_CONST(s, r);
+ return (r);
+}
+#endif
+
+int
+isc_string_strerror_r(int errnum, char *buf, size_t buflen) {
+#if defined(_WIN32) || defined(_WIN64)
+ return (strerror_s(buf, buflen, errnum));
+#else /* if defined(_WIN32) || defined(_WIN64) */
+ return (strerror_r(errnum, buf, buflen));
+#endif /* if defined(_WIN32) || defined(_WIN64) */
+}
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/task.c b/lib/isc/task.c
new file mode 100644
index 0000000..3d8deaf
--- /dev/null
+++ b/lib/isc/task.c
@@ -0,0 +1,1486 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can 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/platform.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_paused, /* not running, paused */
+ task_state_pausing, /* running, waiting to be paused */
+ 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", "paused", "pausing", "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;
+ int pause_cnt;
+ 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;
+ task->pause_cnt = 0;
+
+ 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_paused ||
+ task->state == task_state_pausing ||
+ 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 ||
+ task->state == task_state_paused ||
+ task->state == task_state_pausing);
+ 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_unsendrange(isc_task_t *task, void *sender, isc_eventtype_t first,
+ isc_eventtype_t last, void *tag, isc_eventlist_t *events) {
+ /*
+ * Remove events from a task's event queue.
+ */
+ REQUIRE(VALID_TASK(task));
+
+ XTRACE("isc_task_unsendrange");
+
+ return (dequeue_events(task, sender, first, last, tag, events, false));
+}
+
+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);
+}
+
+void
+isc_task_getcurrenttime(isc_task_t *task, isc_stdtime_t *t) {
+ REQUIRE(VALID_TASK(task));
+ REQUIRE(t != NULL);
+
+ LOCK(&task->lock);
+ *t = task->now;
+ UNLOCK(&task->lock);
+}
+
+void
+isc_task_getcurrenttimex(isc_task_t *task, isc_time_t *t) {
+ REQUIRE(VALID_TASK(task));
+ REQUIRE(t != NULL);
+
+ LOCK(&task->lock);
+ *t = task->tnow;
+ UNLOCK(&task->lock);
+}
+
+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;
+
+ /*
+ * It is possible because that we have a paused task in the queue - it
+ * might have been paused in the meantime and we never hold both queue
+ * and task lock to avoid deadlocks, just bail then.
+ */
+ 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;
+ } else if (task->state == task_state_pausing) {
+ XTRACE("pausing");
+ task->state = task_state_paused;
+ }
+ }
+ break;
+ } else if (task->state == task_state_pausing) {
+ /*
+ * We got a pause request on this task, stop working on
+ * it and switch the state to paused.
+ */
+ XTRACE("pausing");
+ task->state = task_state_paused;
+ 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);
+ if (was_idle) {
+ task->threadid = 0;
+ }
+ 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;
+
+ 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");
+ }
+
+ REQUIRE(atomic_compare_exchange_strong(&manager->exclusive_req,
+ &(bool){ true }, false));
+}
+
+void
+isc_task_pause(isc_task_t *task) {
+ REQUIRE(VALID_TASK(task));
+
+ LOCK(&task->lock);
+ task->pause_cnt++;
+ if (task->pause_cnt > 1) {
+ /*
+ * Someone already paused this task, just increase
+ * the number of pausing clients.
+ */
+ UNLOCK(&task->lock);
+ return;
+ }
+
+ INSIST(task->state == task_state_idle ||
+ task->state == task_state_ready ||
+ task->state == task_state_running);
+ if (task->state == task_state_running) {
+ task->state = task_state_pausing;
+ } else {
+ task->state = task_state_paused;
+ }
+ UNLOCK(&task->lock);
+}
+
+void
+isc_task_unpause(isc_task_t *task) {
+ bool was_idle = false;
+
+ REQUIRE(VALID_TASK(task));
+
+ LOCK(&task->lock);
+ task->pause_cnt--;
+ INSIST(task->pause_cnt >= 0);
+ if (task->pause_cnt > 0) {
+ UNLOCK(&task->lock);
+ return;
+ }
+
+ INSIST(task->state == task_state_paused ||
+ task->state == task_state_pausing);
+ /* If the task was pausing we can't reschedule it */
+ if (task->state == task_state_pausing) {
+ task->state = task_state_running;
+ } else {
+ task->state = task_state_idle;
+ }
+ if (task->state == task_state_idle && !EMPTY(task->events)) {
+ task->state = task_state_ready;
+ was_idle = true;
+ }
+ UNLOCK(&task->lock);
+
+ if (was_idle) {
+ task_ready(task);
+ }
+}
+
+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/tests/Kyuafile b/lib/isc/tests/Kyuafile
new file mode 100644
index 0000000..89b962c
--- /dev/null
+++ b/lib/isc/tests/Kyuafile
@@ -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.
+
+syntax(2)
+test_suite('bind9')
+
+tap_test_program{name='aes_test'}
+tap_test_program{name='buffer_test'}
+tap_test_program{name='counter_test'}
+tap_test_program{name='errno_test'}
+tap_test_program{name='file_test'}
+tap_test_program{name='hash_test'}
+tap_test_program{name='heap_test'}
+tap_test_program{name='hmac_test'}
+tap_test_program{name='ht_test'}
+tap_test_program{name='lex_test'}
+tap_test_program{name='md_test'}
+tap_test_program{name='mem_test'}
+tap_test_program{name='netaddr_test'}
+tap_test_program{name='netmgr_test'}
+tap_test_program{name='parse_test'}
+tap_test_program{name='pool_test'}
+tap_test_program{name='radix_test'}
+tap_test_program{name='quota_test'}
+tap_test_program{name='regex_test'}
+tap_test_program{name='result_test'}
+tap_test_program{name='safe_test'}
+tap_test_program{name='siphash_test'}
+tap_test_program{name='sockaddr_test'}
+tap_test_program{name='socket_test'}
+tap_test_program{name='symtab_test'}
+tap_test_program{name='task_test'}
+tap_test_program{name='taskpool_test'}
+tap_test_program{name='time_test'}
+tap_test_program{name='timer_test'}
diff --git a/lib/isc/tests/Makefile.in b/lib/isc/tests/Makefile.in
new file mode 100644
index 0000000..2a2020f
--- /dev/null
+++ b/lib/isc/tests/Makefile.in
@@ -0,0 +1,227 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. -Iinclude ${ISC_INCLUDES} \
+ ${OPENSSL_CFLAGS} @CMOCKA_CFLAGS@ \
+ ${JSON_C_CFLAGS} \
+ ${LIBUV_CFLAGS} \
+ ${LIBXML2_CFLAGS}
+CDEFINES = -DTESTS="\"${top_builddir}/lib/isc/tests/\""
+
+ISCLIBS = ../libisc.@A@ @NO_LIBTOOL_ISCLIBS@
+ISCDEPLIBS = ../libisc.@A@
+
+LIBS = @LIBS@ @CMOCKA_LIBS@
+
+OBJS = isctest.@O@
+
+SRCS = isctest.c aes_test.c buffer_test.c \
+ counter_test.c crc64_test.c errno_test.c file_test.c hash_test.c \
+ heap_test.c hmac_test.c ht_test.c lex_test.c \
+ mem_test.c md_test.c netaddr_test.c netmgr_test.c \
+ parse_test.c pool_test.c \
+ quota_test.c radix_test.c random_test.c \
+ regex_test.c result_test.c safe_test.c siphash_test.c sockaddr_test.c \
+ socket_test.c stats_test.c symtab_test.c task_test.c \
+ taskpool_test.c time_test.c timer_test.c
+
+SUBDIRS =
+TARGETS = aes_test@EXEEXT@ buffer_test@EXEEXT@ \
+ counter_test@EXEEXT@ crc64_test@EXEEXT@ \
+ errno_test@EXEEXT@ file_test@EXEEXT@ \
+ hash_test@EXEEXT@ heap_test@EXEEXT@ hmac_test@EXEEXT@ \
+ ht_test@EXEEXT@ lex_test@EXEEXT@ \
+ mem_test@EXEEXT@ md_test@EXEEXT@ \
+ netaddr_test@EXEEXT@ netmgr_test@EXEEXT@ \
+ parse_test@EXEEXT@ pool_test@EXEEXT@ \
+ quota_test@EXEEXT@ radix_test@EXEEXT@ \
+ random_test@EXEEXT@ regex_test@EXEEXT@ result_test@EXEEXT@ \
+ safe_test@EXEEXT@ siphash_test@EXEEXT@ sockaddr_test@EXEEXT@ \
+ socket_test@EXEEXT@ stats_test@EXEEXT@ symtab_test@EXEEXT@ \
+ task_test@EXEEXT@ taskpool_test@EXEEXT@ time_test@EXEEXT@ \
+ timer_test@EXEEXT@
+
+@BIND9_MAKE_RULES@
+
+aes_test@EXEEXT@: aes_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ aes_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+buffer_test@EXEEXT@: buffer_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ buffer_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+counter_test@EXEEXT@: counter_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ counter_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+crc64_test@EXEEXT@: crc64_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ crc64_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+errno_test@EXEEXT@: errno_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ errno_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+file_test@EXEEXT@: file_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ file_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+hash_test@EXEEXT@: hash_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ hash_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+heap_test@EXEEXT@: heap_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ heap_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+hmac_test@EXEEXT@: hmac_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ hmac_test.@O@ \
+ ${ISCLIBS} ${OPENSSL_LIBS} ${LIBS}
+
+ht_test@EXEEXT@: ht_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ ht_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+lex_test@EXEEXT@: lex_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ lex_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+md_test@EXEEXT@: md_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ md_test.@O@ \
+ ${ISCLIBS} ${OPENSSL_LIBS} ${LIBS}
+
+mem_test@EXEEXT@: mem_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ mem_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+netaddr_test@EXEEXT@: netaddr_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ netaddr_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+parse_test@EXEEXT@: parse_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ parse_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+pool_test@EXEEXT@: pool_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ pool_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+quota_test@EXEEXT@: quota_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ quota_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+radix_test@EXEEXT@: radix_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ radix_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+random_test@EXEEXT@: random_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ random_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS} -lm
+
+regex_test@EXEEXT@: regex_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ regex_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+result_test@EXEEXT@: result_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ result_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+safe_test@EXEEXT@: safe_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ safe_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+siphash_test@EXEEXT@: siphash_test.@O@ ../siphash.c ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ siphash_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+socket_test@EXEEXT@: socket_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ socket_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+sockaddr_test@EXEEXT@: sockaddr_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ sockaddr_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+stats_test@EXEEXT@: stats_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ stats_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+symtab_test@EXEEXT@: symtab_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ symtab_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+task_test@EXEEXT@: task_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ task_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+taskpool_test@EXEEXT@: taskpool_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ taskpool_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+netmgr_test@EXEEXT@: netmgr_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ netmgr_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS} ${LIBUV_LIBS}
+
+time_test@EXEEXT@: time_test.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ time_test.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+timer_test@EXEEXT@: timer_test.@O@ isctest.@O@ ${ISCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ timer_test.@O@ isctest.@O@ \
+ ${ISCLIBS} ${LIBS}
+
+unit::
+ sh ${top_builddir}/unit/unittest.sh
+
+clean distclean::
+ rm -f ${TARGETS}
+ rm -f atf.out
diff --git a/lib/isc/tests/aes_test.c b/lib/isc/tests/aes_test.c
new file mode 100644
index 0000000..2990184
--- /dev/null
+++ b/lib/isc/tests/aes_test.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/aes.h>
+#include <isc/buffer.h>
+#include <isc/hex.h>
+#include <isc/platform.h>
+#include <isc/region.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+/*
+ * Test data from NIST KAT
+ */
+
+isc_result_t
+tohexstr(unsigned char *d, char *out);
+
+size_t
+fromhexstr(const char *in, unsigned char *d);
+
+unsigned char plaintext[3 * ISC_AES_BLOCK_LENGTH];
+unsigned char ciphertext[ISC_AES_BLOCK_LENGTH];
+char str[2 * ISC_AES_BLOCK_LENGTH + 1];
+unsigned char key[ISC_AES256_KEYLENGTH + 1];
+size_t len;
+
+isc_result_t
+tohexstr(unsigned char *d, char *out) {
+ isc_buffer_t b;
+ isc_region_t r;
+
+ isc_buffer_init(&b, out, 2 * ISC_AES_BLOCK_LENGTH + 1);
+ r.base = d;
+ r.length = ISC_AES_BLOCK_LENGTH;
+ return (isc_hex_totext(&r, 0, "", &b));
+}
+
+size_t
+fromhexstr(const char *in, unsigned char *d) {
+ isc_buffer_t b;
+ isc_result_t ret;
+
+ isc_buffer_init(&b, d, ISC_AES256_KEYLENGTH + 1);
+ ret = isc_hex_decodestring(in, &b);
+ if (ret != ISC_R_SUCCESS) {
+ return (0);
+ }
+ return (isc_buffer_usedlength(&b));
+}
+
+typedef struct aes_testcase {
+ const char *key;
+ const char *input;
+ const char *result;
+} aes_testcase_t;
+
+/* AES 128 test vectors */
+static void
+isc_aes128_test(void **state) {
+ aes_testcase_t testcases[] = { /* Test 1 (KAT ECBVarTxt128 #3) */
+ { "00000000000000000000000000000000",
+ "F0000000000000000000000000000000",
+ "96D9FD5CC4F07441727DF0F33E401A36" },
+ /* Test 2 (KAT ECBVarTxt128 #123) */
+ { "00000000000000000000000000000000",
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0",
+ "F9B0FDA0C4A898F5B9E6F661C4CE4D07" },
+ /* Test 3 (KAT ECBVarKey128 #3) */
+ { "F0000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "970014D634E2B7650777E8E84D03CCD8" },
+ /* Test 4 (KAT ECBVarKey128 #123) */
+ { "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0",
+ "00000000000000000000000000000000",
+ "41C78C135ED9E98C096640647265DA1E" },
+ /* Test 5 (KAT ECBGFSbox128 #3) */
+ { "00000000000000000000000000000000",
+ "6A118A874519E64E9963798A503F1D35",
+ "DC43BE40BE0E53712F7E2BF5CA707209" },
+ /* Test 6 (KAT ECBKeySbox128 #3) */
+ { "B6364AC4E1DE1E285EAF144A2415F7A0",
+ "00000000000000000000000000000000",
+ "5D9B05578FC944B3CF1CCF0E746CD581" },
+ { NULL, NULL, NULL }
+ };
+
+ aes_testcase_t *testcase = testcases;
+
+ UNUSED(state);
+
+ while (testcase->key != NULL) {
+ len = fromhexstr(testcase->key, key);
+ assert_int_equal(len, ISC_AES128_KEYLENGTH);
+ len = fromhexstr(testcase->input, plaintext);
+ assert_int_equal(len, ISC_AES_BLOCK_LENGTH);
+ isc_aes128_crypt(key, plaintext, ciphertext);
+ assert_int_equal(tohexstr(ciphertext, str), ISC_R_SUCCESS);
+ assert_string_equal(str, testcase->result);
+
+ testcase++;
+ }
+}
+
+/* AES 192 test vectors */
+static void
+isc_aes192_test(void **state) {
+ aes_testcase_t testcases[] = {
+ /* Test 1 (KAT ECBVarTxt192 #3) */
+ { "000000000000000000000000000000000000000000000000",
+ "F0000000000000000000000000000000",
+ "2A560364CE529EFC21788779568D5555" },
+ /* Test 2 (KAT ECBVarTxt192 #123) */
+ { "000000000000000000000000000000000000000000000000",
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0",
+ "2AABB999F43693175AF65C6C612C46FB" },
+ /* Test 3 (KAT ECBVarKey192 #3) */
+ { "F00000000000000000000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "180B09F267C45145DB2F826C2582D35C" },
+ /* Test 4 (KAT ECBVarKey192 #187) */
+ { "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0",
+ "00000000000000000000000000000000",
+ "EACF1E6C4224EFB38900B185AB1DFD42" },
+ /* Test 5 (KAT ECBGFSbox192 #3) */
+ { "000000000000000000000000000000000000000000000000",
+ "51719783D3185A535BD75ADC65071CE1",
+ "4F354592FF7C8847D2D0870CA9481B7C" },
+ /* Test 6 (KAT ECBKeySbox192 #3) */
+ { "CD62376D5EBB414917F0C78F05266433DC9192A1EC943300",
+ "00000000000000000000000000000000",
+ "7F6C25FF41858561BB62F36492E93C29" },
+ { NULL, NULL, NULL }
+ };
+
+ aes_testcase_t *testcase = testcases;
+
+ UNUSED(state);
+
+ while (testcase->key != NULL) {
+ len = fromhexstr(testcase->key, key);
+ assert_int_equal(len, ISC_AES192_KEYLENGTH);
+ len = fromhexstr(testcase->input, plaintext);
+ assert_int_equal(len, ISC_AES_BLOCK_LENGTH);
+ isc_aes192_crypt(key, plaintext, ciphertext);
+ assert_int_equal(tohexstr(ciphertext, str), ISC_R_SUCCESS);
+ assert_string_equal(str, testcase->result);
+
+ testcase++;
+ }
+}
+
+/* AES 256 test vectors */
+static void
+isc_aes256_test(void **state) {
+ aes_testcase_t testcases[] = { /* Test 1 (KAT ECBVarTxt256 #3) */
+ { "00000000000000000000000000000000"
+ "00000000000000000000000000000000",
+ "F0000000000000000000000000000000",
+ "7F2C5ECE07A98D8BEE13C51177395FF7" },
+ /* Test 2 (KAT ECBVarTxt256 #123) */
+ { "00000000000000000000000000000000"
+ "00000000000000000000000000000000",
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0",
+ "7240E524BC51D8C4D440B1BE55D1062C" },
+ /* Test 3 (KAT ECBVarKey256 #3) */
+ { "F0000000000000000000000000000000"
+ "00000000000000000000000000000000",
+ "00000000000000000000000000000000",
+ "1C777679D50037C79491A94DA76A9A35" },
+ /* Test 4 (KAT ECBVarKey256 #251) */
+ { "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0",
+ "00000000000000000000000000000000",
+ "03720371A04962EAEA0A852E69972858" },
+ /* Test 5 (KAT ECBGFSbox256 #3) */
+ { "00000000000000000000000000000000"
+ "00000000000000000000000000000000",
+ "8A560769D605868AD80D819BDBA03771",
+ "38F2C7AE10612415D27CA190D27DA8B4" },
+ /* Test 6 (KAT ECBKeySbox256 #3) */
+ { "984CA75F4EE8D706F46C2D98C0BF4A45"
+ "F5B00D791C2DFEB191B5ED8E420FD627",
+ "00000000000000000000000000000000",
+ "4307456A9E67813B452E15FA8FFFE398" },
+ { NULL, NULL, NULL }
+ };
+
+ aes_testcase_t *testcase = testcases;
+
+ UNUSED(state);
+
+ while (testcase->key != NULL) {
+ len = fromhexstr(testcase->key, key);
+ assert_int_equal(len, ISC_AES256_KEYLENGTH);
+ len = fromhexstr(testcase->input, plaintext);
+ assert_int_equal(len, ISC_AES_BLOCK_LENGTH);
+ isc_aes256_crypt(key, plaintext, ciphertext);
+ assert_int_equal(tohexstr(ciphertext, str), ISC_R_SUCCESS);
+ assert_string_equal(str, testcase->result);
+
+ testcase++;
+ }
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_aes128_test),
+ cmocka_unit_test(isc_aes192_test),
+ cmocka_unit_test(isc_aes256_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/buffer_test.c b/lib/isc/tests/buffer_test.c
new file mode 100644
index 0000000..91eb890
--- /dev/null
+++ b/lib/isc/tests/buffer_test.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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+/* reserve space in dynamic buffers */
+static void
+isc_buffer_reserve_test(void **state) {
+ isc_result_t result;
+ isc_buffer_t *b;
+
+ UNUSED(state);
+
+ b = NULL;
+ isc_buffer_allocate(test_mctx, &b, 1024);
+ assert_int_equal(b->length, 1024);
+
+ /*
+ * 1024 bytes should already be available, so this call does
+ * nothing.
+ */
+ result = isc_buffer_reserve(&b, 1024);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(ISC_BUFFER_VALID(b));
+ assert_non_null(b);
+ assert_int_equal(b->length, 1024);
+
+ /*
+ * This call should grow it to 2048 bytes as only 1024 bytes are
+ * available in the buffer.
+ */
+ result = isc_buffer_reserve(&b, 1025);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(ISC_BUFFER_VALID(b));
+ assert_non_null(b);
+ assert_int_equal(b->length, 2048);
+
+ /*
+ * 2048 bytes should already be available, so this call does
+ * nothing.
+ */
+ result = isc_buffer_reserve(&b, 2000);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(ISC_BUFFER_VALID(b));
+ assert_non_null(b);
+ assert_int_equal(b->length, 2048);
+
+ /*
+ * This call should grow it to 4096 bytes as only 2048 bytes are
+ * available in the buffer.
+ */
+ result = isc_buffer_reserve(&b, 3000);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(ISC_BUFFER_VALID(b));
+ assert_non_null(b);
+ assert_int_equal(b->length, 4096);
+
+ /* Consume some of the buffer so we can run the next test. */
+ isc_buffer_add(b, 4096);
+
+ /*
+ * This call should fail and leave buffer untouched.
+ */
+ result = isc_buffer_reserve(&b, UINT_MAX);
+ assert_int_equal(result, ISC_R_NOMEMORY);
+ assert_true(ISC_BUFFER_VALID(b));
+ assert_non_null(b);
+ assert_int_equal(b->length, 4096);
+
+ isc_buffer_free(&b);
+}
+
+/* dynamic buffer automatic reallocation */
+static void
+isc_buffer_dynamic_test(void **state) {
+ isc_buffer_t *b;
+ size_t last_length = 10;
+ int i;
+
+ UNUSED(state);
+
+ b = NULL;
+ isc_buffer_allocate(test_mctx, &b, last_length);
+ assert_non_null(b);
+ assert_int_equal(b->length, last_length);
+
+ isc_buffer_setautorealloc(b, true);
+
+ isc_buffer_putuint8(b, 1);
+
+ for (i = 0; i < 1000; i++) {
+ isc_buffer_putstr(b, "thisisa24charslongstring");
+ }
+ assert_true(b->length - last_length >= 1000 * 24);
+ last_length += 1000 * 24;
+
+ for (i = 0; i < 10000; i++) {
+ isc_buffer_putuint8(b, 1);
+ }
+
+ assert_true(b->length - last_length >= 10000 * 1);
+ last_length += 10000 * 1;
+
+ for (i = 0; i < 10000; i++) {
+ isc_buffer_putuint16(b, 1);
+ }
+
+ assert_true(b->length - last_length >= 10000 * 2);
+
+ last_length += 10000 * 2;
+ for (i = 0; i < 10000; i++) {
+ isc_buffer_putuint24(b, 1);
+ }
+ assert_true(b->length - last_length >= 10000 * 3);
+
+ last_length += 10000 * 3;
+
+ for (i = 0; i < 10000; i++) {
+ isc_buffer_putuint32(b, 1);
+ }
+ assert_true(b->length - last_length >= 10000 * 4);
+
+ isc_buffer_free(&b);
+}
+
+/* copy a region into a buffer */
+static void
+isc_buffer_copyregion_test(void **state) {
+ unsigned char data[] = { 0x11, 0x22, 0x33, 0x44 };
+ isc_buffer_t *b = NULL;
+ isc_result_t result;
+
+ isc_region_t r = {
+ .base = data,
+ .length = sizeof(data),
+ };
+
+ UNUSED(state);
+
+ isc_buffer_allocate(test_mctx, &b, sizeof(data));
+
+ /*
+ * Fill originally allocated buffer space.
+ */
+ result = isc_buffer_copyregion(b, &r);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Appending more data to the buffer should fail.
+ */
+ result = isc_buffer_copyregion(b, &r);
+ assert_int_equal(result, ISC_R_NOSPACE);
+
+ /*
+ * Enable auto reallocation and retry. Appending should now succeed.
+ */
+ isc_buffer_setautorealloc(b, true);
+ result = isc_buffer_copyregion(b, &r);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_free(&b);
+}
+
+/* sprintf() into a buffer */
+static void
+isc_buffer_printf_test(void **state) {
+ unsigned int used, prev_used;
+ const char *empty_fmt;
+ isc_result_t result;
+ isc_buffer_t *b, sb;
+ char buf[8];
+
+ UNUSED(state);
+
+ /*
+ * Prepare a buffer with auto-reallocation enabled.
+ */
+ b = NULL;
+ isc_buffer_allocate(test_mctx, &b, 0);
+ isc_buffer_setautorealloc(b, true);
+
+ /*
+ * Sanity check.
+ */
+ result = isc_buffer_printf(b, "foo");
+ assert_int_equal(result, ISC_R_SUCCESS);
+ used = isc_buffer_usedlength(b);
+ assert_int_equal(used, 3);
+
+ result = isc_buffer_printf(b, "bar");
+ assert_int_equal(result, ISC_R_SUCCESS);
+ used = isc_buffer_usedlength(b);
+ assert_int_equal(used, 3 + 3);
+
+ /*
+ * Also check the terminating NULL byte is there, even though it is not
+ * part of the buffer's used region.
+ */
+ assert_memory_equal(isc_buffer_current(b), "foobar", 7);
+
+ /*
+ * Skip over data from previous check to prevent failures in previous
+ * check from affecting this one.
+ */
+ prev_used = used;
+ isc_buffer_forward(b, prev_used);
+
+ /*
+ * Some standard usage checks.
+ */
+ isc_buffer_printf(b, "%d", 42);
+ used = isc_buffer_usedlength(b);
+ assert_int_equal(used - prev_used, 2);
+
+ isc_buffer_printf(b, "baz%1X", 42);
+ used = isc_buffer_usedlength(b);
+ assert_int_equal(used - prev_used, 2 + 5);
+
+ isc_buffer_printf(b, "%6.1f", 42.42f);
+ used = isc_buffer_usedlength(b);
+ assert_int_equal(used - prev_used, 2 + 5 + 6);
+
+ /*
+ * Also check the terminating NULL byte is there, even though it is not
+ * part of the buffer's used region.
+ */
+ assert_memory_equal(isc_buffer_current(b), "42baz2A 42.4", 14);
+
+ /*
+ * Check an empty format string is properly handled.
+ *
+ * Note: we don't use a string literal for the format string to
+ * avoid triggering [-Werror=format-zero-length].
+ * Note: we have a dummy third argument as some compilers complain
+ * without it.
+ */
+ prev_used = used;
+ empty_fmt = "";
+ result = isc_buffer_printf(b, empty_fmt, "");
+ assert_int_equal(result, ISC_R_SUCCESS);
+ used = isc_buffer_usedlength(b);
+ assert_int_equal(prev_used, used);
+
+ isc_buffer_free(&b);
+
+ /*
+ * Check overflow on a static buffer.
+ */
+ isc_buffer_init(&sb, buf, sizeof(buf));
+ result = isc_buffer_printf(&sb, "123456");
+ assert_int_equal(result, ISC_R_SUCCESS);
+ used = isc_buffer_usedlength(&sb);
+ assert_int_equal(used, 6);
+
+ result = isc_buffer_printf(&sb, "789");
+ assert_int_equal(result, ISC_R_NOSPACE);
+ used = isc_buffer_usedlength(&sb);
+ assert_int_equal(used, 6);
+
+ result = isc_buffer_printf(&sb, "78");
+ assert_int_equal(result, ISC_R_NOSPACE);
+ used = isc_buffer_usedlength(&sb);
+ assert_int_equal(used, 6);
+
+ result = isc_buffer_printf(&sb, "7");
+ assert_int_equal(result, ISC_R_SUCCESS);
+ used = isc_buffer_usedlength(&sb);
+ assert_int_equal(used, 7);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(isc_buffer_reserve_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(isc_buffer_dynamic_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(isc_buffer_copyregion_test,
+ _setup, _teardown),
+ cmocka_unit_test_setup_teardown(isc_buffer_printf_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/counter_test.c b/lib/isc/tests/counter_test.c
new file mode 100644
index 0000000..6a454f8
--- /dev/null
+++ b/lib/isc/tests/counter_test.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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/counter.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+/* test isc_counter object */
+static void
+isc_counter_test(void **state) {
+ isc_result_t result;
+ isc_counter_t *counter = NULL;
+ int i;
+
+ UNUSED(state);
+
+ result = isc_counter_create(test_mctx, 0, &counter);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ for (i = 0; i < 10; i++) {
+ result = isc_counter_increment(counter);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ assert_int_equal(isc_counter_used(counter), 10);
+
+ isc_counter_setlimit(counter, 15);
+ for (i = 0; i < 10; i++) {
+ result = isc_counter_increment(counter);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+
+ assert_int_equal(isc_counter_used(counter), 15);
+
+ isc_counter_detach(&counter);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(isc_counter_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/crc64_test.c b/lib/isc/tests/crc64_test.c
new file mode 100644
index 0000000..1c4f4f4
--- /dev/null
+++ b/lib/isc/tests/crc64_test.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can 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_CMOCKA
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/crc64.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#define TEST_INPUT(x) (x), sizeof(x) - 1
+
+typedef struct hash_testcase {
+ const char *input;
+ size_t input_len;
+ const char *result;
+ int repeats;
+} hash_testcase_t;
+
+static void
+isc_crc64_init_test(void **state) {
+ uint64_t crc;
+
+ UNUSED(state);
+
+ isc_crc64_init(&crc);
+ assert_int_equal(crc, 0xffffffffffffffffUL);
+}
+
+static void
+_crc64(const char *buf, size_t buflen, const char *result, const int repeats) {
+ uint64_t crc;
+
+ isc_crc64_init(&crc);
+ assert_int_equal(crc, 0xffffffffffffffffUL);
+
+ for (int i = 0; i < repeats; i++) {
+ isc_crc64_update(&crc, buf, buflen);
+ }
+
+ isc_crc64_final(&crc);
+
+ char hex[16 + 1];
+ snprintf(hex, sizeof(hex), "%016" PRIX64, crc);
+
+ assert_memory_equal(hex, result, (result ? strlen(result) : 0));
+}
+
+/* 64-bit cyclic redundancy check */
+static void
+isc_crc64_test(void **state) {
+ UNUSED(state);
+
+ _crc64(TEST_INPUT(""), "0000000000000000", 1);
+ _crc64(TEST_INPUT("a"), "CE73F427ACC0A99A", 1);
+ _crc64(TEST_INPUT("abc"), "048B813AF9F49702", 1);
+ _crc64(TEST_INPUT("message digest"), "5273F9EA7A357BF4", 1);
+ _crc64(TEST_INPUT("abcdefghijklmnopqrstuvwxyz"), "59F079F9218BAAA1", 1);
+ _crc64(TEST_INPUT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm"
+ "nopqrstuvwxyz0123456789"),
+ "A36DA8F71E78B6FB", 1);
+ _crc64(TEST_INPUT("123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890"),
+ "81E5EB73C8E7874A", 1);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_crc64_init_test),
+ cmocka_unit_test(isc_crc64_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/errno_test.c b/lib/isc/tests/errno_test.c
new file mode 100644
index 0000000..56da651
--- /dev/null
+++ b/lib/isc/tests/errno_test.c
@@ -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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <errno.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/errno.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+typedef struct {
+ int err;
+ isc_result_t result;
+} testpair_t;
+
+testpair_t testpair[] = { { EPERM, ISC_R_NOPERM },
+ { ENOENT, ISC_R_FILENOTFOUND },
+ { EIO, ISC_R_IOERROR },
+ { EBADF, ISC_R_INVALIDFILE },
+ { ENOMEM, ISC_R_NOMEMORY },
+ { EACCES, ISC_R_NOPERM },
+ { EEXIST, ISC_R_FILEEXISTS },
+ { ENOTDIR, ISC_R_INVALIDFILE },
+ { EINVAL, ISC_R_INVALIDFILE },
+ { ENFILE, ISC_R_TOOMANYOPENFILES },
+ { EMFILE, ISC_R_TOOMANYOPENFILES },
+ { EPIPE, ISC_R_CONNECTIONRESET },
+ { ENAMETOOLONG, ISC_R_INVALIDFILE },
+ { ELOOP, ISC_R_INVALIDFILE },
+#ifdef EOVERFLOW
+ { EOVERFLOW, ISC_R_RANGE },
+#endif /* ifdef EOVERFLOW */
+#ifdef EAFNOSUPPORT
+ { EAFNOSUPPORT, ISC_R_FAMILYNOSUPPORT },
+#endif /* ifdef EAFNOSUPPORT */
+#ifdef EADDRINUSE
+ { EADDRINUSE, ISC_R_ADDRINUSE },
+#endif /* ifdef EADDRINUSE */
+ { EADDRNOTAVAIL, ISC_R_ADDRNOTAVAIL },
+#ifdef ENETDOWN
+ { ENETDOWN, ISC_R_NETDOWN },
+#endif /* ifdef ENETDOWN */
+#ifdef ENETUNREACH
+ { ENETUNREACH, ISC_R_NETUNREACH },
+#endif /* ifdef ENETUNREACH */
+#ifdef ECONNABORTED
+ { ECONNABORTED, ISC_R_CONNECTIONRESET },
+#endif /* ifdef ECONNABORTED */
+#ifdef ECONNRESET
+ { ECONNRESET, ISC_R_CONNECTIONRESET },
+#endif /* ifdef ECONNRESET */
+#ifdef ENOBUFS
+ { ENOBUFS, ISC_R_NORESOURCES },
+#endif /* ifdef ENOBUFS */
+#ifdef ENOTCONN
+ { ENOTCONN, ISC_R_NOTCONNECTED },
+#endif /* ifdef ENOTCONN */
+#ifdef ETIMEDOUT
+ { ETIMEDOUT, ISC_R_TIMEDOUT },
+#endif /* ifdef ETIMEDOUT */
+ { ECONNREFUSED, ISC_R_CONNREFUSED },
+#ifdef EHOSTDOWN
+ { EHOSTDOWN, ISC_R_HOSTDOWN },
+#endif /* ifdef EHOSTDOWN */
+#ifdef EHOSTUNREACH
+ { EHOSTUNREACH, ISC_R_HOSTUNREACH },
+#endif /* ifdef EHOSTUNREACH */
+ { 0, ISC_R_UNEXPECTED } };
+
+/* convert errno to ISC result */
+static void
+isc_errno_toresult_test(void **state) {
+ isc_result_t result, expect;
+ size_t i;
+
+ UNUSED(state);
+
+ for (i = 0; i < sizeof(testpair) / sizeof(testpair[0]); i++) {
+ result = isc_errno_toresult(testpair[i].err);
+ expect = testpair[i].result;
+ assert_int_equal(result, expect);
+ }
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_errno_toresult_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/file_test.c b/lib/isc/tests/file_test.c
new file mode 100644
index 0000000..6d81ff9
--- /dev/null
+++ b/lib/isc/tests/file_test.c
@@ -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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <fcntl.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/file.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#define NAME "internal"
+#define SHA "3bed2cb3a3acf7b6a8ef408420cc682d5520e26976d354254f528c965612054f"
+#define TRUNC_SHA "3bed2cb3a3acf7b6"
+
+#define BAD1 "in/internal"
+#define BADHASH1 "8bbb97a888791399"
+
+#define BAD2 "Internal"
+#define BADHASH2 "2ea1842b445b0c81"
+
+#define F(x) "testdata/file/" x ".test"
+
+static void
+touch(const char *filename) {
+ int fd;
+
+ unlink(filename);
+ fd = creat(filename, 0644);
+ if (fd != -1) {
+ close(fd);
+ }
+}
+
+/* test sanitized filenames */
+static void
+isc_file_sanitize_test(void **state) {
+ isc_result_t result;
+ char buf[1024];
+
+ UNUSED(state);
+
+ assert_return_code(chdir(TESTS), 0);
+
+ result = isc_file_sanitize("testdata/file", NAME, "test", buf, 1024);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(strcmp(buf, F(NAME)), 0);
+
+ touch(F(TRUNC_SHA));
+ result = isc_file_sanitize("testdata/file", NAME, "test", buf, 1024);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(strcmp(buf, F(TRUNC_SHA)), 0);
+
+ touch(F(SHA));
+ result = isc_file_sanitize("testdata/file", NAME, "test", buf, 1024);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(strcmp(buf, F(SHA)), 0);
+
+ result = isc_file_sanitize("testdata/file", BAD1, "test", buf, 1024);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(strcmp(buf, F(BADHASH1)), 0);
+
+ result = isc_file_sanitize("testdata/file", BAD2, "test", buf, 1024);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(strcmp(buf, F(BADHASH2)), 0);
+
+ unlink(F(TRUNC_SHA));
+ unlink(F(SHA));
+}
+
+/* test filename templates */
+static void
+isc_file_template_test(void **state) {
+ isc_result_t result;
+ char buf[1024];
+
+ UNUSED(state);
+
+ assert_return_code(chdir(TESTS), 0);
+
+ result = isc_file_template("/absolute/path", "file-XXXXXXXX", buf,
+ sizeof(buf));
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, "/absolute/file-XXXXXXXX");
+
+ result = isc_file_template("relative/path", "file-XXXXXXXX", buf,
+ sizeof(buf));
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, "relative/file-XXXXXXXX");
+
+ result = isc_file_template("/trailing/slash/", "file-XXXXXXXX", buf,
+ sizeof(buf));
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, "/trailing/slash/file-XXXXXXXX");
+
+ result = isc_file_template("relative/trailing/slash/", "file-XXXXXXXX",
+ buf, sizeof(buf));
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, "relative/trailing/slash/file-XXXXXXXX");
+
+ result = isc_file_template("/", "file-XXXXXXXX", buf, sizeof(buf));
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, "/file-XXXXXXXX");
+
+ result = isc_file_template("noslash", "file-XXXXXXXX", buf,
+ sizeof(buf));
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, "file-XXXXXXXX");
+
+ result = isc_file_template(NULL, "file-XXXXXXXX", buf, sizeof(buf));
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(buf, "file-XXXXXXXX");
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_file_sanitize_test),
+ cmocka_unit_test(isc_file_template_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/hash_test.c b/lib/isc/tests/hash_test.c
new file mode 100644
index 0000000..ad0eb9d
--- /dev/null
+++ b/lib/isc/tests/hash_test.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/hash.h>
+#include <isc/hex.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <pk11/site.h>
+
+#define TEST_INPUT(x) (x), sizeof(x) - 1
+
+/*Hash function test */
+static void
+isc_hash_function_test(void **state) {
+ unsigned int h1;
+ unsigned int h2;
+
+ UNUSED(state);
+
+ /* Immutability of hash function */
+ h1 = isc_hash_function(NULL, 0, true);
+ h2 = isc_hash_function(NULL, 0, true);
+
+ assert_int_equal(h1, h2);
+
+ /* Hash function characteristics */
+ h1 = isc_hash_function("Hello world", 12, true);
+ h2 = isc_hash_function("Hello world", 12, true);
+
+ assert_int_equal(h1, h2);
+
+ /* Case */
+ h1 = isc_hash_function("Hello world", 12, false);
+ h2 = isc_hash_function("heLLo WorLd", 12, false);
+
+ assert_int_equal(h1, h2);
+
+ /* Unequal */
+ h1 = isc_hash_function("Hello world", 12, true);
+ h2 = isc_hash_function("heLLo WorLd", 12, true);
+
+ assert_int_not_equal(h1, h2);
+}
+
+/* Hash function initializer test */
+static void
+isc_hash_initializer_test(void **state) {
+ unsigned int h1;
+ unsigned int h2;
+
+ UNUSED(state);
+
+ h1 = isc_hash_function("Hello world", 12, true);
+ h2 = isc_hash_function("Hello world", 12, true);
+
+ assert_int_equal(h1, h2);
+
+ isc_hash_set_initializer(isc_hash_get_initializer());
+
+ /* Hash value must not change */
+ h2 = isc_hash_function("Hello world", 12, true);
+
+ assert_int_equal(h1, h2);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_hash_function_test),
+ cmocka_unit_test(isc_hash_initializer_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/heap_test.c b/lib/isc/tests/heap_test.c
new file mode 100644
index 0000000..5c41b20
--- /dev/null
+++ b/lib/isc/tests/heap_test.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* ! \file */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/heap.h>
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+struct e {
+ unsigned int value;
+ unsigned int index;
+};
+
+static bool
+compare(void *p1, void *p2) {
+ struct e *e1 = p1;
+ struct e *e2 = p2;
+
+ return (e1->value < e2->value);
+}
+
+static void
+idx(void *p, unsigned int i) {
+ struct e *e = p;
+
+ e->index = i;
+}
+
+/* test isc_heap_delete() */
+static void
+isc_heap_delete_test(void **state) {
+ isc_heap_t *heap = NULL;
+ struct e e1 = { 100, 0 };
+
+ UNUSED(state);
+
+ isc_heap_create(test_mctx, compare, idx, 0, &heap);
+ assert_non_null(heap);
+
+ isc_heap_insert(heap, &e1);
+ assert_int_equal(e1.index, 1);
+
+ isc_heap_delete(heap, e1.index);
+ assert_int_equal(e1.index, 0);
+
+ isc_heap_destroy(&heap);
+ assert_null(heap);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_heap_delete_test),
+ };
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/hmac_test.c b/lib/isc/tests/hmac_test.c
new file mode 100644
index 0000000..74c8106
--- /dev/null
+++ b/lib/isc/tests/hmac_test.c
@@ -0,0 +1,949 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can 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_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/hex.h>
+#include <isc/hmac.h>
+#include <isc/region.h>
+#include <isc/result.h>
+
+#include "../hmac.c"
+
+#define TEST_INPUT(x) (x), sizeof(x) - 1
+
+static int
+_setup(void **state) {
+ isc_hmac_t *hmac = isc_hmac_new();
+ if (hmac == NULL) {
+ return (-1);
+ }
+ *state = hmac;
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ if (*state == NULL) {
+ return (-1);
+ }
+ isc_hmac_free(*state);
+ return (0);
+}
+
+static int
+_reset(void **state) {
+ if (*state == NULL) {
+ return (-1);
+ }
+ if (isc_hmac_reset(*state) != ISC_R_SUCCESS) {
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+isc_hmac_new_test(void **state) {
+ UNUSED(state);
+
+ isc_hmac_t *hmac = isc_hmac_new();
+ assert_non_null(hmac);
+ isc_hmac_free(hmac); /* Cleanup */
+}
+
+static void
+isc_hmac_free_test(void **state) {
+ UNUSED(state);
+
+ isc_hmac_t *hmac = isc_hmac_new();
+ assert_non_null(hmac);
+ isc_hmac_free(hmac); /* Test freeing valid message digest context */
+ isc_hmac_free(NULL); /* Test freeing NULL argument */
+}
+
+static void
+isc_hmac_test(isc_hmac_t *hmac, const void *key, size_t keylen,
+ const isc_md_type_t *type, const char *buf, size_t buflen,
+ const char *result, const int repeats) {
+ assert_non_null(hmac);
+ assert_int_equal(isc_hmac_init(hmac, key, keylen, type), ISC_R_SUCCESS);
+
+ int i;
+
+ for (i = 0; i < repeats; i++) {
+ assert_int_equal(isc_hmac_update(hmac,
+ (const unsigned char *)buf,
+ buflen),
+ ISC_R_SUCCESS);
+ }
+
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ unsigned int digestlen;
+ assert_int_equal(isc_hmac_final(hmac, digest, &digestlen),
+ ISC_R_SUCCESS);
+
+ char hexdigest[ISC_MAX_MD_SIZE * 2 + 3];
+ isc_region_t r = { .base = digest, .length = digestlen };
+ isc_buffer_t b;
+ isc_buffer_init(&b, hexdigest, sizeof(hexdigest));
+
+ assert_return_code(isc_hex_totext(&r, 0, "", &b), ISC_R_SUCCESS);
+
+ assert_memory_equal(hexdigest, result, (result ? strlen(result) : 0));
+ assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS);
+}
+
+static void
+isc_hmac_init_test(void **state) {
+ isc_hmac_t *hmac = *state;
+ assert_non_null(hmac);
+
+ expect_assert_failure(isc_hmac_init(NULL, "", 0, ISC_MD_MD5));
+
+ assert_int_equal(isc_hmac_init(hmac, "", 0, NULL),
+ ISC_R_NOTIMPLEMENTED);
+
+ expect_assert_failure(isc_hmac_init(hmac, NULL, 0, ISC_MD_MD5));
+
+ assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_MD5), ISC_R_SUCCESS);
+ assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS);
+
+ assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA1),
+ ISC_R_SUCCESS);
+ assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS);
+
+ assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA224),
+ ISC_R_SUCCESS);
+ assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS);
+
+ assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA256),
+ ISC_R_SUCCESS);
+ assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS);
+
+ assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA384),
+ ISC_R_SUCCESS);
+ assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS);
+
+ assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA512),
+ ISC_R_SUCCESS);
+ assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS);
+}
+
+static void
+isc_hmac_update_test(void **state) {
+ isc_hmac_t *hmac = *state;
+ assert_non_null(hmac);
+
+ /* Uses message digest context initialized in isc_hmac_init_test() */
+ expect_assert_failure(isc_hmac_update(NULL, NULL, 0));
+
+ assert_int_equal(isc_hmac_update(hmac, NULL, 100), ISC_R_SUCCESS);
+ assert_int_equal(isc_hmac_update(hmac, (const unsigned char *)"", 0),
+ ISC_R_SUCCESS);
+}
+
+static void
+isc_hmac_reset_test(void **state) {
+ isc_hmac_t *hmac = *state;
+#if 0
+ unsigned char digest[ISC_MAX_MD_SIZE] __attribute((unused));
+ unsigned int digestlen __attribute((unused));
+#endif /* if 0 */
+
+ assert_non_null(hmac);
+
+ assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA512),
+ ISC_R_SUCCESS);
+ assert_int_equal(isc_hmac_update(hmac, (const unsigned char *)"a", 1),
+ ISC_R_SUCCESS);
+ assert_int_equal(isc_hmac_update(hmac, (const unsigned char *)"b", 1),
+ ISC_R_SUCCESS);
+
+ assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS);
+
+#if 0
+ /*
+ * This test would require OpenSSL compiled with mock_assert(),
+ * so this could be only manually checked that the test will
+ * segfault when called by hand
+ */
+ expect_assert_failure(isc_hmac_final(hmac,digest,&digestlen));
+#endif /* if 0 */
+}
+
+static void
+isc_hmac_final_test(void **state) {
+ isc_hmac_t *hmac = *state;
+ assert_non_null(hmac);
+
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ unsigned int digestlen;
+
+ /* Fail when message digest context is empty */
+ expect_assert_failure(isc_hmac_final(NULL, digest, &digestlen));
+ /* Fail when output buffer is empty */
+ expect_assert_failure(isc_hmac_final(hmac, NULL, &digestlen));
+
+ assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA512),
+ ISC_R_SUCCESS);
+ assert_int_equal(isc_hmac_final(hmac, digest, NULL), ISC_R_SUCCESS);
+}
+
+static void
+isc_hmac_md5_test(void **state) {
+ isc_hmac_t *hmac = *state;
+
+ /* Test 0 */
+ isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_MD5, TEST_INPUT(""),
+ "74E6F7298A9C2D168935F58C001BAD88", 1);
+
+ /* Test 1 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+ "\x0b\x0b\x0b\x0b\x0b\x0b"),
+ ISC_MD_MD5,
+ TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"),
+ "9294727A3638BB1C13F48EF8158BFC9D", 1);
+
+ /* Test 2 */
+ isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_MD5,
+ TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79"
+ "\x61\x20\x77\x61\x6e\x74\x20\x66\x6f"
+ "\x72\x20\x6e\x6f\x74\x68\x69\x6e\x67\x3f"),
+ "750C783E6AB0B503EAA86E310A5DB738", 1);
+
+ /* Test 3 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa"),
+ ISC_MD_MD5,
+ TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"),
+ "56BE34521D144C88DBB8C733F0E8B3F6", 1);
+ /* Test 4 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
+ "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+ "\x15\x16\x17\x18\x19"),
+ ISC_MD_MD5,
+ TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"),
+ "697EAF0ACA3A3AEA3A75164746FFAA79", 1);
+#if 0
+ /* Test 5 -- unimplemented optional functionality */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+ "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"),
+ ISC_MD_MD5,
+ TEST_INPUT("Test With Truncation"),
+ "4C1A03424B55E07FE7F27BE1",
+ 1);
+ /* Test 6 -- unimplemented optional functionality */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"),
+ ISC_MD_MD5,
+ TEST_INPUT("Test Using Larger Than Block-Size Key - "
+ "Hash Key First"),
+ "AA4AE5E15272D00E95705637CE8A3B55ED402112",
+ 1);
+ /* Test 7 -- unimplemented optional functionality */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"),
+ ISC_MD_MD5,
+ TEST_INPUT("Test Using Larger Than Block-Size Key and "
+ "Larger Than One Block-Size Data"),
+ "E8E99D0F45237D786D6BBAA7965C7808BBFF1A91",
+ 1);
+#endif /* if 0 */
+}
+
+static void
+isc_hmac_sha1_test(void **state) {
+ isc_hmac_t *hmac = *state;
+
+ /* Test 0 */
+ isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_SHA1, TEST_INPUT(""),
+ "FBDB1D1B18AA6C08324B7D64B71FB76370690E1D", 1);
+
+ /* Test 1 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"),
+ ISC_MD_SHA1,
+ TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"),
+ "B617318655057264E28BC0B6FB378C8EF146BE00", 1);
+ /* Test 2 */
+ isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_SHA1,
+ TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61"
+ "\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20"
+ "\x6e\x6f\x74\x68\x69\x6e\x67\x3f"),
+ "EFFCDF6AE5EB2FA2D27416D5F184DF9C259A7C79", 1);
+ /* Test 3 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"),
+ ISC_MD_SHA1,
+ TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"),
+ "125D7342B9AC11CD91A39AF48AA17B4F63F175D3", 1);
+ /* Test 4 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
+ "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+ "\x15\x16\x17\x18\x19"),
+ ISC_MD_SHA1,
+ TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"),
+ "4C9007F4026250C6BC8414F9BF50C86C2D7235DA", 1);
+#if 0
+ /* Test 5 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+ "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"),
+ ISC_MD_SHA1,
+ TEST_INPUT("Test With Truncation"),
+ "4C1A03424B55E07FE7F27BE1",
+ 1);
+#endif /* if 0 */
+ /* Test 6 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"),
+ ISC_MD_SHA1,
+ TEST_INPUT("Test Using Larger Than Block-Size Key - "
+ "Hash Key First"),
+ "AA4AE5E15272D00E95705637CE8A3B55ED402112", 1);
+ /* Test 7 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"),
+ ISC_MD_SHA1,
+ TEST_INPUT("Test Using Larger Than Block-Size Key and "
+ "Larger Than One Block-Size Data"),
+ "E8E99D0F45237D786D6BBAA7965C7808BBFF1A91", 1);
+}
+
+static void
+isc_hmac_sha224_test(void **state) {
+ isc_hmac_t *hmac = *state;
+
+ /* Test 0 */
+ isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_SHA224, TEST_INPUT(""),
+ "5CE14F72894662213E2748D2A6BA234B74263910CEDDE2F5"
+ "A9271524",
+ 1);
+
+ /* Test 1 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"),
+ ISC_MD_SHA224,
+ TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"),
+ "896FB1128ABBDF196832107CD49DF33F47B4B1169912BA"
+ "4F53684B22",
+ 1);
+ /* Test 2 */
+ isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_SHA224,
+ TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61"
+ "\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20"
+ "\x6e\x6f\x74\x68\x69\x6e\x67\x3f"),
+ "A30E01098BC6DBBF45690F3A7E9E6D0F8BBEA2A39E61480"
+ "08FD05E44",
+ 1);
+ /* Test 3 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"),
+ ISC_MD_SHA224,
+ TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"),
+ "7FB3CB3588C6C1F6FFA9694D7D6AD2649365B0C1F65D69"
+ "D1EC8333EA",
+ 1);
+ /* Test 4 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
+ "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+ "\x15\x16\x17\x18\x19"),
+ ISC_MD_SHA224,
+ TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"),
+ "6C11506874013CAC6A2ABC1BB382627CEC6A90D86EFC01"
+ "2DE7AFEC5A",
+ 1);
+#if 0
+ /* Test 5 -- unimplemented optional functionality */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+ "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"),
+ ISC_MD_SHA224,
+ TEST_INPUT("Test With Truncation"),
+ "4C1A03424B55E07FE7F27BE1",
+ 1);
+#endif /* if 0 */
+ /* Test 6 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa"),
+ ISC_MD_SHA224,
+ TEST_INPUT("Test Using Larger Than Block-Size Key - "
+ "Hash Key First"),
+ "95E9A0DB962095ADAEBE9B2D6F0DBCE2D499F112F2D2B7"
+ "273FA6870E",
+ 1);
+ /* Test 7 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa"),
+ ISC_MD_SHA224,
+ TEST_INPUT("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20"
+ "\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67"
+ "\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20"
+ "\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b"
+ "\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20"
+ "\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67"
+ "\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c"
+ "\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64"
+ "\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b"
+ "\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74"
+ "\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65"
+ "\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62"
+ "\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20"
+ "\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41"
+ "\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68"
+ "\x6d\x2e"),
+ "3A854166AC5D9F023F54D517D0B39DBD946770DB9C2B95"
+ "C9F6F565D1",
+ 1);
+}
+
+static void
+isc_hmac_sha256_test(void **state) {
+ isc_hmac_t *hmac = *state;
+
+ /* Test 0 */
+ isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_SHA256, TEST_INPUT(""),
+ "B613679A0814D9EC772F95D778C35FC5FF1697C493715653"
+ "C6C712144292C5AD",
+ 1);
+
+ /* Test 1 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"),
+ ISC_MD_SHA256,
+ TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"),
+ "B0344C61D8DB38535CA8AFCEAF0BF12B881DC200C9833D"
+ "A726E9376C2E32CFF7",
+ 1);
+ /* Test 2 */
+ isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_SHA256,
+ TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61"
+ "\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20"
+ "\x6e\x6f\x74\x68\x69\x6e\x67\x3f"),
+ "5BDCC146BF60754E6A042426089575C75A003F089D2739"
+ "839DEC58B964EC3843",
+ 1);
+ /* Test 3 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"),
+ ISC_MD_SHA256,
+ TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"),
+ "773EA91E36800E46854DB8EBD09181A72959098B3EF8C1"
+ "22D9635514CED565FE",
+ 1);
+ /* Test 4 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
+ "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+ "\x15\x16\x17\x18\x19"),
+ ISC_MD_SHA256,
+ TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"),
+ "82558A389A443C0EA4CC819899F2083A85F0FAA3E578F8"
+ "077A2E3FF46729665B",
+ 1);
+#if 0
+ /* Test 5 -- unimplemented optional functionality */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+ "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"),
+ ISC_MD_SHA256,
+ TEST_INPUT("Test With Truncation"),
+ "4C1A03424B55E07FE7F27BE1",
+ 1);
+#endif /* if 0 */
+ /* Test 6 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa"),
+ ISC_MD_SHA256,
+ TEST_INPUT("Test Using Larger Than Block-Size Key - "
+ "Hash Key First"),
+ "60E431591EE0B67F0D8A26AACBF5B77F8E0BC6213728C5"
+ "140546040F0EE37F54",
+ 1);
+ /* Test 7 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa"),
+ ISC_MD_SHA256,
+ TEST_INPUT("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20"
+ "\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67"
+ "\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20"
+ "\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b"
+ "\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20"
+ "\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67"
+ "\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c"
+ "\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64"
+ "\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b"
+ "\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74"
+ "\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65"
+ "\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62"
+ "\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20"
+ "\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41"
+ "\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68"
+ "\x6d\x2e"),
+ "9B09FFA71B942FCB27635FBCD5B0E944BFDC63644F0713"
+ "938A7F51535C3A35E2",
+ 1);
+}
+
+static void
+isc_hmac_sha384_test(void **state) {
+ isc_hmac_t *hmac = *state;
+
+ /* Test 0 */
+ isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_SHA384, TEST_INPUT(""),
+ "6C1F2EE938FAD2E24BD91298474382CA218C75DB3D83E114"
+ "B3D4367776D14D3551289E75E8209CD4B792302840234ADC",
+ 1);
+
+ /* Test 1 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"),
+ ISC_MD_SHA384,
+ TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"),
+ "AFD03944D84895626B0825F4AB46907F15F9DADBE4101E"
+ "C682AA034C7CEBC59CFAEA9EA9076EDE7F4AF152"
+ "E8B2FA9CB6",
+ 1);
+ /* Test 2 */
+ isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_SHA384,
+ TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61"
+ "\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20"
+ "\x6e\x6f\x74\x68\x69\x6e\x67\x3f"),
+ "AF45D2E376484031617F78D2B58A6B1B9C7EF464F5A01B"
+ "47E42EC3736322445E8E2240CA5E69E2C78B3239"
+ "ECFAB21649",
+ 1);
+ /* Test 3 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"),
+ ISC_MD_SHA384,
+ TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"),
+ "88062608D3E6AD8A0AA2ACE014C8A86F0AA635D947AC9F"
+ "EBE83EF4E55966144B2A5AB39DC13814B94E3AB6"
+ "E101A34F27",
+ 1);
+ /* Test 4 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
+ "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+ "\x15\x16\x17\x18\x19"),
+ ISC_MD_SHA384,
+ TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"),
+ "3E8A69B7783C25851933AB6290AF6CA77A998148085000"
+ "9CC5577C6E1F573B4E6801DD23C4A7D679CCF8A3"
+ "86C674CFFB",
+ 1);
+#if 0
+ /* Test 5 -- unimplemented optional functionality */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+ "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"),
+ ISC_MD_SHA384,
+ TEST_INPUT("Test With Truncation"),
+ "4C1A03424B55E07FE7F27BE1",
+ 1);
+#endif /* if 0 */
+ /* Test 6 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa"),
+ ISC_MD_SHA384,
+ TEST_INPUT("Test Using Larger Than Block-Size Key - "
+ "Hash Key First"),
+ "4ECE084485813E9088D2C63A041BC5B44F9EF1012A2B58"
+ "8F3CD11F05033AC4C60C2EF6AB4030FE8296248D"
+ "F163F44952",
+ 1);
+ /* Test 7 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa"),
+ ISC_MD_SHA384,
+ TEST_INPUT("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20"
+ "\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67"
+ "\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20"
+ "\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b"
+ "\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20"
+ "\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67"
+ "\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c"
+ "\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64"
+ "\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b"
+ "\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74"
+ "\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65"
+ "\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62"
+ "\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20"
+ "\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41"
+ "\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68"
+ "\x6d\x2e"),
+ "6617178E941F020D351E2F254E8FD32C602420FEB0B8FB"
+ "9ADCCEBB82461E99C5A678CC31E799176D3860E6"
+ "110C46523E",
+ 1);
+}
+
+static void
+isc_hmac_sha512_test(void **state) {
+ isc_hmac_t *hmac = *state;
+
+ /* Test 0 */
+ isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_SHA512, TEST_INPUT(""),
+ "B936CEE86C9F87AA5D3C6F2E84CB5A4239A5FE50480A6EC6"
+ "6B70AB5B1F4AC6730C6C515421B327EC1D69402E53DFB49A"
+ "D7381EB067B338FD7B0CB22247225D47",
+ 1);
+
+ /* Test 1 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"),
+ ISC_MD_SHA512,
+ TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"),
+ "87AA7CDEA5EF619D4FF0B4241A1D6CB02379F4E2CE4EC2"
+ "787AD0B30545E17CDEDAA833B7D6B8A702038B27"
+ "4EAEA3F4E4BE9D914EEB61F1702E696C203A126854",
+ 1);
+ /* Test 2 */
+ isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_SHA512,
+ TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61"
+ "\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20"
+ "\x6e\x6f\x74\x68\x69\x6e\x67\x3f"),
+ "164B7A7BFCF819E2E395FBE73B56E0A387BD64222E831F"
+ "D610270CD7EA2505549758BF75C05A994A6D034F"
+ "65F8F0E6FDCAEAB1A34D4A6B4B636E070A38BCE737",
+ 1);
+ /* Test 3 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"),
+ ISC_MD_SHA512,
+ TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"
+ "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"),
+ "FA73B0089D56A284EFB0F0756C890BE9B1B5DBDD8EE81A"
+ "3655F83E33B2279D39BF3E848279A722C806B485"
+ "A47E67C807B946A337BEE8942674278859E13292FB",
+ 1);
+ /* Test 4 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
+ "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+ "\x15\x16\x17\x18\x19"),
+ ISC_MD_SHA512,
+ TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"),
+ "B0BA465637458C6990E5A8C5F61D4AF7E576D97FF94B87"
+ "2DE76F8050361EE3DBA91CA5C11AA25EB4D67927"
+ "5CC5788063A5F19741120C4F2DE2ADEBEB10A298DD",
+ 1);
+#if 0
+ /* Test 5 -- unimplemented optional functionality */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+ "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"),
+ ISC_MD_SHA512,
+ TEST_INPUT("Test With Truncation"),
+ "4C1A03424B55E07FE7F27BE1",
+ 1);
+#endif /* if 0 */
+ /* Test 6 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa"),
+ ISC_MD_SHA512,
+ TEST_INPUT("Test Using Larger Than Block-Size Key - "
+ "Hash Key First"),
+ "80B24263C7C1A3EBB71493C1DD7BE8B49B46D1F41B4AEE"
+ "C1121B013783F8F3526B56D037E05F2598BD0FD2"
+ "215D6A1E5295E64F73F63F0AEC8B915A985D786598",
+ 1);
+ /* Test 7 */
+ isc_hmac_test(hmac,
+ TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa"),
+ ISC_MD_SHA512,
+ TEST_INPUT("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20"
+ "\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67"
+ "\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20"
+ "\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b"
+ "\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20"
+ "\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67"
+ "\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c"
+ "\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64"
+ "\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b"
+ "\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74"
+ "\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65"
+ "\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62"
+ "\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20"
+ "\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41"
+ "\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68"
+ "\x6d\x2e"),
+ "E37B6A775DC87DBAA4DFA9F96E5E3FFDDEBD71F8867289"
+ "865DF5A32D20CDC944B6022CAC3C4982B10D5EEB"
+ "55C3E4DE15134676FB6DE0446065C97440FA8C6A58",
+ 1);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ /* isc_hmac_new() */
+ cmocka_unit_test(isc_hmac_new_test),
+
+ /* isc_hmac_init() */
+ cmocka_unit_test_setup_teardown(isc_hmac_init_test, _reset,
+ _reset),
+
+ /* isc_hmac_reset() */
+ cmocka_unit_test_setup_teardown(isc_hmac_reset_test, _reset,
+ _reset),
+
+ /* isc_hmac_init() -> isc_hmac_update() -> isc_hmac_final() */
+ cmocka_unit_test(isc_hmac_md5_test),
+ cmocka_unit_test(isc_hmac_sha1_test),
+ cmocka_unit_test(isc_hmac_sha224_test),
+ cmocka_unit_test(isc_hmac_sha256_test),
+ cmocka_unit_test(isc_hmac_sha384_test),
+ cmocka_unit_test(isc_hmac_sha512_test),
+
+ cmocka_unit_test_setup_teardown(isc_hmac_update_test, _reset,
+ _reset),
+ cmocka_unit_test_setup_teardown(isc_hmac_final_test, _reset,
+ _reset),
+
+ cmocka_unit_test(isc_hmac_free_test),
+ };
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/ht_test.c b/lib/isc/tests/ht_test.c
new file mode 100644
index 0000000..30cc615
--- /dev/null
+++ b/lib/isc/tests/ht_test.c
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/hash.h>
+#include <isc/ht.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+static void
+test_ht_full(int bits, uintptr_t count) {
+ isc_ht_t *ht = NULL;
+ isc_result_t result;
+ uintptr_t i;
+
+ isc_ht_init(&ht, test_mctx, bits);
+ assert_non_null(ht);
+
+ for (i = 1; i < count; i++) {
+ /*
+ * Note: snprintf() is followed with strlcat()
+ * to ensure we are always filling the 16 byte key.
+ */
+ unsigned char key[16];
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " key of a raw hashtable!!", sizeof(key));
+ result = isc_ht_add(ht, key, 16, (void *)i);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ for (i = 1; i < count; i++) {
+ unsigned char key[16];
+ void *f = NULL;
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " key of a raw hashtable!!", sizeof(key));
+ result = isc_ht_find(ht, key, 16, &f);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_ptr_equal((void *)i, (uintptr_t)f);
+ }
+
+ for (i = 1; i < count; i++) {
+ unsigned char key[16];
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " key of a raw hashtable!!", sizeof(key));
+ result = isc_ht_add(ht, key, 16, (void *)i);
+ assert_int_equal(result, ISC_R_EXISTS);
+ }
+
+ for (i = 1; i < count; i++) {
+ char key[64];
+ /*
+ * Note: the key size is now strlen(key) which is bigger
+ * then the keys added above.
+ */
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " key of a raw hashtable!!", sizeof(key));
+ result = isc_ht_add(ht, (const unsigned char *)key, strlen(key),
+ (void *)i);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ for (i = 1; i < count; i++) {
+ unsigned char key[16];
+ void *f = NULL;
+ /*
+ * Note: case of KEY is now in capitals,
+ */
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " KEY of a raw hashtable!!", sizeof(key));
+ result = isc_ht_find(ht, key, 16, &f);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ assert_null(f);
+ }
+
+ for (i = 1; i < count; i++) {
+ char key[64];
+ void *f = NULL;
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " key of a raw hashtable!!", sizeof(key));
+ result = isc_ht_find(ht, (const unsigned char *)key,
+ strlen(key), &f);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_ptr_equal(f, (void *)i);
+ }
+
+ for (i = 1; i < count; i++) {
+ unsigned char key[16];
+ void *f = NULL;
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " key of a raw hashtable!!", sizeof(key));
+ result = isc_ht_delete(ht, key, 16);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_ht_find(ht, key, 16, &f);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ assert_null(f);
+ }
+
+ for (i = 1; i < count; i++) {
+ unsigned char key[16];
+ /*
+ * Note: upper case KEY.
+ */
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " KEY of a raw hashtable!!", sizeof(key));
+ result = isc_ht_add(ht, key, 16, (void *)i);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ for (i = 1; i < count; i++) {
+ char key[64];
+ void *f = NULL;
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " key of a raw hashtable!!", sizeof(key));
+ result = isc_ht_delete(ht, (const unsigned char *)key,
+ strlen(key));
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_ht_find(ht, (const unsigned char *)key,
+ strlen(key), &f);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ assert_null(f);
+ }
+
+ for (i = 1; i < count; i++) {
+ unsigned char key[16];
+ void *f = NULL;
+ /*
+ * Note: case of KEY is now in capitals,
+ */
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " KEY of a raw hashtable!!", sizeof(key));
+ result = isc_ht_find(ht, key, 16, &f);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_ptr_equal((void *)i, (uintptr_t)f);
+ }
+
+ for (i = 1; i < count; i++) {
+ unsigned char key[16];
+ void *f = NULL;
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, " key of a raw hashtable!!", sizeof(key));
+ result = isc_ht_find(ht, key, 16, &f);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ assert_null(f);
+ }
+
+ isc_ht_destroy(&ht);
+ assert_null(ht);
+}
+
+static void
+test_ht_iterator() {
+ isc_ht_t *ht = NULL;
+ isc_result_t result;
+ isc_ht_iter_t *iter = NULL;
+ uintptr_t i;
+ uintptr_t count = 10000;
+ uint32_t walked;
+ unsigned char key[16];
+ size_t tksize;
+
+ isc_ht_init(&ht, test_mctx, 16);
+ assert_non_null(ht);
+ for (i = 1; i <= count; i++) {
+ /*
+ * Note that the string we're snprintfing is always > 16 bytes
+ * so we are always filling the key.
+ */
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, "key of a raw hashtable!!", sizeof(key));
+ result = isc_ht_add(ht, key, 16, (void *)i);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ walked = 0;
+ isc_ht_iter_create(ht, &iter);
+
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_next(iter))
+ {
+ unsigned char *tkey = NULL;
+ void *v = NULL;
+
+ isc_ht_iter_current(iter, &v);
+ isc_ht_iter_currentkey(iter, &tkey, &tksize);
+ assert_int_equal(tksize, 16);
+ i = (uintptr_t)v;
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, "key of a raw hashtable!!", sizeof(key));
+ assert_memory_equal(key, tkey, 16);
+ walked++;
+ }
+ assert_int_equal(walked, count);
+ assert_int_equal(result, ISC_R_NOMORE);
+
+ /* erase odd */
+ walked = 0;
+ result = isc_ht_iter_first(iter);
+ while (result == ISC_R_SUCCESS) {
+ unsigned char *tkey = NULL;
+ void *v = NULL;
+
+ isc_ht_iter_current(iter, &v);
+ isc_ht_iter_currentkey(iter, &tkey, &tksize);
+ assert_int_equal(tksize, 16);
+ i = (uintptr_t)v;
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, "key of a raw hashtable!!", sizeof(key));
+ assert_memory_equal(key, tkey, 16);
+ if ((uintptr_t)v % 2 == 0) {
+ result = isc_ht_iter_delcurrent_next(iter);
+ } else {
+ result = isc_ht_iter_next(iter);
+ }
+ walked++;
+ }
+ assert_int_equal(result, ISC_R_NOMORE);
+ assert_int_equal(walked, count);
+
+ /* erase even */
+ walked = 0;
+ result = isc_ht_iter_first(iter);
+ while (result == ISC_R_SUCCESS) {
+ unsigned char *tkey = NULL;
+ void *v = NULL;
+
+ isc_ht_iter_current(iter, &v);
+ isc_ht_iter_currentkey(iter, &tkey, &tksize);
+ assert_int_equal(tksize, 16);
+ i = (uintptr_t)v;
+ snprintf((char *)key, sizeof(key), "%u", (unsigned int)i);
+ strlcat((char *)key, "key of a raw hashtable!!", sizeof(key));
+ assert_memory_equal(key, tkey, 16);
+ if ((uintptr_t)v % 2 == 1) {
+ result = isc_ht_iter_delcurrent_next(iter);
+ } else {
+ result = isc_ht_iter_next(iter);
+ }
+ walked++;
+ }
+ assert_int_equal(result, ISC_R_NOMORE);
+ assert_int_equal(walked, count / 2);
+
+ walked = 0;
+ for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_next(iter))
+ {
+ walked++;
+ }
+
+ assert_int_equal(result, ISC_R_NOMORE);
+ assert_int_equal(walked, 0);
+
+ isc_ht_iter_destroy(&iter);
+ assert_null(iter);
+
+ isc_ht_destroy(&ht);
+ assert_null(ht);
+}
+
+/* 20 bit, 200K elements test */
+static void
+isc_ht_20(void **state) {
+ UNUSED(state);
+ test_ht_full(20, 200000);
+}
+
+/* 8 bit, 20000 elements crowded test */
+static void
+isc_ht_8(void **state) {
+ UNUSED(state);
+ test_ht_full(8, 20000);
+}
+
+/* 8 bit, 100 elements corner case test */
+static void
+isc_ht_1(void **state) {
+ UNUSED(state);
+ test_ht_full(1, 100);
+}
+
+/* test hashtable iterator */
+static void
+isc_ht_iterator_test(void **state) {
+ UNUSED(state);
+ test_ht_iterator();
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_ht_20),
+ cmocka_unit_test(isc_ht_8),
+ cmocka_unit_test(isc_ht_1),
+ cmocka_unit_test(isc_ht_iterator_test),
+ };
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/isctest.c b/lib/isc/tests/isctest.c
new file mode 100644
index 0000000..3b0235b
--- /dev/null
+++ b/lib/isc/tests/isctest.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include "isctest.h"
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <isc/buffer.h>
+#include <isc/hash.h>
+#include <isc/managers.h>
+#include <isc/mem.h>
+#include <isc/os.h>
+#include <isc/socket.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+isc_mem_t *test_mctx = NULL;
+isc_log_t *test_lctx = NULL;
+isc_taskmgr_t *taskmgr = NULL;
+isc_timermgr_t *timermgr = NULL;
+isc_socketmgr_t *socketmgr = NULL;
+isc_nm_t *netmgr = NULL;
+isc_task_t *maintask = NULL;
+int ncpus;
+
+static bool test_running = false;
+
+/*
+ * Logging categories: this needs to match the list in bin/named/log.c.
+ */
+static isc_logcategory_t categories[] = { { "", 0 },
+ { "client", 0 },
+ { "network", 0 },
+ { "update", 0 },
+ { "queries", 0 },
+ { "unmatched", 0 },
+ { "update-security", 0 },
+ { "query-errors", 0 },
+ { NULL, 0 } };
+
+static void
+cleanup_managers(void) {
+ if (maintask != NULL) {
+ isc_task_shutdown(maintask);
+ isc_task_destroy(&maintask);
+ }
+
+ isc_managers_destroy(netmgr == NULL ? NULL : &netmgr,
+ taskmgr == NULL ? NULL : &taskmgr);
+
+ if (socketmgr != NULL) {
+ isc_socketmgr_destroy(&socketmgr);
+ }
+ if (timermgr != NULL) {
+ isc_timermgr_destroy(&timermgr);
+ }
+}
+
+static isc_result_t
+create_managers(unsigned int workers) {
+ isc_result_t result;
+ char *p;
+
+ if (workers == 0) {
+ workers = isc_os_ncpus();
+ }
+
+ p = getenv("ISC_TASK_WORKERS");
+ if (p != NULL) {
+ workers = atoi(p);
+ }
+
+ CHECK(isc_managers_create(test_mctx, workers, 0, &netmgr, &taskmgr));
+ CHECK(isc_task_create_bound(taskmgr, 0, &maintask, 0));
+ isc_taskmgr_setexcltask(taskmgr, maintask);
+
+ CHECK(isc_timermgr_create(test_mctx, &timermgr));
+ CHECK(isc_socketmgr_create(test_mctx, &socketmgr));
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cleanup_managers();
+ return (result);
+}
+
+isc_result_t
+isc_test_begin(FILE *logfile, bool start_managers, unsigned int workers) {
+ isc_result_t result;
+
+ INSIST(!test_running);
+ test_running = true;
+
+ isc_mem_debugging |= ISC_MEM_DEBUGRECORD;
+
+ INSIST(test_mctx == NULL);
+ isc_mem_create(&test_mctx);
+
+ if (logfile != NULL) {
+ isc_logdestination_t destination;
+ isc_logconfig_t *logconfig = NULL;
+
+ INSIST(test_lctx == NULL);
+ isc_log_create(test_mctx, &test_lctx, &logconfig);
+ isc_log_registercategories(test_lctx, categories);
+ isc_log_setcontext(test_lctx);
+
+ destination.file.stream = logfile;
+ destination.file.name = NULL;
+ destination.file.versions = ISC_LOG_ROLLNEVER;
+ destination.file.maximum_size = 0;
+ isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC,
+ ISC_LOG_DYNAMIC, &destination, 0);
+ CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL));
+ }
+
+ ncpus = isc_os_ncpus();
+
+ if (start_managers) {
+ CHECK(create_managers(workers));
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ isc_test_end();
+ return (result);
+}
+
+void
+isc_test_end(void) {
+ if (maintask != NULL) {
+ isc_task_detach(&maintask);
+ }
+
+ cleanup_managers();
+
+ if (test_lctx != NULL) {
+ isc_log_destroy(&test_lctx);
+ }
+ if (test_mctx != NULL) {
+ isc_mem_destroy(&test_mctx);
+ }
+
+ test_running = false;
+}
+
+/*
+ * Sleep for 'usec' microseconds.
+ */
+void
+isc_test_nap(uint32_t usec) {
+ struct timespec ts;
+
+ ts.tv_sec = usec / 1000000;
+ ts.tv_nsec = (usec % 1000000) * 1000;
+ nanosleep(&ts, NULL);
+}
diff --git a/lib/isc/tests/isctest.h b/lib/isc/tests/isctest.h
new file mode 100644
index 0000000..4f88e91
--- /dev/null
+++ b/lib/isc/tests/isctest.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.
+ */
+
+/*! \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/netmgr.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#define CHECK(r) \
+ do { \
+ result = (r); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+extern isc_mem_t *test_mctx;
+extern isc_log_t *test_lctx;
+extern isc_taskmgr_t *taskmgr;
+extern isc_timermgr_t *timermgr;
+extern isc_socketmgr_t *socketmgr;
+extern isc_nm_t *netmgr;
+extern int ncpus;
+
+isc_result_t
+isc_test_begin(FILE *logfile, bool start_managers, unsigned int workers);
+/*%<
+ * Begin test, logging to 'logfile' or default if not specified.
+ *
+ * If 'start_managers' is set, start a task manager, timer manager,
+ * and socket manager.
+ *
+ * If 'workers' is zero, use the number of CPUs on the system as a default;
+ * otherwise, set up the task manager with the specified number of worker
+ * threads. The environment variable ISC_TASK_WORKERS overrides this value.
+ */
+
+void
+isc_test_end(void);
+
+void
+isc_test_nap(uint32_t usec);
diff --git a/lib/isc/tests/lex_test.c b/lib/isc/tests/lex_test.c
new file mode 100644
index 0000000..4f8f80d
--- /dev/null
+++ b/lib/isc/tests/lex_test.c
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/lex.h>
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+#define AS_STR(x) (x).value.as_textregion.base
+
+static bool debug = false;
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+/* check handling of 0xff */
+static void
+lex_0xff(void **state) {
+ isc_result_t result;
+ isc_lex_t *lex = NULL;
+ isc_buffer_t death_buf;
+ isc_token_t token;
+
+ unsigned char death[] = { EOF, 'A' };
+
+ UNUSED(state);
+
+ result = isc_lex_create(test_mctx, 1024, &lex);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_init(&death_buf, &death[0], sizeof(death));
+ isc_buffer_add(&death_buf, sizeof(death));
+
+ result = isc_lex_openbuffer(lex, &death_buf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_lex_gettoken(lex, 0, &token);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_lex_destroy(&lex);
+}
+
+/* check setting of source line */
+static void
+lex_setline(void **state) {
+ isc_result_t result;
+ isc_lex_t *lex = NULL;
+ unsigned char text[] = "text\nto\nbe\nprocessed\nby\nlexer";
+ isc_buffer_t buf;
+ isc_token_t token;
+ unsigned long line;
+ int i;
+
+ UNUSED(state);
+
+ result = isc_lex_create(test_mctx, 1024, &lex);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_init(&buf, &text[0], sizeof(text));
+ isc_buffer_add(&buf, sizeof(text));
+
+ result = isc_lex_openbuffer(lex, &buf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_lex_setsourceline(lex, 100);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ for (i = 0; i < 6; i++) {
+ result = isc_lex_gettoken(lex, 0, &token);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ line = isc_lex_getsourceline(lex);
+ assert_int_equal(line, 100U + i);
+ }
+
+ result = isc_lex_gettoken(lex, 0, &token);
+ assert_int_equal(result, ISC_R_EOF);
+
+ line = isc_lex_getsourceline(lex);
+ assert_int_equal(line, 105U);
+
+ isc_lex_destroy(&lex);
+}
+
+static struct {
+ const char *text;
+ const char *string_value;
+ isc_result_t string_result;
+ isc_tokentype_t string_type;
+ const char *qstring_value;
+ isc_result_t qstring_result;
+ isc_tokentype_t qstring_type;
+ const char *qvpair_value;
+ isc_result_t qvpair_result;
+ isc_tokentype_t qvpair_type;
+} parse_tests[] = {
+ { "", "", ISC_R_SUCCESS, isc_tokentype_eof, "", ISC_R_SUCCESS,
+ isc_tokentype_eof, "", ISC_R_SUCCESS, isc_tokentype_eof },
+ { "1234", "1234", ISC_R_SUCCESS, isc_tokentype_string, "1234",
+ ISC_R_SUCCESS, isc_tokentype_string, "1234", ISC_R_SUCCESS,
+ isc_tokentype_string },
+ { "1234=", "1234=", ISC_R_SUCCESS, isc_tokentype_string,
+ "1234=", ISC_R_SUCCESS, isc_tokentype_string, "1234=", ISC_R_SUCCESS,
+ isc_tokentype_vpair },
+ { "1234=foo", "1234=foo", ISC_R_SUCCESS, isc_tokentype_string,
+ "1234=foo", ISC_R_SUCCESS, isc_tokentype_string, "1234=foo",
+ ISC_R_SUCCESS, isc_tokentype_vpair },
+ { "1234=\"foo", "1234=\"foo", ISC_R_SUCCESS, isc_tokentype_string,
+ "1234=\"foo", ISC_R_SUCCESS, isc_tokentype_string, NULL,
+ ISC_R_UNEXPECTEDEND, 0 },
+ { "1234=\"foo\"", "1234=\"foo\"", ISC_R_SUCCESS, isc_tokentype_string,
+ "1234=\"foo\"", ISC_R_SUCCESS, isc_tokentype_string, "1234=foo",
+ ISC_R_SUCCESS, isc_tokentype_qvpair },
+ { "key", "key", ISC_R_SUCCESS, isc_tokentype_string, "key",
+ ISC_R_SUCCESS, isc_tokentype_string, "key", ISC_R_SUCCESS,
+ isc_tokentype_string },
+ { "\"key=", "\"key=", ISC_R_SUCCESS, isc_tokentype_string, NULL,
+ ISC_R_UNEXPECTEDEND, 0, "\"key=", ISC_R_SUCCESS,
+ isc_tokentype_vpair },
+ { "\"key=\"", "\"key=\"", ISC_R_SUCCESS, isc_tokentype_string, "key=",
+ ISC_R_SUCCESS, isc_tokentype_qstring, NULL, ISC_R_UNEXPECTEDEND, 0 },
+ { "key=\"\"", "key=\"\"", ISC_R_SUCCESS, isc_tokentype_string,
+ "key=\"\"", ISC_R_SUCCESS, isc_tokentype_string,
+ "key=", ISC_R_SUCCESS, isc_tokentype_qvpair },
+ { "key=\"a b\"", "key=\"a", ISC_R_SUCCESS, isc_tokentype_string,
+ "key=\"a", ISC_R_SUCCESS, isc_tokentype_string, "key=a b",
+ ISC_R_SUCCESS, isc_tokentype_qvpair },
+ { "key=\"a\tb\"", "key=\"a", ISC_R_SUCCESS, isc_tokentype_string,
+ "key=\"a", ISC_R_SUCCESS, isc_tokentype_string, "key=a\tb",
+ ISC_R_SUCCESS, isc_tokentype_qvpair },
+ /* double quote not immediately after '=' is not special. */
+ { "key=c\"a b\"", "key=c\"a", ISC_R_SUCCESS, isc_tokentype_string,
+ "key=c\"a", ISC_R_SUCCESS, isc_tokentype_string, "key=c\"a",
+ ISC_R_SUCCESS, isc_tokentype_vpair },
+ /* remove special meaning for '=' by escaping */
+ { "key\\=", "key\\=", ISC_R_SUCCESS, isc_tokentype_string,
+ "key\\=", ISC_R_SUCCESS, isc_tokentype_string,
+ "key\\=", ISC_R_SUCCESS, isc_tokentype_string },
+ { "key\\=\"a\"", "key\\=\"a\"", ISC_R_SUCCESS, isc_tokentype_string,
+ "key\\=\"a\"", ISC_R_SUCCESS, isc_tokentype_string, "key\\=\"a\"",
+ ISC_R_SUCCESS, isc_tokentype_string },
+ { "key\\=\"a \"", "key\\=\"a", ISC_R_SUCCESS, isc_tokentype_string,
+ "key\\=\"a", ISC_R_SUCCESS, isc_tokentype_string, "key\\=\"a",
+ ISC_R_SUCCESS, isc_tokentype_string },
+ /* vpair with a key of 'key\=' (would need to be deescaped) */
+ { "key\\==", "key\\==", ISC_R_SUCCESS, isc_tokentype_string,
+ "key\\==", ISC_R_SUCCESS, isc_tokentype_string,
+ "key\\==", ISC_R_SUCCESS, isc_tokentype_vpair },
+ { "key\\==\"\"", "key\\==\"\"", ISC_R_SUCCESS, isc_tokentype_string,
+ "key\\==\"\"", ISC_R_SUCCESS, isc_tokentype_string,
+ "key\\==", ISC_R_SUCCESS, isc_tokentype_qvpair },
+ { "key=\\\\\\\\", "key=\\\\\\\\", ISC_R_SUCCESS, isc_tokentype_string,
+ "key=\\\\\\\\", ISC_R_SUCCESS, isc_tokentype_string, "key=\\\\\\\\",
+ ISC_R_SUCCESS, isc_tokentype_vpair },
+ { "key=\\\\\\\"", "key=\\\\\\\"", ISC_R_SUCCESS, isc_tokentype_string,
+ "key=\\\\\\\"", ISC_R_SUCCESS, isc_tokentype_string, "key=\\\\\\\"",
+ ISC_R_SUCCESS, isc_tokentype_vpair },
+ /* incomplete escape sequence */
+ { "key=\\\"\\", NULL, ISC_R_UNEXPECTEDEND, isc_tokentype_string, NULL,
+ ISC_R_UNEXPECTEDEND, 0, NULL, ISC_R_UNEXPECTEDEND, 0 },
+ /* incomplete escape sequence */
+ { "key=\\", NULL, ISC_R_UNEXPECTEDEND, isc_tokentype_string, NULL,
+ ISC_R_UNEXPECTEDEND, 0, NULL, ISC_R_UNEXPECTEDEND, 0 },
+};
+
+/*%
+ * string
+ */
+static void
+lex_string(void **state) {
+ isc_buffer_t buf;
+ isc_lex_t *lex = NULL;
+ isc_result_t result;
+ isc_token_t token;
+ size_t i;
+
+ UNUSED(state);
+
+ for (i = 0; i < ARRAY_SIZE(parse_tests); i++) {
+ result = isc_lex_create(test_mctx, 1024, &lex);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_constinit(&buf, parse_tests[i].text,
+ strlen(parse_tests[i].text));
+ isc_buffer_add(&buf, strlen(parse_tests[i].text));
+
+ result = isc_lex_openbuffer(lex, &buf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_lex_setsourceline(lex, 100);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(&token, 0, sizeof(token));
+ result = isc_lex_getmastertoken(lex, &token,
+ isc_tokentype_string, true);
+ if (debug) {
+ fprintf(stdout, "# '%s' -> result=%s/%s, type=%d/%d\n",
+ parse_tests[i].text, isc_result_toid(result),
+ isc_result_toid(parse_tests[i].string_result),
+ token.type, parse_tests[i].string_type);
+ }
+
+ assert_int_equal(result, parse_tests[i].string_result);
+ if (result == ISC_R_SUCCESS) {
+ switch (token.type) {
+ case isc_tokentype_string:
+ case isc_tokentype_qstring:
+ case isc_tokentype_vpair:
+ case isc_tokentype_qvpair:
+ if (debug) {
+ fprintf(stdout, "# value='%s'\n",
+ AS_STR(token));
+ }
+ assert_int_equal(token.type,
+ parse_tests[i].string_type);
+ assert_string_equal(
+ AS_STR(token),
+ parse_tests[i].string_value);
+ break;
+ default:
+ assert_int_equal(token.type,
+ parse_tests[i].string_type);
+ break;
+ }
+ }
+
+ isc_lex_destroy(&lex);
+ }
+}
+
+/*%
+ * qstring
+ */
+static void
+lex_qstring(void **state) {
+ isc_buffer_t buf;
+ isc_lex_t *lex = NULL;
+ isc_result_t result;
+ isc_token_t token;
+ size_t i;
+
+ UNUSED(state);
+
+ for (i = 0; i < ARRAY_SIZE(parse_tests); i++) {
+ result = isc_lex_create(test_mctx, 1024, &lex);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_constinit(&buf, parse_tests[i].text,
+ strlen(parse_tests[i].text));
+ isc_buffer_add(&buf, strlen(parse_tests[i].text));
+
+ result = isc_lex_openbuffer(lex, &buf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_lex_setsourceline(lex, 100);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(&token, 0, sizeof(token));
+ result = isc_lex_getmastertoken(lex, &token,
+ isc_tokentype_qstring, true);
+ if (debug) {
+ fprintf(stdout, "# '%s' -> result=%s/%s, type=%d/%d\n",
+ parse_tests[i].text, isc_result_toid(result),
+ isc_result_toid(parse_tests[i].qstring_result),
+ token.type, parse_tests[i].qstring_type);
+ }
+
+ assert_int_equal(result, parse_tests[i].qstring_result);
+ if (result == ISC_R_SUCCESS) {
+ switch (token.type) {
+ case isc_tokentype_string:
+ case isc_tokentype_qstring:
+ case isc_tokentype_vpair:
+ case isc_tokentype_qvpair:
+ if (debug) {
+ fprintf(stdout, "# value='%s'\n",
+ AS_STR(token));
+ }
+ assert_int_equal(token.type,
+ parse_tests[i].qstring_type);
+ assert_string_equal(
+ AS_STR(token),
+ parse_tests[i].qstring_value);
+ break;
+ default:
+ assert_int_equal(token.type,
+ parse_tests[i].qstring_type);
+ break;
+ }
+ }
+
+ isc_lex_destroy(&lex);
+ }
+}
+
+/*%
+ * keypair is <string>=<qstring>. This has implications double quotes
+ * in key names.
+ */
+static void
+lex_keypair(void **state) {
+ isc_buffer_t buf;
+ isc_lex_t *lex = NULL;
+ isc_result_t result;
+ isc_token_t token;
+ size_t i;
+
+ UNUSED(state);
+
+ for (i = 0; i < ARRAY_SIZE(parse_tests); i++) {
+ result = isc_lex_create(test_mctx, 1024, &lex);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_constinit(&buf, parse_tests[i].text,
+ strlen(parse_tests[i].text));
+ isc_buffer_add(&buf, strlen(parse_tests[i].text));
+
+ result = isc_lex_openbuffer(lex, &buf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_lex_setsourceline(lex, 100);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(&token, 0, sizeof(token));
+ result = isc_lex_getmastertoken(lex, &token,
+ isc_tokentype_qvpair, true);
+ if (debug) {
+ fprintf(stdout, "# '%s' -> result=%s/%s, type=%d/%d\n",
+ parse_tests[i].text, isc_result_toid(result),
+ isc_result_toid(parse_tests[i].qvpair_result),
+ token.type, parse_tests[i].qvpair_type);
+ }
+
+ assert_int_equal(result, parse_tests[i].qvpair_result);
+ if (result == ISC_R_SUCCESS) {
+ switch (token.type) {
+ case isc_tokentype_string:
+ case isc_tokentype_qstring:
+ case isc_tokentype_vpair:
+ case isc_tokentype_qvpair:
+ if (debug) {
+ fprintf(stdout, "# value='%s'\n",
+ AS_STR(token));
+ }
+ assert_int_equal(token.type,
+ parse_tests[i].qvpair_type);
+ assert_string_equal(
+ AS_STR(token),
+ parse_tests[i].qvpair_value);
+ break;
+ default:
+ assert_int_equal(token.type,
+ parse_tests[i].qvpair_type);
+ break;
+ }
+ }
+
+ isc_lex_destroy(&lex);
+ }
+}
+
+int
+main(int argc, char *argv[]) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(lex_0xff), cmocka_unit_test(lex_keypair),
+ cmocka_unit_test(lex_setline), cmocka_unit_test(lex_string),
+ cmocka_unit_test(lex_qstring),
+ };
+
+ UNUSED(argv);
+
+ if (argc > 1) {
+ debug = true;
+ }
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/md_test.c b/lib/isc/tests/md_test.c
new file mode 100644
index 0000000..83c44c4
--- /dev/null
+++ b/lib/isc/tests/md_test.c
@@ -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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+
+/* For FIPS_mode() */
+#include <openssl/crypto.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/hex.h>
+#include <isc/md.h>
+#include <isc/region.h>
+#include <isc/result.h>
+
+#include "../md.c"
+
+#define TEST_INPUT(x) (x), sizeof(x) - 1
+
+static int
+_setup(void **state) {
+ isc_md_t *md = isc_md_new();
+ if (md == NULL) {
+ return (-1);
+ }
+ *state = md;
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ if (*state == NULL) {
+ return (-1);
+ }
+ isc_md_free(*state);
+ return (0);
+}
+
+static int
+_reset(void **state) {
+ if (*state == NULL) {
+ return (-1);
+ }
+ if (isc_md_reset(*state) != ISC_R_SUCCESS) {
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+isc_md_new_test(void **state) {
+ UNUSED(state);
+
+ isc_md_t *md = isc_md_new();
+ assert_non_null(md);
+ isc_md_free(md); /* Cleanup */
+}
+
+static void
+isc_md_free_test(void **state) {
+ UNUSED(state);
+
+ isc_md_t *md = isc_md_new();
+ assert_non_null(md);
+ isc_md_free(md); /* Test freeing valid message digest context */
+ isc_md_free(NULL); /* Test freeing NULL argument */
+}
+
+static void
+isc_md_test(isc_md_t *md, const isc_md_type_t *type, const char *buf,
+ size_t buflen, const char *result, const int repeats) {
+ assert_non_null(md);
+ assert_int_equal(isc_md_init(md, type), ISC_R_SUCCESS);
+
+ int i;
+
+ for (i = 0; i < repeats; i++) {
+ assert_int_equal(
+ isc_md_update(md, (const unsigned char *)buf, buflen),
+ ISC_R_SUCCESS);
+ }
+
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ unsigned int digestlen;
+ assert_int_equal(isc_md_final(md, digest, &digestlen), ISC_R_SUCCESS);
+
+ char hexdigest[ISC_MAX_MD_SIZE * 2 + 3];
+ isc_region_t r = { .base = digest, .length = digestlen };
+ isc_buffer_t b;
+ isc_buffer_init(&b, hexdigest, sizeof(hexdigest));
+
+ assert_return_code(isc_hex_totext(&r, 0, "", &b), ISC_R_SUCCESS);
+
+ assert_memory_equal(hexdigest, result, (result ? strlen(result) : 0));
+ assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS);
+}
+
+static void
+isc_md_init_test(void **state) {
+ isc_md_t *md = *state;
+ assert_non_null(md);
+
+ expect_assert_failure(isc_md_init(NULL, ISC_MD_MD5));
+
+ assert_int_equal(isc_md_init(md, NULL), ISC_R_NOTIMPLEMENTED);
+
+ assert_int_equal(isc_md_init(md, ISC_MD_MD5), ISC_R_SUCCESS);
+ assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS);
+
+ assert_int_equal(isc_md_init(md, ISC_MD_SHA1), ISC_R_SUCCESS);
+ assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS);
+
+ assert_int_equal(isc_md_init(md, ISC_MD_SHA224), ISC_R_SUCCESS);
+ assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS);
+
+ assert_int_equal(isc_md_init(md, ISC_MD_SHA256), ISC_R_SUCCESS);
+ assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS);
+
+ assert_int_equal(isc_md_init(md, ISC_MD_SHA384), ISC_R_SUCCESS);
+ assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS);
+
+ assert_int_equal(isc_md_init(md, ISC_MD_SHA512), ISC_R_SUCCESS);
+ assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS);
+}
+
+static void
+isc_md_update_test(void **state) {
+ isc_md_t *md = *state;
+ assert_non_null(md);
+
+ /* Uses message digest context initialized in isc_md_init_test() */
+ expect_assert_failure(isc_md_update(NULL, NULL, 0));
+
+ assert_int_equal(isc_md_update(md, NULL, 100), ISC_R_SUCCESS);
+ assert_int_equal(isc_md_update(md, (const unsigned char *)"", 0),
+ ISC_R_SUCCESS);
+}
+
+static void
+isc_md_reset_test(void **state) {
+ isc_md_t *md = *state;
+#if 0
+ unsigned char digest[ISC_MAX_MD_SIZE] __attribute((unused));
+ unsigned int digestlen __attribute((unused));
+#endif /* if 0 */
+
+ assert_non_null(md);
+
+ assert_int_equal(isc_md_init(md, ISC_MD_SHA512), ISC_R_SUCCESS);
+ assert_int_equal(isc_md_update(md, (const unsigned char *)"a", 1),
+ ISC_R_SUCCESS);
+ assert_int_equal(isc_md_update(md, (const unsigned char *)"b", 1),
+ ISC_R_SUCCESS);
+
+ assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS);
+
+#if 0
+ /*
+ * This test would require OpenSSL compiled with mock_assert(),
+ * so this could be only manually checked that the test will
+ * segfault when called by hand
+ */
+ expect_assert_failure(isc_md_final(md,digest,&digestlen));
+#endif /* if 0 */
+}
+
+static void
+isc_md_final_test(void **state) {
+ isc_md_t *md = *state;
+ assert_non_null(md);
+
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ unsigned int digestlen;
+
+ /* Fail when message digest context is empty */
+ expect_assert_failure(isc_md_final(NULL, digest, &digestlen));
+ /* Fail when output buffer is empty */
+ expect_assert_failure(isc_md_final(md, NULL, &digestlen));
+
+ assert_int_equal(isc_md_init(md, ISC_MD_SHA512), ISC_R_SUCCESS);
+ assert_int_equal(isc_md_final(md, digest, NULL), ISC_R_SUCCESS);
+}
+
+static void
+isc_md_md5_test(void **state) {
+ isc_md_t *md = *state;
+ isc_md_test(md, ISC_MD_MD5, NULL, 0, NULL, 0);
+ isc_md_test(md, ISC_MD_MD5, TEST_INPUT(""),
+ "D41D8CD98F00B204E9800998ECF8427E", 1);
+ isc_md_test(md, ISC_MD_MD5, TEST_INPUT("a"),
+ "0CC175B9C0F1B6A831C399E269772661", 1);
+ isc_md_test(md, ISC_MD_MD5, TEST_INPUT("abc"),
+ "900150983CD24FB0D6963F7D28E17F72", 1);
+ isc_md_test(md, ISC_MD_MD5, TEST_INPUT("message digest"),
+ "F96B697D7CB7938D525A2F31AAF161D0", 1);
+ isc_md_test(md, ISC_MD_MD5, TEST_INPUT("abcdefghijklmnopqrstuvwxyz"),
+ "C3FCD3D76192E4007DFB496CCA67E13B", 1);
+ isc_md_test(md, ISC_MD_MD5,
+ TEST_INPUT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm"
+ "nopqrstuvwxyz0123456789"),
+ "D174AB98D277D9F5A5611C2C9F419D9F", 1);
+ isc_md_test(md, ISC_MD_MD5,
+ TEST_INPUT("123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890"),
+ "57EDF4A22BE3C955AC49DA2E2107B67A", 1);
+}
+
+static void
+isc_md_sha1_test(void **state) {
+ isc_md_t *md = *state;
+ isc_md_test(md, ISC_MD_SHA1, NULL, 0, NULL, 0);
+ isc_md_test(md, ISC_MD_SHA1, TEST_INPUT(""),
+ "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", 1);
+ isc_md_test(md, ISC_MD_SHA1, TEST_INPUT("abc"),
+ "A9993E364706816ABA3E25717850C26C9CD0D89D", 1);
+ isc_md_test(md, ISC_MD_SHA1,
+ TEST_INPUT("abcdbcdecdefdefgefghfghighijhijkijk"
+ "ljklmklmnlmnomnopnopq"),
+ "84983E441C3BD26EBAAE4AA1F95129E5E54670F1", 1);
+ isc_md_test(md, ISC_MD_SHA1, TEST_INPUT("a"),
+ "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F", 1000000);
+ isc_md_test(md, ISC_MD_SHA1,
+ TEST_INPUT("01234567012345670123456701234567"),
+ "DEA356A2CDDD90C7A7ECEDC5EBB563934F460452", 20);
+ isc_md_test(md, ISC_MD_SHA1, TEST_INPUT("\x5e"),
+ "5E6F80A34A9798CAFC6A5DB96CC57BA4C4DB59C2", 1);
+ isc_md_test(md, ISC_MD_SHA1,
+ TEST_INPUT("\x9a\x7d\xfd\xf1\xec\xea\xd0\x6e\xd6\x46"
+ "\xaa\x55\xfe\x75\x71\x46"),
+ "82ABFF6605DBE1C17DEF12A394FA22A82B544A35", 1);
+ isc_md_test(md, ISC_MD_SHA1,
+ TEST_INPUT("\xf7\x8f\x92\x14\x1b\xcd\x17\x0a\xe8\x9b"
+ "\x4f\xba\x15\xa1\xd5\x9f\x3f\xd8\x4d\x22"
+ "\x3c\x92\x51\xbd\xac\xbb\xae\x61\xd0\x5e"
+ "\xd1\x15\xa0\x6a\x7c\xe1\x17\xb7\xbe\xea"
+ "\xd2\x44\x21\xde\xd9\xc3\x25\x92\xbd\x57"
+ "\xed\xea\xe3\x9c\x39\xfa\x1f\xe8\x94\x6a"
+ "\x84\xd0\xcf\x1f\x7b\xee\xad\x17\x13\xe2"
+ "\xe0\x95\x98\x97\x34\x7f\x67\xc8\x0b\x04"
+ "\x00\xc2\x09\x81\x5d\x6b\x10\xa6\x83\x83"
+ "\x6f\xd5\x56\x2a\x56\xca\xb1\xa2\x8e\x81"
+ "\xb6\x57\x66\x54\x63\x1c\xf1\x65\x66\xb8"
+ "\x6e\x3b\x33\xa1\x08\xb0\x53\x07\xc0\x0a"
+ "\xff\x14\xa7\x68\xed\x73\x50\x60\x6a\x0f"
+ "\x85\xe6\xa9\x1d\x39\x6f\x5b\x5c\xbe\x57"
+ "\x7f\x9b\x38\x80\x7c\x7d\x52\x3d\x6d\x79"
+ "\x2f\x6e\xbc\x24\xa4\xec\xf2\xb3\xa4\x27"
+ "\xcd\xbb\xfb"),
+ "CB0082C8F197D260991BA6A460E76E202BAD27B3", 1);
+}
+
+static void
+isc_md_sha224_test(void **state) {
+ isc_md_t *md = *state;
+
+ isc_md_test(md, ISC_MD_SHA224, NULL, 0, NULL, 0);
+ isc_md_test(md, ISC_MD_SHA224, TEST_INPUT(""),
+ "D14A028C2A3A2BC9476102BB288234C415A2B01F828EA62AC5B3E42F",
+ 1);
+ isc_md_test(md, ISC_MD_SHA224, TEST_INPUT("abc"),
+ "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7"
+ "E36C9DA7",
+ 1);
+ isc_md_test(md, ISC_MD_SHA224,
+ TEST_INPUT("abcdbcdecdefdefgefghfghighijhijkijklj"
+ "klmklmnlmnomnopnopq"),
+ "75388B16512776CC5DBA5DA1FD890150B0C6455CB4F58B"
+ "1952522525",
+ 1);
+ isc_md_test(md, ISC_MD_SHA224, TEST_INPUT("a"),
+ "20794655980C91D8BBB4C1EA97618A4BF03F42581948B2"
+ "EE4EE7AD67",
+ 1000000);
+ isc_md_test(md, ISC_MD_SHA224,
+ TEST_INPUT("01234567012345670123456701234567"),
+ "567F69F168CD7844E65259CE658FE7AADFA25216E68ECA"
+ "0EB7AB8262",
+ 20);
+ isc_md_test(md, ISC_MD_SHA224, TEST_INPUT("\x07"),
+ "00ECD5F138422B8AD74C9799FD826C531BAD2FCABC7450"
+ "BEE2AA8C2A",
+ 1);
+ isc_md_test(md, ISC_MD_SHA224,
+ TEST_INPUT("\x18\x80\x40\x05\xdd\x4f\xbd\x15\x56\x29"
+ "\x9d\x6f\x9d\x93\xdf\x62"),
+ "DF90D78AA78821C99B40BA4C966921ACCD8FFB1E98AC38"
+ "8E56191DB1",
+ 1);
+ isc_md_test(md, ISC_MD_SHA224,
+ TEST_INPUT("\x55\xb2\x10\x07\x9c\x61\xb5\x3a\xdd\x52"
+ "\x06\x22\xd1\xac\x97\xd5\xcd\xbe\x8c\xb3"
+ "\x3a\xa0\xae\x34\x45\x17\xbe\xe4\xd7\xba"
+ "\x09\xab\xc8\x53\x3c\x52\x50\x88\x7a\x43"
+ "\xbe\xbb\xac\x90\x6c\x2e\x18\x37\xf2\x6b"
+ "\x36\xa5\x9a\xe3\xbe\x78\x14\xd5\x06\x89"
+ "\x6b\x71\x8b\x2a\x38\x3e\xcd\xac\x16\xb9"
+ "\x61\x25\x55\x3f\x41\x6f\xf3\x2c\x66\x74"
+ "\xc7\x45\x99\xa9\x00\x53\x86\xd9\xce\x11"
+ "\x12\x24\x5f\x48\xee\x47\x0d\x39\x6c\x1e"
+ "\xd6\x3b\x92\x67\x0c\xa5\x6e\xc8\x4d\xee"
+ "\xa8\x14\xb6\x13\x5e\xca\x54\x39\x2b\xde"
+ "\xdb\x94\x89\xbc\x9b\x87\x5a\x8b\xaf\x0d"
+ "\xc1\xae\x78\x57\x36\x91\x4a\xb7\xda\xa2"
+ "\x64\xbc\x07\x9d\x26\x9f\x2c\x0d\x7e\xdd"
+ "\xd8\x10\xa4\x26\x14\x5a\x07\x76\xf6\x7c"
+ "\x87\x82\x73"),
+ "0B31894EC8937AD9B91BDFBCBA294D9ADEFAA18E09305E"
+ "9F20D5C3A4",
+ 1);
+}
+
+static void
+isc_md_sha256_test(void **state) {
+ isc_md_t *md = *state;
+
+ isc_md_test(md, ISC_MD_SHA256, NULL, 0, NULL, 0);
+ isc_md_test(md, ISC_MD_SHA256, TEST_INPUT(""),
+ "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B93"
+ "4CA495991B7852B855",
+ 1);
+
+ isc_md_test(md, ISC_MD_SHA256, TEST_INPUT("abc"),
+ "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A"
+ "9CB410FF61F20015AD",
+ 1);
+ isc_md_test(md, ISC_MD_SHA256,
+ TEST_INPUT("abcdbcdecdefdefgefghfghighijhijkijkljk"
+ "lmklmnlmnomnopnopq"),
+ "248D6A61D20638B8E5C026930C3E6039A33CE45964FF21"
+ "67F6ECEDD419DB06C1",
+ 1);
+ isc_md_test(md, ISC_MD_SHA256, TEST_INPUT("a"),
+ "CDC76E5C9914FB9281A1C7E284D73E67F1809A48A49720"
+ "0E046D39CCC7112CD0",
+ 1000000);
+ isc_md_test(md, ISC_MD_SHA256,
+ TEST_INPUT("01234567012345670123456701234567"),
+ "594847328451BDFA85056225462CC1D867D877FB388DF0"
+ "CE35F25AB5562BFBB5",
+ 20);
+ isc_md_test(md, ISC_MD_SHA256, TEST_INPUT("\x19"),
+ "68AA2E2EE5DFF96E3355E6C7EE373E3D6A4E17F75F9518"
+ "D843709C0C9BC3E3D4",
+ 1);
+ isc_md_test(md, ISC_MD_SHA256,
+ TEST_INPUT("\xe3\xd7\x25\x70\xdc\xdd\x78\x7c\xe3"
+ "\x88\x7a\xb2\xcd\x68\x46\x52"),
+ "175EE69B02BA9B58E2B0A5FD13819CEA573F3940A94F82"
+ "5128CF4209BEABB4E8",
+ 1);
+ isc_md_test(md, ISC_MD_SHA256,
+ TEST_INPUT("\x83\x26\x75\x4e\x22\x77\x37\x2f\x4f\xc1"
+ "\x2b\x20\x52\x7a\xfe\xf0\x4d\x8a\x05\x69"
+ "\x71\xb1\x1a\xd5\x71\x23\xa7\xc1\x37\x76"
+ "\x00\x00\xd7\xbe\xf6\xf3\xc1\xf7\xa9\x08"
+ "\x3a\xa3\x9d\x81\x0d\xb3\x10\x77\x7d\xab"
+ "\x8b\x1e\x7f\x02\xb8\x4a\x26\xc7\x73\x32"
+ "\x5f\x8b\x23\x74\xde\x7a\x4b\x5a\x58\xcb"
+ "\x5c\x5c\xf3\x5b\xce\xe6\xfb\x94\x6e\x5b"
+ "\xd6\x94\xfa\x59\x3a\x8b\xeb\x3f\x9d\x65"
+ "\x92\xec\xed\xaa\x66\xca\x82\xa2\x9d\x0c"
+ "\x51\xbc\xf9\x33\x62\x30\xe5\xd7\x84\xe4"
+ "\xc0\xa4\x3f\x8d\x79\xa3\x0a\x16\x5c\xba"
+ "\xbe\x45\x2b\x77\x4b\x9c\x71\x09\xa9\x7d"
+ "\x13\x8f\x12\x92\x28\x96\x6f\x6c\x0a\xdc"
+ "\x10\x6a\xad\x5a\x9f\xdd\x30\x82\x57\x69"
+ "\xb2\xc6\x71\xaf\x67\x59\xdf\x28\xeb\x39"
+ "\x3d\x54\xd6"),
+ "97DBCA7DF46D62C8A422C941DD7E835B8AD3361763F7E9"
+ "B2D95F4F0DA6E1CCBC",
+ 1);
+}
+
+static void
+isc_md_sha384_test(void **state) {
+ isc_md_t *md = *state;
+
+ isc_md_test(md, ISC_MD_SHA384, NULL, 0, NULL, 0);
+ isc_md_test(md, ISC_MD_SHA384, TEST_INPUT(""),
+ "38B060A751AC96384CD9327EB1B1E36A21FDB71114BE07"
+ "434C0CC7BF63F6E1DA274EDEBFE76F65FBD51AD2F14898"
+ "B95B"
+ "",
+ 1);
+ isc_md_test(md, ISC_MD_SHA384, TEST_INPUT("abc"),
+ "CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED1"
+ "631A8B605A43FF5BED8086072BA1E7CC2358BAEC"
+ "A134C825A7",
+ 1);
+ isc_md_test(md, ISC_MD_SHA384,
+ TEST_INPUT("abcdefghbcdefghicdefghijdefghijkefghijkl"
+ "fghijklmghijklmnhijklmnoijklmnopjklmnopq"
+ "klmnopqrlmnopqrsmnopqrstnopqrstu"),
+ "09330C33F71147E83D192FC782CD1B4753111B173B3B05"
+ "D22FA08086E3B0F712FCC7C71A557E2DB966C3E9"
+ "FA91746039",
+ 1);
+ isc_md_test(md, ISC_MD_SHA384, TEST_INPUT("a"),
+ "9D0E1809716474CB086E834E310A4A1CED149E9C00F248"
+ "527972CEC5704C2A5B07B8B3DC38ECC4EBAE97DD"
+ "D87F3D8985",
+ 1000000);
+ isc_md_test(md, ISC_MD_SHA384,
+ TEST_INPUT("01234567012345670123456701234567"),
+ "2FC64A4F500DDB6828F6A3430B8DD72A368EB7F3A8322A"
+ "70BC84275B9C0B3AB00D27A5CC3C2D224AA6B61A"
+ "0D79FB4596",
+ 20);
+ isc_md_test(md, ISC_MD_SHA384, TEST_INPUT("\xb9"),
+ "BC8089A19007C0B14195F4ECC74094FEC64F01F9092928"
+ "2C2FB392881578208AD466828B1C6C283D2722CF"
+ "0AD1AB6938",
+ 1);
+ isc_md_test(md, ISC_MD_SHA384,
+ TEST_INPUT("\xa4\x1c\x49\x77\x79\xc0\x37\x5f\xf1"
+ "\x0a\x7f\x4e\x08\x59\x17\x39"),
+ "C9A68443A005812256B8EC76B00516F0DBB74FAB26D665"
+ "913F194B6FFB0E91EA9967566B58109CBC675CC2"
+ "08E4C823F7",
+ 1);
+ isc_md_test(md, ISC_MD_SHA384,
+ TEST_INPUT("\x39\x96\x69\xe2\x8f\x6b\x9c\x6d\xbc\xbb"
+ "\x69\x12\xec\x10\xff\xcf\x74\x79\x03\x49"
+ "\xb7\xdc\x8f\xbe\x4a\x8e\x7b\x3b\x56\x21"
+ "\xdb\x0f\x3e\x7d\xc8\x7f\x82\x32\x64\xbb"
+ "\xe4\x0d\x18\x11\xc9\xea\x20\x61\xe1\xc8"
+ "\x4a\xd1\x0a\x23\xfa\xc1\x72\x7e\x72\x02"
+ "\xfc\x3f\x50\x42\xe6\xbf\x58\xcb\xa8\xa2"
+ "\x74\x6e\x1f\x64\xf9\xb9\xea\x35\x2c\x71"
+ "\x15\x07\x05\x3c\xf4\xe5\x33\x9d\x52\x86"
+ "\x5f\x25\xcc\x22\xb5\xe8\x77\x84\xa1\x2f"
+ "\xc9\x61\xd6\x6c\xb6\xe8\x95\x73\x19\x9a"
+ "\x2c\xe6\x56\x5c\xbd\xf1\x3d\xca\x40\x38"
+ "\x32\xcf\xcb\x0e\x8b\x72\x11\xe8\x3a\xf3"
+ "\x2a\x11\xac\x17\x92\x9f\xf1\xc0\x73\xa5"
+ "\x1c\xc0\x27\xaa\xed\xef\xf8\x5a\xad\x7c"
+ "\x2b\x7c\x5a\x80\x3e\x24\x04\xd9\x6d\x2a"
+ "\x77\x35\x7b\xda\x1a\x6d\xae\xed\x17\x15"
+ "\x1c\xb9\xbc\x51\x25\xa4\x22\xe9\x41\xde"
+ "\x0c\xa0\xfc\x50\x11\xc2\x3e\xcf\xfe\xfd"
+ "\xd0\x96\x76\x71\x1c\xf3\xdb\x0a\x34\x40"
+ "\x72\x0e\x16\x15\xc1\xf2\x2f\xbc\x3c\x72"
+ "\x1d\xe5\x21\xe1\xb9\x9b\xa1\xbd\x55\x77"
+ "\x40\x86\x42\x14\x7e\xd0\x96"),
+ "4F440DB1E6EDD2899FA335F09515AA025EE177A79F4B4A"
+ "AF38E42B5C4DE660F5DE8FB2A5B2FBD2A3CBFFD2"
+ "0CFF1288C0",
+ 1);
+}
+
+static void
+isc_md_sha512_test(void **state) {
+ isc_md_t *md = *state;
+
+ isc_md_test(md, ISC_MD_SHA512, NULL, 0, NULL, 0);
+ isc_md_test(md, ISC_MD_SHA512, TEST_INPUT(""),
+ "CF83E1357EEFB8BDF1542850D66D8007D620E4050B5715"
+ "DC83F4A921D36CE9CE47D0D13C5D85F2B0FF8318D2877E"
+ "EC2F63B931BD47417A81A538327AF927DA3E",
+ 1);
+ isc_md_test(md, ISC_MD_SHA512, TEST_INPUT("abc"),
+ "DDAF35A193617ABACC417349AE20413112E6FA4E89A97E"
+ "A20A9EEEE64B55D39A2192992A274FC1A836BA3C"
+ "23A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F",
+ 1);
+ isc_md_test(md, ISC_MD_SHA512,
+ TEST_INPUT("abcdefghbcdefghicdefghijdefghijkefghijkl"
+ "fghijklmghijklmnhijklmnoijklmnopjklmnopq"
+ "klmnopqrlmnopqrsmnopqrstnopqrstu"),
+ "8E959B75DAE313DA8CF4F72814FC143F8F7779C6EB9F7F"
+ "A17299AEADB6889018501D289E4900F7E4331B99"
+ "DEC4B5433AC7D329EEB6DD26545E96E55B874BE909",
+ 1);
+ isc_md_test(md, ISC_MD_SHA512, TEST_INPUT("a"),
+ "E718483D0CE769644E2E42C7BC15B4638E1F98B13B2044"
+ "285632A803AFA973EBDE0FF244877EA60A4CB043"
+ "2CE577C31BEB009C5C2C49AA2E4EADB217AD8CC09B",
+ 1000000);
+ isc_md_test(md, ISC_MD_SHA512,
+ TEST_INPUT("01234567012345670123456701234567"),
+ "89D05BA632C699C31231DED4FFC127D5A894DAD412C0E0"
+ "24DB872D1ABD2BA8141A0F85072A9BE1E2AA04CF"
+ "33C765CB510813A39CD5A84C4ACAA64D3F3FB7BAE9",
+ 20);
+ isc_md_test(md, ISC_MD_SHA512, TEST_INPUT("\xD0"),
+ "9992202938E882E73E20F6B69E68A0A7149090423D93C8"
+ "1BAB3F21678D4ACEEEE50E4E8CAFADA4C85A54EA"
+ "8306826C4AD6E74CECE9631BFA8A549B4AB3FBBA15",
+ 1);
+ isc_md_test(md, ISC_MD_SHA512,
+ TEST_INPUT("\x8d\x4e\x3c\x0e\x38\x89\x19\x14\x91\x81"
+ "\x6e\x9d\x98\xbf\xf0\xa0"),
+ "CB0B67A4B8712CD73C9AABC0B199E9269B20844AFB75AC"
+ "BDD1C153C9828924C3DDEDAAFE669C5FDD0BC66F"
+ "630F6773988213EB1B16F517AD0DE4B2F0C95C90F8",
+ 1);
+ isc_md_test(md, ISC_MD_SHA512,
+ TEST_INPUT("\xa5\x5f\x20\xc4\x11\xaa\xd1\x32\x80\x7a"
+ "\x50\x2d\x65\x82\x4e\x31\xa2\x30\x54\x32"
+ "\xaa\x3d\x06\xd3\xe2\x82\xa8\xd8\x4e\x0d"
+ "\xe1\xde\x69\x74\xbf\x49\x54\x69\xfc\x7f"
+ "\x33\x8f\x80\x54\xd5\x8c\x26\xc4\x93\x60"
+ "\xc3\xe8\x7a\xf5\x65\x23\xac\xf6\xd8\x9d"
+ "\x03\xe5\x6f\xf2\xf8\x68\x00\x2b\xc3\xe4"
+ "\x31\xed\xc4\x4d\xf2\xf0\x22\x3d\x4b\xb3"
+ "\xb2\x43\x58\x6e\x1a\x7d\x92\x49\x36\x69"
+ "\x4f\xcb\xba\xf8\x8d\x95\x19\xe4\xeb\x50"
+ "\xa6\x44\xf8\xe4\xf9\x5e\xb0\xea\x95\xbc"
+ "\x44\x65\xc8\x82\x1a\xac\xd2\xfe\x15\xab"
+ "\x49\x81\x16\x4b\xbb\x6d\xc3\x2f\x96\x90"
+ "\x87\xa1\x45\xb0\xd9\xcc\x9c\x67\xc2\x2b"
+ "\x76\x32\x99\x41\x9c\xc4\x12\x8b\xe9\xa0"
+ "\x77\xb3\xac\xe6\x34\x06\x4e\x6d\x99\x28"
+ "\x35\x13\xdc\x06\xe7\x51\x5d\x0d\x73\x13"
+ "\x2e\x9a\x0d\xc6\xd3\xb1\xf8\xb2\x46\xf1"
+ "\xa9\x8a\x3f\xc7\x29\x41\xb1\xe3\xbb\x20"
+ "\x98\xe8\xbf\x16\xf2\x68\xd6\x4f\x0b\x0f"
+ "\x47\x07\xfe\x1e\xa1\xa1\x79\x1b\xa2\xf3"
+ "\xc0\xc7\x58\xe5\xf5\x51\x86\x3a\x96\xc9"
+ "\x49\xad\x47\xd7\xfb\x40\xd2"),
+ "C665BEFB36DA189D78822D10528CBF3B12B3EEF7260399"
+ "09C1A16A270D48719377966B957A878E72058477"
+ "9A62825C18DA26415E49A7176A894E7510FD1451F5",
+ 1);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ /* isc_md_new() */
+ cmocka_unit_test(isc_md_new_test),
+
+ /* isc_md_init() */
+ cmocka_unit_test_setup_teardown(isc_md_init_test, _reset,
+ _reset),
+
+ /* isc_md_reset() */
+ cmocka_unit_test_setup_teardown(isc_md_reset_test, _reset,
+ _reset),
+
+ /* isc_md_init() -> isc_md_update() -> isc_md_final() */
+ cmocka_unit_test(isc_md_md5_test),
+ cmocka_unit_test(isc_md_sha1_test),
+ cmocka_unit_test(isc_md_sha224_test),
+ cmocka_unit_test(isc_md_sha256_test),
+ cmocka_unit_test(isc_md_sha384_test),
+ cmocka_unit_test(isc_md_sha512_test),
+
+ cmocka_unit_test_setup_teardown(isc_md_update_test, _reset,
+ _reset),
+ cmocka_unit_test_setup_teardown(isc_md_final_test, _reset,
+ _reset),
+
+ cmocka_unit_test(isc_md_free_test),
+ };
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/mem_test.c b/lib/isc/tests/mem_test.c
new file mode 100644
index 0000000..9b180cd
--- /dev/null
+++ b/lib/isc/tests/mem_test.c
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <fcntl.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/file.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/os.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/stdio.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include "../mem_p.h"
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+#define MP1_FREEMAX 10
+#define MP1_FILLCNT 10
+#define MP1_MAXALLOC 30
+
+#define MP2_FREEMAX 25
+#define MP2_FILLCNT 25
+
+/* general memory system tests */
+static void
+isc_mem_test(void **state) {
+ void *items1[50];
+ void *items2[50];
+ void *tmp;
+ isc_mempool_t *mp1 = NULL, *mp2 = NULL;
+ unsigned int i, j;
+ int rval;
+
+ UNUSED(state);
+
+ isc_mempool_create(test_mctx, 24, &mp1);
+ isc_mempool_create(test_mctx, 31, &mp2);
+
+ isc_mempool_setfreemax(mp1, MP1_FREEMAX);
+ isc_mempool_setfillcount(mp1, MP1_FILLCNT);
+ isc_mempool_setmaxalloc(mp1, MP1_MAXALLOC);
+
+ /*
+ * Allocate MP1_MAXALLOC items from the pool. This is our max.
+ */
+ for (i = 0; i < MP1_MAXALLOC; i++) {
+ items1[i] = isc_mempool_get(mp1);
+ assert_non_null(items1[i]);
+ }
+
+ /*
+ * Try to allocate one more. This should fail.
+ */
+ tmp = isc_mempool_get(mp1);
+ assert_null(tmp);
+
+ /*
+ * Free the first 11 items. Verify that there are 10 free items on
+ * the free list (which is our max).
+ */
+ for (i = 0; i < 11; i++) {
+ isc_mempool_put(mp1, items1[i]);
+ items1[i] = NULL;
+ }
+
+#if !__SANITIZE_ADDRESS__
+ rval = isc_mempool_getfreecount(mp1);
+ assert_int_equal(rval, 10);
+#endif /* !__SANITIZE_ADDRESS__ */
+
+ rval = isc_mempool_getallocated(mp1);
+ assert_int_equal(rval, 19);
+
+ /*
+ * Now, beat up on mp2 for a while. Allocate 50 items, then free
+ * them, then allocate 50 more, etc.
+ */
+
+ isc_mempool_setfreemax(mp2, 25);
+ isc_mempool_setfillcount(mp2, 25);
+
+ for (j = 0; j < 500000; j++) {
+ for (i = 0; i < 50; i++) {
+ items2[i] = isc_mempool_get(mp2);
+ assert_non_null(items2[i]);
+ }
+ for (i = 0; i < 50; i++) {
+ isc_mempool_put(mp2, items2[i]);
+ items2[i] = NULL;
+ }
+ }
+
+ /*
+ * Free all the other items and blow away this pool.
+ */
+ for (i = 11; i < MP1_MAXALLOC; i++) {
+ isc_mempool_put(mp1, items1[i]);
+ items1[i] = NULL;
+ }
+
+ isc_mempool_destroy(&mp1);
+ isc_mempool_destroy(&mp2);
+
+ isc_mempool_create(test_mctx, 2, &mp1);
+
+ tmp = isc_mempool_get(mp1);
+ assert_non_null(tmp);
+
+ isc_mempool_put(mp1, tmp);
+
+ isc_mempool_destroy(&mp1);
+}
+
+/* test TotalUse calculation */
+static void
+isc_mem_total_test(void **state) {
+ isc_mem_t *mctx2 = NULL;
+ size_t before, after;
+ ssize_t diff;
+ int i;
+
+ UNUSED(state);
+
+ /* Local alloc, free */
+ mctx2 = NULL;
+ isc_mem_create(&mctx2);
+
+ before = isc_mem_total(mctx2);
+
+ for (i = 0; i < 100000; i++) {
+ void *ptr;
+
+ ptr = isc_mem_allocate(mctx2, 2048);
+ isc_mem_free(mctx2, ptr);
+ }
+
+ after = isc_mem_total(mctx2);
+ diff = after - before;
+
+ /* 2048 +8 bytes extra for size_info */
+ assert_int_equal(diff, (2048 + 8) * 100000);
+
+ /* ISC_MEMFLAG_INTERNAL */
+
+ before = isc_mem_total(test_mctx);
+
+ for (i = 0; i < 100000; i++) {
+ void *ptr;
+
+ ptr = isc_mem_allocate(test_mctx, 2048);
+ isc_mem_free(test_mctx, ptr);
+ }
+
+ after = isc_mem_total(test_mctx);
+ diff = after - before;
+
+ /* 2048 +8 bytes extra for size_info */
+ assert_int_equal(diff, (2048 + 8) * 100000);
+
+ isc_mem_destroy(&mctx2);
+}
+
+/* test InUse calculation */
+static void
+isc_mem_inuse_test(void **state) {
+ isc_mem_t *mctx2 = NULL;
+ size_t before, after;
+ ssize_t diff;
+ void *ptr;
+
+ UNUSED(state);
+
+ mctx2 = NULL;
+ isc_mem_create(&mctx2);
+
+ before = isc_mem_inuse(mctx2);
+ ptr = isc_mem_allocate(mctx2, 1024000);
+ isc_mem_free(mctx2, ptr);
+ after = isc_mem_inuse(mctx2);
+
+ diff = after - before;
+
+ assert_int_equal(diff, 0);
+
+ isc_mem_destroy(&mctx2);
+}
+
+#if ISC_MEM_TRACKLINES
+
+/* test mem with no flags */
+static void
+isc_mem_noflags_test(void **state) {
+ isc_result_t result;
+ isc_mem_t *mctx2 = NULL;
+ char buf[4096], *p, *q;
+ FILE *f;
+ void *ptr;
+
+ result = isc_stdio_open("mem.output", "w", &f);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ UNUSED(state);
+
+ isc_mem_create(&mctx2);
+ isc_mem_debugging = 0;
+ ptr = isc_mem_get(mctx2, 2048);
+ assert_non_null(ptr);
+ isc__mem_printactive(mctx2, f);
+ isc_mem_put(mctx2, ptr, 2048);
+ isc_mem_destroy(&mctx2);
+ isc_stdio_close(f);
+
+ memset(buf, 0, sizeof(buf));
+ result = isc_stdio_open("mem.output", "r", &f);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL);
+ assert_int_equal(result, ISC_R_EOF);
+ isc_stdio_close(f);
+ isc_file_remove("mem.output");
+
+ buf[sizeof(buf) - 1] = 0;
+
+ p = strchr(buf, '\n');
+ assert_non_null(p);
+ assert_in_range(p, 0, buf + sizeof(buf) - 3);
+ p += 2;
+ q = strchr(p, '\n');
+ assert_non_null(q);
+ *q = '\0';
+ assert_string_equal(p, "None.");
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+}
+
+/* test mem with record flag */
+static void
+isc_mem_recordflag_test(void **state) {
+ isc_result_t result;
+ isc_mem_t *mctx2 = NULL;
+ char buf[4096], *p;
+ FILE *f;
+ void *ptr;
+
+ result = isc_stdio_open("mem.output", "w", &f);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ UNUSED(state);
+
+ isc_mem_create(&mctx2);
+ ptr = isc_mem_get(mctx2, 2048);
+ assert_non_null(ptr);
+ isc__mem_printactive(mctx2, f);
+ isc_mem_put(mctx2, ptr, 2048);
+ isc_mem_destroy(&mctx2);
+ isc_stdio_close(f);
+
+ memset(buf, 0, sizeof(buf));
+ result = isc_stdio_open("mem.output", "r", &f);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL);
+ assert_int_equal(result, ISC_R_EOF);
+ isc_stdio_close(f);
+ isc_file_remove("mem.output");
+
+ buf[sizeof(buf) - 1] = 0;
+
+ p = strchr(buf, '\n');
+ assert_non_null(p);
+ assert_in_range(p, 0, buf + sizeof(buf) - 3);
+ assert_memory_equal(p + 2, "ptr ", 4);
+ p = strchr(p + 1, '\n');
+ assert_non_null(p);
+ assert_int_equal(strlen(p), 1);
+}
+
+/* test mem with trace flag */
+static void
+isc_mem_traceflag_test(void **state) {
+ isc_result_t result;
+ isc_mem_t *mctx2 = NULL;
+ char buf[4096], *p;
+ FILE *f;
+ void *ptr;
+
+ /* redirect stderr so we can check trace output */
+ f = freopen("mem.output", "w", stderr);
+ assert_non_null(f);
+
+ UNUSED(state);
+
+ isc_mem_create(&mctx2);
+ isc_mem_debugging = ISC_MEM_DEBUGTRACE;
+ ptr = isc_mem_get(mctx2, 2048);
+ assert_non_null(ptr);
+ isc__mem_printactive(mctx2, f);
+ isc_mem_put(mctx2, ptr, 2048);
+ isc_mem_destroy(&mctx2);
+ isc_stdio_close(f);
+
+ memset(buf, 0, sizeof(buf));
+ result = isc_stdio_open("mem.output", "r", &f);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL);
+ assert_int_equal(result, ISC_R_EOF);
+ isc_stdio_close(f);
+ isc_file_remove("mem.output");
+
+ /* return stderr to TTY so we can see errors */
+ f = freopen("/dev/tty", "w", stderr);
+
+ buf[sizeof(buf) - 1] = 0;
+
+ assert_memory_equal(buf, "add ", 4);
+ p = strchr(buf, '\n');
+ assert_non_null(p);
+ p = strchr(p + 1, '\n');
+ assert_non_null(p);
+ assert_in_range(p, 0, buf + sizeof(buf) - 3);
+ assert_memory_equal(p + 2, "ptr ", 4);
+ p = strchr(p + 1, '\n');
+ assert_non_null(p);
+ assert_memory_equal(p + 1, "del ", 4);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+}
+#endif /* if ISC_MEM_TRACKLINES */
+
+#if !defined(__SANITIZE_THREAD__)
+
+#define ITERS 512
+#define NUM_ITEMS 1024 /* 768 */
+#define ITEM_SIZE 65534
+
+static isc_threadresult_t
+mem_thread(isc_threadarg_t arg) {
+ void *items[NUM_ITEMS];
+ size_t size = *((size_t *)arg);
+
+ for (int i = 0; i < ITERS; i++) {
+ for (int j = 0; j < NUM_ITEMS; j++) {
+ items[j] = isc_mem_get(test_mctx, size);
+ }
+ for (int j = 0; j < NUM_ITEMS; j++) {
+ isc_mem_put(test_mctx, items[j], size);
+ }
+ }
+
+ return ((isc_threadresult_t)0);
+}
+
+static void
+isc_mem_benchmark(void **state) {
+ int nthreads = ISC_MAX(ISC_MIN(isc_os_ncpus(), 32), 1);
+ isc_thread_t threads[32];
+ isc_time_t ts1, ts2;
+ double t;
+ isc_result_t result;
+ size_t size = ITEM_SIZE;
+
+ UNUSED(state);
+
+ result = isc_time_now(&ts1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ for (int i = 0; i < nthreads; i++) {
+ isc_thread_create(mem_thread, &size, &threads[i]);
+ size = size / 2;
+ }
+ for (int i = 0; i < nthreads; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ result = isc_time_now(&ts2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ t = isc_time_microdiff(&ts2, &ts1);
+
+ printf("[ TIME ] isc_mem_benchmark: "
+ "%d isc_mem_{get,put} calls, %f seconds, %f calls/second\n",
+ nthreads * ITERS * NUM_ITEMS, t / 1000000.0,
+ (nthreads * ITERS * NUM_ITEMS) / (t / 1000000.0));
+}
+
+#endif /* __SANITIZE_THREAD */
+
+/*
+ * Main
+ */
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(isc_mem_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(isc_mem_total_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(isc_mem_inuse_test, _setup,
+ _teardown),
+
+#if !defined(__SANITIZE_THREAD__)
+ cmocka_unit_test_setup_teardown(isc_mem_benchmark, _setup,
+ _teardown),
+#endif /* __SANITIZE_THREAD__ */
+#if ISC_MEM_TRACKLINES
+ cmocka_unit_test_setup_teardown(isc_mem_noflags_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(isc_mem_recordflag_test, _setup,
+ _teardown),
+ /*
+ * traceflag_test closes stderr, which causes weird
+ * side effects for any next test trying to use libuv.
+ * This test has to be the last one to avoid problems.
+ */
+ cmocka_unit_test_setup_teardown(isc_mem_traceflag_test, _setup,
+ _teardown),
+#endif /* if ISC_MEM_TRACKLINES */
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/netaddr_test.c b/lib/isc/tests/netaddr_test.c
new file mode 100644
index 0000000..473e747
--- /dev/null
+++ b/lib/isc/tests/netaddr_test.c
@@ -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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/netaddr.h>
+#include <isc/sockaddr.h>
+#include <isc/util.h>
+
+/* test isc_netaddr_isnetzero() */
+static void
+netaddr_isnetzero(void **state) {
+ unsigned int i;
+ struct in_addr ina;
+ struct {
+ const char *address;
+ bool expect;
+ } tests[] = { { "0.0.0.0", true }, { "0.0.0.1", true },
+ { "0.0.1.2", true }, { "0.1.2.3", true },
+ { "10.0.0.0", false }, { "10.9.0.0", false },
+ { "10.9.8.0", false }, { "10.9.8.7", false },
+ { "127.0.0.0", false }, { "127.0.0.1", false } };
+ bool result;
+ isc_netaddr_t netaddr;
+
+ UNUSED(state);
+
+ for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ ina.s_addr = inet_addr(tests[i].address);
+ isc_netaddr_fromin(&netaddr, &ina);
+ result = isc_netaddr_isnetzero(&netaddr);
+ assert_int_equal(result, tests[i].expect);
+ }
+}
+
+/* test isc_netaddr_masktoprefixlen() calculates correct prefix lengths */
+static void
+netaddr_masktoprefixlen(void **state) {
+ struct in_addr na_a;
+ struct in_addr na_b;
+ struct in_addr na_c;
+ struct in_addr na_d;
+ isc_netaddr_t ina_a;
+ isc_netaddr_t ina_b;
+ isc_netaddr_t ina_c;
+ isc_netaddr_t ina_d;
+ unsigned int plen;
+
+ UNUSED(state);
+
+ assert_true(inet_pton(AF_INET, "0.0.0.0", &na_a) >= 0);
+ assert_true(inet_pton(AF_INET, "255.255.255.254", &na_b) >= 0);
+ assert_true(inet_pton(AF_INET, "255.255.255.255", &na_c) >= 0);
+ assert_true(inet_pton(AF_INET, "255.255.255.0", &na_d) >= 0);
+
+ isc_netaddr_fromin(&ina_a, &na_a);
+ isc_netaddr_fromin(&ina_b, &na_b);
+ isc_netaddr_fromin(&ina_c, &na_c);
+ isc_netaddr_fromin(&ina_d, &na_d);
+
+ assert_int_equal(isc_netaddr_masktoprefixlen(&ina_a, &plen),
+ ISC_R_SUCCESS);
+ assert_int_equal(plen, 0);
+
+ assert_int_equal(isc_netaddr_masktoprefixlen(&ina_b, &plen),
+ ISC_R_SUCCESS);
+ assert_int_equal(plen, 31);
+
+ assert_int_equal(isc_netaddr_masktoprefixlen(&ina_c, &plen),
+ ISC_R_SUCCESS);
+ assert_int_equal(plen, 32);
+
+ assert_int_equal(isc_netaddr_masktoprefixlen(&ina_d, &plen),
+ ISC_R_SUCCESS);
+ assert_int_equal(plen, 24);
+}
+
+/* check multicast addresses are detected properly */
+static void
+netaddr_multicast(void **state) {
+ unsigned int i;
+ struct {
+ int family;
+ const char *addr;
+ bool is_multicast;
+ } tests[] = {
+ { AF_INET, "1.2.3.4", false }, { AF_INET, "4.3.2.1", false },
+ { AF_INET, "224.1.1.1", true }, { AF_INET, "1.1.1.244", false },
+ { AF_INET6, "::1", false }, { AF_INET6, "ff02::1", true }
+ };
+
+ UNUSED(state);
+
+ for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ isc_netaddr_t na;
+ struct in_addr in;
+ struct in6_addr in6;
+ int r;
+
+ if (tests[i].family == AF_INET) {
+ r = inet_pton(AF_INET, tests[i].addr,
+ (unsigned char *)&in);
+ assert_int_equal(r, 1);
+ isc_netaddr_fromin(&na, &in);
+ } else {
+ r = inet_pton(AF_INET6, tests[i].addr,
+ (unsigned char *)&in6);
+ assert_int_equal(r, 1);
+ isc_netaddr_fromin6(&na, &in6);
+ }
+
+ assert_int_equal(isc_netaddr_ismulticast(&na),
+ tests[i].is_multicast);
+ }
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(netaddr_isnetzero),
+ cmocka_unit_test(netaddr_masktoprefixlen),
+ cmocka_unit_test(netaddr_multicast),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/netmgr_test.c b/lib/isc/tests/netmgr_test.c
new file mode 100644
index 0000000..8e30359
--- /dev/null
+++ b/lib/isc/tests/netmgr_test.c
@@ -0,0 +1,2218 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <uv.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/nonce.h>
+#include <isc/os.h>
+#include <isc/quota.h>
+#include <isc/refcount.h>
+#include <isc/sockaddr.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "uv_wrap.h"
+#define KEEP_BEFORE
+
+#include "../netmgr/netmgr-int.h"
+#include "../netmgr/udp.c"
+#include "../netmgr/uv-compat.c"
+#include "../netmgr/uv-compat.h"
+#include "../netmgr_p.h"
+#include "isctest.h"
+
+typedef void (*stream_connect_function)(isc_nm_t *nm);
+
+static void
+connect_connect_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg);
+static void
+connect_read_cb(isc_nmhandle_t *handle, isc_result_t eresult,
+ isc_region_t *region, void *cbarg);
+
+isc_nm_t *listen_nm = NULL;
+isc_nm_t *connect_nm = NULL;
+
+static isc_sockaddr_t udp_listen_addr;
+static isc_sockaddr_t udp_connect_addr;
+
+static isc_sockaddr_t tcp_listen_addr;
+static isc_sockaddr_t tcp_connect_addr;
+
+static uint64_t send_magic = 0;
+static uint64_t stop_magic = 0;
+
+static isc_region_t send_msg = { .base = (unsigned char *)&send_magic,
+ .length = sizeof(send_magic) };
+
+static isc_region_t stop_msg = { .base = (unsigned char *)&stop_magic,
+ .length = sizeof(stop_magic) };
+
+static atomic_bool do_send = false;
+static unsigned int workers = 0;
+
+static atomic_int_fast64_t nsends;
+static int_fast64_t esends; /* expected sends */
+
+static atomic_int_fast64_t ssends = 0;
+static atomic_int_fast64_t sreads = 0;
+static atomic_int_fast64_t saccepts = 0;
+
+static atomic_int_fast64_t cconnects = 0;
+static atomic_int_fast64_t csends = 0;
+static atomic_int_fast64_t creads = 0;
+static atomic_int_fast64_t ctimeouts = 0;
+
+static isc_refcount_t active_cconnects;
+static isc_refcount_t active_csends;
+static isc_refcount_t active_creads;
+static isc_refcount_t active_ssends;
+static isc_refcount_t active_sreads;
+
+static isc_quota_t listener_quota;
+static atomic_bool check_listener_quota;
+
+static bool skip_long_tests = false;
+
+static bool allow_send_back = false;
+static bool noanswer = false;
+
+static isc_nm_recv_cb_t connect_readcb = NULL;
+
+#define SKIP_IN_CI \
+ if (skip_long_tests) { \
+ skip(); \
+ return; \
+ }
+
+#define NSENDS 100
+
+/* Timeout for soft-timeout tests (0.05 seconds) */
+#define T_SOFT 50
+
+/* Timeouts in miliseconds */
+#define T_INIT 120 * 1000
+#define T_IDLE 120 * 1000
+#define T_KEEPALIVE 120 * 1000
+#define T_ADVERTISED 120 * 1000
+#define T_CONNECT 30 * 1000
+
+/* Wait for 1 second (1000 * 1000 microseconds) */
+#define WAIT_REPEATS 1000
+#define T_WAIT 1000 /* In microseconds */
+
+#define WAIT_FOR(v, op, val) \
+ { \
+ X(v); \
+ int_fast64_t __r = WAIT_REPEATS; \
+ int_fast64_t __o = 0; \
+ do { \
+ int_fast64_t __l = atomic_load(&v); \
+ if (__l op val) { \
+ break; \
+ }; \
+ if (__o == __l) { \
+ __r--; \
+ } else { \
+ __r = WAIT_REPEATS; \
+ } \
+ __o = __l; \
+ isc_test_nap(T_WAIT); \
+ } while (__r > 0); \
+ X(v); \
+ P(__r); \
+ assert_true(atomic_load(&v) op val); \
+ }
+
+#define WAIT_FOR_EQ(v, val) WAIT_FOR(v, ==, val)
+#define WAIT_FOR_NE(v, val) WAIT_FOR(v, !=, val)
+#define WAIT_FOR_LE(v, val) WAIT_FOR(v, <=, val)
+#define WAIT_FOR_LT(v, val) WAIT_FOR(v, <, val)
+#define WAIT_FOR_GE(v, val) WAIT_FOR(v, >=, val)
+#define WAIT_FOR_GT(v, val) WAIT_FOR(v, >, val)
+
+#define DONE() atomic_store(&do_send, false);
+
+#define CHECK_RANGE_FULL(v) \
+ { \
+ int __v = atomic_load(&v); \
+ assert_true(__v > 1); \
+ }
+
+#define CHECK_RANGE_HALF(v) \
+ { \
+ int __v = atomic_load(&v); \
+ assert_true(__v > 1); \
+ }
+
+/* Enable this to print values while running tests */
+#undef PRINT_DEBUG
+#ifdef PRINT_DEBUG
+#define X(v) \
+ fprintf(stderr, "%s:%s:%d:%s = %" PRId64 "\n", __func__, __FILE__, \
+ __LINE__, #v, atomic_load(&v))
+#define P(v) fprintf(stderr, #v " = %" PRId64 "\n", v)
+#define F() \
+ fprintf(stderr, "%s(%p, %s, %p)\n", __func__, handle, \
+ isc_result_totext(eresult), cbarg)
+#else
+#define X(v)
+#define P(v)
+#define F()
+#endif
+
+#define atomic_assert_int_eq(val, exp) assert_int_equal(atomic_load(&val), exp)
+#define atomic_assert_int_ne(val, exp) \
+ assert_int_not_equal(atomic_load(&val), exp)
+#define atomic_assert_int_le(val, exp) assert_true(atomic_load(&val) <= exp)
+#define atomic_assert_int_lt(val, exp) assert_true(atomic_load(&val) > exp)
+#define atomic_assert_int_ge(val, exp) assert_true(atomic_load(&val) >= exp)
+#define atomic_assert_int_gt(val, exp) assert_true(atomic_load(&val) > exp)
+
+static int
+_setup(void **state __attribute__((unused))) {
+ char *p = NULL;
+
+ if (workers == 0) {
+ workers = isc_os_ncpus();
+ }
+ p = getenv("ISC_TASK_WORKERS");
+ if (p != NULL) {
+ workers = atoi(p);
+ }
+ INSIST(workers != 0);
+
+ if (isc_test_begin(NULL, false, workers) != ISC_R_SUCCESS) {
+ return (-1);
+ }
+
+ signal(SIGPIPE, SIG_IGN);
+
+ if (getenv("CI") == NULL || getenv("CI_ENABLE_ALL_TESTS") != NULL) {
+ esends = NSENDS * workers;
+ } else {
+ esends = workers;
+ skip_long_tests = true;
+ }
+
+ return (0);
+}
+
+static int
+_teardown(void **state __attribute__((unused))) {
+ isc_test_end();
+
+ return (0);
+}
+
+static int
+setup_ephemeral_port(isc_sockaddr_t *addr, sa_family_t family) {
+ socklen_t addrlen = sizeof(*addr);
+ uv_os_sock_t fd;
+ int r;
+
+ isc_sockaddr_fromin6(addr, &in6addr_loopback, 0);
+
+ fd = socket(AF_INET6, family, 0);
+ if (fd < 0) {
+ perror("setup_ephemeral_port: socket()");
+ return (-1);
+ }
+
+ r = bind(fd, (const struct sockaddr *)&addr->type.sa,
+ sizeof(addr->type.sin6));
+ if (r != 0) {
+ perror("setup_ephemeral_port: bind()");
+ isc__nm_closesocket(fd);
+ return (r);
+ }
+
+ r = getsockname(fd, (struct sockaddr *)&addr->type.sa, &addrlen);
+ if (r != 0) {
+ perror("setup_ephemeral_port: getsockname()");
+ isc__nm_closesocket(fd);
+ return (r);
+ }
+
+#if IPV6_RECVERR
+#define setsockopt_on(socket, level, name) \
+ setsockopt(socket, level, name, &(int){ 1 }, sizeof(int))
+
+ r = setsockopt_on(fd, IPPROTO_IPV6, IPV6_RECVERR);
+ if (r != 0) {
+ perror("setup_ephemeral_port");
+ isc__nm_closesocket(fd);
+ return (r);
+ }
+#endif
+
+ return (fd);
+}
+
+static int
+nm_setup(void **state __attribute__((unused))) {
+ uv_os_sock_t tcp_listen_sock = -1;
+ uv_os_sock_t udp_listen_sock = -1;
+
+ udp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+ isc_sockaddr_fromin6(&udp_connect_addr, &in6addr_loopback, 0);
+
+ udp_listen_addr = (isc_sockaddr_t){ .length = 0 };
+ udp_listen_sock = setup_ephemeral_port(&udp_listen_addr, SOCK_DGRAM);
+ if (udp_listen_sock < 0) {
+ return (-1);
+ }
+ isc__nm_closesocket(udp_listen_sock);
+
+ tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+ isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+ tcp_listen_addr = (isc_sockaddr_t){ .length = 0 };
+ tcp_listen_sock = setup_ephemeral_port(&tcp_listen_addr, SOCK_STREAM);
+ if (tcp_listen_sock < 0) {
+ return (-1);
+ }
+ isc__nm_closesocket(tcp_listen_sock);
+
+ atomic_store(&do_send, true);
+ atomic_store(&nsends, esends);
+
+ atomic_store(&saccepts, 0);
+ atomic_store(&sreads, 0);
+ atomic_store(&ssends, 0);
+
+ atomic_store(&cconnects, 0);
+ atomic_store(&csends, 0);
+ atomic_store(&creads, 0);
+ atomic_store(&ctimeouts, 0);
+ allow_send_back = false;
+
+ isc_refcount_init(&active_cconnects, 0);
+ isc_refcount_init(&active_csends, 0);
+ isc_refcount_init(&active_creads, 0);
+ isc_refcount_init(&active_ssends, 0);
+ isc_refcount_init(&active_sreads, 0);
+
+ isc_nonce_buf(&send_magic, sizeof(send_magic));
+ isc_nonce_buf(&stop_magic, sizeof(stop_magic));
+ if (send_magic == stop_magic) {
+ return (-1);
+ }
+
+ isc__netmgr_create(test_mctx, workers, &listen_nm);
+ assert_non_null(listen_nm);
+ isc_nm_settimeouts(listen_nm, T_INIT, T_IDLE, T_KEEPALIVE,
+ T_ADVERTISED);
+
+ isc__netmgr_create(test_mctx, workers, &connect_nm);
+ assert_non_null(connect_nm);
+ isc_nm_settimeouts(connect_nm, T_INIT, T_IDLE, T_KEEPALIVE,
+ T_ADVERTISED);
+
+ isc_quota_init(&listener_quota, 0);
+ atomic_store(&check_listener_quota, false);
+
+ connect_readcb = connect_read_cb;
+ noanswer = false;
+
+ return (0);
+}
+
+static int
+nm_teardown(void **state __attribute__((unused))) {
+ UNUSED(state);
+
+ isc__netmgr_destroy(&connect_nm);
+ assert_null(connect_nm);
+
+ isc__netmgr_destroy(&listen_nm);
+ assert_null(listen_nm);
+
+ WAIT_FOR_EQ(active_cconnects, 0);
+ WAIT_FOR_EQ(active_csends, 0);
+ WAIT_FOR_EQ(active_csends, 0);
+ WAIT_FOR_EQ(active_ssends, 0);
+ WAIT_FOR_EQ(active_sreads, 0);
+
+ isc_refcount_destroy(&active_cconnects);
+ isc_refcount_destroy(&active_csends);
+ isc_refcount_destroy(&active_creads);
+ isc_refcount_destroy(&active_ssends);
+ isc_refcount_destroy(&active_sreads);
+
+ return (0);
+}
+
+/* Callbacks */
+
+static void
+noop_recv_cb(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
+ void *cbarg) {
+ UNUSED(handle);
+ UNUSED(eresult);
+ UNUSED(region);
+ UNUSED(cbarg);
+}
+
+static unsigned int
+noop_accept_cb(isc_nmhandle_t *handle, unsigned int result, void *cbarg) {
+ UNUSED(handle);
+ UNUSED(result);
+ UNUSED(cbarg);
+
+ return (0);
+}
+
+static void
+connect_send_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg);
+
+static void
+connect_send(isc_nmhandle_t *handle);
+
+static void
+connect_send_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
+ isc_nmhandle_t *sendhandle = handle;
+
+ assert_non_null(sendhandle);
+
+ UNUSED(cbarg);
+
+ F();
+
+ if (eresult != ISC_R_SUCCESS) {
+ /* Send failed, we need to stop reading too */
+ isc_nm_cancelread(handle);
+ goto unref;
+ }
+
+ atomic_fetch_add(&csends, 1);
+unref:
+ isc_refcount_decrement(&active_csends);
+ isc_nmhandle_detach(&sendhandle);
+}
+
+static void
+connect_send(isc_nmhandle_t *handle) {
+ isc_nmhandle_t *sendhandle = NULL;
+ isc_refcount_increment0(&active_csends);
+ isc_nmhandle_attach(handle, &sendhandle);
+ isc_nmhandle_setwritetimeout(handle, T_IDLE);
+ if (atomic_fetch_sub(&nsends, 1) > 1) {
+ isc_nm_send(sendhandle, &send_msg, connect_send_cb, NULL);
+ } else {
+ isc_nm_send(sendhandle, &stop_msg, connect_send_cb, NULL);
+ }
+}
+
+static void
+connect_read_cb(isc_nmhandle_t *handle, isc_result_t eresult,
+ isc_region_t *region, void *cbarg) {
+ uint64_t magic = 0;
+
+ UNUSED(cbarg);
+
+ assert_non_null(handle);
+
+ F();
+
+ if (eresult != ISC_R_SUCCESS) {
+ goto unref;
+ }
+
+ assert_true(region->length >= sizeof(magic));
+
+ atomic_fetch_add(&creads, 1);
+
+ memmove(&magic, region->base, sizeof(magic));
+
+ assert_true(magic == stop_magic || magic == send_magic);
+
+ if (magic == send_magic && allow_send_back) {
+ connect_send(handle);
+ return;
+ }
+
+unref:
+ isc_refcount_decrement(&active_creads);
+ isc_nmhandle_detach(&handle);
+}
+
+static void
+connect_connect_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
+ isc_nmhandle_t *readhandle = NULL;
+
+ UNUSED(cbarg);
+
+ F();
+
+ isc_refcount_decrement(&active_cconnects);
+
+ if (eresult != ISC_R_SUCCESS || connect_readcb == NULL) {
+ return;
+ }
+
+ atomic_fetch_add(&cconnects, 1);
+
+ isc_refcount_increment0(&active_creads);
+ isc_nmhandle_attach(handle, &readhandle);
+ isc_nm_read(handle, connect_readcb, NULL);
+
+ connect_send(handle);
+}
+
+static void
+listen_send_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
+ isc_nmhandle_t *sendhandle = handle;
+
+ UNUSED(cbarg);
+ UNUSED(eresult);
+
+ assert_non_null(sendhandle);
+
+ F();
+
+ if (eresult != ISC_R_SUCCESS) {
+ goto unref;
+ }
+
+ atomic_fetch_add(&ssends, 1);
+unref:
+ isc_nmhandle_detach(&sendhandle);
+ isc_refcount_decrement(&active_ssends);
+}
+
+static void
+listen_read_cb(isc_nmhandle_t *handle, isc_result_t eresult,
+ isc_region_t *region, void *cbarg) {
+ uint64_t magic = 0;
+
+ assert_non_null(handle);
+
+ F();
+
+ if (eresult != ISC_R_SUCCESS) {
+ goto unref;
+ }
+
+ atomic_fetch_add(&sreads, 1);
+
+ assert_true(region->length >= sizeof(magic));
+
+ memmove(&magic, region->base, sizeof(magic));
+ assert_true(magic == stop_magic || magic == send_magic);
+
+ if (magic == send_magic) {
+ if (!noanswer) {
+ isc_nmhandle_t *sendhandle = NULL;
+ isc_nmhandle_attach(handle, &sendhandle);
+ isc_refcount_increment0(&active_ssends);
+ isc_nmhandle_setwritetimeout(sendhandle, T_IDLE);
+ isc_nm_send(sendhandle, &send_msg, listen_send_cb,
+ cbarg);
+ }
+ return;
+ }
+
+unref:
+ if (handle == cbarg) {
+ isc_refcount_decrement(&active_sreads);
+ isc_nmhandle_detach(&handle);
+ }
+}
+
+static isc_result_t
+listen_accept_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
+ UNUSED(handle);
+ UNUSED(cbarg);
+
+ F();
+
+ return (eresult);
+}
+
+static isc_result_t
+stream_accept_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
+ isc_nmhandle_t *readhandle = NULL;
+
+ UNUSED(cbarg);
+
+ F();
+
+ if (eresult != ISC_R_SUCCESS) {
+ return (eresult);
+ }
+
+ atomic_fetch_add(&saccepts, 1);
+
+ isc_refcount_increment0(&active_sreads);
+ isc_nmhandle_attach(handle, &readhandle);
+ isc_nm_read(handle, listen_read_cb, readhandle);
+
+ return (ISC_R_SUCCESS);
+}
+
+typedef void (*connect_func)(isc_nm_t *);
+
+static isc_threadresult_t
+connect_thread(isc_threadarg_t arg) {
+ connect_func connect = (connect_func)arg;
+ isc_sockaddr_t connect_addr;
+
+ connect_addr = (isc_sockaddr_t){ .length = 0 };
+ isc_sockaddr_fromin6(&connect_addr, &in6addr_loopback, 0);
+
+ while (atomic_load(&do_send)) {
+ uint_fast32_t active =
+ isc_refcount_increment0(&active_cconnects);
+ if (active > workers) {
+ /*
+ * If we have more active connections than workers,
+ * start slowing down the connections to prevent the
+ * thundering herd problem.
+ */
+ isc_test_nap((active - workers) * 1000);
+ }
+ connect(connect_nm);
+ }
+
+ return ((isc_threadresult_t)0);
+}
+
+/* UDP */
+
+static void
+udp_connect(isc_nm_t *nm) {
+ isc_nm_udpconnect(nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+}
+
+static void
+mock_listenudp_uv_udp_open(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ WILL_RETURN(uv_udp_open, UV_ENOMEM);
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb,
+ NULL, 0, &listen_sock);
+ assert_int_not_equal(result, ISC_R_SUCCESS);
+ assert_null(listen_sock);
+
+ RESET_RETURN;
+}
+
+static void
+mock_listenudp_uv_udp_bind(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ WILL_RETURN(uv_udp_bind, UV_EADDRINUSE);
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb,
+ NULL, 0, &listen_sock);
+ assert_int_not_equal(result, ISC_R_SUCCESS);
+ assert_null(listen_sock);
+
+ RESET_RETURN;
+}
+
+static void
+mock_listenudp_uv_udp_recv_start(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ WILL_RETURN(uv_udp_recv_start, UV_EADDRINUSE);
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb,
+ NULL, 0, &listen_sock);
+ assert_int_not_equal(result, ISC_R_SUCCESS);
+ assert_null(listen_sock);
+
+ RESET_RETURN;
+}
+
+static void
+mock_udpconnect_uv_udp_open(void **state __attribute__((unused))) {
+ WILL_RETURN(uv_udp_open, UV_ENOMEM);
+
+ connect_readcb = NULL;
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+ isc__netmgr_shutdown(connect_nm);
+
+ RESET_RETURN;
+}
+
+static void
+mock_udpconnect_uv_udp_bind(void **state __attribute__((unused))) {
+ WILL_RETURN(uv_udp_bind, UV_ENOMEM);
+
+ connect_readcb = NULL;
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+ isc__netmgr_shutdown(connect_nm);
+
+ RESET_RETURN;
+}
+
+#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0)
+static void
+mock_udpconnect_uv_udp_connect(void **state __attribute__((unused))) {
+ WILL_RETURN(uv_udp_connect, UV_ENOMEM);
+
+ connect_readcb = NULL;
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+ isc__netmgr_shutdown(connect_nm);
+
+ RESET_RETURN;
+}
+#endif
+
+static void
+mock_udpconnect_uv_recv_buffer_size(void **state __attribute__((unused))) {
+ WILL_RETURN(uv_recv_buffer_size, UV_ENOMEM);
+
+ connect_readcb = NULL;
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+ isc__netmgr_shutdown(connect_nm);
+
+ RESET_RETURN;
+}
+
+static void
+mock_udpconnect_uv_send_buffer_size(void **state __attribute__((unused))) {
+ WILL_RETURN(uv_send_buffer_size, UV_ENOMEM);
+
+ connect_readcb = NULL;
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+ isc__netmgr_shutdown(connect_nm);
+
+ RESET_RETURN;
+}
+
+static void
+udp_noop(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb,
+ NULL, 0, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ connect_readcb = NULL;
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+ isc__netmgr_shutdown(connect_nm);
+
+ atomic_assert_int_eq(cconnects, 0);
+ atomic_assert_int_eq(csends, 0);
+ atomic_assert_int_eq(creads, 0);
+ atomic_assert_int_eq(sreads, 0);
+ atomic_assert_int_eq(ssends, 0);
+}
+
+static void
+udp_noresponse(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb,
+ NULL, 0, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+ WAIT_FOR_EQ(csends, 1);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ atomic_assert_int_eq(cconnects, 1);
+ atomic_assert_int_eq(csends, 1);
+ atomic_assert_int_eq(creads, 0);
+ atomic_assert_int_eq(sreads, 0);
+ atomic_assert_int_eq(ssends, 0);
+}
+
+static void
+timeout_retry_cb(isc_nmhandle_t *handle, isc_result_t eresult,
+ isc_region_t *region, void *cbarg) {
+ UNUSED(region);
+ UNUSED(cbarg);
+
+ assert_non_null(handle);
+
+ F();
+
+ if (eresult == ISC_R_TIMEDOUT && atomic_load(&csends) < 5) {
+ isc_nmhandle_settimeout(handle, T_SOFT);
+ connect_send(handle);
+ return;
+ }
+
+ atomic_fetch_add(&ctimeouts, 1);
+
+ isc_refcount_decrement(&active_creads);
+ isc_nmhandle_detach(&handle);
+}
+
+static void
+udp_timeout_recovery(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ SKIP_IN_CI;
+
+ /*
+ * Listen using the noop callback so that client reads will time out.
+ */
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb,
+ NULL, 0, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Connect with client timeout set to 0.05 seconds, then sleep for at
+ * least a second for each 'tick'. timeout_retry_cb() will give up
+ * after five timeouts.
+ */
+ connect_readcb = timeout_retry_cb;
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_SOFT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+ WAIT_FOR_GE(csends, 1);
+ WAIT_FOR_GE(csends, 2);
+ WAIT_FOR_GE(csends, 3);
+ WAIT_FOR_GE(csends, 4);
+ WAIT_FOR_EQ(csends, 5);
+ WAIT_FOR_EQ(ctimeouts, 1);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+}
+
+static void
+udp_recv_one(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ atomic_store(&nsends, 1);
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb,
+ NULL, 0, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+ WAIT_FOR_LE(nsends, 0);
+ WAIT_FOR_EQ(csends, 1);
+ WAIT_FOR_EQ(sreads, 1);
+ WAIT_FOR_EQ(ssends, 0);
+ WAIT_FOR_EQ(creads, 0);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ atomic_assert_int_eq(cconnects, 1);
+ atomic_assert_int_eq(csends, 1);
+ atomic_assert_int_eq(creads, 0);
+ atomic_assert_int_eq(sreads, 1);
+ atomic_assert_int_eq(ssends, 0);
+}
+
+static void
+udp_recv_two(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ atomic_store(&nsends, 2);
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb,
+ NULL, 0, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 2);
+ WAIT_FOR_LE(nsends, 0);
+ WAIT_FOR_EQ(csends, 2);
+ WAIT_FOR_EQ(sreads, 2);
+ WAIT_FOR_EQ(ssends, 1);
+ WAIT_FOR_EQ(creads, 1);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ atomic_assert_int_eq(cconnects, 2);
+ atomic_assert_int_eq(csends, 2);
+ atomic_assert_int_eq(creads, 1);
+ atomic_assert_int_eq(sreads, 2);
+ atomic_assert_int_eq(ssends, 1);
+}
+
+static void
+udp_recv_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+
+ SKIP_IN_CI;
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb,
+ NULL, 0, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, udp_connect, &threads[i]);
+ }
+
+ WAIT_FOR_GE(cconnects, esends);
+ WAIT_FOR_GE(csends, esends);
+ WAIT_FOR_GE(sreads, esends);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ isc__netmgr_shutdown(connect_nm);
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_FULL(csends);
+ CHECK_RANGE_FULL(creads);
+ CHECK_RANGE_FULL(sreads);
+ CHECK_RANGE_FULL(ssends);
+}
+
+static void
+udp_recv_half_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+
+ SKIP_IN_CI;
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb,
+ NULL, 0, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, udp_connect, &threads[i]);
+ }
+
+ WAIT_FOR_GE(cconnects, esends / 2);
+ WAIT_FOR_GE(csends, esends / 2);
+ WAIT_FOR_GE(sreads, esends / 2);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ isc__netmgr_shutdown(connect_nm);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_FULL(csends);
+ CHECK_RANGE_HALF(creads);
+ CHECK_RANGE_HALF(sreads);
+ CHECK_RANGE_HALF(ssends);
+}
+
+static void
+udp_half_recv_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+
+ SKIP_IN_CI;
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb,
+ NULL, 0, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, udp_connect, &threads[i]);
+ }
+
+ WAIT_FOR_GE(cconnects, esends / 2);
+ WAIT_FOR_GE(csends, esends / 2);
+ WAIT_FOR_GE(sreads, esends / 2);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ /* Try to send a little while longer */
+ isc_test_nap((esends / 2) * 10000);
+
+ isc__netmgr_shutdown(connect_nm);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_FULL(csends);
+ CHECK_RANGE_HALF(creads);
+ CHECK_RANGE_HALF(sreads);
+ CHECK_RANGE_HALF(ssends);
+}
+
+static void
+udp_half_recv_half_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+
+ SKIP_IN_CI;
+
+ result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb,
+ NULL, 0, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, udp_connect, &threads[i]);
+ }
+
+ WAIT_FOR_GE(cconnects, esends / 2);
+ WAIT_FOR_GE(csends, esends / 2);
+ WAIT_FOR_GE(sreads, esends / 2);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ isc__netmgr_shutdown(connect_nm);
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_FULL(csends);
+ CHECK_RANGE_HALF(creads);
+ CHECK_RANGE_HALF(sreads);
+ CHECK_RANGE_HALF(ssends);
+}
+
+/* Common stream protocols code */
+
+static isc_quota_t *
+tcp_listener_init_quota(size_t nthreads) {
+ isc_quota_t *quotap = NULL;
+ if (atomic_load(&check_listener_quota)) {
+ unsigned max_quota = ISC_MAX(nthreads / 2, 1);
+ isc_quota_max(&listener_quota, max_quota);
+ quotap = &listener_quota;
+ }
+ return (quotap);
+}
+
+static void
+tcp_connect(isc_nm_t *nm) {
+ isc_nm_tcpconnect(nm, &tcp_connect_addr, &tcp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+}
+
+static stream_connect_function
+get_stream_connect_function(void) {
+ return (tcp_connect);
+}
+
+static isc_result_t
+stream_listen(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;
+
+ result = isc_nm_listentcp(listen_nm, &tcp_listen_addr, accept_cb,
+ accept_cbarg, extrahandlesize, backlog, quota,
+ sockp);
+
+ return (result);
+}
+
+static void
+stream_connect(isc_nm_cb_t cb, void *cbarg, unsigned int timeout,
+ size_t extrahandlesize) {
+ isc_nm_tcpconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr, cb,
+ cbarg, timeout, extrahandlesize);
+}
+
+static void
+stream_noop(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ result = stream_listen(noop_accept_cb, NULL, 0, 0, NULL, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ connect_readcb = NULL;
+ isc_refcount_increment0(&active_cconnects);
+ stream_connect(connect_connect_cb, NULL, T_CONNECT, 0);
+ isc__netmgr_shutdown(connect_nm);
+
+ atomic_assert_int_eq(cconnects, 0);
+ atomic_assert_int_eq(csends, 0);
+ atomic_assert_int_eq(creads, 0);
+ atomic_assert_int_eq(sreads, 0);
+ atomic_assert_int_eq(ssends, 0);
+}
+
+static void
+stream_noresponse(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ result = stream_listen(noop_accept_cb, NULL, 0, 0, NULL, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_refcount_increment0(&active_cconnects);
+ stream_connect(connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+ WAIT_FOR_EQ(csends, 1);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ atomic_assert_int_eq(cconnects, 1);
+ atomic_assert_int_eq(csends, 1);
+ atomic_assert_int_eq(creads, 0);
+ atomic_assert_int_eq(sreads, 0);
+ atomic_assert_int_eq(ssends, 0);
+}
+
+static void
+stream_timeout_recovery(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ SKIP_IN_CI;
+
+ /*
+ * Accept connections but don't send responses, forcing client
+ * reads to time out.
+ */
+ noanswer = true;
+ result = stream_listen(stream_accept_cb, NULL, 0, 0, NULL,
+ &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Shorten all the client timeouts to 0.05 seconds.
+ */
+ isc_nm_settimeouts(connect_nm, T_SOFT, T_SOFT, T_SOFT, T_SOFT);
+ connect_readcb = timeout_retry_cb;
+ isc_refcount_increment0(&active_cconnects);
+ stream_connect(connect_connect_cb, NULL, T_SOFT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+ WAIT_FOR_GE(csends, 1);
+ WAIT_FOR_GE(csends, 2);
+ WAIT_FOR_GE(csends, 3);
+ WAIT_FOR_GE(csends, 4);
+ WAIT_FOR_EQ(csends, 5);
+ WAIT_FOR_EQ(ctimeouts, 1);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+}
+
+static void
+stream_recv_one(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_quota_t *quotap = tcp_listener_init_quota(1);
+
+ atomic_store(&nsends, 1);
+
+ result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap,
+ &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_refcount_increment0(&active_cconnects);
+ stream_connect(connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+ WAIT_FOR_LE(nsends, 0);
+ WAIT_FOR_EQ(csends, 1);
+ WAIT_FOR_EQ(sreads, 1);
+ WAIT_FOR_EQ(ssends, 0);
+ WAIT_FOR_EQ(creads, 0);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ atomic_assert_int_eq(cconnects, 1);
+ atomic_assert_int_eq(csends, 1);
+ atomic_assert_int_eq(creads, 0);
+ atomic_assert_int_eq(sreads, 1);
+ atomic_assert_int_eq(ssends, 0);
+}
+
+static void
+stream_recv_two(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_quota_t *quotap = tcp_listener_init_quota(1);
+
+ atomic_store(&nsends, 2);
+
+ result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap,
+ &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_refcount_increment0(&active_cconnects);
+ stream_connect(connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+
+ isc_refcount_increment0(&active_cconnects);
+ stream_connect(connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 2);
+ WAIT_FOR_LE(nsends, 0);
+ WAIT_FOR_EQ(csends, 2);
+ WAIT_FOR_EQ(sreads, 2);
+ WAIT_FOR_EQ(ssends, 1);
+ WAIT_FOR_EQ(creads, 1);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ atomic_assert_int_eq(cconnects, 2);
+ atomic_assert_int_eq(csends, 2);
+ atomic_assert_int_eq(creads, 1);
+ atomic_assert_int_eq(sreads, 2);
+ atomic_assert_int_eq(ssends, 1);
+}
+
+static void
+stream_recv_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+ isc_quota_t *quotap = tcp_listener_init_quota(workers);
+
+ SKIP_IN_CI;
+
+ result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap,
+ &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, get_stream_connect_function(),
+ &threads[i]);
+ }
+
+ if (allow_send_back) {
+ WAIT_FOR_GE(cconnects, 1);
+ } else {
+ WAIT_FOR_GE(cconnects, esends);
+ }
+ WAIT_FOR_GE(csends, esends);
+ WAIT_FOR_GE(sreads, esends);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ isc__netmgr_shutdown(connect_nm);
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_FULL(csends);
+ CHECK_RANGE_FULL(creads);
+ CHECK_RANGE_FULL(sreads);
+ CHECK_RANGE_FULL(ssends);
+}
+
+static void
+stream_recv_half_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+ isc_quota_t *quotap = tcp_listener_init_quota(workers);
+
+ SKIP_IN_CI;
+
+ result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap,
+ &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, get_stream_connect_function(),
+ &threads[i]);
+ }
+
+ if (allow_send_back) {
+ WAIT_FOR_GE(cconnects, 1);
+ } else {
+ WAIT_FOR_GE(cconnects, esends / 2);
+ }
+ WAIT_FOR_GE(csends, esends / 2);
+ WAIT_FOR_GE(sreads, esends / 2);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ isc__netmgr_shutdown(connect_nm);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_HALF(csends);
+ CHECK_RANGE_HALF(creads);
+ CHECK_RANGE_HALF(sreads);
+ CHECK_RANGE_HALF(ssends);
+}
+
+static void
+stream_half_recv_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+ isc_quota_t *quotap = tcp_listener_init_quota(workers);
+
+ SKIP_IN_CI;
+
+ result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap,
+ &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, get_stream_connect_function(),
+ &threads[i]);
+ }
+
+ if (allow_send_back) {
+ WAIT_FOR_GE(cconnects, 1);
+ } else {
+ WAIT_FOR_GE(cconnects, esends / 2);
+ }
+ WAIT_FOR_GE(csends, esends / 2);
+ WAIT_FOR_GE(sreads, esends / 2);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ /* Try to send a little while longer */
+ isc_test_nap((esends / 2) * 10000);
+
+ isc__netmgr_shutdown(connect_nm);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_HALF(csends);
+ CHECK_RANGE_HALF(creads);
+ CHECK_RANGE_HALF(sreads);
+ CHECK_RANGE_HALF(ssends);
+}
+
+static void
+stream_half_recv_half_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+ isc_quota_t *quotap = tcp_listener_init_quota(workers);
+
+ SKIP_IN_CI;
+
+ result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap,
+ &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, get_stream_connect_function(),
+ &threads[i]);
+ }
+
+ if (allow_send_back) {
+ WAIT_FOR_GE(cconnects, 1);
+ } else {
+ WAIT_FOR_GE(cconnects, esends / 2);
+ }
+ WAIT_FOR_GE(csends, esends / 2);
+ WAIT_FOR_GE(sreads, esends / 2);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ isc__netmgr_shutdown(connect_nm);
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_HALF(csends);
+ CHECK_RANGE_HALF(creads);
+ CHECK_RANGE_HALF(sreads);
+ CHECK_RANGE_HALF(ssends);
+}
+
+/* TCP */
+static void
+tcp_noop(void **state) {
+ stream_noop(state);
+}
+
+static void
+tcp_noresponse(void **state) {
+ stream_noresponse(state);
+}
+
+static void
+tcp_timeout_recovery(void **state) {
+ stream_timeout_recovery(state);
+}
+
+static void
+tcp_recv_one(void **state) {
+ stream_recv_one(state);
+}
+
+static void
+tcp_recv_two(void **state) {
+ stream_recv_two(state);
+}
+
+static void
+tcp_recv_send(void **state) {
+ SKIP_IN_CI;
+ stream_recv_send(state);
+}
+
+static void
+tcp_recv_half_send(void **state) {
+ SKIP_IN_CI;
+ stream_recv_half_send(state);
+}
+
+static void
+tcp_half_recv_send(void **state) {
+ SKIP_IN_CI;
+ stream_half_recv_send(state);
+}
+
+static void
+tcp_half_recv_half_send(void **state) {
+ SKIP_IN_CI;
+ stream_half_recv_half_send(state);
+}
+
+static void
+tcp_recv_send_sendback(void **state) {
+ SKIP_IN_CI;
+ stream_recv_send(state);
+}
+
+static void
+tcp_recv_half_send_sendback(void **state) {
+ SKIP_IN_CI;
+ stream_recv_half_send(state);
+}
+
+static void
+tcp_half_recv_send_sendback(void **state) {
+ SKIP_IN_CI;
+ stream_half_recv_send(state);
+}
+
+static void
+tcp_half_recv_half_send_sendback(void **state) {
+ SKIP_IN_CI;
+ stream_half_recv_half_send(state);
+}
+
+/* TCP Quota */
+
+static void
+tcp_recv_one_quota(void **state) {
+ atomic_store(&check_listener_quota, true);
+ stream_recv_one(state);
+}
+
+static void
+tcp_recv_two_quota(void **state) {
+ atomic_store(&check_listener_quota, true);
+ stream_recv_two(state);
+}
+
+static void
+tcp_recv_send_quota(void **state) {
+ SKIP_IN_CI;
+ atomic_store(&check_listener_quota, true);
+ stream_recv_send(state);
+}
+
+static void
+tcp_recv_half_send_quota(void **state) {
+ SKIP_IN_CI;
+ atomic_store(&check_listener_quota, true);
+ stream_recv_half_send(state);
+}
+
+static void
+tcp_half_recv_send_quota(void **state) {
+ SKIP_IN_CI;
+ atomic_store(&check_listener_quota, true);
+ stream_half_recv_send(state);
+}
+
+static void
+tcp_half_recv_half_send_quota(void **state) {
+ SKIP_IN_CI;
+ atomic_store(&check_listener_quota, true);
+ stream_half_recv_half_send(state);
+}
+
+static void
+tcp_recv_send_quota_sendback(void **state) {
+ SKIP_IN_CI;
+ atomic_store(&check_listener_quota, true);
+ allow_send_back = true;
+ stream_recv_send(state);
+}
+
+static void
+tcp_recv_half_send_quota_sendback(void **state) {
+ SKIP_IN_CI;
+ atomic_store(&check_listener_quota, true);
+ allow_send_back = true;
+ stream_recv_half_send(state);
+}
+
+static void
+tcp_half_recv_send_quota_sendback(void **state) {
+ SKIP_IN_CI;
+ atomic_store(&check_listener_quota, true);
+ allow_send_back = true;
+ stream_half_recv_send(state);
+}
+
+static void
+tcp_half_recv_half_send_quota_sendback(void **state) {
+ SKIP_IN_CI;
+ atomic_store(&check_listener_quota, true);
+ allow_send_back = true;
+ stream_half_recv_half_send(state);
+}
+
+/* TCPDNS */
+
+static void
+tcpdns_connect(isc_nm_t *nm) {
+ isc_nm_tcpdnsconnect(nm, &tcp_connect_addr, &tcp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+}
+
+static void
+tcpdns_noop(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, noop_recv_cb,
+ NULL, noop_accept_cb, NULL, 0, 0, NULL,
+ &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ connect_readcb = NULL;
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+ isc__netmgr_shutdown(connect_nm);
+
+ atomic_assert_int_eq(cconnects, 0);
+ atomic_assert_int_eq(csends, 0);
+ atomic_assert_int_eq(creads, 0);
+ atomic_assert_int_eq(sreads, 0);
+ atomic_assert_int_eq(ssends, 0);
+}
+
+static void
+tcpdns_noresponse(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ isc_refcount_increment0(&active_cconnects);
+ result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, noop_recv_cb,
+ NULL, noop_accept_cb, NULL, 0, 0, NULL,
+ &listen_sock);
+ if (result != ISC_R_SUCCESS) {
+ isc_refcount_decrement(&active_cconnects);
+ isc_test_nap(1000);
+ }
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+ WAIT_FOR_EQ(csends, 1);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ atomic_assert_int_eq(cconnects, 1);
+ atomic_assert_int_eq(csends, 1);
+ atomic_assert_int_eq(creads, 0);
+ atomic_assert_int_eq(sreads, 0);
+ atomic_assert_int_eq(ssends, 0);
+}
+
+static void
+tcpdns_timeout_recovery(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ SKIP_IN_CI;
+
+ /*
+ * Accept connections but don't send responses, forcing client
+ * reads to time out.
+ */
+ noanswer = true;
+ result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr,
+ listen_read_cb, NULL, listen_accept_cb,
+ NULL, 0, 0, NULL, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Shorten all the TCP client timeouts to 0.05 seconds, connect,
+ * then sleep for at least a second for each 'tick'.
+ * timeout_retry_cb() will give up after five timeouts.
+ */
+ connect_readcb = timeout_retry_cb;
+ isc_nm_settimeouts(connect_nm, T_SOFT, T_SOFT, T_SOFT, T_SOFT);
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr,
+ connect_connect_cb, NULL, T_SOFT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+ WAIT_FOR_GE(csends, 1);
+ WAIT_FOR_GE(csends, 2);
+ WAIT_FOR_GE(csends, 3);
+ WAIT_FOR_GE(csends, 4);
+ WAIT_FOR_EQ(csends, 5);
+ WAIT_FOR_EQ(ctimeouts, 1);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+}
+
+static void
+tcpdns_recv_one(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ atomic_store(&nsends, 1);
+
+ result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr,
+ listen_read_cb, NULL, listen_accept_cb,
+ NULL, 0, 0, NULL, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+ WAIT_FOR_LE(nsends, 0);
+ WAIT_FOR_EQ(csends, 1);
+ WAIT_FOR_EQ(sreads, 1);
+ WAIT_FOR_EQ(ssends, 0);
+ WAIT_FOR_EQ(creads, 0);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ atomic_assert_int_eq(cconnects, 1);
+ atomic_assert_int_eq(csends, 1);
+ atomic_assert_int_eq(creads, 0);
+ atomic_assert_int_eq(sreads, 1);
+ atomic_assert_int_eq(ssends, 0);
+}
+
+static void
+tcpdns_recv_two(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+
+ atomic_store(&nsends, 2);
+
+ result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr,
+ listen_read_cb, NULL, listen_accept_cb,
+ NULL, 0, 0, NULL, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 1);
+
+ isc_refcount_increment0(&active_cconnects);
+ isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr,
+ connect_connect_cb, NULL, T_CONNECT, 0);
+
+ WAIT_FOR_EQ(cconnects, 2);
+
+ WAIT_FOR_LE(nsends, 0);
+ WAIT_FOR_EQ(csends, 2);
+ WAIT_FOR_EQ(sreads, 2);
+ WAIT_FOR_EQ(ssends, 1);
+ WAIT_FOR_EQ(creads, 1);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+ isc__netmgr_shutdown(connect_nm);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ atomic_assert_int_eq(cconnects, 2);
+ atomic_assert_int_eq(csends, 2);
+ atomic_assert_int_eq(creads, 1);
+ atomic_assert_int_eq(sreads, 2);
+ atomic_assert_int_eq(ssends, 1);
+}
+
+static void
+tcpdns_recv_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+
+ SKIP_IN_CI;
+
+ result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr,
+ listen_read_cb, NULL, listen_accept_cb,
+ NULL, 0, 0, NULL, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, tcpdns_connect, &threads[i]);
+ }
+
+ WAIT_FOR_GE(cconnects, esends);
+ WAIT_FOR_GE(csends, esends);
+ WAIT_FOR_GE(sreads, esends);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ isc__netmgr_shutdown(connect_nm);
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_FULL(csends);
+ CHECK_RANGE_FULL(creads);
+ CHECK_RANGE_FULL(sreads);
+ CHECK_RANGE_FULL(ssends);
+}
+
+static void
+tcpdns_recv_half_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+
+ SKIP_IN_CI;
+
+ result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr,
+ listen_read_cb, NULL, listen_accept_cb,
+ NULL, 0, 0, NULL, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, tcpdns_connect, &threads[i]);
+ }
+
+ WAIT_FOR_GE(cconnects, esends / 2);
+ WAIT_FOR_GE(csends, esends / 2);
+ WAIT_FOR_GE(sreads, esends / 2);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ isc__netmgr_shutdown(connect_nm);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_HALF(csends);
+ CHECK_RANGE_HALF(creads);
+ CHECK_RANGE_HALF(sreads);
+ CHECK_RANGE_HALF(ssends);
+}
+
+static void
+tcpdns_half_recv_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+
+ SKIP_IN_CI;
+
+ result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr,
+ listen_read_cb, NULL, listen_accept_cb,
+ NULL, 0, 0, NULL, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, tcpdns_connect, &threads[i]);
+ }
+
+ WAIT_FOR_GE(cconnects, esends / 2);
+ WAIT_FOR_GE(csends, esends / 2);
+ WAIT_FOR_GE(sreads, esends / 2);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ /* Try to send a little while longer */
+ isc_test_nap((esends / 2) * 10000);
+
+ isc__netmgr_shutdown(connect_nm);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_HALF(csends);
+ CHECK_RANGE_HALF(creads);
+ CHECK_RANGE_HALF(sreads);
+ CHECK_RANGE_HALF(ssends);
+}
+
+static void
+tcpdns_half_recv_half_send(void **state __attribute__((unused))) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *listen_sock = NULL;
+ isc_thread_t threads[workers];
+
+ SKIP_IN_CI;
+
+ result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr,
+ listen_read_cb, NULL, listen_accept_cb,
+ NULL, 0, 0, NULL, &listen_sock);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ memset(threads, 0, sizeof(threads));
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_create(connect_thread, tcpdns_connect, &threads[i]);
+ }
+
+ WAIT_FOR_GE(cconnects, esends / 2);
+ WAIT_FOR_GE(csends, esends / 2);
+ WAIT_FOR_GE(sreads, esends / 2);
+ WAIT_FOR_GE(ssends, esends / 2);
+ WAIT_FOR_GE(creads, esends / 2);
+
+ isc__netmgr_shutdown(connect_nm);
+ isc_nm_stoplistening(listen_sock);
+ isc_nmsocket_close(&listen_sock);
+ assert_null(listen_sock);
+
+ DONE();
+ for (size_t i = 0; i < workers; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ X(cconnects);
+ X(csends);
+ X(creads);
+ X(sreads);
+ X(ssends);
+
+ CHECK_RANGE_HALF(csends);
+ CHECK_RANGE_HALF(creads);
+ CHECK_RANGE_HALF(sreads);
+ CHECK_RANGE_HALF(ssends);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ /* UDP */
+ cmocka_unit_test_setup_teardown(mock_listenudp_uv_udp_open,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(mock_listenudp_uv_udp_bind,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(
+ mock_listenudp_uv_udp_recv_start, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(mock_udpconnect_uv_udp_open,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(mock_udpconnect_uv_udp_bind,
+ nm_setup, nm_teardown),
+#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0)
+ cmocka_unit_test_setup_teardown(mock_udpconnect_uv_udp_connect,
+ nm_setup, nm_teardown),
+#endif
+ cmocka_unit_test_setup_teardown(
+ mock_udpconnect_uv_recv_buffer_size, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(
+ mock_udpconnect_uv_send_buffer_size, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(udp_noop, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(udp_noresponse, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(udp_timeout_recovery, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(udp_recv_one, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(udp_recv_two, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(udp_recv_send, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(udp_recv_half_send, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(udp_half_recv_send, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(udp_half_recv_half_send,
+ nm_setup, nm_teardown),
+
+ /* TCP */
+ cmocka_unit_test_setup_teardown(tcp_noop, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_noresponse, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_timeout_recovery, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_recv_one, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_recv_two, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_recv_send, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_recv_half_send, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_half_recv_send, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_half_recv_half_send,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_recv_send_sendback,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_recv_half_send_sendback,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_half_recv_send_sendback,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(
+ tcp_half_recv_half_send_sendback, nm_setup,
+ nm_teardown),
+
+ /* TCP Quota */
+ cmocka_unit_test_setup_teardown(tcp_recv_one_quota, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_recv_two_quota, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_recv_send_quota, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_recv_half_send_quota,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_half_recv_send_quota,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_half_recv_half_send_quota,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(tcp_recv_send_quota_sendback,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(
+ tcp_recv_half_send_quota_sendback, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(
+ tcp_half_recv_send_quota_sendback, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(
+ tcp_half_recv_half_send_quota_sendback, nm_setup,
+ nm_teardown),
+
+ /* TCPDNS */
+ cmocka_unit_test_setup_teardown(tcpdns_recv_one, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcpdns_recv_two, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcpdns_noop, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcpdns_noresponse, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcpdns_timeout_recovery,
+ nm_setup, nm_teardown),
+ cmocka_unit_test_setup_teardown(tcpdns_recv_send, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcpdns_recv_half_send, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcpdns_half_recv_send, nm_setup,
+ nm_teardown),
+ cmocka_unit_test_setup_teardown(tcpdns_half_recv_half_send,
+ nm_setup, nm_teardown),
+
+ };
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/parse_test.c b/lib/isc/tests/parse_test.c
new file mode 100644
index 0000000..378690d
--- /dev/null
+++ b/lib/isc/tests/parse_test.c
@@ -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.
+ */
+
+/*! \file */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/parseint.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+/* Test for 32 bit overflow on 64 bit machines in isc_parse_uint32 */
+static void
+parse_overflow(void **state) {
+ isc_result_t result;
+ uint32_t output;
+
+ UNUSED(state);
+
+ result = isc_parse_uint32(&output, "1234567890", 10);
+ assert_int_equal(ISC_R_SUCCESS, result);
+ assert_int_equal(1234567890, output);
+
+ result = isc_parse_uint32(&output, "123456789012345", 10);
+ assert_int_equal(ISC_R_RANGE, result);
+
+ result = isc_parse_uint32(&output, "12345678901234567890", 10);
+ assert_int_equal(ISC_R_RANGE, result);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(parse_overflow, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/pool_test.c b/lib/isc/tests/pool_test.c
new file mode 100644
index 0000000..1ce51f6
--- /dev/null
+++ b/lib/isc/tests/pool_test.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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/mem.h>
+#include <isc/pool.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+static isc_result_t
+poolinit(void **target, void *arg) {
+ isc_result_t result;
+
+ isc_taskmgr_t *mgr = (isc_taskmgr_t *)arg;
+ isc_task_t *task = NULL;
+ result = isc_task_create(mgr, 0, &task);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ *target = (void *)task;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+poolfree(void **target) {
+ isc_task_t *task = *(isc_task_t **)target;
+ isc_task_destroy(&task);
+ *target = NULL;
+}
+
+/* Create a pool */
+static void
+create_pool(void **state) {
+ isc_result_t result;
+ isc_pool_t *pool = NULL;
+
+ UNUSED(state);
+
+ result = isc_pool_create(test_mctx, 8, poolfree, poolinit, taskmgr,
+ &pool);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_pool_count(pool), 8);
+
+ isc_pool_destroy(&pool);
+ assert_null(pool);
+}
+
+/* Resize a pool */
+static void
+expand_pool(void **state) {
+ isc_result_t result;
+ isc_pool_t *pool1 = NULL, *pool2 = NULL, *hold = NULL;
+
+ UNUSED(state);
+
+ result = isc_pool_create(test_mctx, 10, poolfree, poolinit, taskmgr,
+ &pool1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_pool_count(pool1), 10);
+
+ /* resizing to a smaller size should have no effect */
+ hold = pool1;
+ result = isc_pool_expand(&pool1, 5, &pool2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_pool_count(pool2), 10);
+ assert_ptr_equal(pool2, hold);
+ assert_null(pool1);
+ pool1 = pool2;
+ pool2 = NULL;
+
+ /* resizing to the same size should have no effect */
+ hold = pool1;
+ result = isc_pool_expand(&pool1, 10, &pool2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_pool_count(pool2), 10);
+ assert_ptr_equal(pool2, hold);
+ assert_null(pool1);
+ pool1 = pool2;
+ pool2 = NULL;
+
+ /* resizing to larger size should make a new pool */
+ hold = pool1;
+ result = isc_pool_expand(&pool1, 20, &pool2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_pool_count(pool2), 20);
+ assert_ptr_not_equal(pool2, hold);
+ assert_null(pool1);
+
+ isc_pool_destroy(&pool2);
+ assert_null(pool2);
+}
+
+/* Get objects */
+static void
+get_objects(void **state) {
+ isc_result_t result;
+ isc_pool_t *pool = NULL;
+ void *item;
+ isc_task_t *task1 = NULL, *task2 = NULL, *task3 = NULL;
+
+ UNUSED(state);
+
+ result = isc_pool_create(test_mctx, 2, poolfree, poolinit, taskmgr,
+ &pool);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_pool_count(pool), 2);
+
+ item = isc_pool_get(pool);
+ assert_non_null(item);
+ isc_task_attach((isc_task_t *)item, &task1);
+
+ item = isc_pool_get(pool);
+ assert_non_null(item);
+ isc_task_attach((isc_task_t *)item, &task2);
+
+ item = isc_pool_get(pool);
+ assert_non_null(item);
+ isc_task_attach((isc_task_t *)item, &task3);
+
+ isc_task_detach(&task1);
+ isc_task_detach(&task2);
+ isc_task_detach(&task3);
+
+ isc_pool_destroy(&pool);
+ assert_null(pool);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(create_pool, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(expand_pool, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(get_objects, _setup, _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/quota_test.c b/lib/isc/tests/quota_test.c
new file mode 100644
index 0000000..854afd9
--- /dev/null
+++ b/lib/isc/tests/quota_test.c
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/quota.h>
+#include <isc/result.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static void
+isc_quota_get_set_test(void **state) {
+ UNUSED(state);
+ isc_quota_t quota;
+ isc_quota_t *quota2 = NULL;
+ isc_quota_init(&quota, 100);
+
+ assert_int_equal(isc_quota_getmax(&quota), 100);
+ assert_int_equal(isc_quota_getsoft(&quota), 0);
+
+ isc_quota_max(&quota, 50);
+ isc_quota_soft(&quota, 30);
+
+ assert_int_equal(isc_quota_getmax(&quota), 50);
+ assert_int_equal(isc_quota_getsoft(&quota), 30);
+
+ assert_int_equal(isc_quota_getused(&quota), 0);
+ isc_quota_attach(&quota, &quota2);
+ assert_int_equal(isc_quota_getused(&quota), 1);
+ isc_quota_detach(&quota2);
+ assert_int_equal(isc_quota_getused(&quota), 0);
+ isc_quota_destroy(&quota);
+}
+
+static void
+add_quota(isc_quota_t *source, isc_quota_t **target,
+ isc_result_t expected_result, int expected_used) {
+ isc_result_t result;
+
+ *target = NULL;
+
+ result = isc_quota_attach(source, target);
+ assert_int_equal(result, expected_result);
+
+ switch (expected_result) {
+ case ISC_R_SUCCESS:
+ case ISC_R_SOFTQUOTA:
+ assert_ptr_equal(*target, source);
+ break;
+ default:
+ assert_null(*target);
+ break;
+ }
+
+ assert_int_equal(isc_quota_getused(source), expected_used);
+}
+
+static void
+isc_quota_hard_test(void **state) {
+ isc_quota_t quota;
+ isc_quota_t *quotas[110];
+ int i;
+ UNUSED(state);
+
+ isc_quota_init(&quota, 100);
+
+ for (i = 0; i < 100; i++) {
+ add_quota(&quota, &quotas[i], ISC_R_SUCCESS, i + 1);
+ }
+
+ add_quota(&quota, &quotas[100], ISC_R_QUOTA, 100);
+
+ assert_int_equal(isc_quota_getused(&quota), 100);
+
+ isc_quota_detach(&quotas[0]);
+ assert_null(quotas[0]);
+
+ add_quota(&quota, &quotas[100], ISC_R_SUCCESS, 100);
+ add_quota(&quota, &quotas[101], ISC_R_QUOTA, 100);
+
+ for (i = 100; i > 0; i--) {
+ isc_quota_detach(&quotas[i]);
+ assert_null(quotas[i]);
+ assert_int_equal(isc_quota_getused(&quota), i - 1);
+ }
+ assert_int_equal(isc_quota_getused(&quota), 0);
+ isc_quota_destroy(&quota);
+}
+
+static void
+isc_quota_soft_test(void **state) {
+ isc_quota_t quota;
+ isc_quota_t *quotas[110];
+ int i;
+ UNUSED(state);
+
+ isc_quota_init(&quota, 100);
+ isc_quota_soft(&quota, 50);
+
+ for (i = 0; i < 50; i++) {
+ add_quota(&quota, &quotas[i], ISC_R_SUCCESS, i + 1);
+ }
+ for (i = 50; i < 100; i++) {
+ add_quota(&quota, &quotas[i], ISC_R_SOFTQUOTA, i + 1);
+ }
+
+ add_quota(&quota, &quotas[i], ISC_R_QUOTA, 100);
+
+ for (i = 99; i >= 0; i--) {
+ isc_quota_detach(&quotas[i]);
+ assert_null(quotas[i]);
+ assert_int_equal(isc_quota_getused(&quota), i);
+ }
+ assert_int_equal(isc_quota_getused(&quota), 0);
+ isc_quota_destroy(&quota);
+}
+
+static atomic_uint_fast32_t cb_calls = 0;
+static isc_quota_cb_t cbs[30];
+static isc_quota_t *qp;
+
+static void
+callback(isc_quota_t *quota, void *data) {
+ int val = *(int *)data;
+ /* Callback is not called if we get the quota directly */
+ assert_int_not_equal(val, -1);
+
+ /* We get the proper quota pointer */
+ assert_ptr_equal(quota, qp);
+
+ /* Verify that the callbacks are called in order */
+ int v = atomic_fetch_add_relaxed(&cb_calls, 1);
+ assert_int_equal(v, val);
+
+ /*
+ * First 5 will be detached by the test function,
+ * for the last 5 - do a 'chain detach'.
+ */
+ if (v >= 5) {
+ isc_quota_detach(&quota);
+ }
+}
+
+static void
+isc_quota_callback_test(void **state) {
+ isc_result_t result;
+ isc_quota_t quota;
+ isc_quota_t *quotas[30];
+ qp = &quota;
+ /*
+ * - 10 calls that end with SUCCESS
+ * - 10 calls that end with SOFTQUOTA
+ * - 10 callbacks
+ */
+ int ints[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ int i;
+ UNUSED(state);
+
+ isc_quota_init(&quota, 20);
+ isc_quota_soft(&quota, 10);
+
+ for (i = 0; i < 10; i++) {
+ quotas[i] = NULL;
+ isc_quota_cb_init(&cbs[i], callback, &ints[i]);
+ result = isc_quota_attach_cb(&quota, &quotas[i], &cbs[i]);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_ptr_equal(quotas[i], &quota);
+ assert_int_equal(isc_quota_getused(&quota), i + 1);
+ }
+ for (i = 10; i < 20; i++) {
+ quotas[i] = NULL;
+ isc_quota_cb_init(&cbs[i], callback, &ints[i]);
+ result = isc_quota_attach_cb(&quota, &quotas[i], &cbs[i]);
+ assert_int_equal(result, ISC_R_SOFTQUOTA);
+ assert_ptr_equal(quotas[i], &quota);
+ assert_int_equal(isc_quota_getused(&quota), i + 1);
+ }
+
+ for (i = 20; i < 30; i++) {
+ quotas[i] = NULL;
+ isc_quota_cb_init(&cbs[i], callback, &ints[i]);
+ result = isc_quota_attach_cb(&quota, &quotas[i], &cbs[i]);
+ assert_int_equal(result, ISC_R_QUOTA);
+ assert_ptr_equal(quotas[i], NULL);
+ assert_int_equal(isc_quota_getused(&quota), 20);
+ }
+ assert_int_equal(atomic_load(&cb_calls), 0);
+
+ for (i = 0; i < 5; i++) {
+ isc_quota_detach(&quotas[i]);
+ assert_null(quotas[i]);
+ assert_int_equal(isc_quota_getused(&quota), 20);
+ assert_int_equal(atomic_load(&cb_calls), i + 1);
+ }
+ /* That should cause a chain reaction */
+ isc_quota_detach(&quotas[5]);
+ assert_int_equal(atomic_load(&cb_calls), 10);
+
+ /* Release the quotas that we did not released in the callback */
+ for (i = 0; i < 5; i++) {
+ qp = &quota;
+ isc_quota_detach(&qp);
+ }
+
+ for (i = 6; i < 20; i++) {
+ isc_quota_detach(&quotas[i]);
+ assert_null(quotas[i]);
+ assert_int_equal(isc_quota_getused(&quota), 19 - i);
+ }
+ assert_int_equal(atomic_load(&cb_calls), 10);
+
+ assert_int_equal(isc_quota_getused(&quota), 0);
+ isc_quota_destroy(&quota);
+}
+
+/*
+ * Multithreaded quota callback test:
+ * - quota set to 100
+ * - 10 threads, each trying to get 100 quotas.
+ * - creates a separate thread to release it after 10ms
+ */
+
+typedef struct qthreadinfo {
+ atomic_uint_fast32_t direct;
+ atomic_uint_fast32_t callback;
+ isc_quota_t *quota;
+ isc_quota_cb_t callbacks[100];
+} qthreadinfo_t;
+
+static atomic_uint_fast32_t g_tnum = 0;
+/* at most 10 * 100 quota_detach threads */
+isc_thread_t g_threads[10 * 100];
+
+static void *
+quota_detach(void *quotap) {
+ isc_quota_t *quota = (isc_quota_t *)quotap;
+ isc_test_nap(10000);
+ isc_quota_detach(&quota);
+ return ((isc_threadresult_t)0);
+}
+
+static void
+quota_callback(isc_quota_t *quota, void *data) {
+ qthreadinfo_t *qti = (qthreadinfo_t *)data;
+ atomic_fetch_add_relaxed(&qti->callback, 1);
+ int tnum = atomic_fetch_add_relaxed(&g_tnum, 1);
+ isc_thread_create(quota_detach, quota, &g_threads[tnum]);
+}
+
+static isc_threadresult_t
+quota_thread(void *qtip) {
+ qthreadinfo_t *qti = (qthreadinfo_t *)qtip;
+ for (int i = 0; i < 100; i++) {
+ isc_quota_cb_init(&qti->callbacks[i], quota_callback, qti);
+ isc_quota_t *quota = NULL;
+ isc_result_t result = isc_quota_attach_cb(qti->quota, &quota,
+ &qti->callbacks[i]);
+ if (result == ISC_R_SUCCESS) {
+ atomic_fetch_add_relaxed(&qti->direct, 1);
+ int tnum = atomic_fetch_add_relaxed(&g_tnum, 1);
+ isc_thread_create(quota_detach, quota,
+ &g_threads[tnum]);
+ }
+ }
+ return ((isc_threadresult_t)0);
+}
+
+static void
+isc_quota_callback_mt_test(void **state) {
+ UNUSED(state);
+ isc_quota_t quota;
+ int i;
+
+ isc_quota_init(&quota, 100);
+ static qthreadinfo_t qtis[10];
+ isc_thread_t threads[10];
+ for (i = 0; i < 10; i++) {
+ atomic_init(&qtis[i].direct, 0);
+ atomic_init(&qtis[i].callback, 0);
+ qtis[i].quota = &quota;
+ isc_thread_create(quota_thread, &qtis[i], &threads[i]);
+ }
+ for (i = 0; i < 10; i++) {
+ isc_thread_join(threads[i], NULL);
+ }
+
+ for (i = 0; i < (int)atomic_load(&g_tnum); i++) {
+ isc_thread_join(g_threads[i], NULL);
+ }
+ int direct = 0, callback = 0;
+
+ for (i = 0; i < 10; i++) {
+ direct += atomic_load(&qtis[i].direct);
+ callback += atomic_load(&qtis[i].callback);
+ }
+ /* Total quota gained must be 10 threads * 100 tries */
+ assert_int_equal(direct + callback, 10 * 100);
+ /*
+ * At least 100 must be direct, the rest is virtually random:
+ * - in a regular run I'm constantly getting 100:900 ratio
+ * - under rr - usually around ~120:880
+ * - under rr -h - 1000:0
+ */
+ assert_true(direct >= 100);
+
+ isc_quota_destroy(&quota);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_quota_get_set_test),
+ cmocka_unit_test(isc_quota_hard_test),
+ cmocka_unit_test(isc_quota_soft_test),
+ cmocka_unit_test(isc_quota_callback_test),
+ cmocka_unit_test(isc_quota_callback_mt_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/radix_test.c b/lib/isc/tests/radix_test.c
new file mode 100644
index 0000000..132b64d
--- /dev/null
+++ b/lib/isc/tests/radix_test.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/radix.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+/* test radix searching */
+static void
+isc_radix_search_test(void **state) {
+ isc_radix_tree_t *radix = NULL;
+ isc_radix_node_t *node;
+ isc_prefix_t prefix;
+ isc_result_t result;
+ struct in_addr in_addr;
+ isc_netaddr_t netaddr;
+
+ UNUSED(state);
+
+ result = isc_radix_create(test_mctx, &radix, 32);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ in_addr.s_addr = inet_addr("3.3.3.0");
+ isc_netaddr_fromin(&netaddr, &in_addr);
+ NETADDR_TO_PREFIX_T(&netaddr, prefix, 24);
+
+ node = NULL;
+ result = isc_radix_insert(radix, &node, NULL, &prefix);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ node->data[0] = (void *)1;
+ isc_refcount_destroy(&prefix.refcount);
+
+ in_addr.s_addr = inet_addr("3.3.0.0");
+ isc_netaddr_fromin(&netaddr, &in_addr);
+ NETADDR_TO_PREFIX_T(&netaddr, prefix, 16);
+
+ node = NULL;
+ result = isc_radix_insert(radix, &node, NULL, &prefix);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ node->data[0] = (void *)2;
+ isc_refcount_destroy(&prefix.refcount);
+
+ in_addr.s_addr = inet_addr("3.3.3.3");
+ isc_netaddr_fromin(&netaddr, &in_addr);
+ NETADDR_TO_PREFIX_T(&netaddr, prefix, 22);
+
+ node = NULL;
+ result = isc_radix_search(radix, &node, &prefix);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_ptr_equal(node->data[0], (void *)2);
+
+ isc_refcount_destroy(&prefix.refcount);
+
+ isc_radix_destroy(radix, NULL);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(isc_radix_search_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/random_test.c b/lib/isc/tests/random_test.c
new file mode 100644
index 0000000..8025583
--- /dev/null
+++ b/lib/isc/tests/random_test.c
@@ -0,0 +1,894 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * IMPORTANT NOTE:
+ * These tests work by generating a large number of pseudo-random numbers
+ * and then statistically analyzing them to determine whether they seem
+ * random. The test is expected to fail on occasion by random happenstance.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <math.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/commandline.h>
+#include <isc/mem.h>
+#include <isc/nonce.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+#define REPS 25000
+
+typedef double(pvalue_func_t)(isc_mem_t *mctx, uint16_t *values, size_t length);
+
+/* igamc(), igam(), etc. were adapted (and cleaned up) from the Cephes
+ * math library:
+ *
+ * Cephes Math Library Release 2.8: June, 2000
+ * Copyright 1985, 1987, 2000 by Stephen L. Moshier
+ *
+ * The Cephes math library was released into the public domain as part
+ * of netlib.
+ */
+
+static double MACHEP = 1.11022302462515654042E-16;
+static double MAXLOG = 7.09782712893383996843E2;
+static double big = 4.503599627370496e15;
+static double biginv = 2.22044604925031308085e-16;
+
+static double
+igamc(double a, double x);
+static double
+igam(double a, double x);
+
+/* Set to true (or use -v option) for verbose output */
+static bool verbose = false;
+
+typedef enum {
+ ISC_RANDOM8,
+ ISC_RANDOM16,
+ ISC_RANDOM32,
+ ISC_RANDOM_BYTES,
+ ISC_RANDOM_UNIFORM,
+ ISC_NONCE_BYTES
+} isc_random_func;
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+static double
+igamc(double a, double x) {
+ double ans, ax, c, yc, r, t, y, z;
+ double pk, pkm1, pkm2, qk, qkm1, qkm2;
+
+ if ((x <= 0) || (a <= 0)) {
+ return (1.0);
+ }
+
+ if ((x < 1.0) || (x < a)) {
+ return (1.0 - igam(a, x));
+ }
+
+ ax = a * log(x) - x - lgamma(a);
+ if (ax < -MAXLOG) {
+ print_error("# igamc: UNDERFLOW, ax=%f\n", ax);
+ return (0.0);
+ }
+ ax = exp(ax);
+
+ /* continued fraction */
+ y = 1.0 - a;
+ z = x + y + 1.0;
+ c = 0.0;
+ pkm2 = 1.0;
+ qkm2 = x;
+ pkm1 = x + 1.0;
+ qkm1 = z * x;
+ ans = pkm1 / qkm1;
+
+ do {
+ c += 1.0;
+ y += 1.0;
+ z += 2.0;
+ yc = y * c;
+ pk = pkm1 * z - pkm2 * yc;
+ qk = qkm1 * z - qkm2 * yc;
+ if (qk != 0) {
+ r = pk / qk;
+ t = fabs((ans - r) / r);
+ ans = r;
+ } else {
+ t = 1.0;
+ }
+
+ pkm2 = pkm1;
+ pkm1 = pk;
+ qkm2 = qkm1;
+ qkm1 = qk;
+
+ if (fabs(pk) > big) {
+ pkm2 *= biginv;
+ pkm1 *= biginv;
+ qkm2 *= biginv;
+ qkm1 *= biginv;
+ }
+ } while (t > MACHEP);
+
+ return (ans * ax);
+}
+
+static double
+igam(double a, double x) {
+ double ans, ax, c, r;
+
+ if ((x <= 0) || (a <= 0)) {
+ return (0.0);
+ }
+
+ if ((x > 1.0) && (x > a)) {
+ return (1.0 - igamc(a, x));
+ }
+
+ /* Compute x**a * exp(-x) / md_gamma(a) */
+ ax = a * log(x) - x - lgamma(a);
+ if (ax < -MAXLOG) {
+ print_error("# igam: UNDERFLOW, ax=%f\n", ax);
+ return (0.0);
+ }
+ ax = exp(ax);
+
+ /* power series */
+ r = a;
+ c = 1.0;
+ ans = 1.0;
+
+ do {
+ r += 1.0;
+ c *= x / r;
+ ans += c;
+ } while (c / ans > MACHEP);
+
+ return (ans * ax / a);
+}
+
+static int8_t scounts_table[65536];
+static uint8_t bitcounts_table[65536];
+
+static int8_t
+scount_calculate(uint16_t n) {
+ int i;
+ int8_t sc;
+
+ sc = 0;
+ for (i = 0; i < 16; i++) {
+ uint16_t lsb;
+
+ lsb = n & 1;
+ if (lsb != 0) {
+ sc += 1;
+ } else {
+ sc -= 1;
+ }
+
+ n >>= 1;
+ }
+
+ return (sc);
+}
+
+static uint8_t
+bitcount_calculate(uint16_t n) {
+ int i;
+ uint8_t bc;
+
+ bc = 0;
+ for (i = 0; i < 16; i++) {
+ uint16_t lsb;
+
+ lsb = n & 1;
+ if (lsb != 0) {
+ bc += 1;
+ }
+
+ n >>= 1;
+ }
+
+ return (bc);
+}
+
+static void
+tables_init(void) {
+ uint32_t i;
+
+ for (i = 0; i < 65536; i++) {
+ scounts_table[i] = scount_calculate(i);
+ bitcounts_table[i] = bitcount_calculate(i);
+ }
+}
+
+/*
+ * The following code for computing Marsaglia's rank is based on the
+ * implementation in cdbinrnk.c from the diehard tests by George
+ * Marsaglia.
+ *
+ * This function destroys (modifies) the data passed in bits.
+ */
+static uint32_t
+matrix_binaryrank(uint32_t *bits, size_t rows, size_t cols) {
+ size_t i, j, k;
+ unsigned int rt = 0;
+ uint32_t rank = 0;
+ uint32_t tmp;
+
+ for (k = 0; k < rows; k++) {
+ i = k;
+
+ while (rt >= cols || ((bits[i] >> rt) & 1) == 0) {
+ i++;
+
+ if (i < rows) {
+ continue;
+ } else {
+ rt++;
+ if (rt < cols) {
+ i = k;
+ continue;
+ }
+ }
+
+ return (rank);
+ }
+
+ rank++;
+ if (i != k) {
+ tmp = bits[i];
+ bits[i] = bits[k];
+ bits[k] = tmp;
+ }
+
+ for (j = i + 1; j < rows; j++) {
+ if (((bits[j] >> rt) & 1) == 0) {
+ continue;
+ } else {
+ bits[j] ^= bits[k];
+ }
+ }
+
+ rt++;
+ }
+
+ return (rank);
+}
+
+static void
+random_test(pvalue_func_t *func, isc_random_func test_func) {
+ uint32_t m;
+ uint32_t j;
+ uint32_t histogram[11] = { 0 };
+ uint32_t passed;
+ double proportion;
+ double p_hat;
+ double lower_confidence, higher_confidence;
+ double chi_square;
+ double p_value_t;
+ double alpha;
+
+ tables_init();
+
+ m = 1000;
+ passed = 0;
+
+ for (j = 0; j < m; j++) {
+ uint32_t i;
+ uint32_t values[REPS];
+ uint16_t *uniform_values;
+ double p_value;
+
+ switch (test_func) {
+ case ISC_RANDOM8:
+ for (i = 0; i < (sizeof(values) / sizeof(*values)); i++)
+ {
+ values[i] = isc_random8();
+ }
+ break;
+ case ISC_RANDOM16:
+ for (i = 0; i < (sizeof(values) / sizeof(*values)); i++)
+ {
+ values[i] = isc_random16();
+ }
+ break;
+ case ISC_RANDOM32:
+ for (i = 0; i < (sizeof(values) / sizeof(*values)); i++)
+ {
+ values[i] = isc_random32();
+ }
+ break;
+ case ISC_RANDOM_BYTES:
+ isc_random_buf(values, sizeof(values));
+ break;
+ case ISC_RANDOM_UNIFORM:
+ uniform_values = (uint16_t *)values;
+ for (i = 0;
+ i < (sizeof(values) / (sizeof(*uniform_values)));
+ i++)
+ {
+ uniform_values[i] =
+ isc_random_uniform(UINT16_MAX);
+ }
+ break;
+ case ISC_NONCE_BYTES:
+ isc_nonce_buf(values, sizeof(values));
+ break;
+ }
+
+ p_value = (*func)(test_mctx, (uint16_t *)values, REPS * 2);
+ if (p_value >= 0.01) {
+ passed++;
+ }
+
+ assert_in_range(p_value, 0.0, 1.0);
+
+ i = (int)floor(p_value * 10);
+ histogram[i]++;
+ }
+
+ /*
+ * Check proportion of sequences passing a test (see section
+ * 4.2.1 in NIST SP 800-22).
+ */
+ alpha = 0.01; /* the significance level */
+ proportion = (double)passed / (double)m;
+ p_hat = 1.0 - alpha;
+ lower_confidence = p_hat - (3.0 * sqrt((p_hat * (1.0 - p_hat)) / m));
+ higher_confidence = p_hat + (3.0 * sqrt((p_hat * (1.0 - p_hat)) / m));
+
+ if (verbose) {
+ print_message("# passed=%u/1000\n", passed);
+ print_message("# higher_confidence=%f, lower_confidence=%f, "
+ "proportion=%f\n",
+ higher_confidence, lower_confidence, proportion);
+ }
+
+ assert_in_range(proportion, lower_confidence, higher_confidence);
+
+ /*
+ * Check uniform distribution of p-values (see section 4.2.2 in
+ * NIST SP 800-22).
+ */
+
+ /* Fold histogram[10] (p_value = 1.0) into histogram[9] for
+ * interval [0.9, 1.0]
+ */
+ histogram[9] += histogram[10];
+ histogram[10] = 0;
+
+ /* Pre-requisite that at least 55 sequences are processed. */
+ assert_true(m >= 55);
+
+ if (verbose) {
+ print_message("# ");
+ }
+
+ chi_square = 0.0;
+ for (j = 0; j < 10; j++) {
+ double numer;
+ double denom;
+
+ if (verbose) {
+ print_message("hist%u=%u ", j, histogram[j]);
+ }
+
+ numer = (histogram[j] - (m / 10.0)) *
+ (histogram[j] - (m / 10.0));
+ denom = m / 10.0;
+ chi_square += numer / denom;
+ }
+
+ if (verbose) {
+ print_message("\n");
+ }
+
+ p_value_t = igamc(9 / 2.0, chi_square / 2.0);
+
+ assert_true(p_value_t >= 0.0001);
+}
+
+/*
+ * This is a frequency (monobits) test taken from the NIST SP 800-22
+ * RANDOM test suite.
+ */
+static double
+monobit(isc_mem_t *mctx, uint16_t *values, size_t length) {
+ size_t i;
+ int32_t scount;
+ uint32_t numbits;
+ double s_obs;
+ double p_value;
+
+ UNUSED(mctx);
+
+ numbits = length * sizeof(*values) * 8;
+ scount = 0;
+
+ for (i = 0; i < length; i++) {
+ scount += scounts_table[values[i]];
+ }
+
+ /* Preconditions (section 2.1.7 in NIST SP 800-22) */
+ assert_true(numbits >= 100);
+
+ if (verbose) {
+ print_message("# numbits=%u, scount=%d\n", numbits, scount);
+ }
+
+ s_obs = abs(scount) / sqrt(numbits);
+ p_value = erfc(s_obs / sqrt(2.0));
+
+ return (p_value);
+}
+
+/*
+ * This is the runs test taken from the NIST SP 800-22 RNG test suite.
+ */
+static double
+runs(isc_mem_t *mctx, uint16_t *values, size_t length) {
+ size_t i;
+ uint32_t bcount;
+ uint32_t numbits;
+ double pi;
+ double tau;
+ uint32_t j;
+ uint32_t b;
+ uint8_t bit_this;
+ uint8_t bit_prev;
+ uint32_t v_obs;
+ double numer;
+ double denom;
+ double p_value;
+
+ UNUSED(mctx);
+
+ numbits = length * sizeof(*values) * 8;
+ bcount = 0;
+
+ for (i = 0; i < length; i++) {
+ bcount += bitcounts_table[values[i]];
+ }
+
+ if (verbose) {
+ print_message("# numbits=%u, bcount=%u\n", numbits, bcount);
+ }
+
+ pi = (double)bcount / (double)numbits;
+ tau = 2.0 / sqrt(numbits);
+
+ /* Preconditions (section 2.3.7 in NIST SP 800-22) */
+ assert_true(numbits >= 100);
+
+ /*
+ * Pre-condition implied from the monobit test. This can fail
+ * for some sequences, and the p-value is taken as 0 in these
+ * cases.
+ */
+ if (fabs(pi - 0.5) >= tau) {
+ return (0.0);
+ }
+
+ /* Compute v_obs */
+ j = 0;
+ b = 14;
+ bit_prev = (values[j] & (1U << 15)) == 0 ? 0 : 1;
+
+ v_obs = 0;
+
+ for (i = 1; i < numbits; i++) {
+ bit_this = (values[j] & (1U << b)) == 0 ? 0 : 1;
+ if (b == 0) {
+ b = 15;
+ j++;
+ } else {
+ b--;
+ }
+
+ v_obs += bit_this ^ bit_prev;
+
+ bit_prev = bit_this;
+ }
+
+ v_obs += 1;
+
+ numer = fabs(v_obs - (2.0 * numbits * pi * (1.0 - pi)));
+ denom = 2.0 * sqrt(2.0 * numbits) * pi * (1.0 - pi);
+
+ p_value = erfc(numer / denom);
+
+ return (p_value);
+}
+
+/*
+ * This is the block frequency test taken from the NIST SP 800-22 RNG
+ * test suite.
+ */
+static double
+blockfrequency(isc_mem_t *mctx, uint16_t *values, size_t length) {
+ uint32_t i;
+ uint32_t numbits;
+ uint32_t mbits;
+ uint32_t mwords;
+ uint32_t numblocks;
+ double *pi;
+ double chi_square;
+ double p_value;
+
+ numbits = length * sizeof(*values) * 8;
+ mbits = 32000;
+ mwords = mbits / 16;
+ numblocks = numbits / mbits;
+
+ if (verbose) {
+ print_message("# numblocks=%u\n", numblocks);
+ }
+
+ /* Preconditions (section 2.2.7 in NIST SP 800-22) */
+ assert_true(numbits >= 100);
+ assert_true(mbits >= 20);
+ assert_true((double)mbits > (0.01 * numbits));
+ assert_true(numblocks < 100);
+ assert_true(numbits >= (mbits * numblocks));
+
+ pi = isc_mem_get(mctx, numblocks * sizeof(double));
+ assert_non_null(pi);
+
+ for (i = 0; i < numblocks; i++) {
+ uint32_t j;
+ pi[i] = 0.0;
+ for (j = 0; j < mwords; j++) {
+ uint32_t idx;
+
+ idx = i * mwords + j;
+ pi[i] += bitcounts_table[values[idx]];
+ }
+ pi[i] /= mbits;
+ }
+
+ /* Compute chi_square */
+ chi_square = 0.0;
+ for (i = 0; i < numblocks; i++) {
+ chi_square += (pi[i] - 0.5) * (pi[i] - 0.5);
+ }
+
+ chi_square *= 4 * mbits;
+
+ isc_mem_put(mctx, pi, numblocks * sizeof(double));
+
+ if (verbose) {
+ print_message("# chi_square=%f\n", chi_square);
+ }
+
+ p_value = igamc(numblocks * 0.5, chi_square * 0.5);
+
+ return (p_value);
+}
+
+/*
+ * This is the binary matrix rank test taken from the NIST SP 800-22 RNG
+ * test suite.
+ */
+static double
+binarymatrixrank(isc_mem_t *mctx, uint16_t *values, size_t length) {
+ uint32_t i;
+ size_t matrix_m;
+ size_t matrix_q;
+ uint32_t num_matrices;
+ size_t numbits;
+ uint32_t fm_0;
+ uint32_t fm_1;
+ uint32_t fm_rest;
+ double term1;
+ double term2;
+ double term3;
+ double chi_square;
+ double p_value;
+
+ UNUSED(mctx);
+
+ matrix_m = 32;
+ matrix_q = 32;
+ num_matrices = length / ((matrix_m * matrix_q) / 16);
+ numbits = num_matrices * matrix_m * matrix_q;
+
+ /* Preconditions (section 2.5.7 in NIST SP 800-22) */
+ assert_int_equal(matrix_m, 32);
+ assert_int_equal(matrix_q, 32);
+ assert_true(numbits >= (38 * matrix_m * matrix_q));
+
+ fm_0 = 0;
+ fm_1 = 0;
+ fm_rest = 0;
+ for (i = 0; i < num_matrices; i++) {
+ /*
+ * Each uint32_t supplies 32 bits, so a 32x32 bit matrix
+ * takes up uint32_t array of size 32.
+ */
+ uint32_t bits[32];
+ int j;
+ uint32_t rank;
+
+ for (j = 0; j < 32; j++) {
+ size_t idx;
+ uint32_t r1;
+ uint32_t r2;
+
+ idx = i * ((matrix_m * matrix_q) / 16);
+ idx += j * 2;
+
+ r1 = values[idx];
+ r2 = values[idx + 1];
+ bits[j] = (r1 << 16) | r2;
+ }
+
+ rank = matrix_binaryrank(bits, matrix_m, matrix_q);
+
+ if (rank == matrix_m) {
+ fm_0++;
+ } else if (rank == (matrix_m - 1)) {
+ fm_1++;
+ } else {
+ fm_rest++;
+ }
+ }
+
+ /* Compute chi_square */
+ term1 = ((fm_0 - (0.2888 * num_matrices)) *
+ (fm_0 - (0.2888 * num_matrices))) /
+ (0.2888 * num_matrices);
+ term2 = ((fm_1 - (0.5776 * num_matrices)) *
+ (fm_1 - (0.5776 * num_matrices))) /
+ (0.5776 * num_matrices);
+ term3 = ((fm_rest - (0.1336 * num_matrices)) *
+ (fm_rest - (0.1336 * num_matrices))) /
+ (0.1336 * num_matrices);
+
+ chi_square = term1 + term2 + term3;
+
+ if (verbose) {
+ print_message("# fm_0=%u, fm_1=%u, fm_rest=%u, chi_square=%f\n",
+ fm_0, fm_1, fm_rest, chi_square);
+ }
+
+ p_value = exp(-chi_square * 0.5);
+
+ return (p_value);
+}
+
+/***
+ *** Tests for isc_random32() function
+ ***/
+
+/* Monobit test for the RANDOM */
+static void
+isc_random32_monobit(void **state) {
+ UNUSED(state);
+
+ random_test(monobit, ISC_RANDOM32);
+}
+
+/* Runs test for the RANDOM */
+static void
+isc_random32_runs(void **state) {
+ UNUSED(state);
+
+ random_test(runs, ISC_RANDOM32);
+}
+
+/* Block frequency test for the RANDOM */
+static void
+isc_random32_blockfrequency(void **state) {
+ UNUSED(state);
+
+ random_test(blockfrequency, ISC_RANDOM32);
+}
+
+/* Binary matrix rank test for the RANDOM */
+static void
+isc_random32_binarymatrixrank(void **state) {
+ UNUSED(state);
+
+ random_test(binarymatrixrank, ISC_RANDOM32);
+}
+
+/***
+ *** Tests for isc_random_bytes() function
+ ***/
+
+/* Monobit test for the RANDOM */
+static void
+isc_random_bytes_monobit(void **state) {
+ UNUSED(state);
+
+ random_test(monobit, ISC_RANDOM_BYTES);
+}
+
+/* Runs test for the RANDOM */
+static void
+isc_random_bytes_runs(void **state) {
+ UNUSED(state);
+
+ random_test(runs, ISC_RANDOM_BYTES);
+}
+
+/* Block frequency test for the RANDOM */
+static void
+isc_random_bytes_blockfrequency(void **state) {
+ UNUSED(state);
+
+ random_test(blockfrequency, ISC_RANDOM_BYTES);
+}
+
+/* Binary matrix rank test for the RANDOM */
+static void
+isc_random_bytes_binarymatrixrank(void **state) {
+ UNUSED(state);
+
+ random_test(binarymatrixrank, ISC_RANDOM_BYTES);
+}
+
+/***
+ *** Tests for isc_random_uniform() function:
+ ***/
+
+/* Monobit test for the RANDOM */
+static void
+isc_random_uniform_monobit(void **state) {
+ UNUSED(state);
+
+ random_test(monobit, ISC_RANDOM_UNIFORM);
+}
+
+/* Runs test for the RANDOM */
+static void
+isc_random_uniform_runs(void **state) {
+ UNUSED(state);
+
+ random_test(runs, ISC_RANDOM_UNIFORM);
+}
+
+/* Block frequency test for the RANDOM */
+static void
+isc_random_uniform_blockfrequency(void **state) {
+ UNUSED(state);
+
+ random_test(blockfrequency, ISC_RANDOM_UNIFORM);
+}
+
+/* Binary matrix rank test for the RANDOM */
+static void
+isc_random_uniform_binarymatrixrank(void **state) {
+ UNUSED(state);
+
+ random_test(binarymatrixrank, ISC_RANDOM_UNIFORM);
+}
+
+/* Tests for isc_nonce_bytes() function */
+
+/* Monobit test for the RANDOM */
+static void
+isc_nonce_bytes_monobit(void **state) {
+ UNUSED(state);
+
+ random_test(monobit, ISC_NONCE_BYTES);
+}
+
+/* Runs test for the RANDOM */
+static void
+isc_nonce_bytes_runs(void **state) {
+ UNUSED(state);
+
+ random_test(runs, ISC_NONCE_BYTES);
+}
+
+/* Block frequency test for the RANDOM */
+static void
+isc_nonce_bytes_blockfrequency(void **state) {
+ UNUSED(state);
+
+ random_test(blockfrequency, ISC_NONCE_BYTES);
+}
+
+/* Binary matrix rank test for the RANDOM */
+static void
+isc_nonce_bytes_binarymatrixrank(void **state) {
+ UNUSED(state);
+
+ random_test(binarymatrixrank, ISC_NONCE_BYTES);
+}
+
+int
+main(int argc, char **argv) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_random32_monobit),
+ cmocka_unit_test(isc_random32_runs),
+ cmocka_unit_test(isc_random32_blockfrequency),
+ cmocka_unit_test(isc_random32_binarymatrixrank),
+ cmocka_unit_test(isc_random_bytes_monobit),
+ cmocka_unit_test(isc_random_bytes_runs),
+ cmocka_unit_test(isc_random_bytes_blockfrequency),
+ cmocka_unit_test(isc_random_bytes_binarymatrixrank),
+ cmocka_unit_test(isc_random_uniform_monobit),
+ cmocka_unit_test(isc_random_uniform_runs),
+ cmocka_unit_test(isc_random_uniform_blockfrequency),
+ cmocka_unit_test(isc_random_uniform_binarymatrixrank),
+ cmocka_unit_test(isc_nonce_bytes_monobit),
+ cmocka_unit_test(isc_nonce_bytes_runs),
+ cmocka_unit_test(isc_nonce_bytes_blockfrequency),
+ cmocka_unit_test(isc_nonce_bytes_binarymatrixrank),
+ };
+ int c;
+
+ while ((c = isc_commandline_parse(argc, argv, "v")) != -1) {
+ switch (c) {
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/regex_test.c b/lib/isc/tests/regex_test.c
new file mode 100644
index 0000000..700df92
--- /dev/null
+++ b/lib/isc/tests/regex_test.c
@@ -0,0 +1,2374 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif /* ifdef HAVE_REGEX_H */
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/commandline.h>
+#include <isc/print.h>
+#include <isc/regex.h>
+#include <isc/util.h>
+
+/* Set to true (or use -v option) for verbose output */
+static bool verbose = false;
+
+/* test isc_regex_validate() */
+static void
+regex_validate(void **state) {
+ /*
+ * test regex were generated using http://code.google.com/p/regfuzz/
+ * modified to use only printable characters
+ */
+ struct {
+ const char *expression;
+ int expect;
+ int exception; /* regcomp accepts but is
+ * disallowed. */
+ } tests[] = {
+ { "", -1, 0 },
+ { "*", -1, 0 },
+ { ".*", 0, 0 },
+ { ".**", -1, 0 },
+ { ".*\?", -1, 0 },
+ { ".*+", -1, 0 },
+ { "+", -1, 0 },
+ { ".+", 0, 0 },
+ { ".++", -1, 0 },
+ { ".+\?", -1, 0 },
+ { ".+*", -1, 0 },
+ { "\?", -1, 0 },
+ { ".\?", 0, 0 },
+ { ".\?\?", -1, 0 },
+ { ".\?*", -1, 0 },
+ { ".\?+", -1, 0 },
+ { "(", -1, 0 },
+ { "()", 1, 0 },
+ { "(|)", -1, 0 },
+ { "(a|)", -1, 0 },
+ { "(|b)", -1, 0 },
+ { ".{", 0, 0 },
+ { ".{1", -1, 0 },
+ { ".\\{1", 0, 0 },
+ { ".{1}", 0, 0 },
+ { ".\\{1}", 0, 0 },
+ { ".{,", 0, 0 },
+ { ".{,}", 0, 0 },
+ { ".{1,}", 0, 0 },
+ { ".\\{1,}", 0, 0 },
+ { ".{1,\\}", -1, 0 },
+ { ".{1,", -1, 0 },
+ { ".\\{1,", 0, 0 },
+ { ".{1,2}", 0, 0 },
+ { ".{1,2}*", -1, 0 },
+ { ".{1,2}+", -1, 0 },
+ { ".{1,2}\?", -1, 0 },
+ { ".{1,2", -1, 0 },
+ { ".{2,1}", -1, 0 },
+ { "[", -1, 0 },
+ { "[]", -1, 0 },
+ { "[]]", 0, 0 },
+ { "[[]", 0, 0 },
+ { "[^]", -1, 0 },
+ { "[1-2-3]", -1, 0 },
+ { "[1-22-3]", 0, 0 },
+ { "[+--23]", 0, 0 },
+ { "[+--]", 0, 0 },
+ { "[-1]", 0, 0 },
+ { "[1-]", 0, 0 },
+ { "[[.^.]]", 0, 0 },
+ { "[^]]", 0, 0 },
+ { "[^^]", 0, 0 },
+ { "[]]\?", 0, 0 },
+ { "[[]\?", 0, 0 },
+ { "[[..]]", -1, 0 },
+ { "[[...]]", 0, 0 },
+ { "[[..5.]--]", -1, 0 },
+ { "[[.+.]--]", 0, 0 },
+ { "[[..+.]--]", -1, 0 },
+ { "[[.5.]--]", -1, 0 },
+ { "[1-[=x=]]", -1, 0 },
+ { "[[:alpha:]]", 0, 0 },
+ { "[[:alpha:]", -1, 0 },
+ { "[[:alnum:]]", 0, 0 },
+ { "[[:alnum:]", -1, 0 },
+ { "[[:digit:]]", 0, 0 },
+ { "[[:digit:]", -1, 0 },
+ { "[[:punct:]]", 0, 0 },
+ { "[[:punct:]", -1, 0 },
+ { "[[:graph:]]", 0, 0 },
+ { "[[:graph:]", -1, 0 },
+ { "[[:space:]]", 0, 0 },
+ { "[[:space:]", -1, 0 },
+ { "[[:blank:]]", 0, 0 },
+ { "[[:blank:]", -1, 0 },
+ { "[[:upper:]]", 0, 0 },
+ { "[[:upper:]", -1, 0 },
+ { "[[:cntrl:]]", 0, 0 },
+ { "[[:cntrl:]", -1, 0 },
+ { "[[:print:]]", 0, 0 },
+ { "[[:print:]", -1, 0 },
+ { "[[:xdigit:]]", 0, 0 },
+ { "[[:xdigit:]", -1, 0 },
+ { "[[:unknown:]]", -1, 0 },
+ { "\\[", 0, 0 },
+ { "(a)\\1", 1, 0 },
+ { "(a)\\2", -1, 1 },
+ { "\\0", 0, 0 },
+ { "[[][:g(\?(raph:][:alnu)(\?{m:][:space:]h]<Z3})AAA)S[:space:]"
+ "{176,}",
+ 0, 0 },
+ { "(()IIIIIIII(III[[[[[[[[[[[[[[[[[[^[[[[[[[[ [^ "
+ " "
+ "fX][:ascii:].)N[:a(\?<!lpha:])][:punct:]e*y+)a{-124,223}",
+ 3, 0 },
+ { "(pP\\\\\\(\?<!"
+ "\\\\\\\\\\\\\\\\\\\\\\lRRRRRRRRRRRRRRRRBBBBBBBBBBBBBBBB))"
+ "kkkkkkkkkkkkkkkkkkkkk|^",
+ 1, 0 },
+ { "[^[^[{111}(\?=(\?:(\?>/"
+ "r(\?<(\?=!(\?(\?!<!Q(\?:=0_{Meqipm`(\?((\?{x|N)))))|))+]+]Z)"
+ "O{,-215}])}))___________________{}",
+ 0, 0 },
+ { "[C{,-218(\?=}E^< ]PP-Ga)t``````````````````````````{138}", 0,
+ 0 },
+ { "[^h(\?<!(\?>Nn(\?#])))", 0, 0 },
+ { "[(\?!(\?<=[^{,37}AAAA(AAAAAAAAAAAAA])", 0, 0 },
+ { "[^((\?(\?:ms(\?<!xims:A{}(\?{*</H(\?=xL "
+ "$(\?<!,[})))*)qqqqqqqqqqqqqqqqqq)]"
+ "33333333333333333333333333333{[:graph:]p)-+( "
+ "oqD]){-10,}-{247}_______________________X-e[:alpha:][:"
+ "upperword:]_(______wwwwwwwww "
+ "/c[:upperword:][:alnum:][:alnum:][:pun(\?{ct:])[:blankcntrl:"
+ "]})*_*",
+ 2, 0 },
+ { "[(\?<!:lowerprin(\?{t:]{}}){113,})[:punct:]"
+ "IIIIIIIIIIIIIIIIIIIIIIII",
+ 0, 0 },
+ { "PP)", 0, 0 },
+ { "(([^(\?<!((\?>\?=[])p.]}8X[:blankcntrl:],{-119,94})XmF1.{)-)"
+ "[:upperword:])[:digit:]{zg-q",
+ 2, 0 },
+ { "[^[({(\?#254}))Z[l][x50]=444444444444(4444444444u[:punct:]"
+ "\?[:punct:(\?!])])",
+ 1, 0 },
+ { "[^[^[^([^((*4[(^((\?<=])Ec)", 0, 0 },
+ { "(0)Y:8biiiiiiiiiiiiiiiiiii", 1, 0 },
+ { "[^w(\?!)P::::::::::::::(\?#::(\?<=:::::::::]\"\"{}["
+ "3333333333333333(\?<=33333(\?!)9Xja][:alph(\?<=a:])xB1)("
+ "PX8Cf\?4444)qq[:digit:])",
+ 1, 0 },
+ { "([U[^[^].]^m]/306KS7JJJJJJJJ{})", 1, 0 },
+ { "[^[^([^[(\?!(\?>8j`Wg2(\?{,(\?>!#N++++(\?<![++++++)+"
+ "44444444bA:K(\?<!O3([:digit:]3]}}}}}}}}}}}}}}}}}}}}}}}}LP})"
+ "S",
+ 0, 0 },
+ { "[({(\?{,(\?(=213}*))})]WWWWWWWWWWWWWWW[:alnum:])", 0, 0 },
+ { "[:(\?<=ascii:])", 0, 0 },
+ { "[U(\?#)(\?<=+HzE])[:punct:]{-207,170}\?s.!", 0, 0 },
+ { "{}z=jU75~n#soD\"&\?UL`X{xxxxxxxxxxxxxxxxxxxx(xxxxxx${-246,"
+ "27}[:graph:]g\"{_bX)[:alnum:][:punct:]{-79,}-",
+ 1, 0 },
+ { "[^{,-186}@@@@[^(\?{@@(\?>@+(\?>l.]}))*\\BCYX]^W{52,123}("
+ "lXislccccccccccccccccc)-*)",
+ 1, 0 },
+ { "(x42+,)7=]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]", 1, 0 },
+ { "[^(*[:graph:]q/TH\?B(\?{P)]})uZn[:digit:]+2", 0, 0 },
+ { "([XXXXXXXXXXXXXXXXXXXXX[(:alnum:][:space:]i%[:upperw(\?=o("
+ "\?#rd:])) ",
+ 1, 0 },
+ { "(@@@@)", 1, 0 },
+ { "{-18,}[:as[(\?>^[cii:]]{}>+{-46,}{,95}[:punct:]{}"
+ "99999999999999])-{-134}'sK$"
+ "wCKjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj",
+ 0, 0 },
+ { "(l[:alpha:(\?!]))", 1, 0 },
+ { "[[^(\?{]|JJ[:alph(a:]X{})B^][:lowerprint:]n-219{-32}{19,105}"
+ "k4P}){,-144}",
+ 0, 0 },
+ { "[[^]P[:punct:][:alpha:][:xdigit:]syh]|W#JS*(m<2,P-RK)cA@", 1,
+ 0 },
+ { "([^((\?({\?<=)}){[^}^]{}])^P4[:punct:[]$)]", 1, 0 },
+ { "([(\?#:(\?{space:]}):{}{-242,}n)F[:alpha:]3$)d4H3up6qS[:"
+ "blankcntrl:]B:C{}[:upperword:]r",
+ 1, 0 },
+ { "([(\?:]))[:digit:]mLV.{}", 1, 0 },
+ { "[^PPP-[]{[,50}{128,}]111111111111111]p", 0, 0 },
+ { "[^([^([^([[^[([^[^[[2[[[[[[[[[[[[[^[[[[(\?(\?{:[[[[[[(\?([-["
+ ":ascii:]--*)",
+ -1, 0 },
+ { ")!F^DA/ZZZZZZZZZZZZZZZZZZ", 0, 0 },
+ { "[[[[[[[((\?=\?(\?>([[[[[[[^[[[[(\?()[[[K(\?#))])))]7Y[:"
+ "space:]{,-96}pP)[:ascii:]u{-88}:N{-251}uo",
+ 0, 0 },
+ { "t[:x(\?<=digit:])eYYYYYYYYYYYYYYYYYY{,-220}A", 0, 0 },
+ { "[[({10,}[:graph:]Pdddddd(\?#X)])[:alnum:(]]L-C){,50}[:"
+ "blankcntrl:]p[:gra(ph:]){66,}",
+ 0, 0 },
+ { "[^[^]*4br]w[:digit(\?::]n99999999999999999)P[:punct:]pP", 0,
+ 0 },
+ { "[:digit:]{67,247}!N{122})VrXe", 0, 0 },
+ { "[:xdigit:]^[:xdigit:]Z[:alnum:]^^^^1[:upperword:(\?=])[:"
+ "lowerprint:]*JJ-",
+ 0, 0 },
+ { "[[(\?imsximsx:^*e(){,3[6}](V~\?^[:asc(\?!ii:]I.dZ))]$^"
+ "AAAAAAAAAAAAAAAAAAAAAAAA[:space:]k)]",
+ 1, 0 },
+ { "W{,112}[:lowerp(\?<!rint:]$#GT>R7~t'"
+ "\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"9,O).",
+ 0, 0 },
+ { "[^{6((\?>\?:4}(\?<=G))f)"
+ "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKpppppppp(\?=ppppp]{,-101}|[:"
+ "blankcntrl:]Z{-182})",
+ 0, 0 },
+ { "([:punct:]@^,,,,,,,,,,,,,,,,,,,,,,,,,,0\?:-o8NPIIIIIIIII)"
+ "pPKKKKKKKKKKKKKKKKKKKK",
+ 1, 0 },
+ { "([^[[^[^]]]])", 1, 0 },
+ { "[([^[(333\"(\?#\\\\[)(\?isx-x:\"Tx]')", 0, 0 },
+ { "[[n>^>T%.zzzzzzzzzzzzzzzzz$&|Fk.1o7^o, "
+ "^8{202,-12}$[:alnum:]]G[:upperword:]V[:xdigit:]L|[:"
+ "upperword:]KKKKKKKKKKKKYX\"\")xJ "
+ "~B@[{,-68}/][:upperword:]QI.",
+ 0, 0 },
+ { "[^[]tN^hy3\"d@v T[GE\?^~{124,10(\?{2}]})\?[:upperword:]O", 0,
+ 0 },
+ { "d.``````````````````````````[:up(\?=perword:]"
+ "RRRRRRRRRRRRRRR)",
+ 0, 0 },
+ { "[Z{{{{{{{{{{{{{(\?={(\?<!{{{{{{{{{(\?>{{J6N:H[tA+mN3Zmf:p\?]"
+ "\?){-181,82}S4n.b[:lowerpri(\?{nt:]|"
+ "ggggggggggggggggggggggggggggggg}))4)",
+ 0, 0 },
+ { "[^((/////[^////[^/////////[(^/////]fI{240}{-120}+]R]GA)", 0,
+ 0 },
+ { "[-(\?#.)(\?())[:alpha:](\?={(\?#}r)[:space:]PPW]o)", 0, 0 },
+ { "[:lowerp(\?{rint:]})201{46,}[:a[^scii:]0Q{37,}][:blankcntrl:"
+ "]1331",
+ 0, 0 },
+ { "[^(\?!(\?#)\\GIwxKKKKKKKKKK'$KKKKKKKK]l)bbb^&\?", 0, 0 },
+ { "[:ascii:]*[:sp(\?<=ace:])", 0, 0 },
+ { "({-66,}Z{})0I{-111,}[:punct(\?():])", 1, 0 },
+ { "[[^(\?!()%%%%%%%%%%%%%(\?:%%%%%%%%%%%%%%%%)t(\?{VX>B#6sUU("
+ "\?<!UUUUUU(\?=UUU[^UUUUUUUUUUUU(\?((\?:UPPPPPPPPPPP)"
+ "PPPPPPPPPPPPPPP]ffffffffffffffffffffffff)^[:space:]"
+ "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww{243}9[:lowerprint:]Dv[:"
+ "graph:])][:blankcntrl:]V%E[:graph:]})[:space:]{-83,}cQZ{}4{-"
+ "23,135}",
+ 0, 0 },
+ { "({,-76[}]O[:xdi(\?<!git:])\?5))))))))\?d[:lowerprint:]"
+ "b666666[:graph:]c",
+ 1, 0 },
+ { "{}{-145,}[:(\?(spa)ce:])f", 0, 0 },
+ { "[([^].{116,243}]T*[[^:punct(\?[{[^:(\?<!]]8()])[:alnum:])})]"
+ "N{}{,243}*[n]][:graph:]",
+ 1, 0 },
+ { "[^w]8888888888888888_________(__________[:ascii:]BdqTE$^0|"
+ "MNto*i#############[^#################])",
+ 1, 0 },
+ { "[^[[[<[()\?]GGG{,26[}[:alnum:]SSSSS.gggggggg[:graph:]"
+ "CCCCCCCCCCC{79,}{138,191}][:di(git:]u]@]"
+ "JJJJJJJJJJJJJJJJJJJJJJJ[:graph:(\?:][:alnum:]])[:alnum:])]",
+ 0, 0 },
+ { "[^(((BBBBBBBBBB(\?>BBBZvvvvvvvvvv(\?m(sximsx:vvv)iiiiiiii)))"
+ "j>Rs:Sm]0MMMMMMMMMMM|@F)Y]*^#EEEEEEE)*",
+ 0, 0 },
+ { "([^([(U(\?!)<<<<<<<<<<(\?#<<<<(\?<!<<<)(\?=L.{73,})+]n9U}fk%"
+ "Jn}'b Na<%yyyyyyyyyyyy)){-198,}]))[:space:].pP361U]3s@u_9AU "
+ "Te/{s`6=IMZdL1|.ySRo",
+ 1, 0 },
+ { "[[((\?<=\?>(\?#){}]{}`){1,82}){-143[,}]^G", 0, 0 },
+ { "[:digit:]W|[:up(\?<!perword:]{,-101}llllllllllllllllll[:"
+ "upperword:])mmYYYYYYYYYYYYYYYYYYYYYYY*",
+ 0, 0 },
+ { "@NHy)", 0, 0 },
+ { "([^[^]][:alnum:]222[^22222222(\?{2222222222222222][:lo(\?:"
+ "werprint:][:xdigit:]^[:blankcntrl:]s+N)[:alpha:]-"
+ "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNWxxxxxxxxxxxxxxxxxxxxxxxxxxD["
+ ":space:]U)TTTTTTTTTTfffffffffffzzzzzzzzzzzzzzzzzzzzzzzzz})",
+ 1, 0 },
+ { "[^[^[[^[][^[]pP([^\?[^<=(\?=]){158,})]]]][:digit:]]"
+ "K22222222222p^dUKJ`\">@]",
+ 1, 0 },
+ { "[^[^[(\?imsximsx::p(\?{unct:][(\?>:ascii:]5w)]{159}\\Q\?@C]"
+ "4(44444444}[^)|)[:graph:]]C:b)",
+ 1, 0 },
+ { "[^[[(tYri[W<8%1(\?='yt][:lowerprint:[]))1r]][:alnum:][:"
+ "digit:]{48}{-52,-183}+][:alpha:]r][:upperword:]\?{-105,155}{"
+ "-55,-87}pPN#############################{63,232}]",
+ 0, 0 },
+ { "[*(\?>L(\?<(\?>=))]&&&&&&&(&&&&&&&&&&&&&&&&&&))[|WIX]{-62,-"
+ "114}S K=HW60XE<2+W",
+ 1, 0 },
+ { "(00000000000)z\\\\*t{}R{88}[:alnum:]*", 1, 0 },
+ { "(([^(\?=\?gggggg[gLw)]{-250,}[:xdigit:]yZ[:g(raph:]8QNr[:"
+ "space:][:blankcntrl:]A)][:digit:]D)[:xdigit:])",
+ 2, 0 },
+ { "[^([^,(\?<!]*))]", 0, 0 },
+ { "[^(\?{[:alnum:]]}}}}}}}}}}}}}}}}}}}}}}}){-83}", 0, 0 },
+ { "WWWWWWWW[:alnum(\?<=(\?#:]{,-1})@OSSS)[:digit:]", 0, 0 },
+ { "[^(\?!*]+G)", 0, 0 },
+ { "[LLLLLLLLLLLLLLLLLLLLLLLLLLLLLL>s8.>[^{}$(\?(]]XXXXXXX)"
+ "XXXXXXXXXXXXXX[:alpha:]Whii\?p[:xdigit:])+",
+ 0, 0 },
+ { "(7777[:blankcntrl:])", 1, 0 },
+ { "[^C[:digit:]]{}YYYY(YYYYYYYYYYYYYYYY)", 1, 0 },
+ { "on|,#tve%F(w-::::::::::::::::::::::::::::*=->)", 1, 0 },
+ { "([((\?=(\?!((\?=')))27(<{})S-vvvvvvvvvv(\?="
+ "vvvvvvvvvvvvvvvvv[:punct:][:alnum:]}}}}}}}}}}}}}}}}}}}}}}}"
+ "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPgggggggggggggggggggggggggg(\?#("
+ "\?#gggggg<X){}]{-164,61})>+))uQ)W>[:punct:][:xdigit:][:"
+ "digit:][:punct:]{}[:digit:][:space:]){,-105}=xiAyf}o[:alpha:"
+ "]akZSYK+sl{",
+ 1, 0 },
+ { "[^[^]/S:Hq<[:upperword:(\?<=]W[:alnum:]X])1973", 0, 0 },
+ { "[[^[[^([^VVVV(\?!(VVVVVVVVVVVVVVVVVVVVV[VVVVX][^]2))"
+ "98ppppppppppppppppppppppppppppppp/////////////////////"
+ "b.]G{-101,}[:[ascii:]P].=~])AAAAAAAAAAAAA2{-153,}]]]]]]]]]]]"
+ "]]]]]]]]]]]]]]]]]]]]][:alnum:][:lowerprint:]WN/"
+ "D!rD]|4444{180}]V_@3lW#lat]",
+ 0, 0 },
+ { "[^[^([^TTTTT(\?:T(\?:T7777{,59}])[:graph:][:ascii(\?<=:]))f]"
+ "AD{,-43}%%%%%%%%%%%%%%%%)S|[:digit:]FZm<[:blankcntrl:]QT&xj*"
+ "{-114,}$[:xdigit:]042][:xdig[it:]{-180}027[:alpha:][:ascii:]"
+ "[:lowerprint:][:xdigit:]^|[:alnum:][^Mi]z!suQ{-44,-32}[:"
+ "digit:]]",
+ 0, 0 },
+ { ")", 0, 0 },
+ { "''''''''''[:a(\?imsxisx:lnum:])P", 0, 0 },
+ { "(([{20(\?<=8}[:alnum:]pP$`(\?#N)wRH[:graph:]aaaaaaaaaaaaaa("
+ "\?=aaaaaaaaaaaaaaaaP]a)))[:punct:]-\?)A^",
+ 2, 0 },
+ { "[^(.//"
+ "[:punct:]&-333333333333333333333333333(\?<!33)"
+ "LLLLLLLLLLLLLLLLL[:alnum:]$1]~8]|^\"A[:xdigit:]\?[:ascii:]{"
+ "128,}{,-74}[:graph:]{157}3N){-196,184}D",
+ 0, 0 },
+ { "[^($(\?{(\?<=)[#)]})[:space:]]nWML0D{}", 0, 0 },
+ { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,[^]x{213,-93}(\?{A7]V{}})", 0,
+ 0 },
+ { "[k(\?=*)+^[f(])r_H6", 0, 0 },
+ { "[(\?#(\?{)]q})", 0, 0 },
+ { "([GLLLLLLLLLL(\?!((\?:LLLLLLLL]))C#T$Y))^|>W90DDDDDDDDDDD[^"
+ "DDDDDDDDDDDDDDDDDDDD]B[:punct:]c/",
+ 1, 0 },
+ { "[^(\?<!)(\?{b}){,199}A[:space:]+++++++(\?!++++++++{36}Tn])",
+ 0, 0 },
+ { "()[:alpha:]a", 1, 0 },
+ { "[(\?(:blan)kcntrl:])lUUUUUUUUUUUUUUUUUUUUUUU", 0, 0 },
+ { "[^[^(s[[[[[[[[[[[[[[(\?#[[[[[[[)\?`````][:blankcntrl:(\?>]|)"
+ "p1EmmmmmmmmmmmmmmmmmmmmmmmmmmmmL{-241}666666666666666666666)"
+ "]^bLDDDDDDDDDDDDD]",
+ 0, 0 },
+ { "[nn(\?<!nnnnn(\?#n8)=````````````````````{41,}]U,cb*%Y[:"
+ "graph:]).[:alnum:]\\\\\\\\\\gt",
+ 0, 0 },
+ { "()\?5{,-195}lm*Ga[:space:]Y", 1, 0 },
+ { "[(\?:].di)c", 0, 0 },
+ { "([([^([\?{})Za,$S(\?!p(\?{++(\?##V(\?<!Evuil.2(\?<![^[h|[^']"
+ "C)*\"]5]",
+ 1, 0 },
+ { "[((^24(\?#4[^Kkj{}))]]{232}47)077[:alpha:]zzzzzzzz{}", 0,
+ 0 },
+ { "[^(\?:[^F]o$h)-iV%]", 0, 0 },
+ { "[[^[([((([^(\?{[^((\?=)kaSx(\?imsximsx:w3A[`%+A$I{,62}ns&Y!#"
+ "ay "
+ "o9YAo{Y>1((\?>\?#45)Z{,108}{}11111111111111111111111111qqqq)"
+ "\?][:lowerprint:]mbo#)@",
+ 0, 0 },
+ { "[^iii8(888888(\?<!8^]))s", 0, 0 },
+ { "([[(\?(\?:({^]}[)[(r)])G]{,-87}", 1, 0 },
+ { "([[^{249,}(\?>(\?=)]]T()[:bl(\?!ankcntrl:]=jjjjjjjjjjjjjjjj-"
+ ")))t{}[:alpha:]-\":i! Gn[A4Ym7<<<<<<<<<<<<<<<<]",
+ 2, 0 },
+ { "^{}{[^,241(\?#}(\?m(\?ixim:sximsx:]t))+oD)", 0, 0 },
+ { "5[(\?#:xdigit:])", 0, 0 },
+ { "[^f{(\?>,22(9}[^[^])6KKKKKKKKKKKKK)]RRRRRRRRfuK99999999C}"
+ "osnNR]BgCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC[:blankcntrl:]",
+ 0, 0 },
+ { "[^(\?=U){24,}W-{,17(\?:3[^}]q.nQ#PU_|i$$$$$$$$$$$$$$+)[:dig("
+ "\?<!it:]){-98}\?[:upperword:]]",
+ -1, 0 },
+ { "[(\?<=[0(\?!72])euE.]{,-159}[:alnum:]t-:l\?)$"
+ "yyyyyyyyyyyyyyyyyyyyyyyyyyfffffffffffffffffffffffffff",
+ 0, 0 },
+ { "[^[^]q[:asc(\?imsxmsx:ii:]JJJJJJJJJJJJJJJJJJJJ[:graph:]]$)`#"
+ "DdY^qqqqqqqqqqqqqqqqqqqqqqqqqqqu>4^4ta[:alpha:]",
+ 0, 0 },
+ { "(((b0HN)q))p5<T())`7JJv{'cv'#L8BNz", 4, 0 },
+ { "[pFp2VttBg(\?<=7777777777777|TTTTTTTTTTTTTTT[:space:]Z]^p\"["
+ ":blankcntrl:])",
+ 0, 0 },
+ { ")aM@@@@@@@@@@@@@", 0, 0 },
+ { "([^[(\?<![^])", 1, 0 },
+ { "()Z[:ascii:]", 1, 0 },
+ { "(fuPPo)..........................[:xdigit:]{}{,4}*kkkkkkkCx#"
+ ",_=&~)|.2x",
+ 1, 0 },
+ { "[+(\?<=){}++++++[:alnum:](\?=+]s)[:alnum:]~~~~~~"
+ "XXXXXXXXXXXXXXX.[:digit:]",
+ 0, 0 },
+ { "[{}[^^(\?(]))CCCCCCCCCCCCCCCCCCCCEg2cF]{}3", 0, 0 },
+ { "([[[^[^[^([[^[^([(\?<=G[[)=(\?!===(\?isximsx:==(\?#==[^====="
+ "(\?{==================$T[[^^u_TiC.Fo.02>X)uH]$})354b[:alnum:"
+ "]]]EVVVVVVVVVVVVVVVVVVVVVVVVVVVVVz[:digi(\?(t:][:upperword:]"
+ ")",
+ 1, 0 },
+ { "([:blankcntrl:]t-){121,}[:ascii:]444444{}[:graph:]E040", 1,
+ 0 },
+ { "[^{134,}]DzQ\?{-30,191})z,\?1Vfq!z}cgv)ERK)1T/=f\?>'", 0,
+ 0 },
+ { "@v)<yN]'l-/"
+ "KKKKKKKBBBBBBBBBBBBBMa2eLA[:digit(\?<!:])\"\"e|l$&m`_yn[:"
+ "blankcntrl:]uuuuuuuuuuuuuuuuuuu[:punct:]",
+ 0, 0 },
+ { "[[999999999999999(\?<=(\?:(\?ixmx:(\?>))])Y]|){,10}\?{}", 0,
+ 0 },
+ { "([[[(\?!^]P-AA[AAAAAA[A[^A)r]+B]])", 1, 0 },
+ { "3}|[:ascii:][:punct:]()", 1, 0 },
+ { "()dw", 1, 0 },
+ { "[N]{})))))))))))))))))))))))", 0, 0 },
+ { "[[[^([[(\?()(\?#)++([^\?{+++[^+++++++++++(\?!+(\?=+++++++r9/"
+ "n]N7{-219}{-91}pP[:punct:]T]mROm+~[:digit:][:digit:])Y:",
+ 0, 0 },
+ { "[^'Pu[(\?<!D&]_a[:alnum:]E<,F%4&[:xdigit:])][:lowerprint:]",
+ 0, 0 },
+ { "tttt(tttttttttt*uKKUUUUU)", 1, 0 },
+ { "([:ascii:]GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG)+kX______________{"
+ "}GGGGG\?TUH3,{67,77}|[:graph:]C{,-136}{}[:upperword:[]{,-6}&"
+ "]T84]n={C",
+ 1, 0 },
+ { "[:upperword:]DC[:u(\?<=pperword:]*d`H0\?m>~\?N|z#Ar--SO{,-"
+ "141}076)G\?{,-110}M+-[:alpha:]",
+ 0, 0 },
+ { "{,-214}{,10(9})", 1, 0 },
+ { "([^xxxxxxxxxxxxxxxxxMMMMMMMMMMMMMMXW])].[:punct:]Q`{-63,63}"
+ "Uua[:alnum:]\?OQssb#L@@@@@@@@(@@@)[:graph:]",
+ 2, 0 },
+ { "[[^(\?!```[^``````````````(\?<=``(\?>````````M/////(\?!/////"
+ "///////////////"
+ "[^GD!|#li]~)*.$]))Tq!]C[:lowerprint:]Qk[{}]]"
+ "JJJJJJJJJJJJJJJJJJJJJJJ{e])c",
+ 0, 0 },
+ { "$[5(7ES])[:xdigit:]%{MRMtYD&aS&g6jp&ghJ@:!I~4%{"
+ "P\?0vvvvvvvvvvvvvvvvvvvv\\\\\\\\\\\\\\\\\\\\\\\\x54[:"
+ "lowerprint:][:upperword:]",
+ 0, 0 },
+ { "[((([(\?((\?>[:alnum:][):as(\?<!cii:(\?:]Re))K|)|^){-28,89}"
+ "l<H.<H:N)QKuuuuuuuuw8E136P)^)[:ascii:]][:xdigit:]-",
+ 0, 0 },
+ { "(pjvA'x]=D\"qUby\\+'R)r\?C22[:ascii:]", 1, 0 },
+ { "[]*b~y C=#P\"6(gD%#-[^FBt{}]${-244}", 0, 0 },
+ { "[:up(\?!pe(\?=rword:])lA-'yb\"Xk|K_V\"/"
+ "@}:&zUA-)W#{-178,-142}(){-202,}",
+ 1, 0 },
+ { "()1.WldRA-!!!!!!!!!!!!!!!!!", 1, 0 },
+ { "lZZZZZZZZZZZZZZZ(Z[:al(\?:num:])"
+ "ttttttttttttttttttttttttttttttg.)6$yyy",
+ 1, 0 },
+ { "[([^([^[^(([([^[^(([[$(\?{P(\?=(\?<(\?!=(\?#P[^Y])<GA[:"
+ "ascii:][(\?#(\?<!:alpha:](B{100,})]}))\?)XU=",
+ 1, 0 },
+ { "[[dVw{6(\?{9,}2222kkkkkkkkkkkkkkkkkkkkkkkkkk|{}*E]]{}SB{35}-"
+ "w%{eh})<{-178,}",
+ 0, 0 },
+ { "(D(~))", 2, 0 },
+ { "[(:alpha:]{,90}Z|)[:ascii:]Du\?[:grap[^h:]^w+|{}][:ascii:]",
+ 0, 0 },
+ { "[:p(\?<=unct:]kkkkkkkkkkkkkkkkkkkk)", 0, 0 },
+ { "{}[:((\?<!dig((\?#it(\?#:]())p))ZZZZZZZZZZ[:blankcntrl:]){}{"
+ "-124,})[:ascii:]",
+ 1, 0 },
+ { "[[:graph:]{168}lRRRRRRRRRRRRR(\?#RRRRRRRRRRRRRRRRR)rrrr(\?("
+ "rrrrrr)rrrrrrrS[(\?<!@f)6>{,-49})q${98,}J\?]){",
+ 0, 0 },
+ { "([:pu(\?(nc)t:]F{-32,-102}+)\?cpP[:lowerprint:].^)", 1, 0 },
+ { "([{}{210,-238}]1:h)", 1, 0 },
+ { "([]QQQQ[QQQQQQQQQQQQQQQQQQ][:digit:]Z{-20,}Slllllll[:space:]"
+ "C^(@{-174,-156}fx{cf2c}{-242,}rBBBBBBBBBBBBBBBBBBc[:alpha:]"
+ "N\?))$[:graph:][:ascii:]P+nnnnnnnnnnnnnnnnnnnnnnn1N$r>>>>>>>"
+ ">>>>>>>>>>>>>>>>>(>>{,88}{,-234}__________)[:upperword:]R.[:"
+ "alnum:][:lowerprint:]^}\"",
+ 3, 0 },
+ { "([^(\?=]-))$", 1, 0 },
+ { "([:ascii:]\?,D[:upperword:][:xdigit:]tttttttttttt[^tt(\?<!"
+ "ttttttttt21f|.(pP[:punct:])])rrrrrrrr)",
+ 1, 0 },
+ { "([{1(\?=16}iiiiiiiiii((\?<=iiiiiiiiiiiiiiiiii|ZZZZZZZZZZZ("
+ "\?(\?#{ZZZZZZZ))c}))<<<<<(\?#<<<<<<<<<<<d7CVq8]w{-148,-168}"
+ "\\Gp){-230,}D3",
+ 1, 0 },
+ { "[^8888(88888888888EX].[:alnum:]){}", 0, 0 },
+ { "([^][^)2]-[:lower(\?=print:]{,79}[:graph:]n)", 1, 0 },
+ { "[bSi\?x_mp(C)0{64}[:space:]hhh(\?(hhh)hhL){5,130}'w\"$l&[:"
+ "xdigit:][:alpha:]IIIIIIIIIIIIIIIIIIIIIII+-SOOOOOOOOOOOO "
+ " (\?( ) ]f)ed",
+ 0, 0 },
+ { "[[^[(^(C.Jl[^X&Rb64a+Sd])'m[:alpha:])]]]{134,}", 0, 0 },
+ { "()L", 1, 0 },
+ { "[[(({224,(\?#88})@======(\?!=========(\?{=)PPP)i^@p(\?([:"
+ "punct:]})^^[^^^^^^^^^^^^^^^^^^^^^@)m]|{CS{,-3}168)-[:xdigit:"
+ "][:upperword:]hnD=Bns)z)AAAAAAAAAAAAAAAAAAAAAAA[^A{}"
+ "ccccccccccc)SZ]Q-p.sD]]+P",
+ 0, 0 },
+ { "[[^[^]{135,}66666666666666666666[6(666i2M9.!uhmT\?JMm.*(\?!+"
+ ")[:alpha:]eeeeeeeeeeeeeeeeeeeeeeeeeee]]])ZZ[:blankcntrl:][:"
+ "ascii:]",
+ 0, 0 },
+ { "(13[3Ux>{,10}[(\?<=:xdigit:]))PL9{-89,-181}F'''''''''", 1,
+ 0 },
+ { "[^.|(\?{af]})^$XE!$", 0, 0 },
+ { "(WWWWWWWWWWWWWWWWWWWWWWWWWWWW#J)", 1, 0 },
+ { "({}}M7we-216)L[:digit:][:upperword:]", 1, 0 },
+ { "([:aln[^u(\?=m:]))].z", 1, 0 },
+ { "([:alpha:]{(92})%6{41,136})Vij@[:alnum:][:lowerprint:]", 2,
+ 0 },
+ { "[[[++(\?{+++{}})n{{137,}{51,-177}Z[]M*[:ascii:]{(-29,-47}2)$"
+ "e^{,-195}{-156,}^]{}{-225,69}A]{-222,}{,20}m[:blankcntrl:]",
+ 1, 0 },
+ { ")l)[:alnum:][:graph:]g8TTTTTTTTTTTTTTTTLLLLLLLLLLLLLLLLL", 0,
+ 0 },
+ { "[([(\?<=.(\?{)/})mmmmmmmm(\?(mmmmm]{-154,-176}*S)I]", 0, 0 },
+ { "[(([{(\?(\?<!im(\?imsix:sim(sx:,141}])D)l{,42}ttttt[(\?::"
+ "punct:])){-162,-141}{-26,})dU@@@@@@@@@@@@@@@ "
+ "S)\\A\?w|VVVVVVVVV)X.kN{,21}{-208,-52}>[:lowerprint:][:"
+ "ascii:]e-]]]]]]]]]]]]]]]]]]]]]",
+ 0, 0 },
+ { "[^({}(){(66(\?=,}[^]'''''QQQQQQQQQ).P#>^){86,168}Z[(\?<!:"
+ "lowerprint:]{-166,-70}<k",
+ 0, 0 },
+ { "APP[:alpha:][:alnum:]nd[:upperword:(\?(]^"
+ "xxxxxxxxxxxxxxxxxxx)xxxxxxxxx{-70}[:punct:]l)U-",
+ 0, 0 },
+ { "[^(.\"od~(6({[^(\?<!228}\?)\?)######(\?:#########z "
+ ")c(\?<!aQ`(\?{UKSwu[})][^-17]{11,}}][:ascii:]))^RiH+WyspP["
+ "qi&)=p6])[:space:]{-221,}]6p",
+ 0, 0 },
+ { "{-78}()[:xdigit:]{155}{,-92}", 1, 0 },
+ { "[(\?>Q{,147}_____________(\?!______uuuuuuuuuuuuuTr]){74,179}"
+ "{}){,103}{-209,16}*RRRRRRRRRRRRRRRRw{,87}9{144}[:ascii:]'<"
+ "Ab",
+ 0, 0 },
+ { "([666c] {-171}yc,8-k_)EEEEEEEEEEEEEEEEEEEEE<", 1, 0 },
+ { "[^(\?>(\?<!)2(\?imim:)6HwN)^|fc!(\?(d]75))065)G", 0, 0 },
+ { "[[^xDB[:alnum:][:xdigit:]][:digit:]jW]([:alpha:])", 1, 0 },
+ { "[ds~T+[x55[:digit:]X[JJJJJJJ.[(\?::upperword:]){,-14}][:"
+ "xdigit:]bbbbbbbbbbb",
+ 0, 0 },
+ { "[qqqqq(\?<=qqqq(\?(qqq)^G[):ascii:]])W", 0, 0 },
+ { "[:space:]JJJJJJ[:alph(\?<!a:]|[:ascii:(\?(])[:x)digit:]- "
+ "XSstG[:g(\?>raph:])^)Ny6RF_ndoU9@*rxW{4,41}4{}",
+ 0, 0 },
+ { "[:punct:]{162,}j[:aln(um:].....................[^...]\?>z[:"
+ "l[owerprint:]){55,222}]",
+ 0, 0 },
+ { "(>vWa)OXcccccccccccccccccccccccc[:alpha:]C{,-10}81|m1D^T)[:"
+ "lowerprint:]''''[:alpha:]l",
+ 1, 0 },
+ { "(XZcgM/UI-/"
+ "mZq-222){-85,-196}[:alpha:]{114}rrrrrrrrrrrrrrrrrrrrrrrr{,"
+ "157}ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZLkD-&&&&&&&&&&&&&&&-][:"
+ "alnum:]{}{,111}[:digit:]",
+ 1, 0 },
+ { "[^(\?:]MMMMMMMMMMMMMMMMMMMMMMMMMMM)cK["
+ "KKKKKKKKKKKKKKKKKKKKKKKK]P{146}",
+ 0, 0 },
+ { "([^[^wqesa)n\?L(\?<=FH+G[^rCGmfD]w)m1D\"%}]])", 1, 0 },
+ { "[((\?:[^.HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH|S)xd)*[:space:](])["
+ ":xdigit:]ngr'G#/B]-----------------------------",
+ 0, 0 },
+ { ")[:lowerprint(\?<=(:]l))G p", 0, 0 },
+ { "[^[^(\?<(\?<(=(\?imsximx:![(((\?<!\?(^))\?]^)[:xdigit:][:"
+ "graph:]{-104,})Gf+GD*qc)c]f))])",
+ 0, 0 },
+ { "[^([\?())P[:alnum:]w]{-186,-139}-[:space:]RN3w[Fmvpl[:space:"
+ "][:digit:]&&&&&&&&&&&&}(\?#}}}}}}}}}}}}}}}}}}}])z",
+ 0, 0 },
+ { "([[^^*C[()f][(\?=:punct([\?#:]o)]V)]%%%%%%%%%%%%%%%%%%%%%%%%"
+ "%%%%%%[^x{1f948})]]",
+ 1, 0 },
+ { "[(:xdigit:])zE", 0, 0 },
+ { "[:pu(\?(nc)t:])(a*){-51}", 1, 0 },
+ { "[^(.NKKKKKKKKKKKKKKKKKKKKKKKK-[:upperword:][:space:]`MPi>",
+ -1, 0 },
+ { "Nvvv[vv.][:alnu[^m:]+|Crrrrrrrrrrrrrrrrrrrrr[:xdigit:]j1n)v#"
+ "]",
+ 0, 0 },
+ { "[^#}[(\?>:alnum:]).QQQQ[^QQQQQQ!!![!!!!!!!-s.n]se]{-238,}Tf]"
+ "p4721",
+ 0, 0 },
+ { "([((\?#\?<=)+)Hr:-H]z[:graph:].{}oooooo(ooooooooo][:punct:]"
+ "k<gXG@@@@@@@@@@@@@@@@@@@{,-176}){}L`)$",
+ 2, 0 },
+ { "({,249}{-73,}Z&&&&&&&&Ds35MB<v)qqqqqqqqqqqqqqqqqqqqqqqqq", 1,
+ 0 },
+ { "[^.N][:blankcntrl:]))))))))))))))))))))))))))))))", 0, 0 },
+ { "(()*){198,}", 2, 0 },
+ { "{-237,}220{}[:ascii:]```````(`````````````\?{-115,185}){,-"
+ "18}[:punct:]'|Kk",
+ 1, 0 },
+ { "[(\?()])", 0, 0 },
+ { "([(\?#[:alnum:]CQ)}}}}}}}}(\?>}}}}}}}(}}}}}\?310[|))xA5r][[^"
+ ":ascii:]^{,-156}{])CCCCCCCCCCC-145]FzwOD_u\?",
+ 1, 0 },
+ { "[^[^[]{-163}{(-203}[(\?!:upperword:]PPGjZ[:xdi(\?=git(\?#:]{"
+ "-73}s)qqqq(qqqqqqqqqqqqqqqqqq{173,210}[:xdigit:(\?<(\?>=]WW["
+ "^WWWWWWW\?*O)))Q){}08)[(\?(\?<=#:blankcntrl:]{90,}]U)])L)"
+ "ooooooooooooooooooooooooooox--^c[:ascii:]])s)",
+ 2, 0 },
+ { "[(\?!:punc(\?imximx:t[^:]4F<}!)]'M-)tKKKa4904", 0, 0 },
+ { "[^^{}\\(\?<!\\\\\\\\\\\\\\\\\\(\?#\\\\\\\\[:punct:](\?>)"
+ "T000000000(\?(000)00000))+])",
+ 0, 0 },
+ { "L[:p(\?#unct:])", 0, 0 },
+ { "[:upperw(\?<!ord:])", 0, 0 },
+ { "@$\"\"\"\"\"\"\"[\"\"\"\"\"\"\"\"\"\"[^(\"\"\"\"\"(\"\"][]))"
+ "*U{223,138}*o```````````````(\?=[```````````````]{238}"
+ "mmmPPPPPPPPPPPPPPP&&&&&&&&&&&&&&&&&&)sF$[:digit:[]]",
+ 0, 0 },
+ { "[^#Txx[xxxlPB(\?><[^U/)]]{}X3333333333(3333333f*])", 1, 0 },
+ { "<<<<<<<<<<<<<<<[^<<<<<<<<<.][(\?#:ascii:])[:xdigit:]|^", 0,
+ 0 },
+ { "([:punct:]{}){-167,}{-59,}Pd\"", 1, 0 },
+ { "[((\?#{,214})t$)VVV[:xdigit:]{104(\?<=}D][:graph:])|H){1,}{-"
+ "176,}",
+ 0, 0 },
+ { "[[([[^N,,,,,(\?=,,(\?#(\?:,,,,,,,,,,,[^,,,,,,,,,,]<,~4::_.A]"
+ "){-52,}-[:alnum:]Pnnnnnnnnnnnnnnnnnn)d",
+ 0, 0 },
+ { "{-18(3,})uT{4,}", 1, 0 },
+ { "[^[^[(p+c(\?<!b$))(\?:EU(\?(.][^{}]3[:xdigi[^t):][:punct(\?>"
+ ":])[])][:s[^pace:]][:alnum:][:alpha:]]kw06E",
+ 0, 0 },
+ { "[^^^^^^JJJJJJJJ(JJ(\?=JJ(.6[:space:]H]{231,}A^eqqq)[:ascii:("
+ "\?>(])[(\?>:spa(\?:ce:]xxxxxxxxx)@_t-))"
+ "138GNNNNNNNNNNNNNNNNNNNNNNNNNN[:digit:]no!`#E\?&[:"
+ "lowerprint:].)[:graph:]{86,}[:digit:][:alnum:]",
+ 0, 0 },
+ { "[:g(\?<=raph:]a{114,146}(){}0Y[:bl(ankcntrl:])D)\?", 1, 0 },
+ { "[^[^]*H{-192,96}S|]G)6B-kLB", 0, 0 },
+ { "[[^[^][/"
+ "NS8`um(\?{82&{((\?{\?<!-[110,-88}]m)})kkkkkkkk$$$$$$$$$$$$[^"
+ "$$$$$@n%BuK@X!P)y0v!^]YY[YYY[YYYYYYYYYYYYYYYYYY///////"
+ "{}{{{{{{{{{{{{{oiiii})]8{-2[53}w{82,}]{,245}]{-134}]"
+ "fffffffffffffffffff]\"I>DW>9tN%{113}{unE",
+ 0, 0 },
+ { "[:(\?(alpha:]`))Y2sCqWQ104", 0, 0 },
+ { "(([^()Wcccccccc(\?{cccccccccccccccccc(\?<!c(ccccc[:space:]$)"
+ "(\?>)FZ{}{}`|||||||||||||*````````````````````````````'="
+ "dLQmx/"
+ "Y.A7j'o}jn{}:})][:punct:]$|,-)!&Y:Ys#"
+ "ykL7JJJJJJJJJJJJJJJJJJJJJJJJJ8yex>#mv[:punct:](x@)$[:uppe("
+ "\?<!rword:])_)",
+ 3, 0 },
+ { "[[(^HHHHHHHHHHHH(\?imsximx:HH(HHHHHH(\?{HH[HH])qjR>9))i})]a!"
+ "lBW3p{A=or)ShE%[:punct:]{}]5r",
+ 0, 0 },
+ { "[:pu[nc[^t:]]]}}}}}}}[}}}}}}}(\?#}])@@@@@@@@@@@@@@@@@@"
+ "DDDDDDDDDDDDDDDDDDD\?]xA2\?",
+ 0, 0 },
+ { "(.[:alpha:]xB7[:alnu(\?{m:]})RRRRRRRRRRRRRRRRRRRRRRRRRRRL)[:"
+ "space:]G\?",
+ 1, 0 },
+ { "[:blan(\?<!(\?=kcntrl:]){71,})!ooooooooooooN", 0, 0 },
+ { "()e$$$$$$$$$$$$$$$$$$$$iiiiiiii", 1, 0 },
+ { "(b[:ascii:]67777777777777777777777777)({-106}kkk^F----------"
+ "---------------------{13}A)f00000000sBAddddd{-66}kd!D'",
+ 2, 0 },
+ { "(Q ^])[^lf][:space:][:lowerprint:]\?",
+ 1, 0 },
+ { "[[^]\\S{152}W![:digit:][[^:space:(\?(]=pEhwY][:alnum:][:"
+ "digit):][:graph:]])QQIC9h-oowf[:xdigit:]{-52}{,190}"
+ "1111111111111111111fX{-189,226}W",
+ 0, 0 },
+ { "[^(\?!(\?<=)]).h[:as(\?>cii:])[:alnum:]$$$$$[:space:]3$$$$$$"
+ "$$$$$$$$$$$$$$$$$$$$$$$$$1",
+ 0, 0 },
+ { "[[$zQ================(\?<!=(\?>=========(\?====D[^))|i{}"
+ "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?)][:s(pace:]])"
+ ")]",
+ 0, 0 },
+ { "[^{,-[15(\?#6}]Vwjjjjjjjj[jjjjjjjjjjjjjjjjjjS9999)]q]"
+ "rWWWWWWWWWWWWWWWW[:punct:]@@@@@@@@@@@@@@@@@@@@@@@@gO[:"
+ "blankcntrl:]>L[:ascii:]:::::::::::::::::::"
+ "x11uuuuuuuuuuuuuuuuuuuuuuuuuuuuu{-124,114}[:graph:]C#{tcg[:"
+ "xdigit:]gZZZZ[:lowerprint:]nA(_{{{{{{{{{{{{{{{{{{{{SS)\\D[:"
+ "alpha:]",
+ 1, 0 },
+ { "[^(\?())]!T\?[:asc[^ii:]E:4},,]I[^b(\?:n4(njj~+{\?'k{7}{189,"
+ "-194}{ig.[[[[[[(\?#[[[_bs6,JD`1(\?<!WBo]F+{d*VO22z2K1][:"
+ "xdigit:]))Suuuuuuuuuuu[^u{,117}\?YYYYYYYYYYYYYYYYYYYYYYYYB^]"
+ "|q]:eY1GGGGGGGGGGGGGGGGGGGGGGGGGGGGe\?)bU[:punct:]",
+ 0, 0 },
+ { "[\?UA(\?:]\?)[:xdigit:]A^mmmmmmmmmmmmmm>>>>>>>>>>>>>>>>>>>>>"
+ ">>>>>>>[^>>>(\?(>)){,-165}]",
+ 0, 0 },
+ { "([^[][^n(\?{[[p]#})|][^]L|66666666666[:graph:]][:graph:]2[:"
+ "xdigit:][:space:]9b})[:digit(\?imsximsx::]+PZ):{}|E)[:"
+ "xdigit[^:]|>]^[:alpha:]::::::::[:ascii:]````[:ascii:]:",
+ 1, 0 },
+ { "[:lowerprint(\?<!:])", 0, 0 },
+ { "[[^[]{-47}[:lowerprint:][:punct:]L[(\?::g(raph:]lY[:alnum:])"
+ "qWYU)}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}[c%$dp5[:alnum:]DDDDDDDD^"
+ "^%&{,-94}E]{-8,175}[:alpha:]-.^[:digi(t:]CCCC(CCCCCCCCC])."
+ "ax72)",
+ 1, 0 },
+ { "[[^($$$$$$$$$$$$$$$$$$[^$I((\?{\?(u)\"YuK "
+ "ZpOHq[!(\?>t|LQT(|)L[(:ascii:])",
+ 0, 0 },
+ { "[^[^([:graph:](QpPdyDQ`[:alpha:](.X[:digit:]wwwwwwwwwwwwww("
+ "\?imxims:wwwwwwwe(\?<!z)ONNN(\?#)[^])[:space:](KKKKKKKKK{"
+ "113,}327[:xdigit:]k)]CeeeeeeeeeeeeeeeeeMMMMMMMMMMMMMMMMM)[:"
+ "lowerprint:]]HHHHHHHHHHHHHHHHHHH]]]]]]]]]]]]]",
+ 1, 0 },
+ { "[Q(r(\?=)v]dm[:alnum:][:b(\?{lankcntrl:][:xdigit(\?=:])})P[:"
+ "graph:]bd/Rx){50}{-150,-172}",
+ 0, 0 },
+ { "[(\?(im(\?:sxims:))9]))L", 0, 0 },
+ { "[[^[(\?{^Z][^0[:alpha:]]\\XB*{-151}t})][:alnum:]]", 0, 0 },
+ { "[([(D\?/////////////////////.'yvYysU&5AU-]kV)*){,123}z]", 0,
+ 0 },
+ { "[:alnu(\?{m:][:a(\?=lpha:][:alpha:])n}))7[:ascii:][:xdigit:]"
+ "[:punct:]-",
+ 0, 0 },
+ { "[^[:graph:]IIIIIIIIIIIIIIIIIIIIIII][:sp(\?<!ace:])", 0, 0 },
+ { "[[[(\?=[[[cDD(\?<!D(\?:DDDDDDDDDDDD(\?<=DDD(DDDDDD(\?:"
+ "DDDDDDD(\?<=D(\?()])rvp{243,}D$<[:space:]([:lowerpr)int:])])"
+ "Ea{}U[:upperword:][:xdigit(\?#:]or}Z+34gD{/P NJ",
+ 1, 0 },
+ { "[^(,H>)*d2K0DNX5)T(].)[:digit:].", 0, 0 },
+ { "([:punct:(\?#])})JJJJJJJJ[:xdigit:]"
+ "PPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU.......................0hSk{"
+ ",89}[:xdigit:].[:xdigit:]Z",
+ 1, 0 },
+ { "(LGTTTTTTTTTTTTTTTTTTTTTTTTTT[:alpha:]){-106,113}[:punct:]d|"
+ "[:digit:]kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk\?wP",
+ 1, 0 },
+ { "([^[^<N_-k\?{(\?#18}]i]::::::::::::::::::::::::::)1+LLLLn{}/"
+ "){-198}",
+ 1, 0 },
+ { "([[^(AAAAAAAAAA(\?(AAAAA)AAAAf).LzHHHHHHHHHHHHHHHHHHHHH(\?#"
+ "HHHHH|)[ZEEEEE(\?#EEEEEEEEE(\?<!EEEEEEEEsG)q[:punct:]{}][:"
+ "upperword:]D)[:space:][:digit:]+e[:ascii:]].i|JJJJJJJJ+n][:"
+ "xdigit:]Se)P[:lowerprint:]_______________________________.[:"
+ "punct:]pP{-172,86}iiiiiiiiiiiiiiiiiiiiiiiii){,-178}",
+ 1, 0 },
+ { "([\?=[[^,BDRRPZ{129}*D-[:punct:]]])([:upperword:]ud)\?][:"
+ "punct:]A",
+ -1, 0 },
+ { "(([(\?#((\?{\?=^])c-)C[:lowerprint:]xvkR}k\")"
+ "ccccccccccccccccccccNNNNNNN[:alp[ha:]{,93}vhlX:|A]2})nSw)]"
+ "N.",
+ 2, 0 },
+ { "()g/qzyiV(x3d|A0wllllll){162}[:space:]", 2, 0 },
+ { "qqqqqqqqqqqqqqqqqqqqvvvvvvvvvvvv8[:x(\?imsxmsx:digit:][:"
+ "alpha:]''''''''''''''''''''''''''')",
+ 0, 0 },
+ { "({,226}nf^W=vs$xK^=A=M#b,)V", 1, 0 },
+ { "(_T 2BC9N'cccccccccc-87EF#&^eQfDDDn._,m&c`tjAwR "
+ "#~A)[:(\?imsimx:alpha:])/yHYL6|{-40,47}",
+ 1, 0 },
+ { "[[^]{-8(4,138})z[:xdigit:]{180,}]", 1, 0 },
+ { "[([^T____________________(\?:__C(\?<=]-)])+[:ascii:])r[:"
+ "graph:].----------",
+ 0, 0 },
+ { "[f{}LLLL(LLp((((\?<!((((((((((((((({,56}]BR`{,52}){-22,}\?[:"
+ "space:]h>Sow",
+ 0, 0 },
+ { "{-179}^[:alpha:(\?!].a'5wacA3\\\\\\\\AAAAAAAA)~^]wC", 0, 0 },
+ { ">[:digit:]{,-212}+(`)LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL[:ascii:"
+ "][:digit:][:space:]",
+ 1, 0 },
+ { "[[^[[^RBW{,255(}(\?(\?>=(W)_]uu][:blankcntrl:])O)]]", 0, 0 },
+ { "(C_______________________________)2", 1, 0 },
+ { "([/ntf_a3].)", 1, 0 },
+ { "[:space:]+[(:upperword:],c7[:asci(\?<=i:]ggggggggggg)[:"
+ "ascii:]/1$$$$$$$$$$$$$$$$$$$$$$$$$$)",
+ 0, 0 },
+ { "Xq{109}~EEEEEEEE[:upper[^word:]lgB:X(h[:alpha:]B[:space:]].)"
+ "IkaH@3}}H'yK~\?[:upperw(\?#ord:(\?:]){=================[:"
+ "blankcntrl:])",
+ 1, 0 },
+ { "(([[^]]$3Xr^$%%%%%%%%%%%%%%%%%%%%%================U[:ascii:]"
+ ")X).FFFFFFFFFFgO[:punct:]oooooooooooooooooooBC[:blankcntrl:]"
+ "mmmmmmmmmmmmmmmmmmmm[:lowerprint:]rBM~<HAc#Sb&&&&&&&&&&&&&&&"
+ "&&&&&&&&&&&&&&Cy",
+ 2, 0 },
+ { "([([([^(\?:)D]-{M#H "
+ ">rERRRRRRR[^RRRRR(\?>RRRRR])[(\?=^)X]{207,}U])))Z[:"
+ "blankcntrl:]]yyyyyyyyyyyyyyyy\?",
+ 1, 0 },
+ { "[Q(\?{*[^(\?(\?!!])[:graph:]]})[:alnum:]iE)dGGGGGGG[^"
+ "GGGGGGGGGG[:xdigit:]w]",
+ 0, 0 },
+ { "[^Z(\?!6(\?(\?><=)[:graph:])]BBBBBBBBBBBBBBBB^)", 0, 0 },
+ { "[[^([^[^][[[[[[[(\?({[[(\?(\?imsxmsx(\?imsi[ms:::[[[[[[[[[})"
+ ")]$)){12,})|:::::::::::::::::::[:lowerprint:]{}{-96,-147}){"
+ "13,}`[:digit:]]\"^Ca%%%%%%%%%%%%%%%%%%%%%%%%%%"
+ "UUUUUUUUUUUUUUUUUU]]9",
+ 0, 0 },
+ { "[^(\?(\?(\?#!<=))JLBS\"zi)'''''''''''['''''''''''''"
+ "piiiiiiiiiiiii(\?<=iiii]])ZZZZZZZZZZZZZZZZZZ[:space:]",
+ 0, 0 },
+ { "({})[:punct:]", 1, 0 },
+ { "E9[:blankc(\?{ntrl:]})N", 0, 0 },
+ { "[:alph(\?#a:]){198,}sq\?X0B7", 0, 0 },
+ { "[^\\\\\\\\(\\\\\\[\\\\\\\\\\\\[(\?<(\?isximsx:={11(\?(9,}"
+ "\?0])]]))\?FN3M\?{-128,}Z444444)444fbLiVN8)",
+ 0, 0 },
+ { "[[^[^([[[[[[[[[(\?>[[[[[[[[[[[[[[[[[[[[[{53(\?<=,-175(\?>}"
+ "ggggggggggggggggg%))[:alnum:])[:punct:]"
+ "kkkkkkkkkkkkkkkkkkkkkkkkk)+"
+ "Soooooooooooooooooooooooooooooooo](WR+--)x36+llllllllllll{,"
+ "35}]Fqb^=F]KKKKKKaaaaa{,131}",
+ 1, 0 },
+ { "(g\"Ssqw<&{Cl{82,}Mdf|9cIlmCW{}[:digit:]4C{}[:alnum:]PP)", 1,
+ 0 },
+ { "OOOOOOOU[*evVIIIIIIIIIIIIIIIII(\?#(\?#IIII)]PP[:xdigit:]"
+ "2222222222222222[:xdigit:]Kx)p[:digit:]",
+ 0, 0 },
+ { "([[{248,16(\?=5(\?#}][:alpha:])|[:p(\?!unct:(\?(]", 1, 0 },
+ { "[pP((\?=S)(\?#)]$[:aln(\?(um:)]2\?)$GGGGGGGGGGGGGGGGG({-U:c)"
+ "{-61,}[:ascii:]{-202}G",
+ 1, 0 },
+ { "()$D[:alnum:]", 1, 0 },
+ { "[(\?#^]){}[:ascii:]", 0, 0 },
+ { "[uuuuuuuuuuuuuuuuuuuuuuuuuuuuuu]FFFFFFFFFFFFFFFFFFFFFF&2e\?)"
+ "%oP'mc@z2b}n{<b4_Laz^0LLLLLLLLLLLLLLLLLLLLLLL,,,d",
+ 0, 0 },
+ { "{}(^________________''|$)RRRRRRRRRRRRRRRRRRR", 1, 0 },
+ { "(H)####################bbbbbbbbbbbbbbbbVSSSSSSSSSSS|"
+ "tdU\"goeAbPP{-248,81}",
+ 1, 0 },
+ { "[^[(\?ims(\?>xisx:)UHpP*n{}]{}fx14<7OEpE>n2150)"
+ "8888888888888888]^GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGS",
+ 0, 0 },
+ { "(d)+", 1, 0 },
+ { "[^.(\?(>)(\?=e)])al[:space:]x", 0, 0 },
+ { "[^256c(\?!]){-19,}", 0, 0 },
+ { "Q)", 0, 0 },
+ { "[^s\?\?(\?{\?\?\?(\?#\?(\?<!\?\?\?\?\?\?\?\?\?\?\?("
+ "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?{}]F\?j(jjjjjjjjjjjjjjjjjjn)"
+ "kTI1f[{1|(\?<=^[^+[:digit:]{}^s^))})))T]{-17}{CCCCCCCCCCa{-"
+ "21,}{,-146}^uZQB]YuLu-|tUGRMz^^",
+ 1, 0 },
+ { "([^.{}.EE[EEEEEEEE(\?<=EEEEEEEEEEEEEEEU]]-@s))$", 1, 0 },
+ { "[^([((\?#[#])|a)])[cccccccccccccccc][:digit:]LLLLLLL[:alnum:"
+ "]}[P%vzl{}^]&",
+ 0, 0 },
+ { "({}[:space:]E)101+A{-35,11}", 1, 0 },
+ { "(va:7)u[:alpha:]", 1, 0 },
+ { "([^[[rrrrrrrrrr(\?:rrrrrrrrrr(\?<!rrrrrrrrry|D'*AH@a{}\?[:"
+ "space:][:alpha:]^]$ "
+ "{-225}[(\?(:as)(\?(>cii:])){-107,-139}6/"
+ "{^[:upperw(\?imsxmsx:ord:]{,-47} "
+ "]wuH#nAn)GGGGGGGGGGGGGGGGGr[)]T{91}lJ))[:lowerprint:][:"
+ "xdigit:][:lowerprint:])]*",
+ 1, 0 },
+ { "()[:space:]~!$[:alnum:]JJJJ[:ascii:]", 1, 0 },
+ { "[^(\?<=)-]()k", 1, 0 },
+ { "(()W){,8}ea", 2, 0 },
+ { "({,-56}5G&&&&rrrrrrrrrrrrrrrrrrrrrrrrrrk.8) hWJ,TM)0Yd-", 1,
+ 0 },
+ { "(Z-fddddddddddddddddddddddd)-{9}", 1, 0 },
+ { "[^<[(\?!:asc(\?:i(\?<!i:])F])[:alp(ha:]b))-}Wwx8B", 0, 0 },
+ { "[^[^[^([(\?{}(\?=)(\?())-CCCCCCCCCCC(\?=CCCCCCCC(CCCCC(\?:"
+ "CCCCCCCC(\?{l[(\?!:space:]})[:upperwor(\?:d:]{-27}[:al[^pha:"
+ "][:xdigit:]^f",
+ 0, 0 },
+ { "[[^]G@>2!+[:punct:(\?<!]{,189}6ZF[:blankcntrl:][:digit:]{,"
+ "214}){-115,-14}l[:upperword:]{101,}Z[:ascii:]Ld&02|c]<0~<bc",
+ 0, 0 },
+ { "(Q)[:digit:]x", 1, 0 },
+ { "hT[[:alnum:]\?]O[OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOxFF%^(\?(_"
+ "LN "
+ "8uXQT\"*/"
+ "L)+l)>qQ[^]e[:ascii:]PP()[:digit:]NQ8%6d=&2I{-62,-142}w]].e{"
+ "}*",
+ 1, 0 },
+ { "{,-219}xxxtEEEEEEEEEEEEEEEE[:pun(\?(ct:])qqq)"
+ "nnnnnnnnnnnnnnnnnnnnnnnnnnn",
+ 0, 0 },
+ { "[:di(\?>git:])W4", 0, 0 },
+ { "([^y])Fkvto$", 1, 0 },
+ { "[^($$$$$$(\?!$$$$$(\?{$$$$$$(\?<=$$$$$$$$$$$+===)[:alnum:]"
+ "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM)Z]{}^[:blankcntrl:]--"
+ "xxxxxxxxxxxxxxx[^xxxxxxx)\?tVG\?{232,81}{121,}xn{,-226}})"
+ "tttttttttttttttttttttttmu(\?<!&&&&&&&&&&&&&&&&&&&&&&0b]z)$"
+ "87{,-192}{}{-242,}",
+ 0, 0 },
+ { "l[:dig(\?(it:]|s*)aA[:digit(\?<=:].^.))x[:digit:]", 0, 0 },
+ { "[:grap[^(\?#h:]').]Z", 0, 0 },
+ { "[:gra[^ph:]t[:digit:]222222222222(22222222222222222H "
+ "qM]pWZr[:ascii:]-hRb_.)Q{-228,-204}{}",
+ 1, 0 },
+ { "AAAAAAAAAAAAAAA(AA)YeX", 1, 0 },
+ { "(!dqqqF*^){(,-79}s!!!!!!!!!!!!)", 2, 0 },
+ { "[^(\?msxm(\?#sx:]|)ZHYup)j{95}0L:vXB#')d'DX\?m."
+ "T034\\\\\\\\\\\\\\\\\\\\\\y5rV{}S",
+ 0, 0 },
+ { "(W*O+yl([\?!P(\?:)I]${}{-195,-14}[:upperword:]{}[:xdi[^git:]"
+ "[:space:]X[:grap[^h:]~]zzzzzzzzzzzzzzzzzzzzzzzL)+)Y "
+ "b.-=jf{-216,}${/!}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|]",
+ 2, 0 },
+ { "[^\\\\\\\\\\(\?<=\\\\\\\\\\\\\\\\m]{-48,234}[:alpha:]s)", 0,
+ 0 },
+ { "[(\?{U}(\?<!)])LLLLLLLLLLLLLLsssssssssssssssssssssssssss[:"
+ "ascii:][:blankcntrl:]---------b",
+ 0, 0 },
+ { "[^[^[(\?#)(\?imsxims[x:)<<<<[<<<<<<<<<<(\?<!<<<<<<<([^\?(<<<"
+ "<<<<<<<z(\?(zu(\?<=~83}aZpIE)[:alnum:](\?imsximsx:(\?!jrE6("
+ "\?<!\?V(SzDU)000[000000000((\?=\?)=0])L|lOYuWXk",
+ 0, 0 },
+ { "$o[:dig(it:]nnnnnnnnnnnnnnn{-94}|G)[:alpha(\?!:] "
+ "{,-108}D=\?>[:digit:]S[:space:]t",
+ 0, 0 },
+ { "()n", 1, 0 },
+ { "[:upp(erword:]$)<}.vZM<lEY5Y*", 0, 0 },
+ { "[^([^\?>)rCD&{5(\?msxisx:7,}qqqqqqqqqqqqqqqqqq{31,}@w#W:(@("
+ "\?:zp$YYYYA[:alpha:]{1}A)*dZJ\"5OG|\?(\?#a])]|){-150}[:"
+ "xdigit:]",
+ 0, 0 },
+ { "[($)gwo{`\"]{-160,}"
+ "\\\\\\\\\\\\\\\\\\\\\\\\\\66666666666666888888888888",
+ -1, 1 },
+ { "((}DA+Rc000000000000000000)%vvvvvvvvvvvvvvvvvvvvv%C&emZ*[:"
+ "alnum:]#m/"
+ "D[:graph:][:blank[^cntrl:]E{,168})"
+ "kkkkkkkkkk000000000000000]",
+ 2, 0 },
+ { "[^[u*(\?#x01234)oxGGGGG(\?([GGGG)GGGGGGGGG]^U)!!CCCCBM`4QB^"
+ "XEN]{,-60}[:upperword:]G]",
+ 0, 0 },
+ { "(%)~t{S,K^MI3PMo)=b", 1, 0 },
+ { "[[[^]{}eU([:xdigit:]&&&&&&&&&&&&&&&&&)\"W|43[:alpha:][:"
+ "graph:]J8b[:blankcntrl:]gggggQ{,183}{,-254}\?[:ascii(:]{,"
+ "134}",
+ 1, 0 },
+ { "[[([^[^([^(\?=)1RRRRRRRRRRRRRRRRRRRRRR(\?:(\?(\?(\?!=#RRRRR("
+ "\?=RRRR(\?<[^!Ru)])]o[:[graph:[^]{,7})[:digit(\?::]{-215,}e["
+ ":space:]]",
+ 0, 0 },
+ { "({{{{{{{{{{{{{{{{{{KKKKKKKKKKKKKKKKKKKKKKKKKKKKBBBBBBBBBBBB)"
+ "[:space:]0[:alnum:]HcctQA",
+ 1, 0 },
+ { "[^(pP7(HsN[^g{186,-87}\?\?]EQ%u:-Y)+>>>>>>>>>>>>>>>>>>>>>pP]"
+ "[:alpha:]",
+ 0, 0 },
+ { "[(.{141}h|)((\?:\?=@Q} "
+ "ghcC{+*(R)D+][:lo(\?#werprint:]"
+ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz))",
+ 0, 0 },
+ { "[^({}S)PPFl(])-216", 0, 0 },
+ { "[([[^(((([(\?#^[^[^\?4[(:[dig[^it(\?(:]{122,})y\?", 0, 0 },
+ { "[[2${188}u{1(4(\?(1,1(\?{98}e{&tbaoI]q)[:punct:])d}))"
+ "Nqffffffffffffffffffffffffffff[:ascii:]+]",
+ 0, 0 },
+ { "()K-", 1, 0 },
+ { "[[{2(2((\?(\?!()2})])[:alpha:]fVVVVVVVVV{-47}):::::::::::)"
+ "\?vwyyyyyyyyyyyyyyyyyyyyyyyyy-]{}",
+ 0, 0 },
+ { "ivcs)g", 0, 0 },
+ { "(hhhh[^hhhh(\?{h\?]})%%%%%%%%%%%%%%%)\"+38mbY:s9{/d# "
+ "zaNnbQb)b:*zpKI{-26,-189}",
+ 1, 0 },
+ { "S*(#)[:graph:]lllllllll&G)t", 1, 0 },
+ { "([^[(([\?=\?<!)]]___{-63,})]nt", 1, 0 },
+ { "[:b(lankcntrl:][:alpha:]*[:pu[^[nct:][:alpha:]A]$"
+ "aaaaaaaaaaaa*)A[:digit:]U][:alnum:]",
+ 0, 0 },
+ { "[^f[^p000{68(\?isxmx:,}(\?!vvvvvv)$)]PP#*{(})[:punct:]&&&&&&"
+ "&&&&&&&[:punct:]\?][:blankcntrl:]",
+ 1, 0 },
+ { "[^(((\?(\?(()))GGGGGGGGG{(\?!($)))((\?!)V^{228,145}))]{-229}"
+ "Qjjjjj[:punct:]R)",
+ 0, 0 },
+ { "[(Q[^((\?{(\?:]~z)})gE(.<){}|)Kuuuuu$*"
+ "222222222222222222222D]",
+ -1, 0 },
+ { "([^`(\?<=`````[^`````````M]\?)=L74A[:upperword:]]P", 1, 0 },
+ { "(({}[:space:]qv-T){,-192}{-45}{65}9\?X).d", 2, 0 },
+ { "_[(:upperword:]mU(P}qX>\?%)$Lwq[:alpha:]{-115,}============="
+ "==================={127,}",
+ 1, 0 },
+ { "e)", 0, 0 },
+ { "[{,2[5}Klen+D0'YX(\?<=|_H]I,Y\"*/<3sM[:digit:]])#.", 0, 0 },
+ { "[:(xdigit:]){[:digit(\?mxmsx::][:as(\?<=cii:]d!{135})#)pP[:"
+ "space:]Syyyyyyyyyyyyyyyyyyyy\"Gg8",
+ 0, 0 },
+ { "[(\?()])", 0, 0 },
+ { "[^([^[^[[^[:alpha:]SIus[^f<f]}}}}}}}}}}][:xdigit(\?=:]Z{-13}"
+ "*]_[]LLLL)]E[:alnum:]b$)]]]]]]]]]]]]]]]]]]]]]]]]][:"
+ "lowerprint:][:ascii:]{,40}{86,}"
+ "333333333999999999999999999999999999*"
+ "fffffffffffffffffffffffff99999999U9|[:digit:][:upperword:]"
+ "oowwwwwwww[wwwwwwwwww{195}[:xdigit:]]H{-73,153}R+zAz{}r/////"
+ "////////"
+ "{232,}kAoffffffffff[:blankcntrl:]xxxxxxxxxxxxxxx]KKKKKl0,[:"
+ "alpha:]|{,-165}Qc{96}CCCCCCCCCCCCCCCCCCCC/",
+ 0, 0 },
+ { "{}:V(7O-)[:ascii:][:graph:]PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP#",
+ 1, 0 },
+ { "[^(\?<[^=CC(CC$)]* c)BBBBBBBBBBBBBBBBBBBBBBB]z{-18,}",
+ 0, 0 },
+ { "[[qqqqqqqqqqq(\?(qq235|ttttttttttttttttttttttttttttt[[ttt<<<"
+ "<(\?{<<<<<<<<<<<<)<<<<<<<<p)/"
+ "S9(\?{OOOOOOO(\?<!OOOk)})]nIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIb]"
+ "Z})",
+ 0, 0 },
+ { "[^[^(\?>][^((\?<!C(\?!+(\?=)]^8)6nx).)){,-13}[:blankcntrl:]"
+ "\"(L{}){,29}nnnnn{-83}]l[:upperword:])",
+ 1, 0 },
+ { "[(ZZ\"#(\?#Nb(\?<!:U)oRRRR])Zei${Ec/)s", 0, 0 },
+ { "[^[^[(\?(t(\?:3```````)`````)|#CB)//////////////////////////"
+ "///"
+ "*!liB#|CCCCCCCCCCCCCC(\?=CCCCCCa7N]weTTTTTTTTTTTTTTTT1{}o\?{"
+ "}BBBBBBBBBBBBBBBBBBBBBBBB.])u{-218,126}.,[:space:]]",
+ 0, 0 },
+ { "[[([:alnum:])yyy(\?!yyyyyyyyyy(\?!yyyyyyyyyyyyyyyyyyy[:"
+ "graph:]I])Uw*X.^[:ascii:]{,-63}[:digit:]{-88})&&&&&&&&&&&&&&"
+ "]*",
+ 0, 0 },
+ { "[[[^K(\?=KKKKKKKKKKKK(\?:KKKKKKKKK[KKKKKK]]U[:digit:])]dd)({"
+ ",16})xy+Pu)JJJJJJJJJJJJJJJ[:space:][:ascii:][:upperword:]ql_"
+ "jywmt4B+]{-30,}^555555555Xza[:punct:]",
+ 1, 0 },
+ { "[[^^XXX(\?:XXX((XXXXXXXXXXXXXXXXXXXX)v)$N9$"
+ "r\"\"\"\"\"\"\"\"\"\"\"\"\"].{,239}$[:punct:]\"9999][:alpha:"
+ "]{}c){,55}s[:upperword:][:xdigit:]310",
+ 0, 0 },
+ { "[@([^I8oNl)]-{-203,-224}{-78,}KKKKKKKKc{-66}[:xdi(\?=git:]=="
+ "========){}f{-124,}[:upperword:][:lowerprint:]]{}--------l+",
+ 0, 0 },
+ { "[^]ozp+0(\?#\"[(\?()X]))[:blankcntrl:][^e{99,222}"
+ "JJJJJJJJJJJJJJJ3F]\?[:blankcntrl:]l$ot",
+ 0, 0 },
+ { "[[^[[((\?isximx:)2222222222(\?=22222[:graph:])+U)((\?{\?<=("
+ "\?()iYv8qc@#y)G])+}))FvnP\"7OZ-b273[:ascii:]Ak6*`S[:digit:]["
+ ":graph:]]{2}^G{79,}DDDDDbbbbbbbbbbbbbbbbbbbbbbbb(bbbbbbb)|"
+ "tP48y{wNJ_S hJbY]]dc",
+ 1, 0 },
+ { "[:alph(\?{a:]p1[:lowerprint:]}){163,}", 0, 0 },
+ { "W()", 1, 0 },
+ { "()``````````````````````````[:ascii:][:alnum:]{,26}[:graph:"
+ "]",
+ 1, 0 },
+ { "[:al(\?<!num:]|byyy,*)U5%u${190}-{-221,-33}"
+ "k7777777777777777777777777777777+eXXXXXXXXXXXXXXXXX[X(\?(XX)"
+ "XX)S'vEAa]*e",
+ -1, 0 },
+ { "[^(([R_AC[lE'{2(\?{28(]8LTt[]b[:punct:]]O)|2[:graph:][:"
+ "space:]}) "
+ "x3C[:alpha:])uI+dddddddddddddddddddddddd{-165,}"
+ "FFFFFFFFFFFFFFFFFFFFFFF)cccc*[:upperword:]]G{,-38}{24,}"
+ "555555555555555555555555555VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVZ["
+ ":blankcntrl:][:ascii:]",
+ 0, 0 },
+ { "[^QQQQQQQ(\?#QQ(QQQQQQ[:punct:][:space:]){(\?(\?:!}[:graph:]"
+ "t}}[^}}(}}}}}444444[^444444444444444444444]\?]G)E)L{,-103}{"
+ "84,}r$ii]-[:alp(\?<=ha:]S5G~9>n*)P<"
+ "3tttttttttttttttttttttttttt)n{}[:graph:]"
+ "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{,83}[:digit:])"
+ "0BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB[:alpha:]{-155,}{151,}",
+ 0, 0 },
+ { "Ue{,254}+f[:lowerp(\?<=rint:]U.fff)", 0, 0 },
+ { "QQQQQQQQQQQQQQQQQQQQQQQQAY<J)'MPi_u%#2doopqU7/"
+ "{103}[:graph:]e!7{GOr",
+ 0, 0 },
+ { "[^({,[^233}[^d)BBBBBBBBBBBBBBB=======(\?>===========[^=S|[^["
+ ":alpha:]G/]qqqqqqqqq{}[:xdigit:])..k",
+ 0, 0 },
+ { "[([^[[:space:]ffffff(\?=ff]M]))[:xdigit:]UbCI,CzalLU*y5I[:"
+ "digit:]r{-30,180}{-209,-45}Paf]",
+ 0, 0 },
+ { "[^[h(\?{hhhhhhhhhhhhhhhhhhhhh})]{,143}[:lowerprint:][:ascii:"
+ "((\?(\?=])[:asc)ii:])zp]",
+ 0, 0 },
+ { "[[(\?{]})]", 0, 0 },
+ { "[[1\"3m^,(\?<!2((\?!\?#t```````````````````````````)\?)|c^)"
+ "A^~]{61}W\\\\\\vvvvrrrrrrrrrrr[:digit(\?#:])]F[:upperword:]"
+ "dX\\\\",
+ 0, 0 },
+ { "([${144,}(\?<!)-RAk_F(\?imsxisx:=9]z/))", 1, 0 },
+ { "[[^[[[^([[^[^[^([[^([[Uiiiii#####(\?(\?{(\?<!#########(\?=##"
+ "###).^)(.|>2m[M/"
+ "2222222222222222222222222222(\?:22222222222(\?#22(\?:(\?="
+ "22222{,243}]x68+I/"
+ "K)11111111111]\\pP[:graph:]$[:space:]^{}A)[:xdigit:]-={>",
+ 0, 0 },
+ { "[(\?>[(^()Vty2vvvvvvvvvvvvvvvvz^])ZZZZZZZZZZZZZZZZZZZ-------"
+ "---------5\\dVLSp8UE2m+z3X/Sd",
+ 0, 0 },
+ { "[}}}}}}}}}}}}}}}}}}}(\?#}}(\?<=)|*C "
+ "]*29JW7O9mEB]pE_OoxN)[:alpha:]",
+ 0, 0 },
+ { "([^((\?<=\?)D{,200}.[(\?#:ascii:])[:space:].)[:alpha:]D|[:"
+ "graph:]{,-41}*LLUUUUUUUUUUUUU{-189,-131}]qHR<k2@P{27}<^e,ub%"
+ "\?/4){-243}+[:digit:]%*x9lA^",
+ 1, 0 },
+ { "([:alpha:]bT&+_)$Z{,212}x26`", 1, 0 },
+ { "[^([^(A{[^}g(\?()A9p#54b]-------------------------------)."
+ "wzD#=f\\)A)8a]]DNNNNNNNNNNNNNNNNNNNNNNNNNN",
+ 0, 0 },
+ { "(W000000000000000000000000000000)", 1, 0 },
+ { "www(wwwwwwwwwwwww)", 1, 0 },
+ { "()555555555555{18}i+[:alnum:]E {}U", 1, 0 },
+ { "SqbHoooooooooooo[^oooooo([^ooooooo])\\N[:xdigit:]]oooo`", 0,
+ 0 },
+ { "[999999999999999999uE{193,0}lx{7917}[:punct:]4&d]{221,}[:"
+ "digit:]{49,156}[:lowe(\?<=rprint:])[:space:]{-33}w+",
+ 0, 0 },
+ { "[^(\?{})<{220,-193}[(\?=:xdigit:]UUUUUUUUUUUUUUUUUUU'{-18}]"
+ ")",
+ 0, 0 },
+ { "b[(\?<=:upperw(\?{ord:][:digit:]})EEEEEEEEEEEEEEEEEEEEE/////"
+ "/////////////){177}C",
+ 0, 0 },
+ { "(^).[:alnum:][^[(\?=[(\?{[})DA5{)[[I~y&O\?9>])]][:"
+ "blankcntrl:]M[:alpha:]x9[:upperword:]|[:xdigit:]b",
+ 1, 0 },
+ { "()[:digit:][^[U}-]]{,206}V*WJ@R]\?", 1, 0 },
+ { "[^](\?#{}(\?[<=)yv)]r", 0, 0 },
+ { "({,-192}//////////////////////7!eW_0eoL){}", 1, 0 },
+ { "^[:punct:(]+)IIIIIIIII[:punct:]P$pP", 0, 0 },
+ { "[(\?=|U)^-]{-52,-72}[:digit:]*6666666666\?{{{", 0, 0 },
+ { "([^f(\?:+{1((\?=34,}]))^)s0bux7\?5`Bwr[:upperword:])Dy+", 1,
+ 0 },
+ { "AL{}:::::::::::::::::::::::::::::::{,(104}~@,Ysey@h).", 1,
+ 0 },
+ { "[^((.)))(\?()))))))))))))))))))))(\?msxims:))))))))))[)][:"
+ "upperword:][:alpha:])",
+ 0, 0 },
+ { "[^(()f])G^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^T{}N*nK[G]{,61}^^^^^"
+ "^^^]",
+ 0, 0 },
+ { "[(N::(\?<=[:digit:][:graph:][:space:]xB5[(:xdigit:]|Yv{}"
+ "HHHHHHHHHHHHHHHHHHHHHHHHd).[:g(\?<=raph:])[:digit:]<<)[:"
+ "digit:])[:space:]Q[:punct:]x7C]",
+ 0, 0 },
+ { "[^((\?(\?(())a)(\?!){})W)pP3333333333("
+ "33333333333333333333hhh]{})",
+ 0, 0 },
+ { "[^ [ "
+ "a*FFFFF[^FFFFFFFFFFF(\?<[^!FFFF(\?=FF])])L1]{,-52}{B-bxsPKg{"
+ ",8}[:digit:][:punct:][:upperword:]DD${,-131}",
+ 0, 0 },
+ { "($$$$$$$$$$$$$$$$$$$$$$$$$$$$$^pP),,,,,,,,,,,,,(,,,,,,,,,,,,"
+ ")QQQQQQQQQQQQQQQQQQQQQQQQ",
+ 2, 0 },
+ { "[:lowerprint:]|l{(,-54}C{}*-)IIIIIIIIIIIIIIIII", 1, 0 },
+ { "()+", 1, 0 },
+ { "[(([(\?{[:punct:]]|))[[[[[[[[[[})]WWWWWWWW&$$$$$$$[:graph:]",
+ 0, 0 },
+ { "[^(\?{}){(107[(^,}][:space:[]))^w,&aPPPPPP[^PPPPP{117,-213}"
+ "s\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?]]]222222[:d(\?("
+ "igit:]NNNNNN)NNNNNNNNNNNNN8)I",
+ 0, 0 },
+ { "[^(\?<!$)|TTTTTTTTTTTTTTTTTTTTTT(TTTT]a8)2<", 0, 0 },
+ { "([^[]%[^[^]-][:alpha:]37*:[:space:]]lQvu)[:xdigit:][:"
+ "blankcntrl:]",
+ 1, 0 },
+ { "[[Bl_>9C^:\?X_KK]2sw@hHZT!],uuuuuuut|lFW()''''''''''''''''''"
+ "'''[:graph:]<~v{-251}0[:digit:]C[{222,}]{,41}{}*g^UuS/"
+ "{-114}",
+ 1, 0 },
+ { "(D{,-79}[:gra(ph:(\?(]C[:ascii:]))I[tC.%tkllll[^"
+ "llllllllllllllll]&&&&&&)&&&&&&&&&&&&&&&&&&&&&&)]10435",
+ 1, 0 },
+ { "[:al(\?{[^num:]]})}x'[:(\?#xdigit:])"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxKKKKKKKKKKKKKKKKKKKKKKKKKKKKTT"
+ "Tr*%{~f",
+ 0, 0 },
+ { "[ZQKEEEEEEEEEEEEEEEEE(\?<!]3|.~~~~~~~~~~~~~~303)"
+ "33333333333333333",
+ 0, 0 },
+ { "(-62([:ascii:]5555){-230,}<<<<<<SM[:punct:]{72}|E{160,})"
+ "Pfqba!{,-188}DS{ +2tRu\"0JG$",
+ 2, 0 },
+ { "([^(\?:(Ea00000000000000[:punct:][:graph:]{}]))[:xdigit:]{-"
+ "65}t){164,}",
+ 1, 0 },
+ { "[\?$$$$$$$$$$$$$$$$$$$$$$$$$F......(\?(.).q#R:j6%TTLCdtuM|8*"
+ "54<GHoqEh9FBW0:W]L0)o][:upperword:]",
+ 0, 0 },
+ { "[(\?>[:alnum:]W[:space:]]D)|L", 0, 0 },
+ { "(M(MM)[:alnum:]|[:lowerprint:]4)", 2, 0 },
+ { "[[^(\?:{}{2[2(\?>0,})]]]Etu)-)", 0, 0 },
+ { "([^[^^z[:graph:]]#{-144,96}[:punct:]!4LY//////////////////"
+ "SSSSSSSSSSSSSSSSSSSSSSSSS[[^:xdigit:]\?`-!L#p0{52}]%{-121,}["
+ ":graph:]]WqJ>$6UBg{,7}[:blankcntrl:])[:upperword:]y2wW!A[:"
+ "blankcntrl:]0CN\?",
+ 1, 0 },
+ { "[[^(\?:|+bII(IIIIIII(\?(\?>!)275SIIIIIIIIII(IIIIIII(\?="
+ "IIIIII[:graph:]|)`]S\?.}A)[:alnum:]Jgggggggggg{-150,}{-89,})"
+ "[:alpha:]Q)|07be5:j)]",
+ 0, 0 },
+ { "([(\?i(ms(\?=x-x(\?>:))C)]){})>eIqm~lFb[:upperword:][:"
+ "blankcntrl:]w=[:digit:][:graph:]",
+ 1, 0 },
+ { "([HHHHHHHHHHHHHHHHHHHHHHHHHH[^HHH("
+ "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?!!!!!!!!!!!!!!!!!!"
+ "!!{23}]~J=[:ascii:]tttttttttttttttt])-216",
+ 1, 0 },
+ { "B{[^-32,246}{13(\?!0}q>GVQw*[:digit:][:punct:]."
+ "77777777777777777777`T(-t01odD]\?${}{-247}+gV{131})+[:"
+ "lowerprint:]m/z~d",
+ 0, 0 },
+ { "[t[$FV+(\?=E=[^])]-$U{-22[5,}{253,}08g]$[{}][:xdigit:][:"
+ "punct:]{-18}{-173,}]{,-191}V_|90",
+ 0, 0 },
+ { "()$", 1, 0 },
+ { "[^[^((((((((((((((W[(\?::blankcntrl:]&-JH]J){93}LLLLLLL|r{,"
+ "221}tY/172]-AS",
+ 0, 0 },
+ { "[^()(\?{qqqq(\?msimsx:qqqqqqqqqq3999999999999GGGGG|S*W%{,"
+ "128}][:xdigit:]AJt]}\"Zf!lRpr{>){,36}})",
+ 0, 0 },
+ { "[([]^]^)", 0, 0 },
+ { "([.(\?#){}[:alpha:]\?S{2}P%Gw]"
+ "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnYiq5)>i*r<",
+ 1, 0 },
+ { "[ggggggggggg$PPP:S "
+ "(:]N{239,}|A[:lowerprint:]vvvvvvvvvv[:lower(print:]{-184}({-"
+ "133,}+)[:punct:]P/Q.OOOOOOOOOOOOOOOOOOOOOOOOOOOOOO",
+ 1, 0 },
+ { "(RRRRRRR[^RRRRR[RRRRRRRRR])]", 1, 0 },
+ { "[(\?:^])D%", 0, 0 },
+ { "()[]#C[+[j]{,29}-]", 1, 0 },
+ { "(([(\?(((\?{\?!(\?=\?=#[Es*){02$r'}(\?:3pz)"
+ "uPPPPPPPPPPPPPPPP(\?(\?>:PPPP][:graph:][:ascii:]`.)[:punct:]"
+ "[:a(\?mxi:lnum:])r)$)[:xdigit:]$[:(\?=digit:])aa[^]a)\?])"
+ "sQQQQQQQQQQQQQQQQQQQQQQQQQQ^|$)-}))",
+ 2, 0 },
+ { "z@@@@@@@y${}[:(\?:upperword:]l\?{,144}-)", 0, 0 },
+ { "[:aln(\?:(\?>um:(\?imximsx:]){})FGGGGGGGGGG|-p){,105}", 0,
+ 0 },
+ { "[[{17}llllllllllllllll(\?:lllllllll{,(\?#-94}OUUUUUUU(\?#"
+ "UUUUUUUUUUUUUAA]p[:digit:]{-1(57,}5yyyyyyyyyyyyyyyyyyyyy[:"
+ "alnum:]v{-185}^^^^^^^^^^^^^)d[[[p)]))",
+ 1, 0 },
+ { "()|[:digit:].E2o", 1, 0 },
+ { "()3[:lowerprint:]", 1, 0 },
+ { "[(\?{(\?#(\?>SN}[^)z+r^t[:digit:]seP[:alnum:]$b1ZY[U(\?<!"
+ "U4IIIIIIIIIIIII(\?<=IIIIIIIIII]m)]))]4)",
+ 0, 0 },
+ { "{,74} qkk[^p]kbi6>{}000000000000000000000000000000$|)",
+ 0, 0 },
+ { "[:(\?=digit:])v{164}", 0, 0 },
+ { "[:graph:]h[:upper(\?(wo(\?{rd:)])00000[^000000000000})."
+ "4OEVf{,-46}]A",
+ 0, 0 },
+ { "[](((((((((((((((N{{{{{{{{{{{{{{{{,-1}e]a{-166,-44}", 0, 0 },
+ { "([[^[^[(^[]]YYYYYYYYYYY]D.cQ{}[:alpha:]ttttttt000000[^0000("
+ "\?<!0000000000000000N::::::::].][:alpha:]#5\?{}{-253,-193}]"
+ "\\[:ascii:]tS{,35}B)ffffffffffffffffffffffff))/",
+ 1, 0 },
+ { "(G)[:alpha:(\?#])W{-197,-220}w8", 1, 0 },
+ { "{-2[^00,(\?#-([84}ig+)]]l[:graph:][:graph:][:space:])"
+ "aaaaaaaaaaaaaaaaaaa{-208,}ea{,224}",
+ 0, 0 },
+ { "[^[W(\?<=[B[:xdigit:]{255,}FAAAAAAAAAAAAAAAAAAAPP])[:xdigit:"
+ "]+][:lowerprint:]${-195}",
+ 0, 0 },
+ { "[v{104,}BB].HHHHHHHHHHHH[:ascii:]"
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbb(btttttttttttttttttttttttttt){"
+ "180}",
+ 1, 0 },
+ { "[^(i[^iiiiiiiiiiiiiiiiii(ii)n])#######################]", 0,
+ 0 },
+ { "(([:space:])[:g(\?>raph:])[:punct:][:upperword:]LV\"t+t!)[:"
+ "ascii:][:lowerprint:]q",
+ 2, 0 },
+ { "[[[^([7(\?[<!)\\PP~D7L (\?imsimsx:(\?= "
+ "$GS26L3-J(\?()!)]]{-178}%$[:p(\?!unct:]))yyyyyyyyyyyyyy@w,["
+ "11!R86:)G*[(\?(:blankcntrl:]267$~L\?{-108}k[:alnum:]So\?Y/"
+ "eq]-|[:xdigit:]555555555555555555555555555)55555........W*O)"
+ ")][:alnum:]]I{,-126}[:lowerprint:]8\?[:xdigit:]u%wHc6\?:Pc.."
+ ".........................,,,,,,,,,,,,,,,,,,,,,,,,,,,]",
+ 0, 0 },
+ { "((3pPp))QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ", 2, 0 },
+ { "[[^]{-244[}(\?([^|W0E4]UUUUUUUUUUUUUU[:upper)word:][:space:]"
+ "{-57,})+L>R]]$PeFuufcBA`qr!!!!!!!!!!!!!!!!!!!!!!!!!",
+ 0, 0 },
+ { "[[(\?#F^(\?<!)|)fff(\?!fffffffffffffffff(\?{ffffff(\?:"
+ "ffffff[:alnum:])]]c.\?-}))",
+ 0, 0 },
+ { "[^[^((\?:)ww[wwww(\?>wwwww)3z/57z){34}]/(/////////////[^////"
+ "//////////////)]E%)L{-133}]*$]",
+ 1, 0 },
+ { "(!)GS[:ascii:][:punct:]{235}T'&-_h\"", 1, 0 },
+ { "(){}", 1, 0 },
+ { "[[^((\?!(\?<=)*QF[:alpha:])([^[^\?<!x60t(\?<!"
+ "UUUUUUUUUUUUUUUUUUUU)K&d{118}z7nM.G)````````````````````````"
+ "```E:(\?(){31,}){}]k]){,109}[:space:]]ZZ[:xdigit:]]{-68,}`{}"
+ "{}e\?[:alnum:]",
+ 0, 0 },
+ { "[^{223}.^,-qqqqqqqqq((\?!\?>qqqqqqqqqqqqqqqqqqqqqqqP6W0_'O)"
+ "Bur*'6&*t)]{65})+",
+ 0, 0 },
+ { "([(\?=)]wr$7f5ru){100,}[:xdigit:]y{}[:digit:]{}2n@P|9#mru~"
+ "97{-189,73}$a",
+ 1, 0 },
+ { "({-113,213}){-172,221}B[:ascii:]{,-48}", 1, 0 },
+ { "[^[[Xf`````((\?{(\?<=\?imsmsx:`````````(\?!`````````[```("
+ "\?mximsx:``(\?(&|o{xIaO][:)space:]3))\?])+)*<|@@@@@@@@@@@@@@"
+ "@@@@@@@@){-251,}{}]*[:graph:]1!azE\?|-120u*][:lowerprint:]}"
+ ")",
+ 0, 0 },
+ { "[[[^##(\?################(\?>(\?(##t)][:punct:])b))<<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<[:alnum:]y "
+ ">u=l:rp8i3Ci#]46%NIO-W[:space:]IIIIIIIIIIIIIIIIII]W[:space:]"
+ "f]l{-253}",
+ 0, 0 },
+ { "[:graph:]L{-136,175}{[^}h(\?=t)Q]ooooooooo("
+ "ooooooooooooooooo_)[:space:]q\?",
+ 1, 0 },
+ { "()$.", 1, 0 },
+ { "[(\?<!^$.\?{197}B]$)", 0, 0 },
+ { "[:di(git:])[:low(erprint:])qqqqqqqqqqqqqqqq[:digit:]", 0,
+ 0 },
+ { "((zzzzzzzzzzzzAUUUU)l$]VD z~)n", 2, 0 },
+ { "([^[(\?<=^[]{}][.WWWW)044444444444(\?=44(\?{444(\?{("
+ "444444444444e{(\?=}}))..t]+[:(\?<!xdigit:]P]-N}))))|)",
+ 1, 0 },
+ { "\\ce[:(\?#asc(\?{ii:])})[:upperword:]`^", 0, 0 },
+ { "[:graph:(\?<=])[:alpha:]", 0, 0 },
+ { "([:upp(\?=erword:])pC)lp\?", 1, 0 },
+ { "(oooooooooooooo\?fN)-[:alpha:]{-213}[:alnum:]qHEu", 1, 0 },
+ { "[:punct:]TTTTTTTTTTTTTTTTTTT[:d(\?#igit:])[:alpha:]", 0, 0 },
+ { "([^[^[^J4(+++++++++++++++++++++SgDE(\?>\"y8].]::::::::::::::"
+ ":)pP5-]p)O{,199}xxxxxxxxxxxxxxxxxxxxxx[:ascii:]%",
+ 1, 0 },
+ { "([:alpha:]Fs)Z", 1, 0 },
+ { "[()]{209}[:alpha:]hhhhhhhhh(hhhhhhhhhhhhhhhhhhhhh)pP<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<<<<<",
+ 1, 0 },
+ { "-{-8,}.[:(\?imsxx:ascii(\?<!:]{-231}aa*{}K^UQL\?)d\?[:"
+ "lowerprint:]W)q>D9'",
+ 0, 0 },
+ { "[#(\?msximsx:#########################-IIIIIIIIIIIIII(IIII("
+ "\?#IIIII((\?#[^III{})N.[(\?=:lowerprint:]))CwT,,,,,,,,,,,,,,"
+ ",,,,,,Sq]$CCCCCCCCCCCCCCCCCCCCCCCuuuuuuuu])))",
+ 0, 0 },
+ { "[:xdigit:][(\?#]){13}{,75}lllllllll", 0, 0 },
+ { "[c]QQQQQQQQ1+{-252[(\?#}33333])[:upperword:]", 0, 0 },
+ { "P@i #>>PF!@8G<[(\?:^P]-)D", 0, 0 },
+ { "uZZZZZZZZZZZZZZ[^ZZZZZl*-211{199}(\?!p])"
+ "EEEEEEEEEEEEEEEEEEEEEEEEEEED[:lowerp(\?msximsx:rint:])",
+ 0, 0 },
+ { "[(\?!^])021[:graph:]'", 0, 0 },
+ { "\\(\?>[(\?<=:ascii:]{}[:alpha:]d8}G))", 0, 0 },
+ { "[^[((\?!1)[^,a|]\?{,242}[:alnum:])X\"a", 0, 0 },
+ { "pP[((\?simx::a(\?!lnum:]vvvvvvvvvvvvvvvvvvvvvvvvv)|O0)[:"
+ "digit:]ooooooooooooooooooooo)"
+ "\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"",
+ 0, 0 },
+ { "_ L:8J-~ Y$[:uppe(rword:]{,-184}]{}6.A)", 0, 0 },
+ { "{,105}.(9]]{-12})N@0nOOE", 1, 0 },
+ { "HHHHHHHHHH[:xdigit:]uuuuuuuuuuuu{}E^X\\\\\\12601", -1, 1 },
+ { "( o)=\"OU7h{V>", 1, 0 },
+ { "[[:xdigit:])))))$[:xdigit:]+{152}{,-50}(c),,,,,,!!!!!!!!!!!!"
+ "!!(\?>!!!!!!!!!!!!!.[:digit:]i>\"O'i9])-175d_",
+ 0, 0 },
+ { "[([^[^[^([[Eeee[^eeeeeee(\?(\?<!(eeeeeeeeeeeeeeeeeef|]][:"
+ "alph()\?>(\?!(\?>a:]a{,166})/////////////////////"
+ "[:gr[^aph:])Gpu",
+ 0, 0 },
+ { "(7)NNNNNNNNNNN132", 1, 0 },
+ { "[([\?#^[]{QKm$v])][:alp[^ha:]]", 0, 0 },
+ { "(:{86})7{K|[:alpha:]{O", 1, 0 },
+ { "([Y(\?{[[^:alnum:][:alnum:][:digit:][:a(\?(lpha(\?(:].})", 1,
+ 0 },
+ { "[[({29,-30}([[^:digit:])Y]]J=~{,220}[:blankcntrl:])"
+ "0ooooooooooooooooooooooooooooooo[:punct:]&]",
+ 0, 0 },
+ { "[^1Dx32[:alnum:]]{[(\?::punct:]MMMMMMMMMM)12759", 0, 0 },
+ { "([[[]]*|(_])[:u(\?{pperword:]})", 2, 0 },
+ { "[:upper(\?(wo)rd:]){-16,250}", 0, 0 },
+ { "([^{194}i(\?({161)}PP\\S{}{,-14}]))z{208,225}BpPEt", 1, 0 },
+ { "[(\?m-ms:)}&!@29k0sUqzt9}<-x|A$!+G>>>>>>>>>>>>>>>>>>>>>>>>>>"
+ ">>>>>>CCCCCCCCCC-][[:space:]][:space:]El",
+ 0, 0 },
+ { "()[:digit:(\?isx(\?>ix:]K^WQQQQQQQQQQQQQQs)[:lowerprint:])",
+ 1, 0 },
+ { "[a|(\?imix:S(\?(SSSS)SS(\?>S)]W)8t[:ascii:]f$)[:alnum:]"
+ "111111111111111111111[^[:space:]x{12729}+'''''''''''''''']",
+ 0, 0 },
+ { "[^(\?!(\?(\?#=)a)[:punct:]=2)(){}$$$$$$$$$(\?ims(\?#-isx:$$$"
+ "$$$$$$$$$$$$$(\?#$$s)x{294b}##############################"
+ "slllll)]){,209}333333333333333333G:v2/K",
+ 0, 0 },
+ { "[^]ub(\?<=)vQ6(\?#Z\"3.)[:space:]u[[:digit:]]"
+ "7777777777777777U'{}sssssssssss",
+ 0, 0 },
+ { "(([(])`[:ascii:]b)", 2, 0 },
+ { "[[[^[^([^[^(\?=(\?imxisx:[[^w])", 0, 0 },
+ { "pppp(pppppppppp-{-175}Nb>k&)sssss{-190,-54}", 1, 0 },
+ { "()OJ@`'%[:(as(\?!cii(\?#:]))+pffffffffffffffffffffffffffff{,"
+ "162}[:ascii:]5)s-[:graph:]",
+ 1, 0 },
+ { "[(M{}Ux5{jaW/"
+ "{}[^u[:alpha:]s^{84,}PPb@Wt$(\?>nha<Yf41a)]{}[:lowerprint:])"
+ "*[:lowerprint:]][:upperword:]^1gS.^=pp{}"
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFF33333333333{}",
+ 0, 0 },
+ { ")\?L9~h4BQnNp F\\Q{}", 0, 0 },
+ { "($)[:upperwor(\?:d:])N[:alnum:]"
+ "bcccccccc5555555555555555555555555.N[:blankcntrl:]",
+ 1, 0 },
+ { "2222222222222222222ppppppppppppppppp[:lowerprint:]))[^B\\e{{"
+ "{{{f]6#+{,-104}{{{{{{{{{{{{{",
+ 0, 0 },
+ { "<[(\?>:al[^pha:]])\"O\"vN", 0, 0 },
+ { "[(\?>d8E@b.{(\?<=,-250}(\?=mx48[:punct:]^&)]nAeYY)W)-13272",
+ 0, 0 },
+ { "22222222222222222222222222///////////////////"
+ "[:digi(\?#t:]eM)[:lowerprint:][:alpha:][:alpha:]EEEEEEEEEEE",
+ 0, 0 },
+ { "[(\?={38,223})^\\\\\\\\\\\\\\\\L(\?:{,-50}3|)}r]aW\\x70U{-"
+ "110,}8LUf)w]4+oav",
+ 0, 0 },
+ { "G[:upperword:]v[:lowerprint:]-tu)j8CK", 0, 0 },
+ { "[([([^().(\?(\?><=c)'(\?<(='(\?<!''''''''(\?(\?<!!''''''''''"
+ "'(\?=''''''/"
+ "(|dHj(P>L\?q!G))|)(\?=n(\?(^tk)T-z$q!D|2<rc[^{,53})]jZy))))"
+ "6)[:bla)nkcntrl:])010])7pE`l[:space:]([:lowerprint:]"
+ "eXXXXXXXXXXXXXXXXXXXTTTrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr]+[:"
+ "alph(\?!a:]7)444444444444444444444444l{34,}]J{}"
+ "yyyyyyyyyyyyyyyyyyyyyyyyyyy)\?'z9~9s.mA",
+ 1, 0 },
+ { "().", 1, 0 },
+ { "{-205(,}[:al(ph(\?>[^a:]W,[4DLR[^^8THMtVv~KKw(\?>)pPF)].{-"
+ "245,}]))fffffffffd[:alpha:]zzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
+ 1, 0 },
+ { "[^[[^]{-[1(\?imximx:83,}{,182}][:graph:]]^])-bTO X0P", 0,
+ 0 },
+ { "[11111111111(\?#11111111]U[:asc([\?!ii:]{,37}+{-89}){-170,"
+ "218}{-21,})f[:xdigit:]]P.[:xdig(\?:it:]145)YYYYYY$S@:@@@@@@@"
+ "@@{-150,-109}",
+ 0, 0 },
+ { "{-40}<o][^D[(:graph:]]d).Q", 0, 0 },
+ { "()APPLn[:xdigit:]", 1, 0 },
+ { "[([^\?+++++++++++ [ (\?> (\?( (\?{ "
+ "(\?!]E{-29})pP)})ZpP",
+ 0, 0 },
+ { "(t|{}c[^z^\?(@YLD]bSSSSSSSSSSSSSSS)+{{{{{{{{{{{{{{{[:xdigit:"
+ "]n>1)WkF}7",
+ 1, 0 },
+ { "W22[0Q[^d-d{}PPPPPPPPPPPPPPP<^FZ(\?<=\"[U]Yo}9H'cYy]S[:"
+ "alnum:]^8wTDH)^u",
+ 0, 0 },
+ { "([^[(\?:(\?>((\?#$)(\?{^(\?>))///////////(\?>/"
+ "ggggggggggggggggg{1(\?!90,-13}\\D)Dyyyyyyyyyyyy(\?!y(\?<!"
+ "yyyyyyy)})]]$)[:xdigit:]|{}-)#a))nPpP[:lowerprint:]AA)V+q^[:"
+ "blankcntrl:]",
+ 1, 0 },
+ { "([^(\?!]))D{,97}", 1, 0 },
+ { "(c){,141}", 1, 0 },
+ { "nn[:s(\?<=pace:])[:upperword:]ooooooooooooooooooo*^[:space:]"
+ "`{-188,129}mmmmmmmmmmmmm^.",
+ 0, 0 },
+ { "[[G{(\?imsximsx:2(49}{,-46}r(\?(\?=#Gw]u))[:bl(\?>ankcntrl:]"
+ "))(^m+)zSiZ "
+ "F4[!]VV$E{-9,-100}''''('''''''''\?DEOOOOOOOOOOOO############"
+ "###[:space:])HHHH)[:digit:]'////////////",
+ 2, 0 },
+ { "[^*}(\?>)(\?:7Q=#+]KKKKKKKKKKKKKKKKKKKKKKKKKKKG)]]]]]]]]]]]]"
+ "]]]]]]]]]]]]]][:alpha:]-{}",
+ 0, 0 },
+ { "[n(\?<(\?#!nnnnnn55555{205,}!)[:alnum:]^]!!!!!!!!!!!!!!!!!!!"
+ "!!!![:punct:])[:x(\?(digit:]vr)|'n6W5 D&jk[:punct:]5)",
+ 0, 0 },
+ { "[^P(P{(\?i(msxisx:235,}))***])[:alpha:]^", 0, 0 },
+ { "[([t(\?<!(\?<!4])[:u(\?=pperword:]))-])}}}}}}}}}}}}}}}}}c{-"
+ "39,}[:digit:]$-",
+ 0, 0 },
+ { "([^)]{241}[:xdigit:][:upp(\?=erwo(\?(rd:]-xF5b{})q[:ascii:])"
+ "T4U{185}9999999999)()X&Ny[:alpha:]@@@@@@@@@@@@@@@@@@@@@@@@@@"
+ "@@@@@@{69,}[:alnum:]x{d7f8}p-[:digit:]",
+ 2, 0 },
+ { "(f)(${,111}{25,}!\\d{,94}[:blankcntrl:]@[:space:][:ascii:])-"
+ "237{,232}DQVVVVVVVVVVVVVV)-",
+ 2, 0 },
+ { "PP[:g(\?!raph:]){}", 0, 0 },
+ { "([[^-][^4[:digit:]NNNNNNNNNNN]TVU:])[:ascii:]", 1, 0 },
+ { "(([^(\?[[^<=)][:graph:]+iiiiiiiiiiiiiiiiiiiiiiiiii0INFX[:"
+ "xdigi(\?(t:][:blankcntrl:]][:graph:]qM6A[:alpha:][:graph:])"
+ "1*]eFvvvvvvvvvv)v-)U))t{89}",
+ 2, 0 },
+ { "[^ZZZZZZZZZZZZZZZiiiiiiiiiiiii(iiiiiiiiiiiiiii{}))))))))))))"
+ "))))))]))))))))))))))))))))))))[:digit:]-",
+ 0, 0 },
+ { "ddddddddd+zzzzzzzzzzzz[:graph(:])ssssssM{-223}[:graph:]", 0,
+ 0 },
+ { "[:alph(\?>a:])x11{-144,45}.", 0, 0 },
+ { "[]{#y.^(\?{{}&&&&(\?:[^&&&&&&&&)[:punct:]n{190}OylBQ{(\?!-"
+ "73})2u',x(\?#Ds(\?#{})j(\?{-})})u0(((((((\?{(((([:alnum:])"
+ "MC})b=71TncyE>[:xdigit:]*\\f]{}]\"p#!8twZT\")[:punct:][:"
+ "space:]",
+ 0, 0 },
+ { "[^(Z6]8)|'@p8{}[:upperword:]MMMMMMMMMMMMMMMMMMMMMMMMMMMM{}"
+ "7c",
+ 0, 0 },
+ { "$0)@#vp,VcJ.Bdh", 0, 0 },
+ { "[[^(-])nnnn+s`[:alpha:][:blankcnt[^rl:][:upperword:]{-15,}]["
+ ":g(raph:]c]){,-177}6[:upperword:]##################{,-14}",
+ 0, 0 },
+ { "[[(5C{86(,}PPrrrrrrrrrrrrrrrrrrrrr{150,182})N{}LSC|)-[:"
+ "alnum:]{}KKKKKKKKKKKKKKKK<4=~7K3PPPPPPPPPPPPPPPPPPPPPPP[:"
+ "lowerprint:]]]",
+ -1, 0 },
+ { "([^(x{145b[5}^hfc.0)+]z@_&lA{-34,}])X\?", 1, 0 },
+ { "([(\?<=)(\?!])l)L", 1, 0 },
+ { "({-104,}DrPPDF4444444444444[:space:])[:space:]", 1, 0 },
+ { "())))", 1, 0 },
+ { "[[^((\?>\?(\?[{})q5v}r7t(P)xtffffffffffff))]{,-66}kdExX&-"
+ "SCeCzzzzzzzzzEc)E,\"^I]x{e629}|{}]",
+ 0, 0 },
+ { "[h[:punct:]p\\[\\\\(\?:\\\\[^\\\\)Eo#:C$u[^T/"
+ "ysA[*%nM:f]{,221}[:lowerprin[^t:]{]bx{f285}E]E[:alnum:]+]"
+ "1oe3B][:alp(ha:]]fh7}M$l)D{17}",
+ 0, 0 },
+ { "IIIIIIII[^IIIIIIX]-_S[:digit(\?#:])"
+ "33333333333333333333333333[:punct:]iiiiiiiiiiiiiiiiii",
+ 0, 0 },
+ { "[^[[:punct:](\?((\?:^ "
+ "#Q_po(\?=[:alpha:]{}z()(\?!======'wq$Q2)LLLLLLLLLLLLLLLe("
+ "C9gggggggggggggggggg[(\?<=:alnum:]()\?<!{-85,}W[[[[[[[[[[[[["
+ "[[[(\?{[[[[[[^)(]\?])|uuu[uuuuuuuuuuuuuuuuuu{,-20}p${}]MHI&"
+ "7s:\?$[:digit:]-:)_V`*{-52,}{250}$:ME9izF/"
+ "uP[:blankcntrl:]})''''''''''''''''''''''''''''')"
+ "CCCCCCCCCCCCCCCCCCCCCCCCdd[:ascii:][:lowerprint:]."
+ "Mcccccccccc2B{-230,}$[:digit:]",
+ 1, 0 },
+ { "()|mOAuK~P144[:space:]^9dddddddddddddddddddddddddddddd[:"
+ "blankcntrl:]",
+ 1, 0 },
+ { "[^[^[^.L[^-vEUl(\?>(\?=a!Ib1P]])])~~~~~~~]xE9", 0, 0 },
+ { "X()", 1, 0 },
+ { "[^()(\?#G(\?<!)(\?=^r])*,XXXXXXXXXXXXXXXXX@)444444444", 0,
+ 0 },
+ { "([[((\?<=({,-70})-[:xd(\?=igit:]{,138})", -1, 0 },
+ { "[(^]{62,67})", 0, 0 },
+ { "([((])[:space:]))", 1, 0 },
+ { "(a{(109,})[:alpha:]{,-121}{})]RRRRRRRRRRRRRRRRRRRRRRRR{}{"
+ "125,}ttttttttt{46,}`[:space:]",
+ 2, 0 },
+ { "[^[^([q[8]~.IPmiBSspP)]QpX[pT==8@lulANS]]{,-98}]", 0, 0 },
+ { "[^77777777777777777777777(\?>777777])", 0, 0 },
+ { "(),e<^X~{[:alpha:]{}G{70}", 1, 0 },
+ { "({-211,}'){}", 1, 0 },
+ { "[^(\?imsxsx:{}[*])cccccccccccccccccccccccccccccccc<z0W8]$",
+ 0, 0 },
+ { "(){2,89}$z", 1, 0 },
+ { "((050[^\"\"\"\"\"\"\"\"z]8|j{}{,-112}$).pP)qq1~hW}L", 2, 0 },
+ { "[[^[(+xx(\?<!xxxxxxxx(\?!xxxxxxxxxx(\?#(\?>[x))(\?:]r.]]]))["
+ ":graph(\?<=:])))",
+ 0, 0 },
+ { "[^([(\?#)(\?(\?(<=)l|\?(\?!])kkkkkkkkkkkkkkkkkkkkkkkkkk", 0,
+ 0 },
+ { "[:xdigit:]K(KKKKKKK)^3c.OOO{-240,-10}2{-97,-139}*{-34,}[:"
+ "xdigit:]",
+ 1, 0 },
+ { "[([^66666666F(\?>FFFFFFFFFFwpP)LLLLLDeDA&Am$l[:xdigit:]!T5#]"
+ "n[:alpha:]U*)))))))))))))PP]",
+ 0, 0 },
+ { "[[[:punct:]u^[:xdigit:]L(\?:[:xdigit:][[:graph:]PP{21}A[:"
+ "alpha:]8%I(M%b<eE~#C@r=uG~~~~~~~~~~~~~~~~~~~~~~~~~~~~+w]pP)"
+ "T]]$$$$$$$$$$$$$$${-121,}|l",
+ 0, 0 },
+ { "([(107{,-4(\?=}~[^D)])f]{,46}+ri<)", 1, 0 },
+ { "[(\?<=]{,208}+~)", 0, 0 },
+ { "[^444(\?<=4444444[:alnum:]&[,i]0)[:alpha:][:upperword:]", 0,
+ 0 },
+ { "[^([^(\?()*+)SS(\?>SSSSSSSSSSSSSSSSSSSSSS]]]]]]]]]]]]]]]]]]]"
+ "]]]]]]]]]]]{,-1}])[:blankcntrl:]============================"
+ "===[:punct:][:blankcntrl:]Z[:space:][:ascii:]$|$[:"
+ "blankcntrl:] JR.{,133}[:alpha:]$\?)<]",
+ -1, 0 },
+ { "(OL[:u[pperword(:][:s[^pace:].[:spac(e:],,,,]*])$)\?)", 1,
+ 0 },
+ { "(VI[:digit:][:alpha:]6)EG", 1, 0 },
+ { "({}){-2,-40}rrrrrrrrrrrrrrrrrrrrrrr[:punct:]", 1, 0 },
+ { "()q", 1, 0 },
+ { "[^([^[([^C|])]{,-56}[:xdigit:]{-144,}V])fYv{-[40,-58}$@@@@@@"
+ "@@@@@@@]|Y(-]-.]h-[:dig(it:])>>>dddddddddddddddddddddddddd{"
+ "101,}",
+ 1, 0 },
+ { "([P,{1(\?(\?(<=28,-218[^)}LoZX)])!!!!!!!!!!!!!!*[:blank(\?!"
+ "cntrl:]ed)\\\\\\\\\\\\\\\\\\\\[\\L\?][:graph:]:*Y{-108,120}"
+ "xCC)]",
+ 1, 0 },
+ { "(A[:space:]PP{185}a^!!!!!!lllllll)*db\?$Pfr", 1, 0 },
+ { "{-21,-118}kG[(\?{:xdigit:]})[:punct:]{69}"
+ "Qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy5{}TTTTTTTTTTTTTTTTTTTTT",
+ 0, 0 },
+ { "[[^[P(\?<=P$X>0^d.[:punct:](\?#ccccccccccccccccccccccccc{}"
+ "3N000(\?>00000000000000000000000000000]f[:punct:]5)).R======"
+ "=========={,222}^wwwwwwww$)]-{} "
+ "]{,-22}CjP{242,}",
+ 0, 0 },
+ { "[(\?#^]{})", 0, 0 },
+ { "[^([[([([[([^[^(\?:(\?(\?(!)]\"))h>\"RRRRRRRRRRRRRRRR[^"
+ "RRRRR{68,-65}7Q(\?{]",
+ 0, 0 },
+ { "(P{}){175,}PP{}rttttttttttt", 1, 0 },
+ { "[:bla(\?{nkcntrl(\?#:]})))))))))))))))))))))))!!!!sR{})", 0,
+ 0 },
+ { " [:digit:]dAAAAAAAAAAAAA^[:ascii(:]55)^", 0, 0 },
+ { "($*)dZY", -1, 0 },
+ { "[:graph:][:lowerprint:]S[:gr(\?=aph:]{-128,}"
+ "666666666666666666666{}[:upperword:]|"
+ "nnnnnnnnnnnnnnnnnnnnnnnnnnB)c[:xdigit:]{-225,}{-4,}{-192,}"
+ "QQQQQQQQQQQQQQQ@@@@@@@@@@@@@@@@@@@@@@.",
+ 0, 0 },
+ { "([:digit:]s{44,}{}{-31,}c{,-130}pP){-241,}UeN", 1, 0 },
+ { "([^)((\?>\?#{}hK\"V2\?d][KKK(\?imsxim:KKKKKKKKKKKKKKKKKKKK[^"
+ "KKKKKKKKKWWWW[WWWWWWWWWWWWWWWWW)B])_l_3",
+ 1, 0 },
+ { "[(^[(\?!*){[^,91}].j]*]L)*c|[:alpha:]&", 0, 0 },
+ { "[^[[[^[777GGG(\?:W_U(\?imsxms:[:punct:]A]-)[:digit:][:"
+ "blankcntrl(\?(:]][:alnum:)])]WRRRRRRRRRRRRRRRRRRRRRRRRRRR]{"
+ "31,}[:xdigit:]][:xdigit:]))))))))))))))))))))))$[:xdigit:]",
+ 0, 0 },
+ { "[:ascii:]m*[:punct:]#[(\?<!:punct:][:alpha:]-,"
+ "7vyXeeeeeeeeeeeeeeeeeeeeeeeee^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
+ "^%%%%%%%%%%%%%%%%%%%%%%%%%%%%[:digit:]''''''''''''''''')",
+ 0, 0 },
+ { "([^*[(:punct:]9999999999999999999{147,}]j{,193}{171}Z-)){"
+ "208}0[:graph:]yDt",
+ 1, 0 },
+ { "(dw[[:alpha:]U]ttt[tttttttttttttttttttt]Q^171e)[:xdigit:]/",
+ 1, 0 },
+ { "[[^((\?#)Tqqqqqqqqqqqqqqqqqqqqqqqqq105++++++++++++++++++++++"
+ "++++b7V+7dit]])|D",
+ 0, 0 },
+ { "{}P7.Ajh[:xdigit:]^[:blankc((\?(\?<=nt[rl:]FFF)-]){}o|a[:"
+ "grap(\?!h:]))PsssssssssssssssssssssssssssssssN^{-60,}Kb",
+ 0, 0 },
+ { "[:alpha(\?(:]$!_+777777777777777777777777O)666)lll[^llllll[^"
+ "l{{{{{{{{{{{{{{{{{{{{{{|]{-217,}MoEl`7)^)LlU[:alph[a:]({-"
+ "241,27})]]{-212}{,249}n)X",
+ 1, 0 },
+ { "[U|ajP[:alnum:]n[(:digit:]]W)[:graph:]b[:xdigit:].P", 0, 0 },
+ { "(([:low(\?-imsx:erprint:]|{}[:ascii:][:gr(\?:aph:])>>>>>>>>>"
+ ">>>>{,-129}))\?{-226,}^P)R",
+ 2, 0 },
+ { "[^[[nnnnnnnnnn(\?=nnnn(\?!nnnnnnnnnnnn(\?#nnnnnn{,-38}N){"
+ "202,}]$[:alnum:])]t][:alnum:[]^=w){237}][:alpha:]-[:alpha:]+"
+ "e",
+ 0, 0 },
+ { "()[(\?(:digit):]+qc)O88888888{,151}aJ", 1, 0 },
+ { "([^([(\?!sv(\?=)d]{-200,})N))]Z{-73,15}", 1, 0 },
+ { "([\?\?\?\?|||||||||||(\?{||(\?=||||||||-}[))Ehhhhhhhhhhhhh{,"
+ "202}&TcfL((\?:>)((\?!\?>$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$8[:"
+ "alpha:]\\d])]C[:graph:]h*,\"\?u{|mU,a)[:blankcntrl:][:"
+ "lowerp(\?>rint:])PPnP+9.[:xdigit:]*PPjjjjjjjjjj~y<#*scf_\"^"
+ "e[:xdig(\?(i)t(:])~$y)^){-131,77}^L%",
+ 1, 0 },
+ { "[^[(((\?>)$}h9$B5+yhU/"
+ "Nqh$YYYYYYYYYYYYYYYYYYYYYShK)3WHw1vMMMMMMMMMMMMM(\?="
+ "MMMMMMMMMMMM[:alnum:]/"
+ ")dddddddddddd(dddddd\"e5zLW)+![:space:]+BHGHfAS]"
+ "\?IIIIIIIIIIIIIIII*&&&&&&&&&&&&&&&&&&)NNvwDteepjdm<<<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<<<<${61,219}D][:digit:]0",
+ -1, 0 },
+ { "[:punct:][{177,(\?=234}]ix9*)", 0, 0 },
+ { "([^K{,3(\?<=4}]I)\?U)", 1, 0 },
+ { "[([^[[[^([([^[^(\?=])X", 0, 0 },
+ { "[:blankcntrl:(])qd_R\?{\?r[=\"[^[^6]vX8)a+{C%H84CK6Uy#E]sE{"
+ "208}",
+ 0, 0 },
+ { "PPPPPPPPPPPPPPPPPPPPPPPPPPnnnnnnnnnn()[:upperword:]us", 1,
+ 0 },
+ { "x{,46}[:graph:]LU{}CU)", 0, 0 },
+ { "()-t|[^W{}][:lo[^werprint:]{}]\?b5", 1, 0 },
+ { "()x5A", 1, 0 },
+ { "[([^]-217)]s{-47,135}0000000000000000000000000000000{,-108}",
+ 0, 0 },
+ { "[^((\?{[^L\?u]})f", 0, 0 },
+ { "()[[^^(\?{y(\?=VF_(\?<=]D}))]-= {46,})^5bIEQ{,-96}Z", 1, 0 },
+ { "([^{}f[:punct:]\"X%%%%%%%%%%%%%%%%%%%%]5{-194}A[:punct:]"
+ "mnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn+AAAAAAAAAA-)",
+ 1, 0 },
+ { "(CCCCCCCCCCCCCCCCCCCC{-230}352{-182,-68}O4{})", 1, 0 },
+ { "([^[^\?[:space:]$TTTTTTTTTTTTLLLLL[^LLLLLLLLLL[^({}{4,-179}]"
+ "]J] C]){}C{}{-224,})QQQQQQQQQQQQQQQQQ^",
+ 1, 0 },
+ { "([[:alnum:]].){-155,-82}dzI{55,}^", 1, 0 },
+ { "([[:alnum:](\?#{88,-178})[:graph:]NC\"pI[:punct:]rmWd5y^p+"
+ "gUP]YYYYYYYYYYYYYYYYYYYY~{,-62}{,200}{-109}{}+"
+ "333333333333333333333333333333{}p)^.hhhhhhhhhhhhhhh",
+ 1, 0 },
+ { "[000000(\?mmsx:00000000000000000000000)M]]]]2*`[^]QQQQQQQQ("
+ "\?<=QQQQQQQQQQQQQQQQQQQQQQQ])\"<h\?",
+ 0, 0 },
+ { "[^((<g(\?>5j[bbbbbbb(\?{bb)o{}3(\?imxisx:E]g})YYYYY[:"
+ "blankcntr(\?#l:].(()w264[:ascii:]^)[:ascii:]G)&(n "
+ "{^PGn[:xdigit:])nv_e|]{-103,30}",
+ 3, 0 },
+ { "[^(([(\?!{}@[^HCO[[^^D[|]{,-49}][:xdigit:]]c`4[:ascii(\?<!:]"
+ ")$66666666666)*)]PP$Z[:alpha:]{,-235}UK],(aT/"
+ "+6rbMqs60EloA)[:g(\?isx:raph:]!)]z$o{-24,}x1E[:blankcntrl:]"
+ "ZDFvk",
+ 1, 0 },
+ { "[:blank(\?=cntrl:]US@.!\"[:digit:]*E)$16182", 0, 0 },
+ { "[-{}x{3772[}][:(\?<=xdigit:][:u(\?#pperword:].W)aD)<pfN<b=C|"
+ "-{-38}EZdOP|!>ggggggggggggggg\\\\\\\\\\\\\\\\\\\\\\\\\\Ef[:"
+ "space:]\?][:ascii:]{21,}",
+ 0, 0 },
+ { "([:xdigit:]W[:u(pperword(\?::]jS "
+ "[:upperword:]*)[:alpha:]nnnnnnnnnnn))-148}SSu",
+ 1, 0 },
+ { "([^(\?!\?)[(:upperword:])Bx^x$~lCr6*)6", 1, 0 },
+ { "[{,-78}Y[:xdigit:][^s(\?>]P[:space:])]YYYYYYYYY[:punct:][:"
+ "alnum:][:blankcntrl:]",
+ 0, 0 },
+ { "([MMMMMM(\?(MMM)M(\?<=MMMMMMMMMMMMMMM[^M)]en][:punct:]-[:"
+ "alpha:]))Nr[:space:]",
+ 1, 0 },
+ { "~=1([^(\?=(\?:l){}])j{-44}{-18}[^u[:graph:]]{-187,}[:xdigit:"
+ "]w[:alpha:])",
+ 1, 0 },
+ { "[ccccc(\?>c(\?{cccc[ccccwetoCei+)w&-+{,-142}[:alpha:]"
+ "PP66io4(|zkA=],,,,,,,,,,,,,,,,,,,,,Lx5Cx{d2bb}]{188}U~~~~~~~"
+ "~~~~~~~~~~~~~~~~})",
+ 0, 0 },
+ { "Q|0\"[:d(\?:igit:]^{,-174})", 0, 0 },
+ { "[^[(\?>rh])]", 0, 0 },
+ { "[ees{{{{{{{{{{{{{{{{{bbbbbbb4`ml******(\?=****+])", 0, 0 },
+ { "((hdG[((\?<=:dig(it:])[^[:alpha:]$(\?sxi:)x{11390}[(\?{:"
+ "upperword:]~)i 8[:blankcn[trl:(])]+{,-183}Zqp",
+ 2, 0 },
+ { "Dd{D8`+DW={-[53,1(\?<=71}])", 0, 0 },
+ { "[:(\?(alpha:][:punct:])", 0, 0 },
+ { ".LLLLLLLLLLLLLLLLLLLLLLLLLLLL{}pP[:punct:]x0CZ{30,}!!!(!!!!!"
+ "!!!!!!!!!!!!!!!!!!!!==@77.%[:graph:]D)",
+ 1, 0 },
+ { "[^[^[[r(\?#]){-237,}RRRRRRRRRRRRRRRRRRRRRRRR[^Rll(\?!(\?{"
+ "lllll]",
+ 0, 0 },
+ { "()*ooooooooooooooooooooyyyyyyyyyyyyyyy", 1, 0 },
+ { "{,4(}D)JJJJJJJJJJJJJJJJJJJJJJJJJ", 1, 0 },
+ { "((b.D{}[:al[pha:]{64}]{})==========================[:alnum:]"
+ "h>77b)!Ab",
+ 2, 0 },
+ { "([^[^[^oooooooooooooooooooooo][:space:][:punct:]PeniKe*~$"
+ "g\?${>[:lowerprint:]w))))))))))))))){}yyyyyyyyyyyyyyyyyy]pP."
+ "|QhZ]{,190})sssssssssssssr+=[:blankcntrl:]"
+ "WWWWWWWWWWWWWWWWWWWWW",
+ 1, 0 },
+ { "([*(\?{})hhhhhhhhhhhhhhhh]G{,-170}QdErrrrrrrc-"
+ "jjjjjjjjjjjjjjjjjjjjn+{-130,-10})PpDS@Bee",
+ 1, 0 },
+ { "([:b(\?=lankcntrl:]))T[:alnum:]{-224}ywt", 1, 0 },
+ { "([633(\?<=333(\?<=3333333333(333333)^\?]aGA)[:digi(\?>(\?{t:"
+ "])$[[:space:][:xdigit:])|8T\?',_{171}{}{113}b\?5kAv0/"
+ "7{})`huh>xM]C8pYRz]s$Eu08)",
+ 1, 0 },
+ { "-(pP)[:alnum:]$^", 1, 0 },
+ { "[^x(\?{{17681}]P*)U(_t/8E_\"iN})3333333", 1, 0 },
+ { "(([^([[r(\?=[[^^*kx$][:alpha:]:::[:::::[^[^::::::::((\?{\?{:"
+ ":]).^p[:space:]}){52}{}]W{}fn",
+ 2, 0 },
+ { "[:(\?>punct:]Ef[:xdigit:]x{c07b}{-50}Z{129,}YL1T`\\A)x[:"
+ "punc(\?=t:]e[:xdigit:]2c6E46Y)+n ",
+ 0, 0 },
+ { "[^(\?!{,-79}[:punct:]'|}>,)][:blankcntrl:]{-118,-231}{-119,-"
+ "50}:XXXXXXXXXXXXXXXXX-~{}$txlB)3KFL",
+ 0, 0 },
+ { "[^(([^fccccccccccccccccccc(\?<!ccccgQeKMfKzz]X$$$$$$$$$$$$$$"
+ "$$$$$$$$$$$$$$$$$[:l(\?<=(\?<=owerprint:]))s{-97}{}))EUi${,-"
+ "132}'{79}---------{,-93}77777777777777777[:lowerprint:].:H)["
+ ":punct:]nnnnnncP\?s1:dGed{186}N@pppppppppppppppppppppP{-212,"
+ "-110}[:space:][:lowerprint:]$S}7{-112,164}-*.{-184,}"
+ "OOOOOOOOOO]f\?",
+ 0, 0 },
+ { "(([\?#(\?>)])qcU$Q7|82\?{})", 2, 0 },
+ { "[^yyyyyyyyyyyyyyyyyyyy(\?#yyyyyyyyyyya][:ascii:]\?)", 0, 0 },
+ { "(([((\?{)EEEE(\?<!EEEEE(\?:EEEEEE~)}){244,}"
+ "QQQQQQQQQQQQQQQQQQQ(\?>QQQQQQ(\?!QQQQQ][:digit:]\?))"
+ "99999999999999)[:digit:][:upperword:]b))PP{}{}",
+ 2, 0 },
+ { "(K(c=B))", 2, 0 },
+ { "(G`*s\?b[:g(raph:]))", 1, 0 },
+ { "[^[([[[*QQQQQQQQQQQQQQQQ(\?=(\?=QQQQQQ(\?<!"
+ "QQQQQQQQZddddddddd((\?{\?>ddddddddddc{22,}iiiiiiiii("
+ "iiiiiiiiiiiiiii(\?#iiiiiii[^i))\?\?\?\?\?\?]WWW)[:"
+ "lowerprint:])]{-60,202}+[:upperword:]f[:xdigit:][:alnum:]{,-"
+ "214})1~~~~~~~MMMMMMMMMMMMMMMMMM.",
+ 0, 0 },
+ { "({-102,})A.", 1, 0 },
+ { "[((((\?<!(\?[^>(\?#\?()))p\"JD.{}(\?>)))((\?{l(\?<=).'053][:"
+ "xdigit:]N+)})]WWWW%[:asc(\?{ii:]}))B[:alnum:]X){}s[:digit:]",
+ 0, 0 },
+ { "x7&{139}WWWWWWWWWWWWWW[:blankcntr[^(\?<!l:]-71]\"{-167}cqkI)"
+ "[:dig[^it:]{}{}[:digit:]*[:punct:]-[l11111111111111111(\?("
+ "111111111{175,-216}~[:alnum:]`+X1F)vCpWSp(\?>~[^n@f`````````"
+ "````)````````P])Y,N{}{}]{}pXF@)",
+ 0, 0 },
+ { "G[([(\?(^)$])P]^[:alnum:]){,-48}[:blankcntrl:]{}", 0, 0 },
+ { "[[^[^f(\?=f(\?<=fffffff[^fffffffff[^fffffffff(\?<=fff]){-"
+ "194,150}fx{e5a4}V",
+ 0, 0 },
+ { "9[:xdigit(\?{:]})", 0, 0 },
+ { "[^([[(\?>()$xxxxxxxxxxxxxx[xxxxxxxxxxxxxxxx((\?=aA)s13]])pp["
+ "(\?>pppppppppppppppp|{}){20,}]b)]{-179,183}{-204,}[:ascii:])"
+ "]-11111111{}{,132}qooooooooooooooooooo{}${}|9t",
+ 0, 0 },
+ { "([^[{}]\"[^6]*-{,-106}{}u]BR~8WG,U-)[:blankcntrl:]", 1, 0 },
+ { "[''''''''(''''''''''z])c", 0, 0 },
+ { "[^[(\?>])[:alnum:]r[:alnum:]+{,215}D]", 0, 0 },
+ { "([({,127}7Qr(\?:z)pPNev%}(\?msximsx:4(\?<!){}&.D5555(\?<="
+ "55555555555555555555i$[:xdigit:]){,-157}[:graph:]U[:punct:]"
+ "nn(\?=nnnnnnnnnnnn(\?>nn(\?:nnnnnnnn_U{}]E)):^"
+ "oooooooooooooooooooooooooooo)",
+ 1, 0 },
+ { "[^(\?#)(\?<!k2z]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]BW[:alnum:][:"
+ "graph:]{157}Y]s$C)[:graph:]{,-189}",
+ 0, 0 },
+ { "$+CCCCCCCCC[^CCCCCC(\?<=Ca=]r{-81}[:alpha:][:alpha:])E=", -1,
+ 0 },
+ { "[(((\?=\?{([^(\?<=)])>!(([:alnum:]{252}{}})ffffffffffffl){}"
+ "A2r\?~ImE\"[:punct:]){}[:digit:]",
+ 2, 0 },
+ { "([:blank[cntrl:]].t^P)", 1, 0 },
+ { "[^[(\?:X])|rrrrrrrrrrrrrrrrrrrrrrrrrr*P]Q", 0, 0 },
+ { "[[[^(\?{((\?<!))s})(\?<!A){14}(\?:L*+TTTTTTT]U{[^-12([\?!,}"
+ "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?)Y`Y)L]|]]|"
+ "]",
+ 0, 0 },
+ { "hkXzf',]yP$+[:u(pperword:])", -1, 0 },
+ { "(#[:blankcnt(\?iximsx:rl:])$QQQQQQ{}[:digit:])\?A", 1, 0 },
+ { "(B{-34,})*{,106}", 1, 0 },
+ { "[(\?{:graph:]})", 0, 0 },
+ { "((){}{,63}[:punct:]^t[:space:])^17737", 2, 0 },
+ { "([^[SSSSSSSSS[SSSSSSSSSSSSSSS[([[[{38,}]Jn][:alpha:]])])$'",
+ 1, 0 },
+ { "[^({}{95})B{1(\?>15}]x{f779}ZZ,Wo)O[:alpha:][:lowerprint:]{"
+ "81,228}Q[:upperword:]",
+ 0, 0 },
+ { "[[^[^()n[[[[[[[[[[[[^[[[[[[[[[[(\?: G)(\?{K![^m) "
+ "j(\?:C|((\?:n*Xlaa908:n$m,))[:xdigit:]x(\?{{1a5cd}"
+ "pppppppppppppp(\?(pppp)p(pQ)))"
+ "ddddddddddddddddddddddddddddddd]q[:alnum:(\?{]Ga})\?})@[:"
+ "lowerprint:]{,169}[:blankcntrl:][:graph:]]n{-76,}|U\"{,-54}"
+ "t]I{}{-64,-232}]\?].\?{-111,227}) "
+ "@hFp\?j=H$Wbu<{,209}De{,145}{206}-})[:blankcntrl:]",
+ 0, 0 },
+ { "[^[^(LLLLLLLLLLLLLL[^L[L[:alpha:]3{,189}(\?#(\?>n){}^"
+ "EXXXXXXXXXXXXXXXXXXXXXXXXX]c*)^r=$WWWWWWWWWWWWW",
+ 0, 0 },
+ { ")w###################", 0, 0 },
+ { "{,121}[:d(\?(i)git:])E\?[:punct:]LLLLLLLLL[:ascii:]+", 0,
+ 0 },
+ { "([]]]]]]]]]]]]][:space:]Jrt3o.]b)pwwwwwwwwwwwQfm~", 1, 0 },
+ { "[+-{,-120}*(\?!()t*(\?(\?{>G)F)yd]V{}f<\?}){245}"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx[:upperword:]",
+ 0, 0 },
+ { "(DDDDDDDDDDDDDDDDDDDDDDDDDDDDDc[:space:][:pu[^nct:]{-11,12}["
+ ":ascii:][:alpha:]{,155}P])",
+ 1, 0 },
+ { "()ggggggg{-136,-21}", 1, 0 },
+ { "([^((\?<=U\?)(\?=^^^^^^^^^^^[^^^^^^^^^^^^^///(\?#//[////////"
+ "////////////"
+ "(\?()#######b+]$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$^[:digit:])"
+ "\\U]Q8@}4d)\\U",
+ 1, 0 },
+ { "A[:graph(\?::])-mo=U[:upperword:]"
+ "ttttttttttttttttttttttttttt",
+ 0, 0 },
+ { "[^(((\?=\?im-m(sx:)c~~[^~~~~~~~~~~~~~(\?>~~~~~~~~~~~~~"
+ "SSSSSSSSSSSSSSSSSSSS]{51,}[:digit:]{,-179}N))kk["
+ "kkkkkkkkkkkkkkg$)[(\?::punct:]zWl)]|)*",
+ 0, 0 },
+ { "[((\?=()+A)][:graph:]x0B)[:graph:]", 0, 0 },
+ { "(nR%B[:blankcntrl:]C=|en-[:digit:]n[:graph:]HHHH[HH]D\?%[:"
+ "digit:]MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM.z(oF9zW8A7cfff(f))-["
+ ":blankcntrl:][:blankcntrl:]A[:digit:])D{,-243}",
+ 3, 0 },
+ { "([[()]]{,-251(})\?L)uw@", 2, 0 },
+ { "\"|{(,-144})A.ooooooooo(ooooooFFFFFFFFFFFFF\?)n{,-18}", 2,
+ 0 },
+ { "([^([(([[^([000000[0(0(\?!0(\?=0000000])45|E]", 1, 0 },
+ { "[B[[[[[[[[[[[|{}*oKqv%(\?<=wsQ{1pMeK1^6%nLNqi<@ge][:punct:]="
+ " M@* "
+ "D|NwL\\-"
+ "117\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"~"
+ "Qnd]h.O\"01x:[:alpha:]^){}D}\"",
+ 0, 0 },
+ { "([[RRRRRRRRRRRRRRRRRRRRRRRRRRRRxpSrx{7d79}*oJ2`Ft{n1,3g:1H@"
+ "bT$D "
+ "&[n/"
+ "Cg)=ld@Ir{Fk>*4*`(\?>````````````````````(\?:`````.........."
+ "...........]]{,246})7 \"F4[^F|/g)]+e`rw@{,-69}H)",
+ 1, 0 },
+ { "([(\?<=)X[:digit:]PP.[(\?#:((\?#\?#graph:])[:digit:][Q+)(N]["
+ ":alpha:]]f)[:graph:])+Elllllllllllllllll[:digit:]=)pP{uU-"
+ "20bzY|ZKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKt<c",
+ 1, 0 },
+ { "[^(([^$(\?:(\?#w)[(\?::punct:]]d{-149,}[:ascii:])[:"
+ "blankcntrl:]@@@@@[@@@@@@@@@@@@@@[:graph:][:xdigit:]O[:alpha:"
+ "]2$-[:graph:])[:lowerprint:]-\?#S[:blankcntrl:][:alnum:]){-"
+ "77,}]d[:digit:]N5v+Sqqqqqqq^% "
+ "-I4]*.)^[:alnum:]"
+ "JDfjMRU7ttttttttttttjjjjjjjjjjjjjjjjjjjjjjCCCCCCCCCCCCCCCCCC"
+ "CD{,21}{0,67}[:graph:]{,208}B",
+ -1, 0 },
+ { "(%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%[:ascii:])i{}[:lowerprint:]"
+ "epxxxxxxxxxxxxxx[:lowerprint:]r-",
+ 1, 0 },
+ { "([(^w(\?!)()])-s", 1, 0 },
+ { "[aIIIIIIIIIIIII(\?imsxims(\?=x:IIIIIIIm^NXXXXX(\?!("
+ "\?isximsx:XXXXXXXXXXXXXS0]F)z))+"
+ "rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr{,-237})"
+ "ZZZZZZZZZZZZZZZZZZZZZZ",
+ 0, 0 },
+ { "(Z)[:alpha:]", 1, 0 },
+ { "U#Z(=)", 1, 0 },
+ { "([:lowerprint:][:punct:])1cVb*[:xdigit:]&&&&&&&&&&&&&&&&&&&&"
+ "&&&&O",
+ 1, 0 },
+ { "()~K`3/[^*h[]G6[:upperw(\?()ord:]w)[:punct:]]{}", 1, 0 },
+ { "[[[]V[:digit(\?>:])|l*KKKKKKKKKKKKKKKKK,,,,,,,[,,,s.{148,}"
+ "P33333][:lo(\?<!werprin(\?!t:]ZZZZZZZZZZZZZZZZZZZZZZZ]{,-"
+ "229}{-160,}){,-211}XPPP].{}z[:alnum:][:alpha:(\?=]t{166,}"
+ "uuuuu6]i*p(m))[:space:]E|S",
+ 1, 0 },
+ { "[^(h(\?(\?({#2})(\?(\?#>Q){,57}%[:digit:]"
+ "\?\?\?\?\?\?\?\?\?\?.[)]]d{)-49,}f)^O{,68})\?C",
+ 0, 0 },
+ { "(}u])18621", 1, 0 },
+ { "[:as(\?=cii:][^(\?=)(S-{.F-[:punct:]3-105^[:lowerprint:]"
+ "111111111111111111111111---)][:alnum:][:ascii:]JJJJJwHSk",
+ -1, 0 },
+ { "[^3>>>>>>-sZ^^^^(\?>]Y[:di(\?(\?imxim:#git:]{-158,-102}[:"
+ "punct:]{}{87,})))[:upperword:]",
+ 0, 0 },
+ { "[(\?<!^r]$W){}*[:alpha:].[:digit:]", 0, 0 },
+ { "[:ascii(\?::[^])X]-", 0, 0 },
+ { "[([^]Z)[:upperword:]N{}*[:graph:]*^", 0, 0 },
+ { "([[(\?#^[(:graph:]]){205,}[:gr(aph:]T%]^"
+ "MMMMMMMMMMMMMMMMMMMM){) <v\\[:digit:])",
+ 1, 0 },
+ { "[^Y.h~b(\?<=~P{(\?=169,65}\?[^\?\?\?\?\?\?\?\?\?["
+ "\?\?\?\?\?\?\?\?\?K\"s`[yT7oP[:alpha:]{})]zrrrrrrrrrrrrrr)]"
+ "KKKKKKKKKKKKKKK[:digit:]S][:lowerprint:][:digit:]",
+ 0, 0 },
+ { "(s)", 1, 0 },
+ { "[u(\?!uuuuuuuuuuuuuuuuuuuu[:digit:]{,48}[:graph:]WL[:alnum:]"
+ "]v=_)VN>{AjBBBBBBBBBBBBBBBBBBBBBBB[:upperword:]`'W)",
+ 0, 0 },
+ { "[^([[()DN1[^][|]\?]{-104,}])[:space:]][:lowerprint:]r[:"
+ "alpha:].DU",
+ 0, 0 },
+ { "[^((33333333333333333333333(\?<=3333333D))"
+ "kkkkkkkkkkkkkkkkkkkkkkk[k[:alpha:]])]X+",
+ 0, 0 },
+ { "[({,-17})[@e{220,(\?#41}])]]{-213,-225}", 0, 0 },
+ { "[[^(\?#[(\?:^[[(\?(^]))]])]vvvvvvvvvvvvvvvvvvvvv{,96}|m]{-"
+ "79,248}[:alpha:])",
+ 0, 0 },
+ { "([[(\?imsisx:^}$,-[:al(\?>num:]Xqqqqqqqqqqqq{-185,154}]b#+T)"
+ "{-241,})A{-27}[(\?<!:lowerprint:]X)[:punct:]ME-]+"
+ "BBBBBBBBBBBBBBBBa|{-40}M8mhgD 0HU]{16})",
+ -1, 0 },
+ { "[^(\?>([\?()(\?#))]--R1rk^UnP.[(\?!:digit:]])^)[:upperword:]"
+ "{}0000000000000000000000000000000~U{-139,-19}z<L-228",
+ 0, 0 },
+ { "()-:=3uE$[:alnum:]bP%{-210,}", 1, 0 },
+ { "(U)7777]]]]]]]]]]]]]]]]]]]]]]]]]]]]]c::AA[:alpha:]{,3}f1{"
+ "NzH@3lTf{}{",
+ 1, 0 },
+ { "[C{(\?>})RR(\?=R<]p'N~&.-})6]", 0, 0 },
+ { "[^\?[^(\?(lFt]).[^7Q-])kkkkkkkkkkkk]XTFy\"1Deiv!,'xVK", 0,
+ 0 },
+ { "[^$[^[:xdigit:](\?{*{245,99}h8v(\?!)]]u)Z[:punct:]})[:alnum:"
+ "]+|[:blankcntrl:]u{}[:lowerprint:]+bBJ4+k-v{-116}",
+ 0, 0 },
+ { "S)f{,180}[:graph:]&{12,244}", 0, 0 },
+ { "(([[(.()[^^{80(\?>(\?<=,235})ddddddddd[^ddddddddd(\?<=d.__B{"
+ "36}````````````````(\?:```(\?>```````,,,,,,,(\?:,,)P$U,[:"
+ "xdigit:])zzzzzzzzzzzzz]UUUU[uB]n<&[(:ascii:].][:alnum:])\?S]"
+ "{})d{138,}s9========[:lowerprint:]]OOOOOOOOOOOOOOO|"
+ "yyyyyyyyyyyyyyy$LZ[:lowerprint:]EEEEEEE[:ascii:][:punct:]"
+ "VpP^{-48}D){,46}x))2P))a[:lowerprint:]r",
+ 2, 0 },
+ { "[^(((\?<!):())PPPPPPPPPPPPPPP(\?=[PPPPPPP(\?{PPPPPPPP$)})"
+ "77777777777777777]{,-57}::::::::::::(::::::::::::::::)]g{89}"
+ "__________________[:xdigit:]l[:punct:])N",
+ 1, 0 },
+ { ":02-k\?p3I7aEhJ\\265-[:space:]pP[:space:]x0F[:alnum:]aM4[:"
+ "lowerprint:]sA@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+ -1, 1 },
+ { "a[:upper(\?{word:]})X{-173,}-2F[:lowerprint:]", 0, 0 },
+ { "u,w<g*Q002S{,130}{239}[:lower(print:]cr{-165,}#$k<L/"
+ "&)[:blankcntrl:]aaaaaaaaaaaaaaaaaaaaaa[:ascii:]",
+ 0, 0 },
+ { "(xFA^{-161,93})U[:xdigit:]", 1, 0 },
+ { "[^(\?=]{})mE`", 0, 0 },
+ { "[[((\?(\?#:alnum:]])x6CS[:digit:]{-197,}.)N", 0, 0 },
+ { "[^(\?![])C*[:upp(erword:])-176]", 0, 0 },
+ { "[[^[[^[55555555555555555555555555(\?>555(\?<!555)S][]]A[:l("
+ "\?>owerp(rint:]])]*",
+ 0, 0 },
+ { "Au)khgzAfXIZoZ=g[:digit:]){,186}Upvf=x<]Tbd5Rq\?.", 0, 0 },
+ { "b{-176,}B^[:bla(\?(<!nkcntrl:]{-6,133}#B "
+ ":)<<<<<<<<<<<<<<<<<<<)[:alnum:]$}}}}}}}}}}}}}}}}}}}}}}}[:"
+ "xdigit:]tw",
+ 0, 0 },
+ { "(4IIIII(IIIIIIIIIIIIIIIII{})W{-152,-238}){,-56}^{-142,}", 2,
+ 0 },
+ { "[^([[(\?(\?(!)>>>>>>>>>>>>>(>>>>>>>>D)Ix{(1(\?imxmsx:762)c})"
+ ")A)[[[[[[[[[[[[[[5Rp]DDDDDDDDDDDDDDDDDDDD]Us+\\w[:digit:]{-"
+ "47}[:xdigit:][:blankcntrl:])ddddddddddddddd[^ddddddddddddd[:"
+ "digit:]|]]*{-165,-230}{-212}{53,}]\?",
+ 0, 0 },
+ { "[^[^]]|[:(\?:alnum:])}}}}}}}}}}}}}}}}}}}}", 0, 0 },
+ { "VVVVVVVVVVVVVVVVVVVVVVVVVVVV[:d(i(\?#git:])){{{{{{[:digit:]"
+ "ZfQ55555555{}Z",
+ 0, 0 },
+ { "[L][:blankcnt(\?((\?=rl:(\?=]){-35,[^}){)eJb>>>>>>>>>>>>>>>>"
+ ">>>>>>$ [:xdigit:]l0Tv2Tw2@C[:space:]Zc/{*)>]N3j~.dMBBBB",
+ 0, 0 },
+ { "[[^(\?>(([]))])[:graph:]]{65,}as#Q:lQ", 0, 0 },
+ { "[^[fPPUUUUUUUUUUU(\?#UUU[^UUUUUU(\?<="
+ "UUUUUUUUUGGGGGGGGGGGGGGGGGGG((\?{\?=GGGGGG.MK))+]+)&UxFW)"
+ "rwv\?@D.",
+ 0, 0 },
+ { "{-(60,})m", 1, 0 },
+ { "b[(])^w", 0, 0 },
+ { "[][^qVs(\?:(p])X)\?'", 0, 0 },
+ { "()8", 1, 0 },
+ { "(t[:punc[^t:(\?{][:blankcntrl:])})[^8\?]z*]", 1, 0 },
+ { "[:lowerprint:])[:graph:]lppppppppppppppppppppppppppppf", 0,
+ 0 },
+ { "[:alph(a:])[:ascii:]g +z-Bc-U{,%Gk", 0, 0 },
+ { "u[:graph:(\?=]*)W:::", 0, 0 },
+ { "([:alnum(:])l)", 1, 0 },
+ { "[[[}}}}}}}}(\?<!}}}}}}}+(\?{),,,,,,,,,,,,,,(\?!,,,,,,,,]"
+ "99999999999&R[:ascii:]ZZZZ-{-10,}{96}Ed*][:graph:])]}){}{}G{"
+ "-9,}",
+ 0, 0 },
+ { "([^[{}]]Z[[^:graph:]{-47}55555555555555555555555555555[:"
+ "ascii:]s]6,$:3qAew1Y)+)[:punct:]",
+ 1, 0 },
+ { "[[[[[([[[[[[[[[[[[[[[[[[[[[[[[8!1i]')", 0, 0 },
+ { "([((\?(\?#>)(\?{,)At]%M9FSq5)EB", 1, 0 },
+ { "(}````````````````(``{210,})[:(\?#space:]P[:digit:])PP.{-"
+ "227,}$pK~mm ImR|{,51}[:alnum:]<)[:alpha:]",
+ 2, 0 },
+ { "[^(\?<=])[:digit:]", 0, 0 },
+ { "[^'''''''{(\?:178,}e{,16}$QQQQQQQQQQQQQQQQQQQQQQQ$])", 0,
+ 0 },
+ { "[^(\?>@K*)(\?#d18]{78,}B)[:digit:]{-193,}=wg{,59}", 0, 0 },
+ { "[^.{156,}!(\?<=!!!!!!!!!!!!!!(\?{!(\?(!!!!!!!!!!!!!)})"
+ "TTTTTTTTTTTTTTTTTTTTTTTTTTTTT[^}}}}}}}}}}}})}}}}}}}}}}}}}]])"
+ "{}^L#%-{}FC",
+ 0, 0 },
+ { "(eeeee{-169,-100}-fa[:upperword:]N)$Nellllllllllllll", 1,
+ 0 },
+ { "[[(\?!())\?[(\?!:alnum:]e{,28}M])[:punct:]"
+ "CCCCCCCCCCCCCCCCCCCC]{-150,}{-167}",
+ 0, 0 },
+ { "[[@[@(\?#@[@]P]Z{')]{-186,117}]+)7f-", 0, 0 },
+ { "\\Q+kD}]AEM)u ", 0, 0 },
+ { "([(\?{(\?=:::::::::::::&){,210}]^})P{-31,}8[:space:]C[:"
+ "alnum:][:a(scii:]z|[:upperword:])[:alnum:][:graph:])zr~Zk",
+ 1, 0 },
+ { ".[:space:]e[:g(\?{(\?{raph:]})})@@@@@@@@@@@@@wb|~k", 0, 0 },
+ { "()ooooooooo\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"[:graph:]", 1,
+ 0 },
+ { "[^64h(\?(@Eyw][:xdi[git:]pP%%%%%u(uuu[:up[perword:]`8Utdh{)}"
+ "]]))lW[:punct:]W.hhhhhhhhhhhhhhhhhhhhhhhh'm<<}O8`ZXtG.$",
+ 1, 0 },
+ { "BPP[:digit:]bbbbbbbbbbb(bb)S+[:alnum:]", 1, 0 },
+ { "um.[:ascii((\?#\?!:])*)+KKKKKKKKKKKKKKKKKKKKKKKKKS.=<Bf", 0,
+ 0 },
+ { "", -1, 0 },
+ { "(()$[:lowerprint:][:s[pace:]2]bbbbbbbbbyoooooooooooooooooo*{"
+ "39,}$')qV`AcH>,eDl",
+ -1, 0 },
+ { "(()[^])e{-241,}", -1, 0 },
+ { "()[:alpha:]rliiiiiiii[:alnum:]Mb*QW9N.>\?{115,}&u*j", -1,
+ 0 },
+ { "()[]p", -1, 0 },
+ { "(I[^]pfL)$[:punct:]", -1, 0 },
+ { "([])>>>>>>>>>>[:alnum:]", -1, 0 },
+ { "([])O\\\\\\\\\\\\\\fffffffffffffffffffffff=s6jCZy/"
+ "b+ir2'*{151,}",
+ -1, 0 },
+ { "([])nnnnnnnnnnnnnnnnnnnnnnnnnn[:xdigit:]^N$f", -1, 0 },
+ { "([]M)[:lowerprint:]a(pg$Z[:punct:])77777777777.", -1, 0 },
+ { "([]XXXXXXXXXXXXXXXXXXXXXX-===========)", -1, 0 },
+ { "([]lkX{-224}[:blankcntrl:]$gPKIZlSC#F@XX "
+ "I'^}{234}yZm)uuuuuuuuuuuuuuuuuuuuuurS",
+ -1, 0 },
+ { "([^0kYkg9])IIIIIIIIIIIIIIIIIIIIII/"
+ "{(192,-118}l+FoSD6\?A)c[:xdigit:]`````````````````e-{-4,-"
+ "170}x{4620}Z[:upperword:]",
+ -1, 0 },
+ { "([^[^[^()(\?>){}B]XYF+#[:alpha:]{-85((,-55[^}t]n).{,-33}]]("
+ "bQJ!|O+{175,})RFh)Z+^.{137,}:VpP[:alpha:]-MceqVVkkkk("
+ "kkkkkkkkkkkkkkkkkk)"
+ "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?{-"
+ "115,-67})``````````````````````````````",
+ -1, 0 },
+ { "([^]EzU[:alnum:]+^^^^^^^^^^^^^^^^^^^)[:xdigit:]HHHHHHHH$"
+ "66666666666666666666666666666666UUUUUUUUUUUUUUUUUUUUL{}iiii{"
+ "-76}X",
+ -1, 0 },
+ { "([^]~~~~~~~~~~{240,})]NOp", -1, 0 },
+ { "(sb)[:digit:]VVVVVVVVx{9569}52,|]", -1, 0 },
+ { "(x{19762}){}", -1, 0 },
+ { "-[:xdigit:][]", -1, 0 },
+ { "121|", -1, 0 },
+ { "141[:xdigit:][:lowerprint:]{24}{59,191}[:digit:]/", -1, 0 },
+ { "G[^],,,,,,,,,,,,,+\"DiX", -1, 0 },
+ { "Gm(ho9:\"8{-188,-200}Z[:blankcntrl:]{,171}"
+ "\?\?\?\?\?\?\?\?\?\?\?[:blankcntrl:]LLLLLLLLLLLLLLLLLLLLLLL{"
+ "}^[:graph:][:blankc(\?#ntrl:])w",
+ -1, 0 },
+ { "N\"\"\"\"\"\"\"-------------------------|[:alnum:]"
+ "AAAAAAAAAAAAAAAAAAAAf\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?",
+ -1, 0 },
+ { "U{-30,}^\?\?\?", -1, 0 },
+ { "W^*04rAY(Ee*>[^o3[]]_)", -1, 0 },
+ { "X[^]}*C[:alnum:]", -1, 0 },
+ { "[${,-3}]+^\?[|x8A|][:space:]'''''['''''"
+ "JJJJJJJJJJJJJJJJJJJJJJJJJJJJJyl}.Y7G]",
+ -1, 0 },
+ { "[()&[&&&]\?\?["
+ "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?"
+ "pg%k8ug`Wqk4|NR{h[CK5Ez=]jHpQw&`{:]{,91}D",
+ -1, 0 },
+ { "[(\?#(\?:)[)([\?>)(\?>(\?:[:alnum:])]G]{85}[^)w]N]gYrUs|",
+ -1, 0 },
+ { "[(\?<=)[:digit:]\?]{152,}VR|", -1, 0 },
+ { "[****(\?>**********(\?<!*******Q)Vr){[^25,}*:"
+ "FFFFFFFFFFFFFFFFFFFFFFFF(\?{FFFF(({}D]|",
+ -1, 0 },
+ { "[:ascii:]+{124,}:*]\?$-{92}D[:lowerprint:]``````````````````"
+ "```",
+ -1, 0 },
+ { "[:ascii:]\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?A<", -1, 0 },
+ { "[:blankcntrl:]p\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?$"
+ "\?TTTTTTTTTTTTTTTTT[:ascii:][:upperword:]",
+ -1, 0 },
+ { "[:punct:]{254}DDDDDDDDDDDDDDD@[:alpha:]Z\?\?-----R", -1, 0 },
+ { "[:upperword:]J\?\?nqCAdfyW5", -1, 0 },
+ { "[:upperword:]{-39}|", -1, 0 },
+ { "[:xdigit:]^\?", -1, 0 },
+ { "[Z*e ]NdmP\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?", -1, 0 },
+ { "[[:punct:]q]ex{15625}-", -1, 0 },
+ { "[[[^([^L((\?{b(\?=C\?]-134{,-207}[:ascii:]Hz}XIz}|", -1, 0 },
+ { "[[^V(\?:(\?<!(\?>))TTTTTTTTTTTTTTTTTTTTTTT)[:punct:][:digit:"
+ "]]GGGGGGGGGGGGGGGGGGGGG,]|.{-224}{96}{239,}1",
+ -1, 0 },
+ { "[[^^PP]{,-222}{182}{141}]zFD}-.", -1, 0 },
+ { "[] Hn&[:xdigit:][:upperword:]f", -1, 0 },
+ { "[]$.B", -1, 0 },
+ { "[]&&&&&&&&&&&&&&&&&&&&&&&", -1, 0 },
+ { "[]()[:xdigit:]er063{132,140}$", -1, 0 },
+ { "[]+1434", -1, 0 },
+ { "[]-", -1, 0 },
+ { "[]-#yyK", -1, 0 },
+ { "[]-(S$5)AxbdTKO[:alnum:]", -1, 0 },
+ { "[]2883", -1, 0 },
+ { "[]2dhd-[:alpha:]"
+ "sssssssssssssssss55555555555555555555555555555555Z[:punct:]",
+ -1, 0 },
+ { "[]4", -1, 0 },
+ { "[]44444444444444444G", -1, 0 },
+ { "[]\?", -1, 0 },
+ { "[]A", -1, 0 },
+ { "[]Gap8bc", -1, 0 },
+ { "[]OOOO", -1, 0 },
+ { "[]PP", -1, 0 },
+ { "[]QQ", -1, 0 },
+ { "[]WaFaGO,o", -1, 0 },
+ { "[]Z", -1, 0 },
+ { "[][:alpha:]|[:digit:]Ls$I-Ff~+xA3e", -1, 0 },
+ { "[][:ascii:]-218", -1, 0 },
+ { "[][:ascii:]N}}}}}}}}}}}}}}}-{137,}8682", -1, 0 },
+ { "[][:lowerprint:]Ur", -1, 0 },
+ { "[][:space:]15097", -1, 0 },
+ { "[][:xdigit:]", -1, 0 },
+ { "[]dpSSSSSSSS", -1, 0 },
+ { "[]e13768", -1, 0 },
+ { "[]gT", -1, 0 },
+ { "[]h", -1, 0 },
+ { "[]n", -1, 0 },
+ { "[]vvvvvvvvvvvvvvvvvvvvvvvvvv*[:xdigit:]", -1, 0 },
+ { "[]{,-212}1111111111111111111C3821", -1, 0 },
+ { "[]{-128,}hc", -1, 0 },
+ { "[]{-181,}&[:xdigit:].\?}}}}}}}}}}}}}}}}}}}}}}", -1, 0 },
+ { "[]{}F&}i`7|ZAH", -1, 0 },
+ { "[^(\?())u{196,}pP][r^ndddddddddddddddddddddd]{31,246}\?J",
+ -1, 0 },
+ { "[^.ii.1-S]lwwwwwwwwwwwwwwwwww[^wwwwwwwwwwwwww[:alnum:]DOpP+<"
+ "N][^]44{179}{-194,56}",
+ -1, 0 },
+ { "[^2[:alnum:]]\?t\?\?", -1, 0 },
+ { "[^[((\?{[^^<<<<(\?(\?<!{)})(\?<!]{,184}{-213}|", -1, 0 },
+ { "[^[^[]\?{89,}PPsvf{[:space:]]]vd{161,}", -1, 0 },
+ { "[^[^].]+{0}s", -1, 0 },
+ { "[^]${}", -1, 0 },
+ { "[^]([:punct:]),%[:xdigit:]w^0\?{-233}", -1, 0 },
+ { "[^]-", -1, 0 },
+ { "[^].^", -1, 0 },
+ { "[^]6743", -1, 0 },
+ { "[^]JD", -1, 0 },
+ { "[^]N=[:upperword:]zzzzzzzzzzzzzzzzz.", -1, 0 },
+ { "[^]OLz_6", -1, 0 },
+ { "[^]PP[:digit:]0eBEx=", -1, 0 },
+ { "[^]SHzuKp", -1, 0 },
+ { "[^][:upperword:]{111}-TpmXw", -1, 0 },
+ { "[^]^''''''''z{-73,}", -1, 0 },
+ { "[^]^{,141}e", -1, 0 },
+ { "[^]aaaaaaaaaaaaaaaaaaa{-98,43}", -1, 0 },
+ { "[^]f", -1, 0 },
+ { "[^]l", -1, 0 },
+ { "[^]n\"Wt", -1, 0 },
+ { "[^]pPZ\?q+m0LJ+", -1, 0 },
+ { "[^]p[:upperword:]L:", -1, 0 },
+ { "[^]q\?{,-18}-", -1, 0 },
+ { "[^]s[:space:(\?<=]$", -1, 0 },
+ { "[^]{,58}t", -1, 0 },
+ { "[^]{255,}JJJJJJJJJJJJJJJJJJJJJJJJJJ", -1, 0 },
+ { "[^]{45}", -1, 0 },
+ { "[^]{W", -1, 0 },
+ { "[^]{}{-22}", -1, 0 },
+ { "[^]{}{}{}[:xdigit:]+", -1, 0 },
+ { "[^]|9{,-108}{}.LVIJJJJJJJJJJJJJJJPP", -1, 0 },
+ { "[^{,-254}]|", -1, 0 },
+ { "[o(\?{(\?<=}[))f++++++++++++++++"
+ "777777777777777777777777yzPPs]"
+ "\?\?dRRRRRRRRRRRRRRRRRRRRRRRRRRRR&]>%fffffffffff",
+ -1, 0 },
+ { "aW|", -1, 0 },
+ { "cT{}[]C^r2``tm", -1, 0 },
+ { "kkkkkkkkkkkkkkkkkkkkkkk[:blankcntrl:]|{}3{26,}{151,}[:punct:"
+ "]JJJlH$gP%(2WUE%%%%%%%%%%%%%%%%%%%%a){ibf{}\?",
+ -1, 0 },
+ { "lZ\?\?\?\?\?\?\?\?\?\?\?-P2eZt[:punct:]", -1, 0 },
+ { "vF3qn[^]N.", -1, 0 },
+ { "wwwwwwwwwwwwww{-176,}275[^]>."
+ "UUUUUUUUUUUUUUUUUUUUeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee2$Yd",
+ -1, 0 },
+ { "{-197,223}bf]]]]]]]]]]\?&}/s\?\?~c", -1, 0 },
+ { "{-37,}EpP|", -1, 0 },
+ { "{}@]a[][:xdigit:]z{a", -1, 0 },
+ { "}02|", -1, 0 },
+ { "}}}}}}}}}(}}){}[llll]^N|", -1, 0 },
+ };
+ unsigned int i;
+ int r;
+
+ UNUSED(state);
+
+#ifdef HAVE_REGEX_H
+ /*
+ * Check if we get the expected response.
+ */
+ for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
+ regex_t preg;
+
+ memset(&preg, 0, sizeof(preg));
+ r = regcomp(&preg, tests[i].expression, REG_EXTENDED);
+ if (((r != 0 && tests[i].expect != -1) ||
+ (r == 0 && tests[i].expect == -1)) &&
+ !tests[i].exception)
+ {
+ if (verbose) {
+ print_error("regcomp(%s) -> %s expected %s\n",
+ tests[i].expression,
+ r != 0 ? "bad" : "good",
+ tests[i].expect == -1 ? "bad"
+ : "good");
+ }
+ } else if (r == 0 &&
+ preg.re_nsub != (unsigned int)tests[i].expect &&
+ !tests[i].exception)
+ {
+ if (verbose) {
+ print_error("%s preg.re_nsub %lu expected %d\n",
+ tests[i].expression,
+ (unsigned long)preg.re_nsub,
+ tests[i].expect);
+ }
+ tests[i].expect = preg.re_nsub;
+ }
+ if (r == 0) {
+ regfree(&preg);
+ }
+ }
+#endif /* ifdef HAVE_REGEX_H */
+
+ /*
+ * Check if we get the expected response.
+ */
+ for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
+ r = isc_regex_validate(tests[i].expression);
+ if (r != tests[i].expect) {
+ print_error("# %s -> %d expected %d\n",
+ tests[i].expression, r, tests[i].expect);
+ }
+ assert_int_equal(r, tests[i].expect);
+ }
+}
+
+int
+main(int argc, char **argv) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(regex_validate),
+ };
+ int c;
+
+ while ((c = isc_commandline_parse(argc, argv, "v")) != -1) {
+ switch (c) {
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/result_test.c b/lib/isc/tests/result_test.c
new file mode 100644
index 0000000..a0f6702
--- /dev/null
+++ b/lib/isc/tests/result_test.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <pk11/result.h>
+
+/* convert result to identifier string */
+static void
+isc_result_toid_test(void **state) {
+ const char *id;
+
+ UNUSED(state);
+
+ id = isc_result_toid(ISC_R_SUCCESS);
+ assert_string_equal("ISC_R_SUCCESS", id);
+
+ id = isc_result_toid(ISC_R_FAILURE);
+ assert_string_equal("ISC_R_FAILURE", id);
+}
+
+/* convert result to description string */
+static void
+isc_result_totext_test(void **state) {
+ const char *str;
+
+ UNUSED(state);
+
+ str = isc_result_totext(ISC_R_SUCCESS);
+ assert_string_equal("success", str);
+
+ str = isc_result_totext(ISC_R_FAILURE);
+ assert_string_equal("failure", str);
+}
+
+/* check tables are populated */
+static void
+tables(void **state) {
+ const char *str;
+ isc_result_t result;
+
+ UNUSED(state);
+
+ pk11_result_register();
+
+ for (result = 0; result < ISC_R_NRESULTS; result++) {
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+ }
+
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+
+ for (result = ISC_RESULTCLASS_PK11;
+ result < (ISC_RESULTCLASS_PK11 + PK11_R_NRESULTS); result++)
+ {
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+ }
+
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_result_toid_test),
+ cmocka_unit_test(isc_result_totext_test),
+ cmocka_unit_test(tables),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/safe_test.c b/lib/isc/tests/safe_test.c
new file mode 100644
index 0000000..89bd3ba
--- /dev/null
+++ b/lib/isc/tests/safe_test.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 */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/safe.h>
+#include <isc/util.h>
+
+/* test isc_safe_memequal() */
+static void
+isc_safe_memequal_test(void **state) {
+ UNUSED(state);
+
+ assert_true(isc_safe_memequal("test", "test", 4));
+ assert_true(!isc_safe_memequal("test", "tesc", 4));
+ assert_true(
+ isc_safe_memequal("\x00\x00\x00\x00", "\x00\x00\x00\x00", 4));
+ assert_true(
+ !isc_safe_memequal("\x00\x00\x00\x00", "\x00\x00\x00\x01", 4));
+ assert_true(
+ !isc_safe_memequal("\x00\x00\x00\x02", "\x00\x00\x00\x00", 4));
+}
+
+/* test isc_safe_memwipe() */
+static void
+isc_safe_memwipe_test(void **state) {
+ UNUSED(state);
+
+ /* These should pass. */
+ isc_safe_memwipe(NULL, 0);
+ isc_safe_memwipe((void *)-1, 0);
+
+ /*
+ * isc_safe_memwipe(ptr, size) should function same as
+ * memset(ptr, 0, size);
+ */
+ {
+ char buf1[4] = { 1, 2, 3, 4 };
+ char buf2[4] = { 1, 2, 3, 4 };
+
+ isc_safe_memwipe(buf1, sizeof(buf1));
+ memset(buf2, 0, sizeof(buf2));
+
+ assert_int_equal(memcmp(buf1, buf2, sizeof(buf1)), 0);
+ }
+
+ /*
+ * Boundary test.
+ */
+ {
+ char buf1[4] = { 1, 2, 3, 4 };
+ char buf2[4] = { 1, 2, 3, 4 };
+
+ /*
+ * We wipe 3 elements on purpose, keeping the 4th in
+ * place.
+ */
+ isc_safe_memwipe(buf1, 3);
+ memset(buf2, 0, 3);
+
+ assert_int_equal(memcmp(buf1, buf2, sizeof(buf1)), 0);
+ }
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_safe_memequal_test),
+ cmocka_unit_test(isc_safe_memwipe_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/siphash_test.c b/lib/isc/tests/siphash_test.c
new file mode 100644
index 0000000..72c7126
--- /dev/null
+++ b/lib/isc/tests/siphash_test.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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/siphash.h>
+
+#include "../siphash.c"
+
+const uint8_t vectors_sip64[64][8] = {
+ { 0x31, 0x0e, 0x0e, 0xdd, 0x47, 0xdb, 0x6f, 0x72 },
+ { 0xfd, 0x67, 0xdc, 0x93, 0xc5, 0x39, 0xf8, 0x74 },
+ { 0x5a, 0x4f, 0xa9, 0xd9, 0x09, 0x80, 0x6c, 0x0d },
+ { 0x2d, 0x7e, 0xfb, 0xd7, 0x96, 0x66, 0x67, 0x85 },
+ { 0xb7, 0x87, 0x71, 0x27, 0xe0, 0x94, 0x27, 0xcf },
+ { 0x8d, 0xa6, 0x99, 0xcd, 0x64, 0x55, 0x76, 0x18 },
+ { 0xce, 0xe3, 0xfe, 0x58, 0x6e, 0x46, 0xc9, 0xcb },
+ { 0x37, 0xd1, 0x01, 0x8b, 0xf5, 0x00, 0x02, 0xab },
+ { 0x62, 0x24, 0x93, 0x9a, 0x79, 0xf5, 0xf5, 0x93 },
+ { 0xb0, 0xe4, 0xa9, 0x0b, 0xdf, 0x82, 0x00, 0x9e },
+ { 0xf3, 0xb9, 0xdd, 0x94, 0xc5, 0xbb, 0x5d, 0x7a },
+ { 0xa7, 0xad, 0x6b, 0x22, 0x46, 0x2f, 0xb3, 0xf4 },
+ { 0xfb, 0xe5, 0x0e, 0x86, 0xbc, 0x8f, 0x1e, 0x75 },
+ { 0x90, 0x3d, 0x84, 0xc0, 0x27, 0x56, 0xea, 0x14 },
+ { 0xee, 0xf2, 0x7a, 0x8e, 0x90, 0xca, 0x23, 0xf7 },
+ { 0xe5, 0x45, 0xbe, 0x49, 0x61, 0xca, 0x29, 0xa1 },
+ { 0xdb, 0x9b, 0xc2, 0x57, 0x7f, 0xcc, 0x2a, 0x3f },
+ { 0x94, 0x47, 0xbe, 0x2c, 0xf5, 0xe9, 0x9a, 0x69 },
+ { 0x9c, 0xd3, 0x8d, 0x96, 0xf0, 0xb3, 0xc1, 0x4b },
+ { 0xbd, 0x61, 0x79, 0xa7, 0x1d, 0xc9, 0x6d, 0xbb },
+ { 0x98, 0xee, 0xa2, 0x1a, 0xf2, 0x5c, 0xd6, 0xbe },
+ { 0xc7, 0x67, 0x3b, 0x2e, 0xb0, 0xcb, 0xf2, 0xd0 },
+ { 0x88, 0x3e, 0xa3, 0xe3, 0x95, 0x67, 0x53, 0x93 },
+ { 0xc8, 0xce, 0x5c, 0xcd, 0x8c, 0x03, 0x0c, 0xa8 },
+ { 0x94, 0xaf, 0x49, 0xf6, 0xc6, 0x50, 0xad, 0xb8 },
+ { 0xea, 0xb8, 0x85, 0x8a, 0xde, 0x92, 0xe1, 0xbc },
+ { 0xf3, 0x15, 0xbb, 0x5b, 0xb8, 0x35, 0xd8, 0x17 },
+ { 0xad, 0xcf, 0x6b, 0x07, 0x63, 0x61, 0x2e, 0x2f },
+ { 0xa5, 0xc9, 0x1d, 0xa7, 0xac, 0xaa, 0x4d, 0xde },
+ { 0x71, 0x65, 0x95, 0x87, 0x66, 0x50, 0xa2, 0xa6 },
+ { 0x28, 0xef, 0x49, 0x5c, 0x53, 0xa3, 0x87, 0xad },
+ { 0x42, 0xc3, 0x41, 0xd8, 0xfa, 0x92, 0xd8, 0x32 },
+ { 0xce, 0x7c, 0xf2, 0x72, 0x2f, 0x51, 0x27, 0x71 },
+ { 0xe3, 0x78, 0x59, 0xf9, 0x46, 0x23, 0xf3, 0xa7 },
+ { 0x38, 0x12, 0x05, 0xbb, 0x1a, 0xb0, 0xe0, 0x12 },
+ { 0xae, 0x97, 0xa1, 0x0f, 0xd4, 0x34, 0xe0, 0x15 },
+ { 0xb4, 0xa3, 0x15, 0x08, 0xbe, 0xff, 0x4d, 0x31 },
+ { 0x81, 0x39, 0x62, 0x29, 0xf0, 0x90, 0x79, 0x02 },
+ { 0x4d, 0x0c, 0xf4, 0x9e, 0xe5, 0xd4, 0xdc, 0xca },
+ { 0x5c, 0x73, 0x33, 0x6a, 0x76, 0xd8, 0xbf, 0x9a },
+ { 0xd0, 0xa7, 0x04, 0x53, 0x6b, 0xa9, 0x3e, 0x0e },
+ { 0x92, 0x59, 0x58, 0xfc, 0xd6, 0x42, 0x0c, 0xad },
+ { 0xa9, 0x15, 0xc2, 0x9b, 0xc8, 0x06, 0x73, 0x18 },
+ { 0x95, 0x2b, 0x79, 0xf3, 0xbc, 0x0a, 0xa6, 0xd4 },
+ { 0xf2, 0x1d, 0xf2, 0xe4, 0x1d, 0x45, 0x35, 0xf9 },
+ { 0x87, 0x57, 0x75, 0x19, 0x04, 0x8f, 0x53, 0xa9 },
+ { 0x10, 0xa5, 0x6c, 0xf5, 0xdf, 0xcd, 0x9a, 0xdb },
+ { 0xeb, 0x75, 0x09, 0x5c, 0xcd, 0x98, 0x6c, 0xd0 },
+ { 0x51, 0xa9, 0xcb, 0x9e, 0xcb, 0xa3, 0x12, 0xe6 },
+ { 0x96, 0xaf, 0xad, 0xfc, 0x2c, 0xe6, 0x66, 0xc7 },
+ { 0x72, 0xfe, 0x52, 0x97, 0x5a, 0x43, 0x64, 0xee },
+ { 0x5a, 0x16, 0x45, 0xb2, 0x76, 0xd5, 0x92, 0xa1 },
+ { 0xb2, 0x74, 0xcb, 0x8e, 0xbf, 0x87, 0x87, 0x0a },
+ { 0x6f, 0x9b, 0xb4, 0x20, 0x3d, 0xe7, 0xb3, 0x81 },
+ { 0xea, 0xec, 0xb2, 0xa3, 0x0b, 0x22, 0xa8, 0x7f },
+ { 0x99, 0x24, 0xa4, 0x3c, 0xc1, 0x31, 0x57, 0x24 },
+ { 0xbd, 0x83, 0x8d, 0x3a, 0xaf, 0xbf, 0x8d, 0xb7 },
+ { 0x0b, 0x1a, 0x2a, 0x32, 0x65, 0xd5, 0x1a, 0xea },
+ { 0x13, 0x50, 0x79, 0xa3, 0x23, 0x1c, 0xe6, 0x60 },
+ { 0x93, 0x2b, 0x28, 0x46, 0xe4, 0xd7, 0x06, 0x66 },
+ { 0xe1, 0x91, 0x5f, 0x5c, 0xb1, 0xec, 0xa4, 0x6c },
+ { 0xf3, 0x25, 0x96, 0x5c, 0xa1, 0x6d, 0x62, 0x9f },
+ { 0x57, 0x5f, 0xf2, 0x8e, 0x60, 0x38, 0x1b, 0xe5 },
+ { 0x72, 0x45, 0x06, 0xeb, 0x4c, 0x32, 0x8a, 0x95 }
+};
+
+const uint8_t vectors_hsip32[64][4] = {
+ { 0xa9, 0x35, 0x9f, 0x5b }, { 0x27, 0x47, 0x5a, 0xb8 },
+ { 0xfa, 0x62, 0xa6, 0x03 }, { 0x8a, 0xfe, 0xe7, 0x04 },
+ { 0x2a, 0x6e, 0x46, 0x89 }, { 0xc5, 0xfa, 0xb6, 0x69 },
+ { 0x58, 0x63, 0xfc, 0x23 }, { 0x8b, 0xcf, 0x63, 0xc5 },
+ { 0xd0, 0xb8, 0x84, 0x8f }, { 0xf8, 0x06, 0xe7, 0x79 },
+ { 0x94, 0xb0, 0x79, 0x34 }, { 0x08, 0x08, 0x30, 0x50 },
+ { 0x57, 0xf0, 0x87, 0x2f }, { 0x77, 0xe6, 0x63, 0xff },
+ { 0xd6, 0xff, 0xf8, 0x7c }, { 0x74, 0xfe, 0x2b, 0x97 },
+ { 0xd9, 0xb5, 0xac, 0x84 }, { 0xc4, 0x74, 0x64, 0x5b },
+ { 0x46, 0x5b, 0x8d, 0x9b }, { 0x7b, 0xef, 0xe3, 0x87 },
+ { 0xe3, 0x4d, 0x10, 0x45 }, { 0x61, 0x3f, 0x62, 0xb3 },
+ { 0x70, 0xf3, 0x67, 0xfe }, { 0xe6, 0xad, 0xb8, 0xbd },
+ { 0x27, 0x40, 0x0c, 0x63 }, { 0x26, 0x78, 0x78, 0x75 },
+ { 0x4f, 0x56, 0x7b, 0x5f }, { 0x3a, 0xb0, 0xe6, 0x69 },
+ { 0xb0, 0x64, 0x40, 0x00 }, { 0xff, 0x67, 0x0f, 0xb4 },
+ { 0x50, 0x9e, 0x33, 0x8b }, { 0x5d, 0x58, 0x9f, 0x1a },
+ { 0xfe, 0xe7, 0x21, 0x12 }, { 0x33, 0x75, 0x32, 0x59 },
+ { 0x6a, 0x43, 0x4f, 0x8c }, { 0xfe, 0x28, 0xb7, 0x29 },
+ { 0xe7, 0x5c, 0xc6, 0xec }, { 0x69, 0x7e, 0x8d, 0x54 },
+ { 0x63, 0x68, 0x8b, 0x0f }, { 0x65, 0x0b, 0x62, 0xb4 },
+ { 0xb6, 0xbc, 0x18, 0x40 }, { 0x5d, 0x07, 0x45, 0x05 },
+ { 0x24, 0x42, 0xfd, 0x2e }, { 0x7b, 0xb7, 0x86, 0x3a },
+ { 0x77, 0x05, 0xd5, 0x48 }, { 0xd7, 0x52, 0x08, 0xb1 },
+ { 0xb6, 0xd4, 0x99, 0xc8 }, { 0x08, 0x92, 0x20, 0x2e },
+ { 0x69, 0xe1, 0x2c, 0xe3 }, { 0x8d, 0xb5, 0x80, 0xe5 },
+ { 0x36, 0x97, 0x64, 0xc6 }, { 0x01, 0x6e, 0x02, 0x04 },
+ { 0x3b, 0x85, 0xf3, 0xd4 }, { 0xfe, 0xdb, 0x66, 0xbe },
+ { 0x1e, 0x69, 0x2a, 0x3a }, { 0xc6, 0x89, 0x84, 0xc0 },
+ { 0xa5, 0xc5, 0xb9, 0x40 }, { 0x9b, 0xe9, 0xe8, 0x8c },
+ { 0x7d, 0xbc, 0x81, 0x40 }, { 0x7c, 0x07, 0x8e, 0xc5 },
+ { 0xd4, 0xe7, 0x6c, 0x73 }, { 0x42, 0x8f, 0xcb, 0xb9 },
+ { 0xbd, 0x83, 0x99, 0x7a }, { 0x59, 0xea, 0x4a, 0x74 }
+};
+
+static void
+isc_siphash24_test(void **state) {
+ UNUSED(state);
+
+ uint8_t in[64], out[8], key[16];
+ for (size_t i = 0; i < ARRAY_SIZE(key); i++) {
+ key[i] = i;
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(in); i++) {
+ in[i] = i;
+ isc_siphash24(key, in, i, out);
+ assert_memory_equal(out, vectors_sip64[i], 8);
+ }
+}
+
+static void
+isc_halfsiphash24_test(void **state) {
+ UNUSED(state);
+
+ uint8_t in[64], out[4], key[16];
+ for (size_t i = 0; i < ARRAY_SIZE(key); i++) {
+ key[i] = i;
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(in); i++) {
+ in[i] = i;
+ isc_halfsiphash24(key, in, i, out);
+ assert_memory_equal(out, vectors_hsip32[i], 4);
+ }
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_siphash24_test),
+ cmocka_unit_test(isc_halfsiphash24_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/sockaddr_test.c b/lib/isc/tests/sockaddr_test.c
new file mode 100644
index 0000000..6719a6e
--- /dev/null
+++ b/lib/isc/tests/sockaddr_test.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/sockaddr.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+/* test sockaddr hash */
+static void
+sockaddr_hash(void **state) {
+ isc_sockaddr_t addr;
+ struct in_addr in;
+ struct in6_addr in6;
+ unsigned int h1, h2, h3, h4;
+ int ret;
+
+ UNUSED(state);
+
+ in.s_addr = inet_addr("127.0.0.1");
+ isc_sockaddr_fromin(&addr, &in, 1);
+ h1 = isc_sockaddr_hash(&addr, true);
+ h2 = isc_sockaddr_hash(&addr, false);
+ assert_int_not_equal(h1, h2);
+
+ ret = inet_pton(AF_INET6, "::ffff:127.0.0.1", &in6);
+ assert_int_equal(ret, 1);
+ isc_sockaddr_fromin6(&addr, &in6, 1);
+ h3 = isc_sockaddr_hash(&addr, true);
+ h4 = isc_sockaddr_hash(&addr, false);
+ assert_int_equal(h1, h3);
+ assert_int_equal(h2, h4);
+}
+
+/* test isc_sockaddr_isnetzero() */
+static void
+sockaddr_isnetzero(void **state) {
+ isc_sockaddr_t addr;
+ struct in_addr in;
+ struct in6_addr in6;
+ bool r;
+ int ret;
+ size_t i;
+ struct {
+ const char *string;
+ bool expect;
+ } data4[] = {
+ { "0.0.0.0", true }, { "0.0.0.1", true },
+ { "0.0.1.0", true }, { "0.1.0.0", true },
+ { "1.0.0.0", false }, { "0.0.0.127", true },
+ { "0.0.0.255", true }, { "127.0.0.1", false },
+ { "255.255.255.255", false },
+ };
+ /*
+ * Mapped addresses are currently not netzero.
+ */
+ struct {
+ const char *string;
+ bool expect;
+ } data6[] = {
+ { "::ffff:0.0.0.0", false },
+ { "::ffff:0.0.0.1", false },
+ { "::ffff:0.0.0.127", false },
+ { "::ffff:0.0.0.255", false },
+ { "::ffff:127.0.0.1", false },
+ { "::ffff:255.255.255.255", false },
+ };
+
+ UNUSED(state);
+
+ for (i = 0; i < sizeof(data4) / sizeof(data4[0]); i++) {
+ in.s_addr = inet_addr(data4[i].string);
+ isc_sockaddr_fromin(&addr, &in, 1);
+ r = isc_sockaddr_isnetzero(&addr);
+ assert_int_equal(r, data4[i].expect);
+ }
+
+ for (i = 0; i < sizeof(data6) / sizeof(data6[0]); i++) {
+ ret = inet_pton(AF_INET6, data6[i].string, &in6);
+ assert_int_equal(ret, 1);
+ isc_sockaddr_fromin6(&addr, &in6, 1);
+ r = isc_sockaddr_isnetzero(&addr);
+ assert_int_equal(r, data6[i].expect);
+ }
+}
+
+/*
+ * test that isc_sockaddr_eqaddrprefix() returns true when prefixes of a
+ * and b are equal, and false when they are not equal
+ */
+static void
+sockaddr_eqaddrprefix(void **state) {
+ struct in_addr ina_a;
+ struct in_addr ina_b;
+ struct in_addr ina_c;
+ isc_sockaddr_t isa_a;
+ isc_sockaddr_t isa_b;
+ isc_sockaddr_t isa_c;
+
+ UNUSED(state);
+
+ assert_true(inet_pton(AF_INET, "194.100.32.87", &ina_a) >= 0);
+ assert_true(inet_pton(AF_INET, "194.100.32.80", &ina_b) >= 0);
+ assert_true(inet_pton(AF_INET, "194.101.32.87", &ina_c) >= 0);
+
+ isc_sockaddr_fromin(&isa_a, &ina_a, 0);
+ isc_sockaddr_fromin(&isa_b, &ina_b, 42);
+ isc_sockaddr_fromin(&isa_c, &ina_c, 0);
+
+ assert_true(isc_sockaddr_eqaddrprefix(&isa_a, &isa_b, 0));
+ assert_true(isc_sockaddr_eqaddrprefix(&isa_a, &isa_b, 29));
+ assert_true(isc_sockaddr_eqaddrprefix(&isa_a, &isa_c, 8));
+
+ assert_false(isc_sockaddr_eqaddrprefix(&isa_a, &isa_b, 30));
+ assert_false(isc_sockaddr_eqaddrprefix(&isa_a, &isa_b, 32));
+ assert_false(isc_sockaddr_eqaddrprefix(&isa_a, &isa_c, 16));
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(sockaddr_hash, _setup,
+ _teardown),
+ cmocka_unit_test(sockaddr_isnetzero),
+ cmocka_unit_test(sockaddr_eqaddrprefix),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/socket_test.c b/lib/isc/tests/socket_test.c
new file mode 100644
index 0000000..ab19b06
--- /dev/null
+++ b/lib/isc/tests/socket_test.c
@@ -0,0 +1,833 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can 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_CMOCKA
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/atomic.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/socket.h>
+#include <isc/task.h>
+
+#include "../unix/socket_p.h"
+#include "isctest.h"
+
+static bool recv_dscp;
+static unsigned int recv_dscp_value;
+static bool recv_trunc;
+isc_socket_t *s1 = NULL, *s2 = NULL, *s3 = NULL;
+isc_task_t *test_task = NULL;
+
+/*
+ * Helper functions
+ */
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ if (s1 != NULL) {
+ isc_socket_detach(&s1);
+ }
+ if (s2 != NULL) {
+ isc_socket_detach(&s2);
+ }
+ if (s3 != NULL) {
+ isc_socket_detach(&s3);
+ }
+ if (test_task != NULL) {
+ isc_task_detach(&test_task);
+ }
+
+ isc_test_end();
+
+ return (0);
+}
+
+typedef struct {
+ atomic_bool done;
+ isc_result_t result;
+ isc_socket_t *socket;
+} completion_t;
+
+static void
+completion_init(completion_t *completion) {
+ atomic_init(&completion->done, false);
+ completion->socket = NULL;
+}
+
+static void
+accept_done(isc_task_t *task, isc_event_t *event) {
+ isc_socket_newconnev_t *nevent = (isc_socket_newconnev_t *)event;
+ completion_t *completion = event->ev_arg;
+
+ UNUSED(task);
+
+ completion->result = nevent->result;
+ atomic_store(&completion->done, true);
+ if (completion->result == ISC_R_SUCCESS) {
+ completion->socket = nevent->newsocket;
+ }
+
+ isc_event_free(&event);
+}
+
+static void
+event_done(isc_task_t *task, isc_event_t *event) {
+ isc_socketevent_t *sev = NULL;
+ isc_socket_connev_t *connev = NULL;
+ completion_t *completion = event->ev_arg;
+ UNUSED(task);
+
+ switch (event->ev_type) {
+ case ISC_SOCKEVENT_RECVDONE:
+ case ISC_SOCKEVENT_SENDDONE:
+ sev = (isc_socketevent_t *)event;
+ completion->result = sev->result;
+ if ((sev->attributes & ISC_SOCKEVENTATTR_DSCP) != 0) {
+ recv_dscp = true;
+ recv_dscp_value = sev->dscp;
+ } else {
+ recv_dscp = false;
+ }
+ recv_trunc = ((sev->attributes & ISC_SOCKEVENTATTR_TRUNC) != 0);
+ break;
+ case ISC_SOCKEVENT_CONNECT:
+ connev = (isc_socket_connev_t *)event;
+ completion->result = connev->result;
+ break;
+ default:
+ assert_false(true);
+ }
+ atomic_store(&completion->done, true);
+ isc_event_free(&event);
+}
+
+static isc_result_t
+waitfor(completion_t *completion) {
+ int i = 0;
+ while (!atomic_load(&completion->done) && i++ < 5000) {
+ isc_test_nap(1000);
+ }
+ if (atomic_load(&completion->done)) {
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_FAILURE);
+}
+
+static void
+waitbody(void) {
+ isc_test_nap(1000);
+}
+
+static isc_result_t
+waitfor2(completion_t *c1, completion_t *c2) {
+ int i = 0;
+
+ while (!(atomic_load(&c1->done) && atomic_load(&c2->done)) &&
+ i++ < 5000)
+ {
+ waitbody();
+ }
+ if (atomic_load(&c1->done) && atomic_load(&c2->done)) {
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_FAILURE);
+}
+
+/*
+ * Individual unit tests
+ */
+
+/* Test UDP sendto/recv (IPv4) */
+static void
+udp_sendto_test(void **state) {
+ isc_result_t result;
+ isc_sockaddr_t addr1, addr2;
+ struct in_addr in;
+ char sendbuf[BUFSIZ], recvbuf[BUFSIZ];
+ completion_t completion;
+ isc_region_t r;
+
+ UNUSED(state);
+
+ in.s_addr = inet_addr("127.0.0.1");
+ isc_sockaddr_fromin(&addr1, &in, 0);
+ isc_sockaddr_fromin(&addr2, &in, 0);
+
+ result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_bind(s1, &addr1, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s1, &addr1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr1) != 0);
+
+ result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_bind(s2, &addr2, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s2, &addr2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr2) != 0);
+
+ result = isc_task_create(taskmgr, 0, &test_task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ snprintf(sendbuf, sizeof(sendbuf), "Hello");
+ r.base = (void *)sendbuf;
+ r.length = strlen(sendbuf) + 1;
+
+ completion_init(&completion);
+ result = isc_socket_sendto(s1, &r, test_task, event_done, &completion,
+ &addr2, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+
+ r.base = (void *)recvbuf;
+ r.length = BUFSIZ;
+ completion_init(&completion);
+ result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_string_equal(recvbuf, "Hello");
+}
+
+/* Test UDP sendto/recv with duplicated socket */
+static void
+udp_dup_test(void **state) {
+ isc_result_t result;
+ isc_sockaddr_t addr1, addr2;
+ struct in_addr in;
+ char sendbuf[BUFSIZ], recvbuf[BUFSIZ];
+ completion_t completion;
+ isc_region_t r;
+
+ UNUSED(state);
+
+ in.s_addr = inet_addr("127.0.0.1");
+ isc_sockaddr_fromin(&addr1, &in, 0);
+ isc_sockaddr_fromin(&addr2, &in, 0);
+
+ result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_bind(s1, &addr1, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s1, &addr1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr1) != 0);
+
+ result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_bind(s2, &addr2, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s2, &addr2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr2) != 0);
+
+ result = isc_socket_dup(s2, &s3);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_create(taskmgr, 0, &test_task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ snprintf(sendbuf, sizeof(sendbuf), "Hello");
+ r.base = (void *)sendbuf;
+ r.length = strlen(sendbuf) + 1;
+
+ completion_init(&completion);
+ result = isc_socket_sendto(s1, &r, test_task, event_done, &completion,
+ &addr2, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+
+ snprintf(sendbuf, sizeof(sendbuf), "World");
+ r.base = (void *)sendbuf;
+ r.length = strlen(sendbuf) + 1;
+
+ completion_init(&completion);
+ result = isc_socket_sendto(s1, &r, test_task, event_done, &completion,
+ &addr2, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+
+ r.base = (void *)recvbuf;
+ r.length = BUFSIZ;
+ completion_init(&completion);
+ result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_string_equal(recvbuf, "Hello");
+
+ r.base = (void *)recvbuf;
+ r.length = BUFSIZ;
+ completion_init(&completion);
+ result = isc_socket_recv(s3, &r, 1, test_task, event_done, &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_string_equal(recvbuf, "World");
+}
+
+/* Test UDP sendto/recv (IPv4) */
+static void
+udp_dscp_v4_test(void **state) {
+ isc_result_t result;
+ isc_sockaddr_t addr1, addr2;
+ struct in_addr in;
+ char sendbuf[BUFSIZ], recvbuf[BUFSIZ];
+ completion_t completion;
+ isc_region_t r;
+ isc_socketevent_t *socketevent;
+
+ UNUSED(state);
+
+ in.s_addr = inet_addr("127.0.0.1");
+ isc_sockaddr_fromin(&addr1, &in, 0);
+ isc_sockaddr_fromin(&addr2, &in, 0);
+
+ result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_bind(s1, &addr1, ISC_SOCKET_REUSEADDRESS);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s1, &addr1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr1) != 0);
+
+ result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_bind(s2, &addr2, ISC_SOCKET_REUSEADDRESS);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s2, &addr2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr2) != 0);
+
+ result = isc_task_create(taskmgr, 0, &test_task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ snprintf(sendbuf, sizeof(sendbuf), "Hello");
+ r.base = (void *)sendbuf;
+ r.length = strlen(sendbuf) + 1;
+
+ completion_init(&completion);
+
+ socketevent = isc_socket_socketevent(
+ test_mctx, s1, ISC_SOCKEVENT_SENDDONE, event_done, &completion);
+ assert_non_null(socketevent);
+
+ if ((isc_net_probedscp() & ISC_NET_DSCPPKTV4) != 0) {
+ socketevent->dscp = 056; /* EF */
+ socketevent->attributes |= ISC_SOCKEVENTATTR_DSCP;
+ } else if ((isc_net_probedscp() & ISC_NET_DSCPSETV4) != 0) {
+ isc_socket_dscp(s1, 056); /* EF */
+ socketevent->dscp = 0;
+ socketevent->attributes &= ~ISC_SOCKEVENTATTR_DSCP;
+ }
+
+ recv_dscp = false;
+ recv_dscp_value = 0;
+
+ result = isc_socket_sendto2(s1, &r, test_task, &addr2, NULL,
+ socketevent, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+
+ r.base = (void *)recvbuf;
+ r.length = BUFSIZ;
+ completion_init(&completion);
+ result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_string_equal(recvbuf, "Hello");
+
+ if ((isc_net_probedscp() & ISC_NET_DSCPRECVV4) != 0) {
+ assert_true(recv_dscp);
+ assert_int_equal(recv_dscp_value, 056);
+ } else {
+ assert_false(recv_dscp);
+ }
+}
+
+/* Test UDP sendto/recv (IPv6) */
+static void
+udp_dscp_v6_test(void **state) {
+ isc_result_t result;
+ isc_sockaddr_t addr1, addr2;
+ struct in6_addr in6;
+ char sendbuf[BUFSIZ], recvbuf[BUFSIZ];
+ completion_t completion;
+ isc_region_t r;
+ isc_socketevent_t *socketevent;
+ int n;
+
+ UNUSED(state);
+
+ n = inet_pton(AF_INET6, "::1", &in6.s6_addr);
+ assert_true(n == 1);
+ isc_sockaddr_fromin6(&addr1, &in6, 0);
+ isc_sockaddr_fromin6(&addr2, &in6, 0);
+
+ result = isc_socket_create(socketmgr, PF_INET6, isc_sockettype_udp,
+ &s1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_bind(s1, &addr1, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s1, &addr1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr1) != 0);
+
+ result = isc_socket_create(socketmgr, PF_INET6, isc_sockettype_udp,
+ &s2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_bind(s2, &addr2, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s2, &addr2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr2) != 0);
+
+ result = isc_task_create(taskmgr, 0, &test_task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ snprintf(sendbuf, sizeof(sendbuf), "Hello");
+ r.base = (void *)sendbuf;
+ r.length = strlen(sendbuf) + 1;
+
+ completion_init(&completion);
+
+ socketevent = isc_socket_socketevent(
+ test_mctx, s1, ISC_SOCKEVENT_SENDDONE, event_done, &completion);
+ assert_non_null(socketevent);
+
+ if ((isc_net_probedscp() & ISC_NET_DSCPPKTV6) != 0) {
+ socketevent->dscp = 056; /* EF */
+ socketevent->attributes = ISC_SOCKEVENTATTR_DSCP;
+ } else if ((isc_net_probedscp() & ISC_NET_DSCPSETV6) != 0) {
+ isc_socket_dscp(s1, 056); /* EF */
+ }
+
+ recv_dscp = false;
+ recv_dscp_value = 0;
+
+ result = isc_socket_sendto2(s1, &r, test_task, &addr2, NULL,
+ socketevent, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+
+ r.base = (void *)recvbuf;
+ r.length = BUFSIZ;
+ completion_init(&completion);
+ result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_string_equal(recvbuf, "Hello");
+ if ((isc_net_probedscp() & ISC_NET_DSCPRECVV6) != 0) {
+ assert_true(recv_dscp);
+ assert_int_equal(recv_dscp_value, 056);
+ } else {
+ assert_false(recv_dscp);
+ }
+}
+
+/* Test TCP sendto/recv (IPv4) */
+static void
+tcp_dscp_v4_test(void **state) {
+ isc_result_t result;
+ isc_sockaddr_t addr1;
+ struct in_addr in;
+ char sendbuf[BUFSIZ], recvbuf[BUFSIZ];
+ completion_t completion, completion2;
+ isc_region_t r;
+
+ UNUSED(state);
+
+ in.s_addr = inet_addr("127.0.0.1");
+ isc_sockaddr_fromin(&addr1, &in, 0);
+
+ result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_tcp, &s1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_socket_bind(s1, &addr1, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s1, &addr1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr1) != 0);
+
+ result = isc_socket_listen(s1, 3);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_tcp, &s2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_create(taskmgr, 0, &test_task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ completion_init(&completion2);
+ result = isc_socket_accept(s1, test_task, accept_done, &completion2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ completion_init(&completion);
+ result = isc_socket_connect(s2, &addr1, test_task, event_done,
+ &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor2(&completion, &completion2);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_true(atomic_load(&completion2.done));
+ assert_int_equal(completion2.result, ISC_R_SUCCESS);
+ s3 = completion2.socket;
+
+ isc_socket_dscp(s2, 056); /* EF */
+
+ snprintf(sendbuf, sizeof(sendbuf), "Hello");
+ r.base = (void *)sendbuf;
+ r.length = strlen(sendbuf) + 1;
+
+ recv_dscp = false;
+ recv_dscp_value = 0;
+
+ completion_init(&completion);
+ result = isc_socket_sendto(s2, &r, test_task, event_done, &completion,
+ NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+
+ r.base = (void *)recvbuf;
+ r.length = BUFSIZ;
+ completion_init(&completion);
+ result = isc_socket_recv(s3, &r, 1, test_task, event_done, &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_string_equal(recvbuf, "Hello");
+
+ if ((isc_net_probedscp() & ISC_NET_DSCPRECVV4) != 0) {
+ if (recv_dscp) {
+ assert_int_equal(recv_dscp_value, 056);
+ }
+ } else {
+ assert_false(recv_dscp);
+ }
+}
+
+/* Test TCP sendto/recv (IPv6) */
+static void
+tcp_dscp_v6_test(void **state) {
+ isc_result_t result;
+ isc_sockaddr_t addr1;
+ struct in6_addr in6;
+ char sendbuf[BUFSIZ], recvbuf[BUFSIZ];
+ completion_t completion, completion2;
+ isc_region_t r;
+ int n;
+
+ UNUSED(state);
+
+ n = inet_pton(AF_INET6, "::1", &in6.s6_addr);
+ assert_true(n == 1);
+ isc_sockaddr_fromin6(&addr1, &in6, 0);
+
+ result = isc_socket_create(socketmgr, PF_INET6, isc_sockettype_tcp,
+ &s1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_socket_bind(s1, &addr1, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s1, &addr1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr1) != 0);
+
+ result = isc_socket_listen(s1, 3);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_socket_create(socketmgr, PF_INET6, isc_sockettype_tcp,
+ &s2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_create(taskmgr, 0, &test_task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ completion_init(&completion2);
+ result = isc_socket_accept(s1, test_task, accept_done, &completion2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ completion_init(&completion);
+ result = isc_socket_connect(s2, &addr1, test_task, event_done,
+ &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor2(&completion, &completion2);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_true(atomic_load(&completion2.done));
+ assert_int_equal(completion2.result, ISC_R_SUCCESS);
+ s3 = completion2.socket;
+
+ isc_socket_dscp(s2, 056); /* EF */
+
+ snprintf(sendbuf, sizeof(sendbuf), "Hello");
+ r.base = (void *)sendbuf;
+ r.length = strlen(sendbuf) + 1;
+
+ recv_dscp = false;
+ recv_dscp_value = 0;
+
+ completion_init(&completion);
+ result = isc_socket_sendto(s2, &r, test_task, event_done, &completion,
+ NULL, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+
+ r.base = (void *)recvbuf;
+ r.length = BUFSIZ;
+ completion_init(&completion);
+ result = isc_socket_recv(s3, &r, 1, test_task, event_done, &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_string_equal(recvbuf, "Hello");
+
+ if ((isc_net_probedscp() & ISC_NET_DSCPRECVV6) != 0) {
+ /*
+ * IPV6_RECVTCLASS is undefined for TCP however
+ * if we do get it it should be the value we set.
+ */
+ if (recv_dscp) {
+ assert_int_equal(recv_dscp_value, 056);
+ }
+ } else {
+ assert_false(recv_dscp);
+ }
+}
+
+/* probe dscp capabilities */
+static void
+net_probedscp_test(void **state) {
+ unsigned int n;
+
+ UNUSED(state);
+
+ n = isc_net_probedscp();
+ assert_true((n & ~ISC_NET_DSCPALL) == 0);
+
+ /* ISC_NET_DSCPSETV4 MUST be set if any is set. */
+ if (n & (ISC_NET_DSCPPKTV4 | ISC_NET_DSCPRECVV4)) {
+ assert_true((n & ISC_NET_DSCPSETV4) != 0);
+ }
+
+ /* ISC_NET_DSCPSETV6 MUST be set if any is set. */
+ if (n & (ISC_NET_DSCPPKTV6 | ISC_NET_DSCPRECVV6)) {
+ assert_true((n & ISC_NET_DSCPSETV6) != 0);
+ }
+
+#if 0
+ fprintf(stdout,"IPv4:%s%s%s\n",
+ (n & ISC_NET_DSCPSETV4) ? " set" : "none",
+ (n & ISC_NET_DSCPPKTV4) ? " packet" : "",
+ (n & ISC_NET_DSCPRECVV4) ? " receive" : "");
+
+ fprintf(stdout,"IPv6:%s%s%s\n",
+ (n & ISC_NET_DSCPSETV6) ? " set" : "none",
+ (n & ISC_NET_DSCPPKTV6) ? " packet" : "",
+ (n & ISC_NET_DSCPRECVV6) ? " receive" : "");
+#endif /* if 0 */
+}
+
+/* Test UDP truncation detection */
+static void
+udp_trunc_test(void **state) {
+ isc_result_t result;
+ isc_sockaddr_t addr1, addr2;
+ struct in_addr in;
+ char sendbuf[BUFSIZ * 2], recvbuf[BUFSIZ];
+ completion_t completion;
+ isc_region_t r;
+ isc_socketevent_t *socketevent;
+
+ UNUSED(state);
+
+ in.s_addr = inet_addr("127.0.0.1");
+ isc_sockaddr_fromin(&addr1, &in, 0);
+ isc_sockaddr_fromin(&addr2, &in, 0);
+
+ result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_bind(s1, &addr1, ISC_SOCKET_REUSEADDRESS);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s1, &addr1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr1) != 0);
+ result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_bind(s2, &addr2, ISC_SOCKET_REUSEADDRESS);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_socket_getsockname(s2, &addr2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_true(isc_sockaddr_getport(&addr2) != 0);
+
+ result = isc_task_create(taskmgr, 0, &test_task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Send a message that will not be truncated.
+ */
+ memset(sendbuf, 0xff, sizeof(sendbuf));
+ snprintf(sendbuf, sizeof(sendbuf), "Hello");
+ r.base = (void *)sendbuf;
+ r.length = strlen(sendbuf) + 1;
+
+ completion_init(&completion);
+
+ socketevent = isc_socket_socketevent(
+ test_mctx, s1, ISC_SOCKEVENT_SENDDONE, event_done, &completion);
+ assert_non_null(socketevent);
+
+ result = isc_socket_sendto2(s1, &r, test_task, &addr2, NULL,
+ socketevent, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+
+ r.base = (void *)recvbuf;
+ r.length = BUFSIZ;
+ completion_init(&completion);
+ recv_trunc = false;
+ result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_string_equal(recvbuf, "Hello");
+ assert_false(recv_trunc);
+
+ /*
+ * Send a message that will be truncated.
+ */
+ memset(sendbuf, 0xff, sizeof(sendbuf));
+ snprintf(sendbuf, sizeof(sendbuf), "Hello");
+ r.base = (void *)sendbuf;
+ r.length = sizeof(sendbuf);
+
+ completion_init(&completion);
+
+ socketevent = isc_socket_socketevent(
+ test_mctx, s1, ISC_SOCKEVENT_SENDDONE, event_done, &completion);
+ assert_non_null(socketevent);
+
+ result = isc_socket_sendto2(s1, &r, test_task, &addr2, NULL,
+ socketevent, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+
+ r.base = (void *)recvbuf;
+ r.length = BUFSIZ;
+ completion_init(&completion);
+ recv_trunc = false;
+ result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ waitfor(&completion);
+ assert_true(atomic_load(&completion.done));
+ assert_int_equal(completion.result, ISC_R_SUCCESS);
+ assert_string_equal(recvbuf, "Hello");
+ assert_true(recv_trunc);
+}
+
+/*
+ * Main
+ */
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(udp_sendto_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(udp_dup_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(tcp_dscp_v4_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(tcp_dscp_v6_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(udp_dscp_v4_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(udp_dscp_v6_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(net_probedscp_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(udp_trunc_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/stats_test.c b/lib/isc/tests/stats_test.c
new file mode 100644
index 0000000..89b2462
--- /dev/null
+++ b/lib/isc/tests/stats_test.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/stats.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+/* test stats */
+static void
+isc_stats_basic_test(void **state) {
+ isc_stats_t *stats = NULL;
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_stats_create(test_mctx, &stats, 4);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_stats_ncounters(stats), 4);
+
+ /* Default all 0. */
+ for (int i = 0; i < isc_stats_ncounters(stats); i++) {
+ assert_int_equal(isc_stats_get_counter(stats, i), 0);
+ }
+
+ /* Test increment. */
+ for (int i = 0; i < isc_stats_ncounters(stats); i++) {
+ isc_stats_increment(stats, i);
+ assert_int_equal(isc_stats_get_counter(stats, i), 1);
+ isc_stats_increment(stats, i);
+ assert_int_equal(isc_stats_get_counter(stats, i), 2);
+ }
+
+ /* Test decrement. */
+ for (int i = 0; i < isc_stats_ncounters(stats); i++) {
+ isc_stats_decrement(stats, i);
+ assert_int_equal(isc_stats_get_counter(stats, i), 1);
+ isc_stats_decrement(stats, i);
+ assert_int_equal(isc_stats_get_counter(stats, i), 0);
+ }
+
+ /* Test set. */
+ for (int i = 0; i < isc_stats_ncounters(stats); i++) {
+ isc_stats_set(stats, i, i);
+ assert_int_equal(isc_stats_get_counter(stats, i), i);
+ }
+
+ /* Test update if greater. */
+ for (int i = 0; i < isc_stats_ncounters(stats); i++) {
+ isc_stats_update_if_greater(stats, i, i);
+ assert_int_equal(isc_stats_get_counter(stats, i), i);
+ isc_stats_update_if_greater(stats, i, i + 1);
+ assert_int_equal(isc_stats_get_counter(stats, i), i + 1);
+ }
+
+ /* Test resize. */
+ isc_stats_resize(&stats, 3);
+ assert_int_equal(isc_stats_ncounters(stats), 4);
+ isc_stats_resize(&stats, 4);
+ assert_int_equal(isc_stats_ncounters(stats), 4);
+ isc_stats_resize(&stats, 5);
+ assert_int_equal(isc_stats_ncounters(stats), 5);
+
+ /* Existing counters are retained */
+ for (int i = 0; i < isc_stats_ncounters(stats); i++) {
+ uint32_t expect = i + 1;
+ if (i == 4) {
+ expect = 0;
+ }
+ assert_int_equal(isc_stats_get_counter(stats, i), expect);
+ }
+
+ isc_stats_detach(&stats);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(isc_stats_basic_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/symtab_test.c b/lib/isc/tests/symtab_test.c
new file mode 100644
index 0000000..4b82308
--- /dev/null
+++ b/lib/isc/tests/symtab_test.c
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/print.h>
+#include <isc/symtab.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+static void
+undefine(char *key, unsigned int type, isc_symvalue_t value, void *arg) {
+ UNUSED(arg);
+
+ assert_int_equal(type, 1);
+ isc_mem_free(test_mctx, key);
+ isc_mem_free(test_mctx, value.as_pointer);
+}
+
+/* test symbol table growth */
+static void
+symtab_grow(void **state) {
+ isc_result_t result;
+ isc_symtab_t *st = NULL;
+ isc_symvalue_t value;
+ isc_symexists_t policy = isc_symexists_reject;
+ int i;
+
+ UNUSED(state);
+
+ result = isc_symtab_create(test_mctx, 3, undefine, NULL, false, &st);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_non_null(st);
+
+ /* Nothing should be in the table yet */
+
+ /*
+ * Put 1024 entries in the table (this should necessate
+ * regrowing the hash table several times
+ */
+ for (i = 0; i < 1024; i++) {
+ char str[16], *key;
+
+ snprintf(str, sizeof(str), "%04x", i);
+ key = isc_mem_strdup(test_mctx, str);
+ assert_non_null(key);
+ value.as_pointer = isc_mem_strdup(test_mctx, str);
+ assert_non_null(value.as_pointer);
+ result = isc_symtab_define(st, key, 1, value, policy);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ if (result != ISC_R_SUCCESS) {
+ undefine(key, 1, value, NULL);
+ }
+ }
+
+ /*
+ * Try to put them in again; this should fail
+ */
+ for (i = 0; i < 1024; i++) {
+ char str[16], *key;
+
+ snprintf(str, sizeof(str), "%04x", i);
+ key = isc_mem_strdup(test_mctx, str);
+ assert_non_null(key);
+ value.as_pointer = isc_mem_strdup(test_mctx, str);
+ assert_non_null(value.as_pointer);
+ result = isc_symtab_define(st, key, 1, value, policy);
+ assert_int_equal(result, ISC_R_EXISTS);
+ undefine(key, 1, value, NULL);
+ }
+
+ /*
+ * Retrieve them; this should succeed
+ */
+ for (i = 0; i < 1024; i++) {
+ char str[16];
+
+ snprintf(str, sizeof(str), "%04x", i);
+ result = isc_symtab_lookup(st, str, 0, &value);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_string_equal(str, (char *)value.as_pointer);
+ }
+
+ /*
+ * Undefine them
+ */
+ for (i = 0; i < 1024; i++) {
+ char str[16];
+
+ snprintf(str, sizeof(str), "%04x", i);
+ result = isc_symtab_undefine(st, str, 1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ /*
+ * Retrieve them again; this should fail
+ */
+ for (i = 0; i < 1024; i++) {
+ char str[16];
+
+ snprintf(str, sizeof(str), "%04x", i);
+ result = isc_symtab_lookup(st, str, 0, &value);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ }
+
+ isc_symtab_destroy(&st);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(symtab_grow, _setup, _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/task_test.c b/lib/isc/tests/task_test.c
new file mode 100644
index 0000000..7d7132d
--- /dev/null
+++ b/lib/isc/tests/task_test.c
@@ -0,0 +1,1595 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+
+#include <cmocka.h>
+
+#include <isc/atomic.h>
+#include <isc/cmocka.h>
+#include <isc/commandline.h>
+#include <isc/condition.h>
+#include <isc/managers.h>
+#include <isc/mem.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+/* Set to true (or use -v option) for verbose output */
+static bool verbose = false;
+
+static isc_mutex_t lock;
+static isc_condition_t cv;
+
+atomic_int_fast32_t counter;
+static int active[10];
+static atomic_bool done, done2;
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ isc_mutex_init(&lock);
+
+ isc_condition_init(&cv);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_setup2(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ isc_mutex_init(&lock);
+
+ isc_condition_init(&cv);
+
+ /* Two worker threads */
+ result = isc_test_begin(NULL, true, 2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_setup4(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ isc_mutex_init(&lock);
+
+ isc_condition_init(&cv);
+
+ /* Four worker threads */
+ result = isc_test_begin(NULL, true, 4);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+ isc_condition_destroy(&cv);
+
+ return (0);
+}
+
+static void
+set(isc_task_t *task, isc_event_t *event) {
+ atomic_int_fast32_t *value = (atomic_int_fast32_t *)event->ev_arg;
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+ atomic_store(value, atomic_fetch_add(&counter, 1));
+}
+
+#include <isc/thread.h>
+
+static void
+set_and_drop(isc_task_t *task, isc_event_t *event) {
+ atomic_int_fast32_t *value = (atomic_int_fast32_t *)event->ev_arg;
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+ LOCK(&lock);
+ atomic_store(value, atomic_fetch_add(&counter, 1));
+ UNLOCK(&lock);
+}
+
+/* Create a task */
+static void
+create_task(void **state) {
+ isc_result_t result;
+ isc_task_t *task = NULL;
+
+ UNUSED(state);
+
+ result = isc_task_create(taskmgr, 0, &task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_task_destroy(&task);
+ assert_null(task);
+}
+
+/* Process events */
+static void
+all_events(void **state) {
+ isc_result_t result;
+ isc_task_t *task = NULL;
+ isc_event_t *event = NULL;
+ atomic_int_fast32_t a, b;
+ int i = 0;
+
+ UNUSED(state);
+
+ atomic_init(&counter, 1);
+ atomic_init(&a, 0);
+ atomic_init(&b, 0);
+
+ result = isc_task_create(taskmgr, 0, &task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* First event */
+ event = isc_event_allocate(test_mctx, task, ISC_TASKEVENT_TEST, set, &a,
+ sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&a), 0);
+ isc_task_send(task, &event);
+
+ event = isc_event_allocate(test_mctx, task, ISC_TASKEVENT_TEST, set, &b,
+ sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&b), 0);
+ isc_task_send(task, &event);
+
+ while ((atomic_load(&a) == 0 || atomic_load(&b) == 0) && i++ < 5000) {
+ isc_test_nap(1000);
+ }
+
+ assert_int_not_equal(atomic_load(&a), 0);
+ assert_int_not_equal(atomic_load(&b), 0);
+
+ isc_task_destroy(&task);
+ assert_null(task);
+}
+
+/* Privileged events */
+static void
+privileged_events(void **state) {
+ isc_result_t result;
+ isc_task_t *task1 = NULL, *task2 = NULL;
+ isc_event_t *event = NULL;
+ atomic_int_fast32_t a, b, c, d, e;
+ int i = 0;
+
+ UNUSED(state);
+
+ atomic_init(&counter, 1);
+ atomic_init(&a, -1);
+ atomic_init(&b, -1);
+ atomic_init(&c, -1);
+ atomic_init(&d, -1);
+ atomic_init(&e, -1);
+
+ /*
+ * Pause the net/task manager so we can fill up the work
+ * queue without things happening while we do it.
+ */
+ isc_nm_pause(netmgr);
+ isc_taskmgr_setmode(taskmgr, isc_taskmgrmode_privileged);
+
+ result = isc_task_create(taskmgr, 0, &task1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_task_setname(task1, "privileged", NULL);
+ assert_false(isc_task_getprivilege(task1));
+ isc_task_setprivilege(task1, true);
+ assert_true(isc_task_getprivilege(task1));
+
+ result = isc_task_create(taskmgr, 0, &task2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_task_setname(task2, "normal", NULL);
+ assert_false(isc_task_getprivilege(task2));
+
+ /* First event: privileged */
+ event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST, set,
+ &a, sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&a), -1);
+ isc_task_send(task1, &event);
+
+ /* Second event: not privileged */
+ event = isc_event_allocate(test_mctx, task2, ISC_TASKEVENT_TEST, set,
+ &b, sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&b), -1);
+ isc_task_send(task2, &event);
+
+ /* Third event: privileged */
+ event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST, set,
+ &c, sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&c), -1);
+ isc_task_send(task1, &event);
+
+ /* Fourth event: privileged */
+ event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST, set,
+ &d, sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&d), -1);
+ isc_task_send(task1, &event);
+
+ /* Fifth event: not privileged */
+ event = isc_event_allocate(test_mctx, task2, ISC_TASKEVENT_TEST, set,
+ &e, sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&e), -1);
+ isc_task_send(task2, &event);
+
+ isc_nm_resume(netmgr);
+
+ /* We're waiting for *all* variables to be set */
+ while ((atomic_load(&a) < 0 || atomic_load(&b) < 0 ||
+ atomic_load(&c) < 0 || atomic_load(&d) < 0 ||
+ atomic_load(&e) < 0) &&
+ i++ < 5000)
+ {
+ isc_test_nap(1000);
+ }
+
+ /*
+ * We can't guarantee what order the events fire, but
+ * we do know the privileged tasks that set a, c, and d
+ * would have fired first.
+ */
+ assert_true(atomic_load(&a) <= 3);
+ assert_true(atomic_load(&c) <= 3);
+ assert_true(atomic_load(&d) <= 3);
+
+ /* ...and the non-privileged tasks that set b and e, last */
+ assert_true(atomic_load(&b) > 3);
+ assert_true(atomic_load(&e) > 3);
+
+ assert_int_equal(atomic_load(&counter), 6);
+
+ isc_task_setprivilege(task1, false);
+ assert_false(isc_task_getprivilege(task1));
+
+ isc_task_destroy(&task1);
+ assert_null(task1);
+ isc_task_destroy(&task2);
+ assert_null(task2);
+}
+
+/*
+ * Edge case: this tests that the task manager behaves as expected when
+ * we explicitly set it into normal mode *while* running privileged.
+ */
+static void
+privilege_drop(void **state) {
+ isc_result_t result;
+ isc_task_t *task1 = NULL, *task2 = NULL;
+ isc_event_t *event = NULL;
+ atomic_int_fast32_t a, b, c, d, e; /* non valid states */
+ int i = 0;
+
+ UNUSED(state);
+
+ atomic_init(&counter, 1);
+ atomic_init(&a, -1);
+ atomic_init(&b, -1);
+ atomic_init(&c, -1);
+ atomic_init(&d, -1);
+ atomic_init(&e, -1);
+
+ /*
+ * Pause the net/task manager so we can fill up the work queue
+ * without things happening while we do it.
+ */
+ isc_nm_pause(netmgr);
+ isc_taskmgr_setmode(taskmgr, isc_taskmgrmode_privileged);
+
+ result = isc_task_create(taskmgr, 0, &task1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_task_setname(task1, "privileged", NULL);
+ assert_false(isc_task_getprivilege(task1));
+ isc_task_setprivilege(task1, true);
+ assert_true(isc_task_getprivilege(task1));
+
+ result = isc_task_create(taskmgr, 0, &task2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_task_setname(task2, "normal", NULL);
+ assert_false(isc_task_getprivilege(task2));
+
+ /* First event: privileged */
+ event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST,
+ set_and_drop, &a, sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&a), -1);
+ isc_task_send(task1, &event);
+
+ /* Second event: not privileged */
+ event = isc_event_allocate(test_mctx, task2, ISC_TASKEVENT_TEST,
+ set_and_drop, &b, sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&b), -1);
+ isc_task_send(task2, &event);
+
+ /* Third event: privileged */
+ event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST,
+ set_and_drop, &c, sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&c), -1);
+ isc_task_send(task1, &event);
+
+ /* Fourth event: privileged */
+ event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST,
+ set_and_drop, &d, sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&d), -1);
+ isc_task_send(task1, &event);
+
+ /* Fifth event: not privileged */
+ event = isc_event_allocate(test_mctx, task2, ISC_TASKEVENT_TEST,
+ set_and_drop, &e, sizeof(isc_event_t));
+ assert_non_null(event);
+
+ assert_int_equal(atomic_load(&e), -1);
+ isc_task_send(task2, &event);
+
+ isc_nm_resume(netmgr);
+
+ /* We're waiting for all variables to be set. */
+ while ((atomic_load(&a) == -1 || atomic_load(&b) == -1 ||
+ atomic_load(&c) == -1 || atomic_load(&d) == -1 ||
+ atomic_load(&e) == -1) &&
+ i++ < 5000)
+ {
+ isc_test_nap(1000);
+ }
+
+ /*
+ * We need to check that all privilege mode events were fired
+ * in privileged mode, and non privileged in non-privileged.
+ */
+ assert_true(atomic_load(&a) <= 3);
+ assert_true(atomic_load(&c) <= 3);
+ assert_true(atomic_load(&d) <= 3);
+
+ /* ...and neither of the non-privileged tasks did... */
+ assert_true(atomic_load(&b) > 3);
+ assert_true(atomic_load(&e) > 3);
+
+ /* ...but all five of them did run. */
+ assert_int_equal(atomic_load(&counter), 6);
+
+ isc_task_destroy(&task1);
+ assert_null(task1);
+ isc_task_destroy(&task2);
+ assert_null(task2);
+}
+
+static void
+sleep_cb(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+ int p = *(int *)event->ev_arg;
+ if (p == 1) {
+ /*
+ * Signal the main thread that we're running, so that
+ * it can trigger the race.
+ */
+ LOCK(&lock);
+ atomic_store(&done2, true);
+ SIGNAL(&cv);
+ UNLOCK(&lock);
+ /*
+ * Wait for the operations in the main thread to be finished.
+ */
+ LOCK(&lock);
+ while (!atomic_load(&done)) {
+ WAIT(&cv, &lock);
+ }
+ UNLOCK(&lock);
+ } else {
+ /*
+ * Wait for the operations in the main thread to be finished.
+ */
+ LOCK(&lock);
+ atomic_store(&done2, true);
+ SIGNAL(&cv);
+ UNLOCK(&lock);
+ }
+ isc_event_free(&event);
+}
+
+static void
+pause_unpause(void **state) {
+ isc_result_t result;
+ isc_task_t *task = NULL;
+ isc_event_t *event1, *event2 = NULL;
+ UNUSED(state);
+ atomic_store(&done, false);
+ atomic_store(&done2, false);
+
+ result = isc_task_create(taskmgr, 0, &task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ event1 = isc_event_allocate(test_mctx, task, ISC_TASKEVENT_TEST,
+ sleep_cb, &(int){ 1 }, sizeof(isc_event_t));
+ assert_non_null(event1);
+ event2 = isc_event_allocate(test_mctx, task, ISC_TASKEVENT_TEST,
+ sleep_cb, &(int){ 2 }, sizeof(isc_event_t));
+ assert_non_null(event2);
+ isc_task_send(task, &event1);
+ isc_task_send(task, &event2);
+ /* Wait for event1 to be running */
+ LOCK(&lock);
+ while (!atomic_load(&done2)) {
+ WAIT(&cv, &lock);
+ }
+ UNLOCK(&lock);
+ /* Pause-unpause-detach is what causes the race */
+ isc_task_pause(task);
+ isc_task_unpause(task);
+ isc_task_detach(&task);
+ /* Signal event1 to finish */
+ LOCK(&lock);
+ atomic_store(&done2, false);
+ atomic_store(&done, true);
+ SIGNAL(&cv);
+ UNLOCK(&lock);
+ /* Wait for event2 to finish */
+ LOCK(&lock);
+ while (!atomic_load(&done2)) {
+ WAIT(&cv, &lock);
+ }
+ UNLOCK(&lock);
+}
+
+/*
+ * Basic task functions:
+ */
+static void
+basic_cb(isc_task_t *task, isc_event_t *event) {
+ int i, j;
+
+ UNUSED(task);
+
+ j = 0;
+ for (i = 0; i < 1000000; i++) {
+ j += 100;
+ }
+
+ UNUSED(j);
+
+ if (verbose) {
+ print_message("# task %s\n", (char *)event->ev_arg);
+ }
+
+ isc_event_free(&event);
+}
+
+static void
+basic_shutdown(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ if (verbose) {
+ print_message("# shutdown %s\n", (char *)event->ev_arg);
+ }
+
+ isc_event_free(&event);
+}
+
+static void
+basic_tick(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ if (verbose) {
+ print_message("# %s\n", (char *)event->ev_arg);
+ }
+
+ isc_event_free(&event);
+}
+
+static char one[] = "1";
+static char two[] = "2";
+static char three[] = "3";
+static char four[] = "4";
+static char tick[] = "tick";
+static char tock[] = "tock";
+
+static void
+basic(void **state) {
+ isc_result_t result;
+ isc_task_t *task1 = NULL;
+ isc_task_t *task2 = NULL;
+ isc_task_t *task3 = NULL;
+ isc_task_t *task4 = NULL;
+ isc_event_t *event = NULL;
+ isc_timer_t *ti1 = NULL;
+ isc_timer_t *ti2 = NULL;
+ isc_time_t absolute;
+ isc_interval_t interval;
+ char *testarray[] = { one, one, one, one, one, one, one, one,
+ one, two, three, four, two, three, four, NULL };
+ int i;
+
+ UNUSED(state);
+
+ result = isc_task_create(taskmgr, 0, &task1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_task_create(taskmgr, 0, &task2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_task_create(taskmgr, 0, &task3);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_task_create(taskmgr, 0, &task4);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_onshutdown(task1, basic_shutdown, one);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_task_onshutdown(task2, basic_shutdown, two);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_task_onshutdown(task3, basic_shutdown, three);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = isc_task_onshutdown(task4, basic_shutdown, four);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_time_settoepoch(&absolute);
+ isc_interval_set(&interval, 1, 0);
+ result = isc_timer_create(timermgr, isc_timertype_ticker, &absolute,
+ &interval, task1, basic_tick, tick, &ti1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ ti2 = NULL;
+ isc_time_settoepoch(&absolute);
+ isc_interval_set(&interval, 1, 0);
+ result = isc_timer_create(timermgr, isc_timertype_ticker, &absolute,
+ &interval, task2, basic_tick, tock, &ti2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+#ifndef WIN32
+ sleep(2);
+#else /* ifndef WIN32 */
+ Sleep(2000);
+#endif /* ifndef WIN32 */
+
+ for (i = 0; testarray[i] != NULL; i++) {
+ /*
+ * Note: (void *)1 is used as a sender here, since some
+ * compilers don't like casting a function pointer to a
+ * (void *).
+ *
+ * In a real use, it is more likely the sender would be a
+ * structure (socket, timer, task, etc) but this is just a
+ * test program.
+ */
+ event = isc_event_allocate(test_mctx, (void *)1, 1, basic_cb,
+ testarray[i], sizeof(*event));
+ assert_non_null(event);
+ isc_task_send(task1, &event);
+ }
+
+ (void)isc_task_purge(task3, NULL, 0, 0);
+
+ isc_task_detach(&task1);
+ isc_task_detach(&task2);
+ isc_task_detach(&task3);
+ isc_task_detach(&task4);
+
+#ifndef WIN32
+ sleep(10);
+#else /* ifndef WIN32 */
+ Sleep(10000);
+#endif /* ifndef WIN32 */
+ isc_timer_destroy(&ti1);
+ isc_timer_destroy(&ti2);
+}
+
+/*
+ * Exclusive mode test:
+ * When one task enters exclusive mode, all other active
+ * tasks complete first.
+ */
+static int
+spin(int n) {
+ int i;
+ int r = 0;
+ for (i = 0; i < n; i++) {
+ r += i;
+ if (r > 1000000) {
+ r = 0;
+ }
+ }
+ return (r);
+}
+
+static void
+exclusive_cb(isc_task_t *task, isc_event_t *event) {
+ int taskno = *(int *)(event->ev_arg);
+
+ if (verbose) {
+ print_message("# task enter %d\n", taskno);
+ }
+
+ /* task chosen from the middle of the range */
+ if (taskno == 6) {
+ isc_result_t result;
+ int i;
+
+ result = isc_task_beginexclusive(task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ for (i = 0; i < 10; i++) {
+ assert_int_equal(active[i], 0);
+ }
+
+ isc_task_endexclusive(task);
+ atomic_store(&done, true);
+ } else {
+ active[taskno]++;
+ (void)spin(10000000);
+ active[taskno]--;
+ }
+
+ if (verbose) {
+ print_message("# task exit %d\n", taskno);
+ }
+
+ if (atomic_load(&done)) {
+ isc_mem_put(event->ev_destroy_arg, event->ev_arg, sizeof(int));
+ isc_event_free(&event);
+ atomic_fetch_sub(&counter, 1);
+ } else {
+ isc_task_send(task, &event);
+ }
+}
+
+static void
+task_exclusive(void **state) {
+ isc_task_t *tasks[10];
+ isc_result_t result;
+ int i;
+
+ UNUSED(state);
+
+ atomic_init(&counter, 0);
+
+ for (i = 0; i < 10; i++) {
+ isc_event_t *event = NULL;
+ int *v;
+
+ tasks[i] = NULL;
+
+ if (i == 6) {
+ /* task chosen from the middle of the range */
+ result = isc_task_create_bound(taskmgr, 0, &tasks[i],
+ 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_taskmgr_setexcltask(taskmgr, tasks[6]);
+ } else {
+ result = isc_task_create(taskmgr, 0, &tasks[i]);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ v = isc_mem_get(test_mctx, sizeof *v);
+ assert_non_null(v);
+
+ *v = i;
+
+ event = isc_event_allocate(test_mctx, NULL, 1, exclusive_cb, v,
+ sizeof(*event));
+ assert_non_null(event);
+
+ isc_task_send(tasks[i], &event);
+ atomic_fetch_add(&counter, 1);
+ }
+
+ for (i = 0; i < 10; i++) {
+ isc_task_detach(&tasks[i]);
+ }
+
+ while (atomic_load(&counter) > 0) {
+ isc_test_nap(1000);
+ }
+}
+
+/*
+ * Max tasks test:
+ * The task system can create and execute many tasks. Tests with 10000.
+ */
+static void
+maxtask_shutdown(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ if (event->ev_arg != NULL) {
+ isc_task_destroy((isc_task_t **)&event->ev_arg);
+ } else {
+ LOCK(&lock);
+ atomic_store(&done, true);
+ SIGNAL(&cv);
+ UNLOCK(&lock);
+ }
+
+ isc_event_free(&event);
+}
+
+static void
+maxtask_cb(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+
+ if (event->ev_arg != NULL) {
+ isc_task_t *newtask = NULL;
+
+ event->ev_arg = (void *)(((uintptr_t)event->ev_arg) - 1);
+
+ /*
+ * Create a new task and forward the message.
+ */
+ result = isc_task_create(taskmgr, 0, &newtask);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_onshutdown(newtask, maxtask_shutdown,
+ (void *)task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_task_send(newtask, &event);
+ } else if (task != NULL) {
+ isc_task_destroy(&task);
+ isc_event_free(&event);
+ }
+}
+
+static void
+manytasks(void **state) {
+ isc_mem_t *mctx = NULL;
+ isc_event_t *event = NULL;
+ uintptr_t ntasks = 10000;
+
+ UNUSED(state);
+
+ if (verbose) {
+ print_message("# Testing with %lu tasks\n",
+ (unsigned long)ntasks);
+ }
+
+ isc_mutex_init(&lock);
+ isc_condition_init(&cv);
+
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+ isc_mem_create(&mctx);
+
+ isc_managers_create(mctx, 4, 0, &netmgr, &taskmgr);
+
+ atomic_init(&done, false);
+
+ event = isc_event_allocate(mctx, (void *)1, 1, maxtask_cb,
+ (void *)ntasks, sizeof(*event));
+ assert_non_null(event);
+
+ LOCK(&lock);
+ maxtask_cb(NULL, event);
+ while (!atomic_load(&done)) {
+ WAIT(&cv, &lock);
+ }
+ UNLOCK(&lock);
+
+ isc_managers_destroy(&netmgr, &taskmgr);
+
+ isc_mem_destroy(&mctx);
+ isc_condition_destroy(&cv);
+ isc_mutex_destroy(&lock);
+}
+
+/*
+ * Shutdown test:
+ * When isc_task_shutdown() is called, shutdown events are posted
+ * in LIFO order.
+ */
+
+static int nevents = 0;
+static int nsdevents = 0;
+static int senders[4];
+atomic_bool ready, all_done;
+
+static void
+sd_sde1(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ assert_int_equal(nevents, 256);
+ assert_int_equal(nsdevents, 1);
+ ++nsdevents;
+
+ if (verbose) {
+ print_message("# shutdown 1\n");
+ }
+
+ isc_event_free(&event);
+
+ atomic_store(&all_done, true);
+}
+
+static void
+sd_sde2(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ assert_int_equal(nevents, 256);
+ assert_int_equal(nsdevents, 0);
+ ++nsdevents;
+
+ if (verbose) {
+ print_message("# shutdown 2\n");
+ }
+
+ isc_event_free(&event);
+}
+
+static void
+sd_event1(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ LOCK(&lock);
+ while (!atomic_load(&ready)) {
+ WAIT(&cv, &lock);
+ }
+ UNLOCK(&lock);
+
+ if (verbose) {
+ print_message("# event 1\n");
+ }
+
+ isc_event_free(&event);
+}
+
+static void
+sd_event2(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ ++nevents;
+
+ if (verbose) {
+ print_message("# event 2\n");
+ }
+
+ isc_event_free(&event);
+}
+
+static void
+task_shutdown(void **state) {
+ isc_result_t result;
+ isc_eventtype_t event_type;
+ isc_event_t *event = NULL;
+ isc_task_t *task = NULL;
+ int i;
+
+ UNUSED(state);
+
+ nevents = nsdevents = 0;
+ event_type = 3;
+ atomic_init(&ready, false);
+ atomic_init(&all_done, false);
+
+ LOCK(&lock);
+
+ result = isc_task_create(taskmgr, 0, &task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * This event causes the task to wait on cv.
+ */
+ event = isc_event_allocate(test_mctx, &senders[1], event_type,
+ sd_event1, NULL, sizeof(*event));
+ assert_non_null(event);
+ isc_task_send(task, &event);
+
+ /*
+ * Now we fill up the task's event queue with some events.
+ */
+ for (i = 0; i < 256; ++i) {
+ event = isc_event_allocate(test_mctx, &senders[1], event_type,
+ sd_event2, NULL, sizeof(*event));
+ assert_non_null(event);
+ isc_task_send(task, &event);
+ }
+
+ /*
+ * Now we register two shutdown events.
+ */
+ result = isc_task_onshutdown(task, sd_sde1, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_onshutdown(task, sd_sde2, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_task_shutdown(task);
+ isc_task_detach(&task);
+
+ /*
+ * Now we free the task by signaling cv.
+ */
+ atomic_store(&ready, true);
+ SIGNAL(&cv);
+ UNLOCK(&lock);
+
+ while (!atomic_load(&all_done)) {
+ isc_test_nap(1000);
+ }
+
+ assert_int_equal(nsdevents, 2);
+}
+
+/*
+ * Post-shutdown test:
+ * After isc_task_shutdown() has been called, any call to
+ * isc_task_onshutdown() will return ISC_R_SHUTTINGDOWN.
+ */
+static void
+psd_event1(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ LOCK(&lock);
+
+ while (!atomic_load(&done)) {
+ WAIT(&cv, &lock);
+ }
+
+ UNLOCK(&lock);
+
+ isc_event_free(&event);
+}
+
+static void
+psd_sde(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ isc_event_free(&event);
+}
+
+static void
+post_shutdown(void **state) {
+ isc_result_t result;
+ isc_eventtype_t event_type;
+ isc_event_t *event;
+ isc_task_t *task;
+
+ UNUSED(state);
+
+ atomic_init(&done, false);
+ event_type = 4;
+
+ isc_condition_init(&cv);
+
+ LOCK(&lock);
+
+ task = NULL;
+ result = isc_task_create(taskmgr, 0, &task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * This event causes the task to wait on cv.
+ */
+ event = isc_event_allocate(test_mctx, &senders[1], event_type,
+ psd_event1, NULL, sizeof(*event));
+ assert_non_null(event);
+ isc_task_send(task, &event);
+
+ isc_task_shutdown(task);
+
+ result = isc_task_onshutdown(task, psd_sde, NULL);
+ assert_int_equal(result, ISC_R_SHUTTINGDOWN);
+
+ /*
+ * Release the task.
+ */
+ atomic_store(&done, true);
+
+ SIGNAL(&cv);
+ UNLOCK(&lock);
+
+ isc_task_detach(&task);
+}
+
+/*
+ * Helper for the purge tests below:
+ */
+
+#define SENDERCNT 3
+#define TYPECNT 4
+#define TAGCNT 5
+#define NEVENTS (SENDERCNT * TYPECNT * TAGCNT)
+
+static bool testrange;
+static void *purge_sender;
+static isc_eventtype_t purge_type_first;
+static isc_eventtype_t purge_type_last;
+static void *purge_tag;
+static int eventcnt;
+
+atomic_bool started;
+
+static void
+pg_event1(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ LOCK(&lock);
+ while (!atomic_load(&started)) {
+ WAIT(&cv, &lock);
+ }
+ UNLOCK(&lock);
+
+ isc_event_free(&event);
+}
+
+static void
+pg_event2(isc_task_t *task, isc_event_t *event) {
+ bool sender_match = false;
+ bool type_match = false;
+ bool tag_match = false;
+
+ UNUSED(task);
+
+ if ((purge_sender == NULL) || (purge_sender == event->ev_sender)) {
+ sender_match = true;
+ }
+
+ if (testrange) {
+ if ((purge_type_first <= event->ev_type) &&
+ (event->ev_type <= purge_type_last))
+ {
+ type_match = true;
+ }
+ } else {
+ if (purge_type_first == event->ev_type) {
+ type_match = true;
+ }
+ }
+
+ if ((purge_tag == NULL) || (purge_tag == event->ev_tag)) {
+ tag_match = true;
+ }
+
+ if (sender_match && type_match && tag_match) {
+ if ((event->ev_attributes & ISC_EVENTATTR_NOPURGE) != 0) {
+ if (verbose) {
+ print_message("# event %p,%d,%p "
+ "matched but was not "
+ "purgeable\n",
+ event->ev_sender,
+ (int)event->ev_type,
+ event->ev_tag);
+ }
+ ++eventcnt;
+ } else if (verbose) {
+ print_message("# event %p,%d,%p not purged\n",
+ event->ev_sender, (int)event->ev_type,
+ event->ev_tag);
+ }
+ } else {
+ ++eventcnt;
+ }
+
+ isc_event_free(&event);
+}
+
+static void
+pg_sde(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ LOCK(&lock);
+ atomic_store(&done, true);
+ SIGNAL(&cv);
+ UNLOCK(&lock);
+
+ isc_event_free(&event);
+}
+
+static void
+test_purge(int sender, int type, int tag, int exp_purged) {
+ isc_result_t result;
+ isc_task_t *task = NULL;
+ isc_event_t *eventtab[NEVENTS];
+ isc_event_t *event = NULL;
+ isc_interval_t interval;
+ isc_time_t now;
+ int sender_cnt, type_cnt, tag_cnt, event_cnt, i;
+ int purged = 0;
+
+ atomic_init(&started, false);
+ atomic_init(&done, false);
+ eventcnt = 0;
+
+ isc_condition_init(&cv);
+
+ result = isc_task_create(taskmgr, 0, &task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_onshutdown(task, pg_sde, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Block the task on cv.
+ */
+ event = isc_event_allocate(test_mctx, (void *)1, 9999, pg_event1, NULL,
+ sizeof(*event));
+
+ assert_non_null(event);
+ isc_task_send(task, &event);
+
+ /*
+ * Fill the task's queue with some messages with varying
+ * sender, type, tag, and purgeable attribute values.
+ */
+ event_cnt = 0;
+ for (sender_cnt = 0; sender_cnt < SENDERCNT; ++sender_cnt) {
+ for (type_cnt = 0; type_cnt < TYPECNT; ++type_cnt) {
+ for (tag_cnt = 0; tag_cnt < TAGCNT; ++tag_cnt) {
+ eventtab[event_cnt] = isc_event_allocate(
+ test_mctx,
+ &senders[sender + sender_cnt],
+ (isc_eventtype_t)(type + type_cnt),
+ pg_event2, NULL, sizeof(*event));
+
+ assert_non_null(eventtab[event_cnt]);
+
+ eventtab[event_cnt]->ev_tag =
+ (void *)((uintptr_t)tag + tag_cnt);
+
+ /*
+ * Mark events as non-purgeable if
+ * sender, type and tag are all
+ * odd-numbered. (There should be 4
+ * of these out of 60 events total.)
+ */
+ if (((sender_cnt % 2) != 0) &&
+ ((type_cnt % 2) != 0) &&
+ ((tag_cnt % 2) != 0))
+ {
+ eventtab[event_cnt]->ev_attributes |=
+ ISC_EVENTATTR_NOPURGE;
+ }
+ ++event_cnt;
+ }
+ }
+ }
+
+ for (i = 0; i < event_cnt; ++i) {
+ isc_task_send(task, &eventtab[i]);
+ }
+
+ if (testrange) {
+ /*
+ * We're testing isc_task_purgerange.
+ */
+ purged = isc_task_purgerange(
+ task, purge_sender, (isc_eventtype_t)purge_type_first,
+ (isc_eventtype_t)purge_type_last, purge_tag);
+ assert_int_equal(purged, exp_purged);
+ } else {
+ /*
+ * We're testing isc_task_purge.
+ */
+ if (verbose) {
+ print_message("# purge events %p,%u,%p\n", purge_sender,
+ purge_type_first, purge_tag);
+ }
+ purged = isc_task_purge(task, purge_sender,
+ (isc_eventtype_t)purge_type_first,
+ purge_tag);
+ if (verbose) {
+ print_message("# purged %d expected %d\n", purged,
+ exp_purged);
+ }
+
+ assert_int_equal(purged, exp_purged);
+ }
+
+ /*
+ * Unblock the task, allowing event processing.
+ */
+ LOCK(&lock);
+ atomic_store(&started, true);
+ SIGNAL(&cv);
+
+ isc_task_shutdown(task);
+
+ isc_interval_set(&interval, 5, 0);
+
+ /*
+ * Wait for shutdown processing to complete.
+ */
+ while (!atomic_load(&done)) {
+ result = isc_time_nowplusinterval(&now, &interval);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ WAITUNTIL(&cv, &lock, &now);
+ }
+
+ UNLOCK(&lock);
+
+ isc_task_detach(&task);
+
+ assert_int_equal(eventcnt, event_cnt - exp_purged);
+}
+
+/*
+ * Purge test:
+ * A call to isc_task_purge(task, sender, type, tag) purges all events of
+ * type 'type' and with tag 'tag' not marked as unpurgeable from sender
+ * from the task's " queue and returns the number of events purged.
+ */
+static void
+purge(void **state) {
+ UNUSED(state);
+
+ /* Try purging on a specific sender. */
+ if (verbose) {
+ print_message("# testing purge on 2,4,8 expecting 1\n");
+ }
+ purge_sender = &senders[2];
+ purge_type_first = 4;
+ purge_type_last = 4;
+ purge_tag = (void *)8;
+ testrange = false;
+ test_purge(1, 4, 7, 1);
+
+ /* Try purging on all senders. */
+ if (verbose) {
+ print_message("# testing purge on 0,4,8 expecting 3\n");
+ }
+ purge_sender = NULL;
+ purge_type_first = 4;
+ purge_type_last = 4;
+ purge_tag = (void *)8;
+ testrange = false;
+ test_purge(1, 4, 7, 3);
+
+ /* Try purging on all senders, specified type, all tags. */
+ if (verbose) {
+ print_message("# testing purge on 0,4,0 expecting 15\n");
+ }
+ purge_sender = NULL;
+ purge_type_first = 4;
+ purge_type_last = 4;
+ purge_tag = NULL;
+ testrange = false;
+ test_purge(1, 4, 7, 15);
+
+ /* Try purging on a specified tag, no such type. */
+ if (verbose) {
+ print_message("# testing purge on 0,99,8 expecting 0\n");
+ }
+ purge_sender = NULL;
+ purge_type_first = 99;
+ purge_type_last = 99;
+ purge_tag = (void *)8;
+ testrange = false;
+ test_purge(1, 4, 7, 0);
+
+ /* Try purging on specified sender, type, all tags. */
+ if (verbose) {
+ print_message("# testing purge on 3,5,0 expecting 5\n");
+ }
+ purge_sender = &senders[3];
+ purge_type_first = 5;
+ purge_type_last = 5;
+ purge_tag = NULL;
+ testrange = false;
+ test_purge(1, 4, 7, 5);
+}
+
+/*
+ * Purge range test:
+ * A call to isc_event_purgerange(task, sender, first, last, tag) purges
+ * all events not marked unpurgeable from sender 'sender' and of type within
+ * the range 'first' to 'last' inclusive from the task's event queue and
+ * returns the number of tasks purged.
+ */
+
+static void
+purgerange(void **state) {
+ UNUSED(state);
+
+ /* Now let's try some ranges. */
+ /* testing purgerange on 2,4-5,8 expecting 1 */
+ purge_sender = &senders[2];
+ purge_type_first = 4;
+ purge_type_last = 5;
+ purge_tag = (void *)8;
+ testrange = true;
+ test_purge(1, 4, 7, 1);
+
+ /* Try purging on all senders. */
+ if (verbose) {
+ print_message("# testing purge on 0,4-5,8 expecting 5\n");
+ }
+ purge_sender = NULL;
+ purge_type_first = 4;
+ purge_type_last = 5;
+ purge_tag = (void *)8;
+ testrange = true;
+ test_purge(1, 4, 7, 5);
+
+ /* Try purging on all senders, specified type, all tags. */
+ if (verbose) {
+ print_message("# testing purge on 0,5-6,0 expecting 28\n");
+ }
+ purge_sender = NULL;
+ purge_type_first = 5;
+ purge_type_last = 6;
+ purge_tag = NULL;
+ testrange = true;
+ test_purge(1, 4, 7, 28);
+
+ /* Try purging on a specified tag, no such type. */
+ if (verbose) {
+ print_message("# testing purge on 0,99-101,8 expecting 0\n");
+ }
+ purge_sender = NULL;
+ purge_type_first = 99;
+ purge_type_last = 101;
+ purge_tag = (void *)8;
+ testrange = true;
+ test_purge(1, 4, 7, 0);
+
+ /* Try purging on specified sender, type, all tags. */
+ if (verbose) {
+ print_message("# testing purge on 3,5-6,0 expecting 10\n");
+ }
+ purge_sender = &senders[3];
+ purge_type_first = 5;
+ purge_type_last = 6;
+ purge_tag = NULL;
+ testrange = true;
+ test_purge(1, 4, 7, 10);
+}
+
+/*
+ * Helpers for purge event tests
+ */
+static void
+pge_event1(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ LOCK(&lock);
+ while (!atomic_load(&started)) {
+ WAIT(&cv, &lock);
+ }
+ UNLOCK(&lock);
+
+ isc_event_free(&event);
+}
+
+static void
+pge_event2(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ ++eventcnt;
+ isc_event_free(&event);
+}
+
+static void
+pge_sde(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ LOCK(&lock);
+ atomic_store(&done, true);
+ SIGNAL(&cv);
+ UNLOCK(&lock);
+
+ isc_event_free(&event);
+}
+
+static void
+try_purgeevent(bool purgeable) {
+ isc_result_t result;
+ isc_task_t *task = NULL;
+ bool purged;
+ isc_event_t *event1 = NULL;
+ isc_event_t *event2 = NULL;
+ isc_event_t *event2_clone = NULL;
+ isc_time_t now;
+ isc_interval_t interval;
+
+ atomic_init(&started, false);
+ atomic_init(&done, false);
+ eventcnt = 0;
+
+ isc_condition_init(&cv);
+
+ result = isc_task_create(taskmgr, 0, &task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_onshutdown(task, pge_sde, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Block the task on cv.
+ */
+ event1 = isc_event_allocate(test_mctx, (void *)1, (isc_eventtype_t)1,
+ pge_event1, NULL, sizeof(*event1));
+ assert_non_null(event1);
+ isc_task_send(task, &event1);
+
+ event2 = isc_event_allocate(test_mctx, (void *)1, (isc_eventtype_t)1,
+ pge_event2, NULL, sizeof(*event2));
+ assert_non_null(event2);
+
+ event2_clone = event2;
+
+ if (purgeable) {
+ event2->ev_attributes &= ~ISC_EVENTATTR_NOPURGE;
+ } else {
+ event2->ev_attributes |= ISC_EVENTATTR_NOPURGE;
+ }
+
+ isc_task_send(task, &event2);
+
+ purged = isc_task_purgeevent(task, event2_clone);
+ assert_int_equal(purgeable, purged);
+
+ /*
+ * Unblock the task, allowing event processing.
+ */
+ LOCK(&lock);
+ atomic_store(&started, true);
+ SIGNAL(&cv);
+
+ isc_task_shutdown(task);
+
+ isc_interval_set(&interval, 5, 0);
+
+ /*
+ * Wait for shutdown processing to complete.
+ */
+ while (!atomic_load(&done)) {
+ result = isc_time_nowplusinterval(&now, &interval);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ WAITUNTIL(&cv, &lock, &now);
+ }
+
+ UNLOCK(&lock);
+
+ isc_task_detach(&task);
+
+ assert_int_equal(eventcnt, (purgeable ? 0 : 1));
+}
+
+/*
+ * Purge event test:
+ * When the event is marked as purgeable, a call to
+ * isc_task_purgeevent(task, event) purges the event 'event' from the
+ * task's queue and returns true.
+ */
+
+static void
+purgeevent(void **state) {
+ UNUSED(state);
+
+ try_purgeevent(true);
+}
+
+int
+main(int argc, char **argv) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(manytasks),
+ cmocka_unit_test_setup_teardown(all_events, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(basic, _setup2, _teardown),
+ cmocka_unit_test_setup_teardown(create_task, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(pause_unpause, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(post_shutdown, _setup2,
+ _teardown),
+ cmocka_unit_test_setup_teardown(privilege_drop, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(privileged_events, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(purge, _setup2, _teardown),
+ cmocka_unit_test_setup_teardown(purgeevent, _setup2, _teardown),
+ cmocka_unit_test_setup_teardown(purgerange, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(task_shutdown, _setup4,
+ _teardown),
+ cmocka_unit_test_setup_teardown(task_exclusive, _setup4,
+ _teardown),
+ };
+ struct CMUnitTest selected[sizeof(tests) / sizeof(tests[0])];
+ size_t i;
+ int c;
+
+ memset(selected, 0, sizeof(selected));
+
+ while ((c = isc_commandline_parse(argc, argv, "lt:v")) != -1) {
+ switch (c) {
+ case 'l':
+ for (i = 0; i < (sizeof(tests) / sizeof(tests[0])); i++)
+ {
+ if (tests[i].name != NULL) {
+ fprintf(stdout, "%s\n", tests[i].name);
+ }
+ }
+ return (0);
+ case 't':
+ if (!cmocka_add_test_byname(
+ tests, isc_commandline_argument, selected))
+ {
+ fprintf(stderr, "unknown test '%s'\n",
+ isc_commandline_argument);
+ exit(1);
+ }
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (selected[0].name != NULL) {
+ return (cmocka_run_group_tests(selected, NULL, NULL));
+ } else {
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+ }
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/taskpool_test.c b/lib/isc/tests/taskpool_test.c
new file mode 100644
index 0000000..64a4641
--- /dev/null
+++ b/lib/isc/tests/taskpool_test.c
@@ -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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/task.h>
+#include <isc/taskpool.h>
+#include <isc/util.h>
+
+#include "isctest.h"
+
+#define TASK_MAGIC ISC_MAGIC('T', 'A', 'S', 'K')
+#define VALID_TASK(t) ISC_MAGIC_VALID(t, TASK_MAGIC)
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = isc_test_begin(NULL, true, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+/* Create a taskpool */
+static void
+create_pool(void **state) {
+ isc_result_t result;
+ isc_taskpool_t *pool = NULL;
+
+ UNUSED(state);
+
+ result = isc_taskpool_create(taskmgr, test_mctx, 8, 2, false, &pool);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_taskpool_size(pool), 8);
+
+ isc_taskpool_destroy(&pool);
+ assert_null(pool);
+}
+
+/* Resize a taskpool */
+static void
+expand_pool(void **state) {
+ isc_result_t result;
+ isc_taskpool_t *pool1 = NULL, *pool2 = NULL, *hold = NULL;
+
+ UNUSED(state);
+
+ result = isc_taskpool_create(taskmgr, test_mctx, 10, 2, false, &pool1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_taskpool_size(pool1), 10);
+
+ /* resizing to a smaller size should have no effect */
+ hold = pool1;
+ result = isc_taskpool_expand(&pool1, 5, false, &pool2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_taskpool_size(pool2), 10);
+ assert_ptr_equal(pool2, hold);
+ assert_null(pool1);
+ pool1 = pool2;
+ pool2 = NULL;
+
+ /* resizing to the same size should have no effect */
+ hold = pool1;
+ result = isc_taskpool_expand(&pool1, 10, false, &pool2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_taskpool_size(pool2), 10);
+ assert_ptr_equal(pool2, hold);
+ assert_null(pool1);
+ pool1 = pool2;
+ pool2 = NULL;
+
+ /* resizing to larger size should make a new pool */
+ hold = pool1;
+ result = isc_taskpool_expand(&pool1, 20, false, &pool2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_taskpool_size(pool2), 20);
+ assert_ptr_not_equal(pool2, hold);
+ assert_null(pool1);
+
+ isc_taskpool_destroy(&pool2);
+ assert_null(pool2);
+}
+
+/* Get tasks */
+static void
+get_tasks(void **state) {
+ isc_result_t result;
+ isc_taskpool_t *pool = NULL;
+ isc_task_t *task1 = NULL, *task2 = NULL, *task3 = NULL;
+
+ UNUSED(state);
+
+ result = isc_taskpool_create(taskmgr, test_mctx, 2, 2, false, &pool);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_taskpool_size(pool), 2);
+
+ /* two tasks in pool; make sure we can access them more than twice */
+ isc_taskpool_gettask(pool, &task1);
+ assert_true(VALID_TASK(task1));
+
+ isc_taskpool_gettask(pool, &task2);
+ assert_true(VALID_TASK(task2));
+
+ isc_taskpool_gettask(pool, &task3);
+ assert_true(VALID_TASK(task3));
+
+ isc_task_destroy(&task1);
+ isc_task_destroy(&task2);
+ isc_task_destroy(&task3);
+
+ isc_taskpool_destroy(&pool);
+ assert_null(pool);
+}
+
+/* Set privileges */
+static void
+set_privilege(void **state) {
+ isc_result_t result;
+ isc_taskpool_t *pool = NULL;
+ isc_task_t *task1 = NULL, *task2 = NULL, *task3 = NULL;
+
+ UNUSED(state);
+
+ result = isc_taskpool_create(taskmgr, test_mctx, 2, 2, true, &pool);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_taskpool_size(pool), 2);
+
+ isc_taskpool_gettask(pool, &task1);
+ isc_taskpool_gettask(pool, &task2);
+ isc_taskpool_gettask(pool, &task3);
+
+ assert_true(VALID_TASK(task1));
+ assert_true(VALID_TASK(task2));
+ assert_true(VALID_TASK(task3));
+
+ assert_true(isc_task_getprivilege(task1));
+ assert_true(isc_task_getprivilege(task2));
+ assert_true(isc_task_getprivilege(task3));
+
+ isc_task_destroy(&task1);
+ isc_task_destroy(&task2);
+ isc_task_destroy(&task3);
+
+ isc_taskpool_destroy(&pool);
+ assert_null(pool);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(create_pool, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(expand_pool, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(get_tasks, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(set_privilege, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/testdata/file/keep b/lib/isc/tests/testdata/file/keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/isc/tests/testdata/file/keep
diff --git a/lib/isc/tests/time_test.c b/lib/isc/tests/time_test.c
new file mode 100644
index 0000000..870d9bf
--- /dev/null
+++ b/lib/isc/tests/time_test.c
@@ -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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/result.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include "../time.c"
+
+#define NS_PER_S 1000000000 /*%< Nanoseconds per second. */
+#define MAX_NS (NS_PER_S - 1)
+
+struct time_vectors {
+ isc_time_t a;
+ isc_interval_t b;
+ isc_time_t r;
+ isc_result_t result;
+};
+
+const struct time_vectors vectors_add[8] = {
+ { { 0, 0 }, { 0, 0 }, { 0, 0 }, ISC_R_SUCCESS },
+ { { 0, MAX_NS }, { 0, MAX_NS }, { 1, MAX_NS - 1 }, ISC_R_SUCCESS },
+ { { 0, NS_PER_S / 2 }, { 0, NS_PER_S / 2 }, { 1, 0 }, ISC_R_SUCCESS },
+ { { UINT_MAX, MAX_NS }, { 0, 0 }, { UINT_MAX, MAX_NS }, ISC_R_SUCCESS },
+ { { UINT_MAX, 0 }, { 0, MAX_NS }, { UINT_MAX, MAX_NS }, ISC_R_SUCCESS },
+ { { UINT_MAX, 0 }, { 1, 0 }, { 0, 0 }, ISC_R_RANGE },
+ { { UINT_MAX, MAX_NS }, { 0, 1 }, { 0, 0 }, ISC_R_RANGE },
+ { { UINT_MAX / 2 + 1, NS_PER_S / 2 },
+ { UINT_MAX / 2, NS_PER_S / 2 },
+ { 0, 0 },
+ ISC_R_RANGE },
+};
+
+const struct time_vectors vectors_sub[7] = {
+ { { 0, 0 }, { 0, 0 }, { 0, 0 }, ISC_R_SUCCESS },
+ { { 1, 0 }, { 0, MAX_NS }, { 0, 1 }, ISC_R_SUCCESS },
+ { { 1, NS_PER_S / 2 },
+ { 0, MAX_NS },
+ { 0, NS_PER_S / 2 + 1 },
+ ISC_R_SUCCESS },
+ { { UINT_MAX, MAX_NS }, { UINT_MAX, 0 }, { 0, MAX_NS }, ISC_R_SUCCESS },
+ { { 0, 0 }, { 1, 0 }, { 0, 0 }, ISC_R_RANGE },
+ { { 0, 0 }, { 0, MAX_NS }, { 0, 0 }, ISC_R_RANGE },
+};
+
+static void
+isc_time_add_test(void **state) {
+ UNUSED(state);
+
+ for (size_t i = 0; i < ARRAY_SIZE(vectors_add); i++) {
+ isc_time_t r = { UINT_MAX, UINT_MAX };
+ isc_result_t result = isc_time_add(&(vectors_add[i].a),
+ &(vectors_add[i].b), &r);
+ assert_int_equal(result, vectors_add[i].result);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ assert_int_equal(r.seconds, vectors_add[i].r.seconds);
+ assert_int_equal(r.nanoseconds, vectors_add[i].r.nanoseconds);
+ }
+
+ expect_assert_failure((void)isc_time_add(&(isc_time_t){ 0, MAX_NS + 1 },
+ &(isc_interval_t){ 0, 0 },
+ &(isc_time_t){ 0, 0 }));
+ expect_assert_failure((void)isc_time_add(
+ &(isc_time_t){ 0, 0 }, &(isc_interval_t){ 0, MAX_NS + 1 },
+ &(isc_time_t){ 0, 0 }));
+
+ expect_assert_failure((void)isc_time_add((isc_time_t *)NULL,
+ &(isc_interval_t){ 0, 0 },
+ &(isc_time_t){ 0, 0 }));
+ expect_assert_failure((void)isc_time_add(&(isc_time_t){ 0, 0 },
+ (isc_interval_t *)NULL,
+ &(isc_time_t){ 0, 0 }));
+ expect_assert_failure((void)isc_time_add(
+ &(isc_time_t){ 0, 0 }, &(isc_interval_t){ 0, 0 }, NULL));
+}
+
+static void
+isc_time_sub_test(void **state) {
+ UNUSED(state);
+
+ for (size_t i = 0; i < ARRAY_SIZE(vectors_sub); i++) {
+ isc_time_t r = { UINT_MAX, UINT_MAX };
+ isc_result_t result = isc_time_subtract(
+ &(vectors_sub[i].a), &(vectors_sub[i].b), &r);
+ assert_int_equal(result, vectors_sub[i].result);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ assert_int_equal(r.seconds, vectors_sub[i].r.seconds);
+ assert_int_equal(r.nanoseconds, vectors_sub[i].r.nanoseconds);
+ }
+
+ expect_assert_failure((void)isc_time_subtract(
+ &(isc_time_t){ 0, MAX_NS + 1 }, &(isc_interval_t){ 0, 0 },
+ &(isc_time_t){ 0, 0 }));
+ expect_assert_failure((void)isc_time_subtract(
+ &(isc_time_t){ 0, 0 }, &(isc_interval_t){ 0, MAX_NS + 1 },
+ &(isc_time_t){ 0, 0 }));
+
+ expect_assert_failure((void)isc_time_subtract((isc_time_t *)NULL,
+ &(isc_interval_t){ 0, 0 },
+ &(isc_time_t){ 0, 0 }));
+ expect_assert_failure((void)isc_time_subtract(&(isc_time_t){ 0, 0 },
+ (isc_interval_t *)NULL,
+ &(isc_time_t){ 0, 0 }));
+ expect_assert_failure((void)isc_time_subtract(
+ &(isc_time_t){ 0, 0 }, &(isc_interval_t){ 0, 0 }, NULL));
+}
+
+/* parse http time stamp */
+static void
+isc_time_parsehttptimestamp_test(void **state) {
+ isc_result_t result;
+ isc_time_t t, x;
+ char buf[ISC_FORMATHTTPTIMESTAMP_SIZE];
+
+ UNUSED(state);
+
+ setenv("TZ", "America/Los_Angeles", 1);
+ result = isc_time_now(&t);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_time_formathttptimestamp(&t, buf, sizeof(buf));
+ result = isc_time_parsehttptimestamp(buf, &x);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(isc_time_seconds(&t), isc_time_seconds(&x));
+}
+
+/* print UTC in ISO8601 */
+static void
+isc_time_formatISO8601_test(void **state) {
+ isc_result_t result;
+ isc_time_t t;
+ char buf[64];
+
+ UNUSED(state);
+
+ setenv("TZ", "America/Los_Angeles", 1);
+ result = isc_time_now(&t);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* check formatting: yyyy-mm-ddThh:mm:ssZ */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_formatISO8601(&t, buf, sizeof(buf));
+ assert_int_equal(strlen(buf), 20);
+ assert_int_equal(buf[4], '-');
+ assert_int_equal(buf[7], '-');
+ assert_int_equal(buf[10], 'T');
+ assert_int_equal(buf[13], ':');
+ assert_int_equal(buf[16], ':');
+ assert_int_equal(buf[19], 'Z');
+
+ /* check time conversion correctness */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_settoepoch(&t);
+ isc_time_formatISO8601(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "1970-01-01T00:00:00Z");
+
+ memset(buf, 'X', sizeof(buf));
+ isc_time_set(&t, 1450000000, 123000000);
+ isc_time_formatISO8601(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "2015-12-13T09:46:40Z");
+}
+
+/* print UTC in ISO8601 with milliseconds */
+static void
+isc_time_formatISO8601ms_test(void **state) {
+ isc_result_t result;
+ isc_time_t t;
+ char buf[64];
+
+ UNUSED(state);
+
+ setenv("TZ", "America/Los_Angeles", 1);
+ result = isc_time_now(&t);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* check formatting: yyyy-mm-ddThh:mm:ss.sssZ */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_formatISO8601ms(&t, buf, sizeof(buf));
+ assert_int_equal(strlen(buf), 24);
+ assert_int_equal(buf[4], '-');
+ assert_int_equal(buf[7], '-');
+ assert_int_equal(buf[10], 'T');
+ assert_int_equal(buf[13], ':');
+ assert_int_equal(buf[16], ':');
+ assert_int_equal(buf[19], '.');
+ assert_int_equal(buf[23], 'Z');
+
+ /* check time conversion correctness */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_settoepoch(&t);
+ isc_time_formatISO8601ms(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "1970-01-01T00:00:00.000Z");
+
+ memset(buf, 'X', sizeof(buf));
+ isc_time_set(&t, 1450000000, 123000000);
+ isc_time_formatISO8601ms(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "2015-12-13T09:46:40.123Z");
+}
+
+/* print UTC in ISO8601 with microseconds */
+static void
+isc_time_formatISO8601us_test(void **state) {
+ isc_result_t result;
+ isc_time_t t;
+ char buf[64];
+
+ UNUSED(state);
+
+ setenv("TZ", "America/Los_Angeles", 1);
+ result = isc_time_now_hires(&t);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* check formatting: yyyy-mm-ddThh:mm:ss.ssssssZ */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_formatISO8601us(&t, buf, sizeof(buf));
+ assert_int_equal(strlen(buf), 27);
+ assert_int_equal(buf[4], '-');
+ assert_int_equal(buf[7], '-');
+ assert_int_equal(buf[10], 'T');
+ assert_int_equal(buf[13], ':');
+ assert_int_equal(buf[16], ':');
+ assert_int_equal(buf[19], '.');
+ assert_int_equal(buf[26], 'Z');
+
+ /* check time conversion correctness */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_settoepoch(&t);
+ isc_time_formatISO8601us(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "1970-01-01T00:00:00.000000Z");
+
+ memset(buf, 'X', sizeof(buf));
+ isc_time_set(&t, 1450000000, 123456000);
+ isc_time_formatISO8601us(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "2015-12-13T09:46:40.123456Z");
+}
+
+/* print local time in ISO8601 */
+static void
+isc_time_formatISO8601L_test(void **state) {
+ isc_result_t result;
+ isc_time_t t;
+ char buf[64];
+
+ UNUSED(state);
+
+ setenv("TZ", "America/Los_Angeles", 1);
+ result = isc_time_now(&t);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* check formatting: yyyy-mm-ddThh:mm:ss */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_formatISO8601L(&t, buf, sizeof(buf));
+ assert_int_equal(strlen(buf), 19);
+ assert_int_equal(buf[4], '-');
+ assert_int_equal(buf[7], '-');
+ assert_int_equal(buf[10], 'T');
+ assert_int_equal(buf[13], ':');
+ assert_int_equal(buf[16], ':');
+
+ /* check time conversion correctness */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_settoepoch(&t);
+ isc_time_formatISO8601L(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "1969-12-31T16:00:00");
+
+ memset(buf, 'X', sizeof(buf));
+ isc_time_set(&t, 1450000000, 123000000);
+ isc_time_formatISO8601L(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "2015-12-13T01:46:40");
+}
+
+/* print local time in ISO8601 with milliseconds */
+static void
+isc_time_formatISO8601Lms_test(void **state) {
+ isc_result_t result;
+ isc_time_t t;
+ char buf[64];
+
+ UNUSED(state);
+
+ setenv("TZ", "America/Los_Angeles", 1);
+ result = isc_time_now(&t);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* check formatting: yyyy-mm-ddThh:mm:ss.sss */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_formatISO8601Lms(&t, buf, sizeof(buf));
+ assert_int_equal(strlen(buf), 23);
+ assert_int_equal(buf[4], '-');
+ assert_int_equal(buf[7], '-');
+ assert_int_equal(buf[10], 'T');
+ assert_int_equal(buf[13], ':');
+ assert_int_equal(buf[16], ':');
+ assert_int_equal(buf[19], '.');
+
+ /* check time conversion correctness */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_settoepoch(&t);
+ isc_time_formatISO8601Lms(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "1969-12-31T16:00:00.000");
+
+ memset(buf, 'X', sizeof(buf));
+ isc_time_set(&t, 1450000000, 123000000);
+ isc_time_formatISO8601Lms(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "2015-12-13T01:46:40.123");
+}
+
+/* print local time in ISO8601 with microseconds */
+static void
+isc_time_formatISO8601Lus_test(void **state) {
+ isc_result_t result;
+ isc_time_t t;
+ char buf[64];
+
+ UNUSED(state);
+
+ setenv("TZ", "America/Los_Angeles", 1);
+ result = isc_time_now_hires(&t);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* check formatting: yyyy-mm-ddThh:mm:ss.ssssss */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_formatISO8601Lus(&t, buf, sizeof(buf));
+ assert_int_equal(strlen(buf), 26);
+ assert_int_equal(buf[4], '-');
+ assert_int_equal(buf[7], '-');
+ assert_int_equal(buf[10], 'T');
+ assert_int_equal(buf[13], ':');
+ assert_int_equal(buf[16], ':');
+ assert_int_equal(buf[19], '.');
+
+ /* check time conversion correctness */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_settoepoch(&t);
+ isc_time_formatISO8601Lus(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "1969-12-31T16:00:00.000000");
+
+ memset(buf, 'X', sizeof(buf));
+ isc_time_set(&t, 1450000000, 123456000);
+ isc_time_formatISO8601Lus(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "2015-12-13T01:46:40.123456");
+}
+
+/* print UTC time as yyyymmddhhmmsssss */
+static void
+isc_time_formatshorttimestamp_test(void **state) {
+ isc_result_t result;
+ isc_time_t t;
+ char buf[64];
+
+ UNUSED(state);
+
+ setenv("TZ", "America/Los_Angeles", 1);
+ result = isc_time_now(&t);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /* check formatting: yyyymmddhhmmsssss */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_formatshorttimestamp(&t, buf, sizeof(buf));
+ assert_int_equal(strlen(buf), 17);
+
+ /* check time conversion correctness */
+ memset(buf, 'X', sizeof(buf));
+ isc_time_settoepoch(&t);
+ isc_time_formatshorttimestamp(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "19700101000000000");
+
+ memset(buf, 'X', sizeof(buf));
+ isc_time_set(&t, 1450000000, 123000000);
+ isc_time_formatshorttimestamp(&t, buf, sizeof(buf));
+ assert_string_equal(buf, "20151213094640123");
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(isc_time_add_test),
+ cmocka_unit_test(isc_time_sub_test),
+ cmocka_unit_test(isc_time_parsehttptimestamp_test),
+ cmocka_unit_test(isc_time_formatISO8601_test),
+ cmocka_unit_test(isc_time_formatISO8601ms_test),
+ cmocka_unit_test(isc_time_formatISO8601us_test),
+ cmocka_unit_test(isc_time_formatISO8601L_test),
+ cmocka_unit_test(isc_time_formatISO8601Lms_test),
+ cmocka_unit_test(isc_time_formatISO8601Lus_test),
+ cmocka_unit_test(isc_time_formatshorttimestamp_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/timer_test.c b/lib/isc/tests/timer_test.c
new file mode 100644
index 0000000..9bf4cf7
--- /dev/null
+++ b/lib/isc/tests/timer_test.c
@@ -0,0 +1,634 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/atomic.h>
+#include <isc/commandline.h>
+#include <isc/condition.h>
+#include <isc/mem.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include "../timer.c"
+#include "isctest.h"
+
+/* Set to true (or use -v option) for verbose output */
+static bool verbose = false;
+
+#define FUDGE_SECONDS 0 /* in absence of clock_getres() */
+#define FUDGE_NANOSECONDS 500000000 /* in absence of clock_getres() */
+
+static isc_timer_t *timer = NULL;
+static isc_condition_t cv;
+static isc_mutex_t mx;
+static isc_time_t endtime;
+static isc_mutex_t lasttime_mx;
+static isc_time_t lasttime;
+static int seconds;
+static int nanoseconds;
+static atomic_int_fast32_t eventcnt;
+static atomic_uint_fast32_t errcnt;
+static int nevents;
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ /* Timer tests require two worker threads */
+ result = isc_test_begin(NULL, true, 2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ atomic_init(&errcnt, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ isc_test_end();
+
+ return (0);
+}
+
+static void
+test_shutdown(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+
+ UNUSED(task);
+
+ /*
+ * Signal shutdown processing complete.
+ */
+ result = isc_mutex_lock(&mx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_condition_signal(&cv);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_mutex_unlock(&mx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_event_free(&event);
+}
+
+static void
+setup_test(isc_timertype_t timertype, isc_time_t *expires,
+ isc_interval_t *interval,
+ void (*action)(isc_task_t *, isc_event_t *)) {
+ isc_result_t result;
+ isc_task_t *task = NULL;
+ isc_time_settoepoch(&endtime);
+ atomic_init(&eventcnt, 0);
+
+ isc_mutex_init(&mx);
+ isc_mutex_init(&lasttime_mx);
+
+ isc_condition_init(&cv);
+
+ atomic_store(&errcnt, ISC_R_SUCCESS);
+
+ LOCK(&mx);
+
+ result = isc_task_create(taskmgr, 0, &task);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_onshutdown(task, test_shutdown, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_mutex_lock(&lasttime_mx);
+ result = isc_time_now(&lasttime);
+ isc_mutex_unlock(&lasttime_mx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_timer_create(timermgr, timertype, expires, interval, task,
+ action, (void *)timertype, &timer);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Wait for shutdown processing to complete.
+ */
+ while (atomic_load(&eventcnt) != nevents) {
+ result = isc_condition_wait(&cv, &mx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ UNLOCK(&mx);
+
+ assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
+
+ isc_task_detach(&task);
+ isc_mutex_destroy(&mx);
+ isc_mutex_destroy(&lasttime_mx);
+ (void)isc_condition_destroy(&cv);
+}
+
+static void
+set_global_error(isc_result_t result) {
+ (void)atomic_compare_exchange_strong(
+ &errcnt, &(uint_fast32_t){ ISC_R_SUCCESS }, result);
+}
+
+static void
+subthread_assert_true(bool expected, const char *file, unsigned int line) {
+ if (!expected) {
+ printf("# %s:%u subthread_assert_true\n", file, line);
+ set_global_error(ISC_R_UNEXPECTED);
+ }
+}
+#define subthread_assert_true(expected) \
+ subthread_assert_true(expected, __FILE__, __LINE__)
+
+static void
+subthread_assert_int_equal(int observed, int expected, const char *file,
+ unsigned int line) {
+ if (observed != expected) {
+ printf("# %s:%u subthread_assert_int_equal(%d != %d)\n", file,
+ line, observed, expected);
+ set_global_error(ISC_R_UNEXPECTED);
+ }
+}
+#define subthread_assert_int_equal(observed, expected) \
+ subthread_assert_int_equal(observed, expected, __FILE__, __LINE__)
+
+static void
+subthread_assert_result_equal(isc_result_t result, isc_result_t expected,
+ const char *file, unsigned int line) {
+ if (result != expected) {
+ printf("# %s:%u subthread_assert_result_equal(%u != %u)\n",
+ file, line, result, expected);
+ set_global_error(result);
+ }
+}
+#define subthread_assert_result_equal(observed, expected) \
+ subthread_assert_result_equal(observed, expected, __FILE__, __LINE__)
+
+static void
+ticktock(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ isc_time_t now;
+ isc_time_t base;
+ isc_time_t ulim;
+ isc_time_t llim;
+ isc_interval_t interval;
+ isc_eventtype_t expected_event_type;
+
+ int tick = atomic_fetch_add(&eventcnt, 1);
+
+ if (verbose) {
+ print_message("# tick %d\n", tick);
+ }
+
+ expected_event_type = ISC_TIMEREVENT_LIFE;
+ if ((uintptr_t)event->ev_arg == isc_timertype_ticker) {
+ expected_event_type = ISC_TIMEREVENT_TICK;
+ }
+
+ if (event->ev_type != expected_event_type) {
+ print_error("# expected event type %u, got %u\n",
+ expected_event_type, event->ev_type);
+ }
+
+ result = isc_time_now(&now);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ isc_interval_set(&interval, seconds, nanoseconds);
+ isc_mutex_lock(&lasttime_mx);
+ result = isc_time_add(&lasttime, &interval, &base);
+ isc_mutex_unlock(&lasttime_mx);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
+ result = isc_time_add(&base, &interval, &ulim);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ result = isc_time_subtract(&base, &interval, &llim);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
+ subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
+
+ isc_interval_set(&interval, 0, 0);
+ isc_mutex_lock(&lasttime_mx);
+ result = isc_time_add(&now, &interval, &lasttime);
+ isc_mutex_unlock(&lasttime_mx);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ isc_event_free(&event);
+
+ if (atomic_load(&eventcnt) == nevents) {
+ result = isc_time_now(&endtime);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+ isc_timer_destroy(&timer);
+ isc_task_shutdown(task);
+ }
+}
+
+/*
+ * Individual unit tests
+ */
+
+/* timer type ticker */
+static void
+ticker(void **state) {
+ isc_time_t expires;
+ isc_interval_t interval;
+
+ UNUSED(state);
+
+ nevents = 12;
+ seconds = 0;
+ nanoseconds = 500000000;
+
+ isc_interval_set(&interval, seconds, nanoseconds);
+ isc_time_settoepoch(&expires);
+
+ setup_test(isc_timertype_ticker, &expires, &interval, ticktock);
+}
+
+/* timer type once reaches lifetime */
+static void
+once_life(void **state) {
+ isc_result_t result;
+ isc_time_t expires;
+ isc_interval_t interval;
+
+ UNUSED(state);
+
+ nevents = 1;
+ seconds = 1;
+ nanoseconds = 100000000;
+
+ isc_interval_set(&interval, seconds, nanoseconds);
+ result = isc_time_nowplusinterval(&expires, &interval);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_interval_set(&interval, 0, 0);
+
+ setup_test(isc_timertype_once, &expires, &interval, ticktock);
+}
+
+static void
+test_idle(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ isc_time_t now;
+ isc_time_t base;
+ isc_time_t ulim;
+ isc_time_t llim;
+ isc_interval_t interval;
+
+ int tick = atomic_fetch_add(&eventcnt, 1);
+
+ if (verbose) {
+ print_message("# tick %d\n", tick);
+ }
+
+ result = isc_time_now(&now);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ isc_interval_set(&interval, seconds, nanoseconds);
+ isc_mutex_lock(&lasttime_mx);
+ result = isc_time_add(&lasttime, &interval, &base);
+ isc_mutex_unlock(&lasttime_mx);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
+ result = isc_time_add(&base, &interval, &ulim);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ result = isc_time_subtract(&base, &interval, &llim);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
+ subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
+
+ isc_interval_set(&interval, 0, 0);
+ isc_mutex_lock(&lasttime_mx);
+ isc_time_add(&now, &interval, &lasttime);
+ isc_mutex_unlock(&lasttime_mx);
+
+ subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_IDLE);
+
+ isc_event_free(&event);
+
+ isc_timer_destroy(&timer);
+ isc_task_shutdown(task);
+}
+
+/* timer type once idles out */
+static void
+once_idle(void **state) {
+ isc_result_t result;
+ isc_time_t expires;
+ isc_interval_t interval;
+
+ UNUSED(state);
+
+ nevents = 1;
+ seconds = 1;
+ nanoseconds = 200000000;
+
+ isc_interval_set(&interval, seconds + 1, nanoseconds);
+ result = isc_time_nowplusinterval(&expires, &interval);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_interval_set(&interval, seconds, nanoseconds);
+
+ setup_test(isc_timertype_once, &expires, &interval, test_idle);
+}
+
+/* timer reset */
+static void
+test_reset(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ isc_time_t now;
+ isc_time_t base;
+ isc_time_t ulim;
+ isc_time_t llim;
+ isc_time_t expires;
+ isc_interval_t interval;
+
+ int tick = atomic_fetch_add(&eventcnt, 1);
+
+ if (verbose) {
+ print_message("# tick %d\n", tick);
+ }
+
+ /*
+ * Check expired time.
+ */
+
+ result = isc_time_now(&now);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ isc_interval_set(&interval, seconds, nanoseconds);
+ isc_mutex_lock(&lasttime_mx);
+ result = isc_time_add(&lasttime, &interval, &base);
+ isc_mutex_unlock(&lasttime_mx);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
+ result = isc_time_add(&base, &interval, &ulim);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ result = isc_time_subtract(&base, &interval, &llim);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
+ subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
+
+ isc_interval_set(&interval, 0, 0);
+ isc_mutex_lock(&lasttime_mx);
+ isc_time_add(&now, &interval, &lasttime);
+ isc_mutex_unlock(&lasttime_mx);
+
+ int _eventcnt = atomic_load(&eventcnt);
+
+ if (_eventcnt < 3) {
+ subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_TICK);
+
+ if (_eventcnt == 2) {
+ isc_interval_set(&interval, seconds, nanoseconds);
+ result = isc_time_nowplusinterval(&expires, &interval);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ isc_interval_set(&interval, 0, 0);
+ result = isc_timer_reset(timer, isc_timertype_once,
+ &expires, &interval, false);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+ }
+
+ isc_event_free(&event);
+ } else {
+ subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_LIFE);
+
+ isc_event_free(&event);
+ isc_timer_destroy(&timer);
+ isc_task_shutdown(task);
+ }
+}
+
+static void
+reset(void **state) {
+ isc_time_t expires;
+ isc_interval_t interval;
+
+ UNUSED(state);
+
+ nevents = 3;
+ seconds = 0;
+ nanoseconds = 750000000;
+
+ isc_interval_set(&interval, seconds, nanoseconds);
+ isc_time_settoepoch(&expires);
+
+ setup_test(isc_timertype_ticker, &expires, &interval, test_reset);
+}
+
+static atomic_bool startflag;
+static atomic_bool shutdownflag;
+static isc_timer_t *tickertimer = NULL;
+static isc_timer_t *oncetimer = NULL;
+static isc_task_t *task1 = NULL;
+static isc_task_t *task2 = NULL;
+
+/*
+ * task1 blocks on mx while events accumulate
+ * in its queue, until signaled by task2.
+ */
+
+static void
+tick_event(isc_task_t *task, isc_event_t *event) {
+ isc_result_t result;
+ isc_time_t expires;
+ isc_interval_t interval;
+
+ UNUSED(task);
+
+ if (!atomic_load(&startflag)) {
+ if (verbose) {
+ print_message("# tick_event %d\n", -1);
+ }
+ isc_event_free(&event);
+ return;
+ }
+
+ int tick = atomic_fetch_add(&eventcnt, 1);
+ if (verbose) {
+ print_message("# tick_event %d\n", tick);
+ }
+
+ /*
+ * On the first tick, purge all remaining tick events
+ * and then shut down the task.
+ */
+ if (tick == 0) {
+ isc_time_settoepoch(&expires);
+ isc_interval_set(&interval, seconds, 0);
+ result = isc_timer_reset(tickertimer, isc_timertype_ticker,
+ &expires, &interval, true);
+ subthread_assert_result_equal(result, ISC_R_SUCCESS);
+
+ isc_task_shutdown(task);
+ }
+
+ isc_event_free(&event);
+}
+
+static void
+once_event(isc_task_t *task, isc_event_t *event) {
+ if (verbose) {
+ print_message("# once_event\n");
+ }
+
+ /*
+ * Allow task1 to start processing events.
+ */
+ atomic_store(&startflag, true);
+
+ isc_event_free(&event);
+ isc_task_shutdown(task);
+}
+
+static void
+shutdown_purge(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+ UNUSED(event);
+
+ if (verbose) {
+ print_message("# shutdown_event\n");
+ }
+
+ /*
+ * Signal shutdown processing complete.
+ */
+ atomic_store(&shutdownflag, 1);
+
+ isc_event_free(&event);
+}
+
+/* timer events purged */
+static void
+purge(void **state) {
+ isc_result_t result;
+ isc_time_t expires;
+ isc_interval_t interval;
+
+ UNUSED(state);
+
+ atomic_init(&startflag, 0);
+ atomic_init(&shutdownflag, 0);
+ atomic_init(&eventcnt, 0);
+ seconds = 1;
+ nanoseconds = 0;
+
+ result = isc_task_create(taskmgr, 0, &task1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_onshutdown(task1, shutdown_purge, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = isc_task_create(taskmgr, 0, &task2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_time_settoepoch(&expires);
+ isc_interval_set(&interval, seconds, 0);
+
+ tickertimer = NULL;
+ result = isc_timer_create(timermgr, isc_timertype_ticker, &expires,
+ &interval, task1, tick_event, NULL,
+ &tickertimer);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ oncetimer = NULL;
+
+ isc_interval_set(&interval, (seconds * 2) + 1, 0);
+ result = isc_time_nowplusinterval(&expires, &interval);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_interval_set(&interval, 0, 0);
+ result = isc_timer_create(timermgr, isc_timertype_once, &expires,
+ &interval, task2, once_event, NULL,
+ &oncetimer);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Wait for shutdown processing to complete.
+ */
+ while (!atomic_load(&shutdownflag)) {
+ isc_test_nap(1000);
+ }
+
+ assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
+
+ assert_int_equal(atomic_load(&eventcnt), 1);
+
+ isc_timer_destroy(&tickertimer);
+ isc_timer_destroy(&oncetimer);
+ isc_task_destroy(&task1);
+ isc_task_destroy(&task2);
+}
+
+int
+main(int argc, char **argv) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(ticker), cmocka_unit_test(once_life),
+ cmocka_unit_test(once_idle), cmocka_unit_test(reset),
+ cmocka_unit_test(purge),
+ };
+ int c;
+
+ while ((c = isc_commandline_parse(argc, argv, "v")) != -1) {
+ switch (c) {
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/uv_wrap.h b/lib/isc/tests/uv_wrap.h
new file mode 100644
index 0000000..968a624
--- /dev/null
+++ b/lib/isc/tests/uv_wrap.h
@@ -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.
+ */
+
+#if HAVE_CMOCKA
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <uv.h>
+
+#include <isc/atomic.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include "../netmgr/uv-compat.h"
+
+/* uv_udp_t */
+
+int
+__wrap_uv_udp_open(uv_udp_t *handle, uv_os_sock_t sock);
+int
+__wrap_uv_udp_bind(uv_udp_t *handle, const struct sockaddr *addr,
+ unsigned int flags);
+#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0)
+int
+__wrap_uv_udp_connect(uv_udp_t *handle, const struct sockaddr *addr);
+int
+__wrap_uv_udp_getpeername(const uv_udp_t *handle, struct sockaddr *name,
+ int *namelen);
+#endif /* UV_VERSION_HEX >= UV_VERSION(1, 27, 0) */
+int
+__wrap_uv_udp_getsockname(const uv_udp_t *handle, struct sockaddr *name,
+ int *namelen);
+int
+__wrap_uv_udp_send(uv_udp_send_t *req, uv_udp_t *handle, const uv_buf_t bufs[],
+ unsigned int nbufs, const struct sockaddr *addr,
+ uv_udp_send_cb send_cb);
+int
+__wrap_uv_udp_recv_start(uv_udp_t *handle, uv_alloc_cb alloc_cb,
+ uv_udp_recv_cb recv_cb);
+int
+__wrap_uv_udp_recv_stop(uv_udp_t *handle);
+
+/* uv_tcp_t */
+int
+__wrap_uv_tcp_open(uv_tcp_t *handle, uv_os_sock_t sock);
+int
+__wrap_uv_tcp_bind(uv_tcp_t *handle, const struct sockaddr *addr,
+ unsigned int flags);
+int
+__wrap_uv_tcp_getsockname(const uv_tcp_t *handle, struct sockaddr *name,
+ int *namelen);
+int
+__wrap_uv_tcp_getpeername(const uv_tcp_t *handle, struct sockaddr *name,
+ int *namelen);
+int
+__wrap_uv_tcp_connect(uv_connect_t *req, uv_tcp_t *handle,
+ const struct sockaddr *addr, uv_connect_cb cb);
+
+/* uv_stream_t */
+int
+__wrap_uv_listen(uv_stream_t *stream, int backlog, uv_connection_cb cb);
+int
+__wrap_uv_accept(uv_stream_t *server, uv_stream_t *client);
+
+/* uv_handle_t */
+int
+__wrap_uv_send_buffer_size(uv_handle_t *handle, int *value);
+int
+__wrap_uv_recv_buffer_size(uv_handle_t *handle, int *value);
+int
+__wrap_uv_fileno(const uv_handle_t *handle, uv_os_fd_t *fd);
+
+/* uv_timer_t */
+/* FIXME */
+/*
+ * uv_timer_init
+ * uv_timer_start
+ */
+
+static atomic_int __state_uv_udp_open = 0;
+
+int
+__wrap_uv_udp_open(uv_udp_t *handle, uv_os_sock_t sock) {
+ if (atomic_load(&__state_uv_udp_open) == 0) {
+ return (uv_udp_open(handle, sock));
+ }
+ return (atomic_load(&__state_uv_udp_open));
+}
+
+static atomic_int __state_uv_udp_bind = 0;
+
+int
+__wrap_uv_udp_bind(uv_udp_t *handle, const struct sockaddr *addr,
+ unsigned int flags) {
+ if (atomic_load(&__state_uv_udp_bind) == 0) {
+ return (uv_udp_bind(handle, addr, flags));
+ }
+ return (atomic_load(&__state_uv_udp_bind));
+}
+
+static atomic_int __state_uv_udp_connect = 0;
+#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0)
+int
+__wrap_uv_udp_connect(uv_udp_t *handle, const struct sockaddr *addr) {
+ if (atomic_load(&__state_uv_udp_connect) == 0) {
+ return (uv_udp_connect(handle, addr));
+ }
+ return (atomic_load(&__state_uv_udp_connect));
+}
+#endif /* UV_VERSION_HEX >= UV_VERSION(1, 27, 0) */
+
+static atomic_int __state_uv_udp_getpeername = 0;
+#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0)
+int
+__wrap_uv_udp_getpeername(const uv_udp_t *handle, struct sockaddr *name,
+ int *namelen) {
+ if (atomic_load(&__state_uv_udp_getpeername) == 0) {
+ return (uv_udp_getpeername(handle, name, namelen));
+ }
+ return (atomic_load(&__state_uv_udp_getpeername));
+}
+#endif /* UV_VERSION_HEX >= UV_VERSION(1, 27, 0) */
+
+static atomic_int __state_uv_udp_getsockname = 0;
+int
+__wrap_uv_udp_getsockname(const uv_udp_t *handle, struct sockaddr *name,
+ int *namelen) {
+ if (atomic_load(&__state_uv_udp_getsockname) == 0) {
+ return (uv_udp_getsockname(handle, name, namelen));
+ }
+ return (atomic_load(&__state_uv_udp_getsockname));
+}
+
+static atomic_int __state_uv_udp_send = 0;
+int
+__wrap_uv_udp_send(uv_udp_send_t *req, uv_udp_t *handle, const uv_buf_t bufs[],
+ unsigned int nbufs, const struct sockaddr *addr,
+ uv_udp_send_cb send_cb) {
+ if (atomic_load(&__state_uv_udp_send) == 0) {
+ return (uv_udp_send(req, handle, bufs, nbufs, addr, send_cb));
+ }
+ return (atomic_load(&__state_uv_udp_send));
+}
+
+static atomic_int __state_uv_udp_recv_start = 0;
+int
+__wrap_uv_udp_recv_start(uv_udp_t *handle, uv_alloc_cb alloc_cb,
+ uv_udp_recv_cb recv_cb) {
+ if (atomic_load(&__state_uv_udp_recv_start) == 0) {
+ return (uv_udp_recv_start(handle, alloc_cb, recv_cb));
+ }
+ return (atomic_load(&__state_uv_udp_recv_start));
+}
+
+static atomic_int __state_uv_udp_recv_stop = 0;
+int
+__wrap_uv_udp_recv_stop(uv_udp_t *handle) {
+ if (atomic_load(&__state_uv_udp_recv_stop) == 0) {
+ return (uv_udp_recv_stop(handle));
+ }
+ return (atomic_load(&__state_uv_udp_recv_stop));
+}
+
+static atomic_int __state_uv_tcp_open = 0;
+int
+__wrap_uv_tcp_open(uv_tcp_t *handle, uv_os_sock_t sock) {
+ if (atomic_load(&__state_uv_tcp_open) == 0) {
+ return (uv_tcp_open(handle, sock));
+ }
+ return (atomic_load(&__state_uv_tcp_open));
+}
+
+static atomic_int __state_uv_tcp_bind = 0;
+int
+__wrap_uv_tcp_bind(uv_tcp_t *handle, const struct sockaddr *addr,
+ unsigned int flags) {
+ if (atomic_load(&__state_uv_tcp_bind) == 0) {
+ return (uv_tcp_bind(handle, addr, flags));
+ }
+ return (atomic_load(&__state_uv_tcp_bind));
+}
+
+static atomic_int __state_uv_tcp_getsockname = 0;
+int
+__wrap_uv_tcp_getsockname(const uv_tcp_t *handle, struct sockaddr *name,
+ int *namelen) {
+ if (atomic_load(&__state_uv_tcp_getsockname) == 0) {
+ return (uv_tcp_getsockname(handle, name, namelen));
+ }
+ return (atomic_load(&__state_uv_tcp_getsockname));
+}
+
+static atomic_int __state_uv_tcp_getpeername = 0;
+int
+__wrap_uv_tcp_getpeername(const uv_tcp_t *handle, struct sockaddr *name,
+ int *namelen) {
+ if (atomic_load(&__state_uv_tcp_getpeername) == 0) {
+ return (uv_tcp_getpeername(handle, name, namelen));
+ }
+ return (atomic_load(&__state_uv_tcp_getpeername));
+}
+
+static atomic_int __state_uv_tcp_connect = 0;
+int
+__wrap_uv_tcp_connect(uv_connect_t *req, uv_tcp_t *handle,
+ const struct sockaddr *addr, uv_connect_cb cb) {
+ if (atomic_load(&__state_uv_tcp_connect) == 0) {
+ return (uv_tcp_connect(req, handle, addr, cb));
+ }
+ return (atomic_load(&__state_uv_tcp_connect));
+}
+
+static atomic_int __state_uv_listen = 0;
+int
+__wrap_uv_listen(uv_stream_t *stream, int backlog, uv_connection_cb cb) {
+ if (atomic_load(&__state_uv_listen) == 0) {
+ return (uv_listen(stream, backlog, cb));
+ }
+ return (atomic_load(&__state_uv_listen));
+}
+
+static atomic_int __state_uv_accept = 0;
+int
+__wrap_uv_accept(uv_stream_t *server, uv_stream_t *client) {
+ if (atomic_load(&__state_uv_accept) == 0) {
+ return (uv_accept(server, client));
+ }
+ return (atomic_load(&__state_uv_accept));
+}
+
+static atomic_int __state_uv_send_buffer_size = 0;
+int
+__wrap_uv_send_buffer_size(uv_handle_t *handle, int *value) {
+ if (atomic_load(&__state_uv_send_buffer_size) == 0) {
+ return (uv_send_buffer_size(handle, value));
+ }
+ return (atomic_load(&__state_uv_send_buffer_size));
+}
+
+static atomic_int __state_uv_recv_buffer_size = 0;
+int
+__wrap_uv_recv_buffer_size(uv_handle_t *handle, int *value) {
+ if (atomic_load(&__state_uv_recv_buffer_size) == 0) {
+ return (uv_recv_buffer_size(handle, value));
+ }
+ return (atomic_load(&__state_uv_recv_buffer_size));
+}
+
+static atomic_int __state_uv_fileno = 0;
+int
+__wrap_uv_fileno(const uv_handle_t *handle, uv_os_fd_t *fd) {
+ if (atomic_load(&__state_uv_fileno) == 0) {
+ return (uv_fileno(handle, fd));
+ }
+ return (atomic_load(&__state_uv_fileno));
+}
+
+#define uv_udp_open(...) __wrap_uv_udp_open(__VA_ARGS__)
+#define uv_udp_bind(...) __wrap_uv_udp_bind(__VA_ARGS__)
+#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0)
+#define uv_udp_connect(...) __wrap_uv_udp_connect(__VA_ARGS__)
+#define uv_udp_getpeername(...) __wrap_uv_udp_getpeername(__VA_ARGS__)
+#endif /* UV_VERSION_HEX >= UV_VERSION(1, 27, 0) */
+#define uv_udp_getsockname(...) __wrap_uv_udp_getsockname(__VA_ARGS__)
+#define uv_udp_send(...) __wrap_uv_udp_send(__VA_ARGS__)
+#define uv_udp_recv_start(...) __wrap_uv_udp_recv_start(__VA_ARGS__)
+#define uv_udp_recv_stop(...) __wrap_uv_udp_recv_stop(__VA_ARGS__)
+
+#define uv_tcp_open(...) __wrap_uv_tcp_open(__VA_ARGS__)
+#define uv_tcp_bind(...) __wrap_uv_tcp_bind(__VA_ARGS__)
+#define uv_tcp_getsockname(...) __wrap_uv_tcp_getsockname(__VA_ARGS__)
+#define uv_tcp_getpeername(...) __wrap_uv_tcp_getpeername(__VA_ARGS__)
+#define uv_tcp_connect(...) __wrap_uv_tcp_connect(__VA_ARGS__)
+
+#define uv_listen(...) __wrap_uv_listen(__VA_ARGS__)
+#define uv_accept(...) __wrap_uv_accept(__VA_ARGS__)
+
+#define uv_send_buffer_size(...) __wrap_uv_send_buffer_size(__VA_ARGS__)
+#define uv_recv_buffer_size(...) __wrap_uv_recv_buffer_size(__VA_ARGS__)
+#define uv_fileno(...) __wrap_uv_fileno(__VA_ARGS__)
+
+#define RESET_RETURN \
+ { \
+ atomic_store(&__state_uv_udp_open, 0); \
+ atomic_store(&__state_uv_udp_bind, 0); \
+ atomic_store(&__state_uv_udp_connect, 0); \
+ atomic_store(&__state_uv_udp_getpeername, 0); \
+ atomic_store(&__state_uv_udp_getsockname, 0); \
+ atomic_store(&__state_uv_udp_send, 0); \
+ atomic_store(&__state_uv_udp_recv_start, 0); \
+ atomic_store(&__state_uv_udp_recv_stop, 0); \
+ atomic_store(&__state_uv_tcp_open, 0); \
+ atomic_store(&__state_uv_tcp_bind, 0); \
+ atomic_store(&__state_uv_tcp_getpeername, 0); \
+ atomic_store(&__state_uv_tcp_getsockname, 0); \
+ atomic_store(&__state_uv_tcp_connect, 0); \
+ atomic_store(&__state_uv_listen, 0); \
+ atomic_store(&__state_uv_accept, 0); \
+ atomic_store(&__state_uv_send_buffer_size, 0); \
+ atomic_store(&__state_uv_recv_buffer_size, 0); \
+ atomic_store(&__state_uv_fileno, 0); \
+ }
+
+#define WILL_RETURN(func, value) atomic_store(&__state_##func, value)
+
+#endif /* HAVE_CMOCKA */
diff --git a/lib/isc/timer.c b/lib/isc/timer.c
new file mode 100644
index 0000000..e659f58
--- /dev/null
+++ b/lib/isc/timer.c
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/platform.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#ifdef OPENSSL_LEAKS
+#include <openssl/err.h>
+#endif /* ifdef OPENSSL_LEAKS */
+
+#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 *manager0);
+
+static isc_result_t
+schedule(isc_timer_t *timer, isc_time_t *now, bool signal_ok) {
+ isc_timermgr_t *manager;
+ isc_time_t due;
+ int cmp;
+
+ /*!
+ * 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.
+ */
+ 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 void
+deschedule(isc_timer_t *timer) {
+ bool need_wakeup = false;
+ isc_timermgr_t *manager;
+
+ /*
+ * The caller must ensure locking.
+ */
+
+ manager = timer->manager;
+ if (timer->index > 0) {
+ 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(__FILE__, __LINE__,
+ "%s: %u",
+ "couldn't schedule "
+ "timer",
+ result);
+ }
+ }
+ } else {
+ manager->due = timer->due;
+ done = true;
+ }
+ }
+}
+
+static isc_threadresult_t
+#ifdef _WIN32 /* XXXDCL */
+ WINAPI
+#endif /* ifdef _WIN32 */
+ 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);
+
+#ifdef OPENSSL_LEAKS
+ ERR_remove_state(0);
+#endif /* ifdef OPENSSL_LEAKS */
+
+ 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..84c5b2f
--- /dev/null
+++ b/lib/isc/timer_p.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_TIMER_P_H
+#define ISC_TIMER_P_H
+
+/*! \file */
+
+isc_result_t
+isc__timermgr_nextevent(isc_timermgr_t *timermgr, isc_time_t *when);
+
+void
+isc__timermgr_dispatch(isc_timermgr_t *timermgr);
+
+#endif /* ISC_TIMER_P_H */
diff --git a/lib/isc/tls.c b/lib/isc/tls.c
new file mode 100644
index 0000000..859c73c
--- /dev/null
+++ b/lib/isc/tls.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.
+ */
+
+#include <inttypes.h>
+
+#include <openssl/bn.h>
+#include <openssl/conf.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/opensslv.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+
+#include <isc/atomic.h>
+#include <isc/log.h>
+#include <isc/mutex.h>
+#include <isc/mutexblock.h>
+#include <isc/once.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "openssl_shim.h"
+#include "tls_p.h"
+
+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 and ISC_MEM_DEBUGSIZE or ISC_MEM_DEBUGCTX flags are
+ * added, neither isc_mem_put() nor isc_mem_free() can be used
+ * to free up the memory allocated here because the flags were
+ * not set when calling isc_mem_get() or isc_mem_allocate()
+ * here.
+ *
+ * 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)
+ 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(__FILE__, __LINE__,
+ "OpenSSL pseudorandom number generator "
+ "cannot be initialized (see the `PRNG not "
+ "seeded' message in the OpenSSL FAQ)");
+ }
+
+ REQUIRE(atomic_compare_exchange_strong(&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)
+ 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
+
+ REQUIRE(atomic_compare_exchange_strong(&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));
+}
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..be451a9
--- /dev/null
+++ b/lib/isc/trampoline.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.
+ */
+
+/*! \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 when the isc_mem_debugging flags are changed
+ * later and ISC_MEM_DEBUGSIZE or ISC_MEM_DEBUGCTX flags are
+ * added, neither isc_mem_put() nor isc_mem_free() can be used
+ * to free up the memory allocated here because the flags were
+ * not set when calling isc_mem_get() or isc_mem_allocate()
+ * here.
+ *
+ * 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;
+#if defined(HAVE_THREAD_LOCAL)
+#include <threads.h>
+thread_local size_t isc_tid_v = SIZE_MAX;
+#elif defined(HAVE___THREAD)
+__thread size_t isc_tid_v = SIZE_MAX;
+#elif HAVE___DECLSPEC_THREAD
+__declspec(thread) size_t isc_tid_v = SIZE_MAX;
+#endif /* if defined(HAVE_THREAD_LOCAL) */
+static size_t isc__trampoline_min = 1;
+static size_t isc__trampoline_max = 65;
+
+static isc_once_t start_once = ISC_ONCE_INIT;
+static isc_once_t stop_once = ISC_ONCE_INIT;
+
+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);
+}
+
+static void
+do_init(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_initialize(void) {
+ isc_once_do(&start_once, do_init);
+}
+
+static void
+do_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]);
+}
+
+void
+isc__trampoline_shutdown(void) {
+ isc_once_do(&stop_once, do_shutdown);
+}
+
+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..4599ea1
--- /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() (or Windows equivalent), 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/unix/Makefile.in b/lib/isc/unix/Makefile.in
new file mode 100644
index 0000000..4af7d6e
--- /dev/null
+++ b/lib/isc/unix/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+CINCLUDES = -I${srcdir}/include \
+ -I${srcdir}/../pthreads/include \
+ -I../include \
+ -I${srcdir}/../include \
+ -I${srcdir}/.. \
+ ${OPENSSL_CFLAGS} \
+ ${JSON_C_CFLAGS} \
+ ${LIBXML2_CFLAGS}
+
+CDEFINES =
+CWARNINGS =
+
+# Alphabetically
+OBJS = pk11_api.@O@ \
+ dir.@O@ errno.@O@ errno2result.@O@ \
+ file.@O@ fsaccess.@O@ interfaceiter.@O@ \
+ meminfo.@O@ \
+ net.@O@ os.@O@ resource.@O@ socket.@O@ stdio.@O@ stdtime.@O@ \
+ syslog.@O@ time.@O@
+
+# Alphabetically
+SRCS = pk11_api.c \
+ dir.c errno.c errno2result.c \
+ file.c fsaccess.c interfaceiter.c meminfo.c \
+ net.c os.c resource.c socket.c stdio.c stdtime.c \
+ syslog.c time.c
+
+SUBDIRS = include
+TARGETS = ${OBJS}
+
+@BIND9_MAKE_RULES@
+
+interfaceiter.@O@: interfaceiter.c ifiter_getifaddrs.c
diff --git a/lib/isc/unix/dir.c b/lib/isc/unix/dir.c
new file mode 100644
index 0000000..b7eabe9
--- /dev/null
+++ b/lib/isc/unix/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/unix/errno.c b/lib/isc/unix/errno.c
new file mode 100644
index 0000000..5875f3b
--- /dev/null
+++ b/lib/isc/unix/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/unix/errno2result.c b/lib/isc/unix/errno2result.c
new file mode 100644
index 0000000..6f752df
--- /dev/null
+++ b/lib/isc/unix/errno2result.c
@@ -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.
+ */
+
+/*! \file */
+
+#include "errno2result.h"
+#include <stdbool.h>
+
+#include <isc/platform.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 ENOENT:
+ return (ISC_R_FILENOTFOUND);
+ case EACCES:
+ case EPERM:
+ 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/unix/errno2result.h b/lib/isc/unix/errno2result.h
new file mode 100644
index 0000000..05de1f2
--- /dev/null
+++ b/lib/isc/unix/errno2result.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef UNIX_ERRNO2RESULT_H
+#define UNIX_ERRNO2RESULT_H 1
+
+/*! \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
+
+#endif /* UNIX_ERRNO2RESULT_H */
diff --git a/lib/isc/unix/file.c b/lib/isc/unix/file.c
new file mode 100644
index 0000000..0604f14
--- /dev/null
+++ b/lib/isc/unix/file.c
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/platform.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);
+}
+
+void *
+isc_file_mmap(void *addr, size_t len, int prot, int flags, int fd,
+ off_t offset) {
+#ifdef HAVE_MMAP
+ return (mmap(addr, len, prot, flags, fd, offset));
+#else /* ifdef HAVE_MMAP */
+ void *buf;
+ ssize_t ret;
+ off_t end;
+
+ UNUSED(addr);
+ UNUSED(prot);
+ UNUSED(flags);
+
+ end = lseek(fd, 0, SEEK_END);
+ lseek(fd, offset, SEEK_SET);
+ if (end - offset < (off_t)len) {
+ len = end - offset;
+ }
+
+ buf = malloc(len);
+ if (buf == NULL) {
+ return (NULL);
+ }
+
+ ret = read(fd, buf, len);
+ if (ret != (ssize_t)len) {
+ free(buf);
+ buf = NULL;
+ }
+
+ return (buf);
+#endif /* ifdef HAVE_MMAP */
+}
+
+int
+isc_file_munmap(void *addr, size_t len) {
+#ifdef HAVE_MMAP
+ return (munmap(addr, len));
+#else /* ifdef HAVE_MMAP */
+ UNUSED(len);
+
+ free(addr);
+ return (0);
+#endif /* ifdef HAVE_MMAP */
+}
+
+#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/unix/fsaccess.c b/lib/isc/unix/fsaccess.c
new file mode 100644
index 0000000..306cdfd
--- /dev/null
+++ b/lib/isc/unix/fsaccess.c
@@ -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.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "errno2result.h"
+
+/*! \file
+ * \brief
+ * The OS-independent part of the API is in lib/isc.
+ */
+#include "../fsaccess.c"
+
+isc_result_t
+isc_fsaccess_set(const char *path, isc_fsaccess_t access) {
+ struct stat statb;
+ mode_t mode;
+ bool is_dir = false;
+ isc_fsaccess_t bits;
+ isc_result_t result;
+
+ if (stat(path, &statb) != 0) {
+ return (isc__errno2result(errno));
+ }
+
+ if ((statb.st_mode & S_IFDIR) != 0) {
+ is_dir = true;
+ } else if ((statb.st_mode & S_IFREG) == 0) {
+ return (ISC_R_INVALIDFILE);
+ }
+
+ result = check_bad_bits(access, is_dir);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Done with checking bad bits. Set mode_t.
+ */
+ mode = 0;
+
+#define SET_AND_CLEAR1(modebit) \
+ if ((access & bits) != 0) { \
+ mode |= modebit; \
+ access &= ~bits; \
+ }
+#define SET_AND_CLEAR(user, group, other) \
+ SET_AND_CLEAR1(user); \
+ bits <<= STEP; \
+ SET_AND_CLEAR1(group); \
+ bits <<= STEP; \
+ SET_AND_CLEAR1(other);
+
+ bits = ISC_FSACCESS_READ | ISC_FSACCESS_LISTDIRECTORY;
+
+ SET_AND_CLEAR(S_IRUSR, S_IRGRP, S_IROTH);
+
+ bits = ISC_FSACCESS_WRITE | ISC_FSACCESS_CREATECHILD |
+ ISC_FSACCESS_DELETECHILD;
+
+ SET_AND_CLEAR(S_IWUSR, S_IWGRP, S_IWOTH);
+
+ bits = ISC_FSACCESS_EXECUTE | ISC_FSACCESS_ACCESSCHILD;
+
+ SET_AND_CLEAR(S_IXUSR, S_IXGRP, S_IXOTH);
+
+ INSIST(access == 0);
+
+ if (chmod(path, mode) < 0) {
+ return (isc__errno2result(errno));
+ }
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/unix/ifiter_getifaddrs.c b/lib/isc/unix/ifiter_getifaddrs.c
new file mode 100644
index 0000000..9f8b31a
--- /dev/null
+++ b/lib/isc/unix/ifiter_getifaddrs.c
@@ -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.
+ */
+
+/*! \file
+ * \brief
+ * Obtain the list of network interfaces using the getifaddrs(3) library.
+ */
+
+#include <ifaddrs.h>
+#include <stdbool.h>
+
+#include <isc/strerr.h>
+
+/*% 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(__FILE__, __LINE__,
+ "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;
+}
diff --git a/lib/isc/unix/include/.clang-format b/lib/isc/unix/include/.clang-format
new file mode 120000
index 0000000..e919bba
--- /dev/null
+++ b/lib/isc/unix/include/.clang-format
@@ -0,0 +1 @@
+../../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/isc/unix/include/Makefile.in b/lib/isc/unix/include/Makefile.in
new file mode 100644
index 0000000..60a0a67
--- /dev/null
+++ b/lib/isc/unix/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = isc
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/isc/unix/include/isc/Makefile.in b/lib/isc/unix/include/isc/Makefile.in
new file mode 100644
index 0000000..06f7806
--- /dev/null
+++ b/lib/isc/unix/include/isc/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+HEADERS = align.h dir.h net.h netdb.h offset.h stat.h \
+ stdatomic.h stdtime.h syslog.h time.h
+
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/isc
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} $(srcdir)/$$i ${DESTDIR}${includedir}/isc || exit 1; \
+ done
+
+uninstall::
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/isc/$$i || exit 1; \
+ done
diff --git a/lib/isc/unix/include/isc/align.h b/lib/isc/unix/include/isc/align.h
new file mode 100644
index 0000000..7b72e9d
--- /dev/null
+++ b/lib/isc/unix/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/unix/include/isc/dir.h b/lib/isc/unix/include/isc/dir.h
new file mode 100644
index 0000000..89d1338
--- /dev/null
+++ b/lib/isc/unix/include/isc/dir.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_DIR_H
+#define ISC_DIR_H 1
+
+/*! \file */
+
+#include <dirent.h>
+
+#include <isc/lang.h>
+#include <isc/platform.h>
+#include <isc/result.h>
+
+#include <sys/types.h> /* Required on some systems. */
+
+/*% 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
+
+#endif /* ISC_DIR_H */
diff --git a/lib/isc/unix/include/isc/net.h b/lib/isc/unix/include/isc/net.h
new file mode 100644
index 0000000..ead9c7f
--- /dev/null
+++ b/lib/isc/unix/include/isc/net.h
@@ -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.
+ */
+
+#ifndef ISC_NET_H
+#define ISC_NET_H 1
+
+/*****
+***** 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/platform.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.
+ */
+
+#define ISC_NET_DSCPRECVV4 0x01 /* Can receive sent DSCP value IPv4 */
+#define ISC_NET_DSCPRECVV6 0x02 /* Can receive sent DSCP value IPv6 */
+#define ISC_NET_DSCPSETV4 0x04 /* Can set DSCP on socket IPv4 */
+#define ISC_NET_DSCPSETV6 0x08 /* Can set DSCP on socket IPv6 */
+#define ISC_NET_DSCPPKTV4 0x10 /* Can set DSCP on per packet IPv4 */
+#define ISC_NET_DSCPPKTV6 0x20 /* Can set DSCP on per packet IPv6 */
+#define ISC_NET_DSCPALL 0x3f /* All valid flags */
+
+unsigned int
+isc_net_probedscp(void);
+/*%<
+ * Probe the level of DSCP support.
+ */
+
+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
+
+#endif /* ISC_NET_H */
diff --git a/lib/isc/unix/include/isc/netdb.h b/lib/isc/unix/include/isc/netdb.h
new file mode 100644
index 0000000..dab845a
--- /dev/null
+++ b/lib/isc/unix/include/isc/netdb.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_NETDB_H
+#define ISC_NETDB_H 1
+
+/*****
+***** 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>
+
+#endif /* ISC_NETDB_H */
diff --git a/lib/isc/unix/include/isc/offset.h b/lib/isc/unix/include/isc/offset.h
new file mode 100644
index 0000000..fa24ae4
--- /dev/null
+++ b/lib/isc/unix/include/isc/offset.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_OFFSET_H
+#define ISC_OFFSET_H 1
+
+/*! \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;
+
+#endif /* ISC_OFFSET_H */
diff --git a/lib/isc/unix/include/isc/stat.h b/lib/isc/unix/include/isc/stat.h
new file mode 100644
index 0000000..6ddddd7
--- /dev/null
+++ b/lib/isc/unix/include/isc/stat.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_STAT_H
+#define ISC_STAT_H 1
+
+/*****
+***** 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>
+
+#endif /* ISC_STAT_H */
diff --git a/lib/isc/unix/include/isc/stdatomic.h b/lib/isc/unix/include/isc/stdatomic.h
new file mode 100644
index 0000000..f071b6a
--- /dev/null
+++ b/lib/isc/unix/include/isc/stdatomic.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#if HAVE_UCHAR_H
+#include <uchar.h>
+#endif /* HAVE_UCHAR_H */
+
+#if !defined(__has_feature)
+#define __has_feature(x) 0
+#endif /* if !defined(__has_feature) */
+
+#if !defined(__has_extension)
+#define __has_extension(x) __has_feature(x)
+#endif /* if !defined(__has_extension) */
+
+#if !defined(__GNUC_PREREQ__)
+#if defined(__GNUC__) && defined(__GNUC_MINOR__)
+#define __GNUC_PREREQ__(maj, min) \
+ ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+#else /* if defined(__GNUC__) && defined(__GNUC_MINOR__) */
+#define __GNUC_PREREQ__(maj, min) 0
+#endif /* if defined(__GNUC__) && defined(__GNUC_MINOR__) */
+#endif /* if !defined(__GNUC_PREREQ__) */
+
+#if !defined(__CLANG_ATOMICS) && !defined(__GNUC_ATOMICS)
+#if __has_extension(c_atomic) || __has_extension(cxx_atomic)
+#define __CLANG_ATOMICS
+#elif __GNUC_PREREQ__(4, 7)
+#define __GNUC_ATOMICS
+#elif !defined(__GNUC__)
+#error "isc/stdatomic.h does not support your compiler"
+#endif /* if __has_extension(c_atomic) || __has_extension(cxx_atomic) */
+#endif /* if !defined(__CLANG_ATOMICS) && !defined(__GNUC_ATOMICS) */
+
+#ifndef __ATOMIC_RELAXED
+#define __ATOMIC_RELAXED 0
+#endif /* ifndef __ATOMIC_RELAXED */
+#ifndef __ATOMIC_CONSUME
+#define __ATOMIC_CONSUME 1
+#endif /* ifndef __ATOMIC_CONSUME */
+#ifndef __ATOMIC_ACQUIRE
+#define __ATOMIC_ACQUIRE 2
+#endif /* ifndef __ATOMIC_ACQUIRE */
+#ifndef __ATOMIC_RELEASE
+#define __ATOMIC_RELEASE 3
+#endif /* ifndef __ATOMIC_RELEASE */
+#ifndef __ATOMIC_ACQ_REL
+#define __ATOMIC_ACQ_REL 4
+#endif /* ifndef __ATOMIC_ACQ_REL */
+#ifndef __ATOMIC_SEQ_CST
+#define __ATOMIC_SEQ_CST 5
+#endif /* ifndef __ATOMIC_SEQ_CST */
+
+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
+};
+
+typedef enum memory_order 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;
+
+#if defined(__CLANG_ATOMICS) /* __c11_atomic builtins */
+#define atomic_init(obj, desired) __c11_atomic_init(obj, desired)
+#define atomic_load_explicit(obj, order) __c11_atomic_load(obj, order)
+#define atomic_store_explicit(obj, desired, order) \
+ __c11_atomic_store(obj, desired, order)
+#define atomic_fetch_add_explicit(obj, arg, order) \
+ __c11_atomic_fetch_add(obj, arg, order)
+#define atomic_fetch_sub_explicit(obj, arg, order) \
+ __c11_atomic_fetch_sub(obj, arg, order)
+#define atomic_fetch_and_explicit(obj, arg, order) \
+ __c11_atomic_fetch_and(obj, arg, order)
+#define atomic_fetch_or_explicit(obj, arg, order) \
+ __c11_atomic_fetch_or(obj, arg, order)
+#define atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \
+ fail) \
+ __c11_atomic_compare_exchange_strong_explicit(obj, expected, desired, \
+ succ, fail)
+#define atomic_compare_exchange_weak_explicit(obj, expected, desired, succ, \
+ fail) \
+ __c11_atomic_compare_exchange_weak_explicit(obj, expected, desired, \
+ succ, fail)
+#define atomic_exchange_explicit(obj, desired, order) \
+ __c11_atomic_exchange_explicit(obj, expected, order)
+#elif defined(__GNUC_ATOMICS) /* __atomic builtins */
+#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)
+#else /* __sync builtins */
+#define atomic_init(obj, desired) (*obj = desired)
+#define atomic_load_explicit(obj, order) __sync_fetch_and_add(obj, 0)
+#define atomic_store_explicit(obj, desired, order) \
+ do { \
+ __sync_synchronize(); \
+ *obj = desired; \
+ __sync_synchronize(); \
+ } while (0);
+#define atomic_fetch_add_explicit(obj, arg, order) \
+ __sync_fetch_and_add(obj, arg)
+#define atomic_fetch_sub_explicit(obj, arg, order) \
+ __sync_fetch_and_sub(obj, arg, order)
+#define atomic_fetch_and_explicit(obj, arg, order) \
+ __sync_fetch_and_and(obj, arg, order)
+#define atomic_fetch_or_explicit(obj, arg, order) \
+ __sync_fetch_and_or(obj, arg, order)
+#define atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \
+ fail) \
+ ({ \
+ __typeof__(obj) __v; \
+ _Bool __r; \
+ __v = (__typeof__(obj))__sync_val_compare_and_swap( \
+ obj, *(expected), desired); \
+ __r = ((__typeof__(obj))*(expected) == __v); \
+ *(expected) = __v; \
+ __r; \
+ })
+#define atomic_compare_exchange_weak_explicit(obj, expected, desired, succ, \
+ fail) \
+ atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \
+ fail)
+#define atomic_exchange_explicit(obj, desired, order) \
+ __sync_lock_test_and_set(obj, desired)
+
+#endif /* if defined(__CLANG_ATOMICS) */
+
+#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/unix/include/isc/stdtime.h b/lib/isc/unix/include/isc/stdtime.h
new file mode 100644
index 0000000..e38a2c6
--- /dev/null
+++ b/lib/isc/unix/include/isc/stdtime.h
@@ -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.
+ */
+
+#ifndef ISC_STDTIME_H
+#define ISC_STDTIME_H 1
+
+/*! \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
+
+#endif /* ISC_STDTIME_H */
diff --git a/lib/isc/unix/include/isc/syslog.h b/lib/isc/unix/include/isc/syslog.h
new file mode 100644
index 0000000..f46dc1a
--- /dev/null
+++ b/lib/isc/unix/include/isc/syslog.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_SYSLOG_H
+#define ISC_SYSLOG_H 1
+
+/*! \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
+
+#endif /* ISC_SYSLOG_H */
diff --git a/lib/isc/unix/include/isc/time.h b/lib/isc/unix/include/isc/time.h
new file mode 100644
index 0000000..ecec64d
--- /dev/null
+++ b/lib/isc/unix/include/isc/time.h
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_TIME_H
+#define ISC_TIME_H 1
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+/***
+ *** 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.
+ */
+
+/***
+ *** 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
+
+#endif /* ISC_TIME_H */
diff --git a/lib/isc/unix/interfaceiter.c b/lib/isc/unix/interfaceiter.c
new file mode 100644
index 0000000..a1f6c9d
--- /dev/null
+++ b/lib/isc/unix/interfaceiter.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 <inttypes.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 */
+
+#include "ifiter_getifaddrs.c"
+
+#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/unix/meminfo.c b/lib/isc/unix/meminfo.c
new file mode 100644
index 0000000..612ccea
--- /dev/null
+++ b/lib/isc/unix/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/unix/net.c b/lib/isc/unix/net.c
new file mode 100644
index 0000000..1e43b4b
--- /dev/null
+++ b/lib/isc/unix/net.c
@@ -0,0 +1,860 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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_once_t once_dscp = 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 unsigned int dscp_result = 0;
+
+static isc_result_t
+try_proto(int domain) {
+ int s;
+ isc_result_t result = ISC_R_SUCCESS;
+ char strbuf[ISC_STRERRORSIZE];
+
+ 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:
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "socket() failed: %s", strbuf);
+ 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);
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ unix_result = try_proto(PF_UNIX);
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+}
+
+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;
+ char strbuf[ISC_STRERRORSIZE];
+#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) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s",
+ strbuf);
+ 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) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s",
+ strbuf);
+ 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;
+ char strbuf[ISC_STRERRORSIZE];
+ 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) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s",
+ strbuf);
+ 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 ISC_CMSG_IP_TOS || defined(IPV6_TCLASS)
+
+static socklen_t
+cmsg_len(socklen_t len) {
+#ifdef CMSG_LEN
+ return (CMSG_LEN(len));
+#else /* ifdef CMSG_LEN */
+ socklen_t hdrlen;
+
+ /*
+ * Cast NULL so that any pointer arithmetic performed by CMSG_DATA
+ * is correct.
+ */
+ hdrlen = (socklen_t)CMSG_DATA(((struct cmsghdr *)NULL));
+ return (hdrlen + len);
+#endif /* ifdef CMSG_LEN */
+}
+
+static socklen_t
+cmsg_space(socklen_t len) {
+#ifdef CMSG_SPACE
+ return (CMSG_SPACE(len));
+#else /* ifdef CMSG_SPACE */
+ struct msghdr msg;
+ struct cmsghdr *cmsgp;
+ /*
+ * XXX: The buffer length is an ad-hoc value, but should be enough
+ * in a practical sense.
+ */
+ char dummybuf[sizeof(struct cmsghdr) + 1024];
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_control = dummybuf;
+ msg.msg_controllen = sizeof(dummybuf);
+
+ cmsgp = (struct cmsghdr *)dummybuf;
+ cmsgp->cmsg_len = cmsg_len(len);
+
+ cmsgp = CMSG_NXTHDR(&msg, cmsgp);
+ if (cmsgp != NULL) {
+ return ((char *)cmsgp - (char *)msg.msg_control);
+ } else {
+ return (0);
+ }
+#endif /* ifdef CMSG_SPACE */
+}
+
+/*
+ * Make a fd non-blocking.
+ */
+static isc_result_t
+make_nonblock(int fd) {
+ int ret;
+ int flags;
+ char strbuf[ISC_STRERRORSIZE];
+#ifdef USE_FIONBIO_IOCTL
+ int on = 1;
+
+ ret = ioctl(fd, FIONBIO, (char *)&on);
+#else /* ifdef USE_FIONBIO_IOCTL */
+ flags = fcntl(fd, F_GETFL, 0);
+ flags |= PORT_NONBLOCK;
+ ret = fcntl(fd, F_SETFL, flags);
+#endif /* ifdef USE_FIONBIO_IOCTL */
+
+ if (ret == -1) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+#ifdef USE_FIONBIO_IOCTL
+ "ioctl(%d, FIONBIO, &on): %s", fd,
+#else /* ifdef USE_FIONBIO_IOCTL */
+ "fcntl(%d, F_SETFL, %d): %s", fd, flags,
+#endif /* ifdef USE_FIONBIO_IOCTL */
+ strbuf);
+
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static bool
+cmsgsend(int s, int level, int type, struct addrinfo *res) {
+ char strbuf[ISC_STRERRORSIZE];
+ struct sockaddr_storage ss;
+ socklen_t len = sizeof(ss);
+ struct msghdr msg;
+ union {
+ struct cmsghdr h;
+ unsigned char b[256];
+ } control;
+ struct cmsghdr *cmsgp;
+ int dscp = (46 << 2); /* Expedited forwarding. */
+ struct iovec iovec;
+ char buf[1] = { 0 };
+ isc_result_t result;
+
+ if (bind(s, res->ai_addr, res->ai_addrlen) < 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10),
+ "bind: %s", strbuf);
+ return (false);
+ }
+
+ if (getsockname(s, (struct sockaddr *)&ss, &len) < 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10),
+ "getsockname: %s", strbuf);
+ return (false);
+ }
+
+ iovec.iov_base = buf;
+ iovec.iov_len = sizeof(buf);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = (struct sockaddr *)&ss;
+ msg.msg_namelen = len;
+ msg.msg_iov = &iovec;
+ msg.msg_iovlen = 1;
+ msg.msg_control = (void *)&control;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+
+ cmsgp = msg.msg_control;
+
+ switch (type) {
+#ifdef IP_TOS
+ case IP_TOS:
+ memset(cmsgp, 0, cmsg_space(sizeof(char)));
+ cmsgp->cmsg_level = level;
+ cmsgp->cmsg_type = type;
+ cmsgp->cmsg_len = cmsg_len(sizeof(char));
+ *(unsigned char *)CMSG_DATA(cmsgp) = dscp;
+ msg.msg_controllen += cmsg_space(sizeof(char));
+ break;
+#endif /* ifdef IP_TOS */
+#ifdef IPV6_TCLASS
+ case IPV6_TCLASS:
+ memset(cmsgp, 0, cmsg_space(sizeof(dscp)));
+ cmsgp->cmsg_level = level;
+ cmsgp->cmsg_type = type;
+ cmsgp->cmsg_len = cmsg_len(sizeof(dscp));
+ memmove(CMSG_DATA(cmsgp), &dscp, sizeof(dscp));
+ msg.msg_controllen += cmsg_space(sizeof(dscp));
+ break;
+#endif /* ifdef IPV6_TCLASS */
+ default:
+ UNREACHABLE();
+ }
+
+ if (sendmsg(s, &msg, 0) < 0) {
+ int debug = ISC_LOG_DEBUG(10);
+ const char *typestr;
+ switch (errno) {
+#ifdef ENOPROTOOPT
+ case ENOPROTOOPT:
+#endif /* ifdef ENOPROTOOPT */
+#ifdef EOPNOTSUPP
+ case EOPNOTSUPP:
+#endif /* ifdef EOPNOTSUPP */
+ case EINVAL:
+ case EPERM:
+ break;
+ default:
+ debug = ISC_LOG_NOTICE;
+ }
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ if (debug != ISC_LOG_NOTICE) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10),
+ "sendmsg: %s", strbuf);
+ } else {
+ typestr = (type == IP_TOS) ? "IP_TOS" : "IPV6_TCLASS";
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "probing "
+ "sendmsg() with %s=%02x failed: %s",
+ typestr, dscp, strbuf);
+ }
+ return (false);
+ }
+
+ /*
+ * Make sure the message actually got sent.
+ */
+ result = make_nonblock(s);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ iovec.iov_base = buf;
+ iovec.iov_len = sizeof(buf);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = (struct sockaddr *)&ss;
+ msg.msg_namelen = sizeof(ss);
+ msg.msg_iov = &iovec;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+
+ if (recvmsg(s, &msg, 0) < 0) {
+ return (false);
+ }
+
+ return (true);
+}
+#endif /* if ISC_CMSG_IP_TOS || defined(IPV6_TCLASS) */
+
+static void
+try_dscp_v4(void) {
+#ifdef IP_TOS
+ char strbuf[ISC_STRERRORSIZE];
+ struct addrinfo hints, *res0;
+ int s, dscp = 0, n;
+#ifdef IP_RECVTOS
+ int on = 1;
+#endif /* IP_RECVTOS */
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+#ifdef AI_NUMERICHOST
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+#else /* ifdef AI_NUMERICHOST */
+ hints.ai_flags = AI_PASSIVE;
+#endif /* ifdef AI_NUMERICHOST */
+
+ n = getaddrinfo("127.0.0.1", NULL, &hints, &res0);
+ if (n != 0 || res0 == NULL) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10),
+ "getaddrinfo(127.0.0.1): %s", gai_strerror(n));
+ return;
+ }
+
+ s = socket(res0->ai_family, res0->ai_socktype, res0->ai_protocol);
+
+ if (s == -1) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10),
+ "socket: %s", strbuf);
+ freeaddrinfo(res0);
+ return;
+ }
+
+ if (setsockopt(s, IPPROTO_IP, IP_TOS, &dscp, sizeof(dscp)) == 0) {
+ dscp_result |= ISC_NET_DSCPSETV4;
+ }
+
+#ifdef IP_RECVTOS
+ on = 1;
+ if (setsockopt(s, IPPROTO_IP, IP_RECVTOS, &on, sizeof(on)) == 0) {
+ dscp_result |= ISC_NET_DSCPRECVV4;
+ }
+#endif /* IP_RECVTOS */
+
+#if ISC_CMSG_IP_TOS
+ if (cmsgsend(s, IPPROTO_IP, IP_TOS, res0)) {
+ dscp_result |= ISC_NET_DSCPPKTV4;
+ }
+#endif /* ISC_CMSG_IP_TOS */
+
+ freeaddrinfo(res0);
+ close(s);
+
+#endif /* IP_TOS */
+}
+
+static void
+try_dscp_v6(void) {
+#ifdef IPV6_TCLASS
+ char strbuf[ISC_STRERRORSIZE];
+ struct addrinfo hints, *res0;
+ int s, dscp = 0, n;
+#if defined(IPV6_RECVTCLASS)
+ int on = 1;
+#endif /* IPV6_RECVTCLASS */
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+#ifdef AI_NUMERICHOST
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+#else /* ifdef AI_NUMERICHOST */
+ hints.ai_flags = AI_PASSIVE;
+#endif /* ifdef AI_NUMERICHOST */
+
+ n = getaddrinfo("::1", NULL, &hints, &res0);
+ if (n != 0 || res0 == NULL) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10),
+ "getaddrinfo(::1): %s", gai_strerror(n));
+ return;
+ }
+
+ s = socket(res0->ai_family, res0->ai_socktype, res0->ai_protocol);
+ if (s == -1) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10),
+ "socket: %s", strbuf);
+ freeaddrinfo(res0);
+ return;
+ }
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &dscp, sizeof(dscp)) == 0)
+ {
+ dscp_result |= ISC_NET_DSCPSETV6;
+ }
+
+#ifdef IPV6_RECVTCLASS
+ on = 1;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVTCLASS, &on, sizeof(on)) == 0)
+ {
+ dscp_result |= ISC_NET_DSCPRECVV6;
+ }
+#endif /* IPV6_RECVTCLASS */
+
+ if (cmsgsend(s, IPPROTO_IPV6, IPV6_TCLASS, res0)) {
+ dscp_result |= ISC_NET_DSCPPKTV6;
+ }
+
+ freeaddrinfo(res0);
+ close(s);
+
+#endif /* IPV6_TCLASS */
+}
+
+static void
+try_dscp(void) {
+ try_dscp_v4();
+ try_dscp_v6();
+}
+
+static void
+initialize_dscp(void) {
+ RUNTIME_CHECK(isc_once_do(&once_dscp, try_dscp) == ISC_R_SUCCESS);
+}
+
+unsigned int
+isc_net_probedscp(void) {
+ initialize_dscp();
+ return (dscp_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/unix/os.c b/lib/isc/unix/os.c
new file mode 100644
index 0000000..5577587
--- /dev/null
+++ b/lib/isc/unix/os.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <isc/os.h>
+
+#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) */
+
+unsigned int
+isc_os_ncpus(void) {
+ long ncpus = 0;
+
+#if defined(HAVE_SYSCONF)
+ ncpus = sysconf_ncpus();
+#endif /* if defined(HAVE_SYSCONF) */
+#if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME)
+ if (ncpus <= 0) {
+ ncpus = sysctl_ncpus();
+ }
+#endif /* if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME) */
+ if (ncpus <= 0) {
+ ncpus = 1;
+ }
+
+ return ((unsigned int)ncpus);
+}
diff --git a/lib/isc/unix/pk11_api.c b/lib/isc/unix/pk11_api.c
new file mode 100644
index 0000000..babd1a9
--- /dev/null
+++ b/lib/isc/unix/pk11_api.c
@@ -0,0 +1,708 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <dlfcn.h>
+#include <string.h>
+
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/stdio.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <pkcs11/pkcs11.h>
+
+#define KEEP_PKCS11_NAMES
+#include <pk11/internal.h>
+#include <pk11/pk11.h>
+
+static void *hPK11 = NULL;
+static char loaderrmsg[1024];
+
+CK_RV
+pkcs_C_Initialize(CK_VOID_PTR pReserved) {
+ CK_C_Initialize sym;
+
+ if (hPK11 != NULL) {
+ return (CKR_CRYPTOKI_ALREADY_INITIALIZED);
+ }
+
+ hPK11 = dlopen(pk11_get_lib_name(), RTLD_NOW);
+
+ if (hPK11 == NULL) {
+ snprintf(loaderrmsg, sizeof(loaderrmsg),
+ "dlopen(\"%s\") failed: %s\n", pk11_get_lib_name(),
+ dlerror());
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ sym = (CK_C_Initialize)dlsym(hPK11, "C_Initialize");
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(pReserved));
+}
+
+char *
+pk11_get_load_error_message(void) {
+ return (loaderrmsg);
+}
+
+CK_RV
+pkcs_C_Finalize(CK_VOID_PTR pReserved) {
+ CK_C_Finalize sym;
+ CK_RV rv;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ sym = (CK_C_Finalize)dlsym(hPK11, "C_Finalize");
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ rv = (*sym)(pReserved);
+ if ((rv == CKR_OK) && (dlclose(hPK11) != 0)) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ hPK11 = NULL;
+ return (rv);
+}
+
+CK_RV
+pkcs_C_GetSlotList(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR pSlotList,
+ CK_ULONG_PTR pulCount) {
+ static CK_C_GetSlotList sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_GetSlotList)dlsym(hPK11, "C_GetSlotList");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(tokenPresent, pSlotList, pulCount));
+}
+
+CK_RV
+pkcs_C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) {
+ static CK_C_GetTokenInfo sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_GetTokenInfo)dlsym(hPK11, "C_GetTokenInfo");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(slotID, pInfo));
+}
+
+CK_RV
+pkcs_C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type,
+ CK_MECHANISM_INFO_PTR pInfo) {
+ static CK_C_GetMechanismInfo sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_GetMechanismInfo)dlsym(hPK11, "C_GetMechanismInfo");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(slotID, type, pInfo));
+}
+
+CK_RV
+pkcs_C_OpenSession(CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication,
+ CK_RV (*Notify)(CK_SESSION_HANDLE hSession,
+ CK_NOTIFICATION event,
+ CK_VOID_PTR pApplication),
+ CK_SESSION_HANDLE_PTR phSession) {
+ static CK_C_OpenSession sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ hPK11 = dlopen(pk11_get_lib_name(), RTLD_NOW);
+ }
+ if (hPK11 == NULL) {
+ snprintf(loaderrmsg, sizeof(loaderrmsg),
+ "dlopen(\"%s\") failed: %s\n", pk11_get_lib_name(),
+ dlerror());
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_OpenSession)dlsym(hPK11, "C_OpenSession");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(slotID, flags, pApplication, Notify, phSession));
+}
+
+CK_RV
+pkcs_C_CloseSession(CK_SESSION_HANDLE hSession) {
+ static CK_C_CloseSession sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_CloseSession)dlsym(hPK11, "C_CloseSession");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession));
+}
+
+CK_RV
+pkcs_C_Login(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType,
+ CK_CHAR_PTR pPin, CK_ULONG usPinLen) {
+ static CK_C_Login sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_Login)dlsym(hPK11, "C_Login");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, userType, pPin, usPinLen));
+}
+
+CK_RV
+pkcs_C_Logout(CK_SESSION_HANDLE hSession) {
+ static CK_C_Logout sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_Logout)dlsym(hPK11, "C_Logout");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession));
+}
+
+CK_RV
+pkcs_C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
+ CK_ULONG usCount, CK_OBJECT_HANDLE_PTR phObject) {
+ static CK_C_CreateObject sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_CreateObject)dlsym(hPK11, "C_CreateObject");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pTemplate, usCount, phObject));
+}
+
+CK_RV
+pkcs_C_DestroyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject) {
+ static CK_C_DestroyObject sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_DestroyObject)dlsym(hPK11, "C_DestroyObject");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, hObject));
+}
+
+CK_RV
+pkcs_C_GetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
+ CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount) {
+ static CK_C_GetAttributeValue sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_GetAttributeValue)dlsym(hPK11, "C_"
+ "GetAttributeValue");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, hObject, pTemplate, usCount));
+}
+
+CK_RV
+pkcs_C_SetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
+ CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount) {
+ static CK_C_SetAttributeValue sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_SetAttributeValue)dlsym(hPK11, "C_"
+ "SetAttributeValue");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, hObject, pTemplate, usCount));
+}
+
+CK_RV
+pkcs_C_FindObjectsInit(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
+ CK_ULONG usCount) {
+ static CK_C_FindObjectsInit sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_FindObjectsInit)dlsym(hPK11, "C_FindObjectsInit");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pTemplate, usCount));
+}
+
+CK_RV
+pkcs_C_FindObjects(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE_PTR phObject,
+ CK_ULONG usMaxObjectCount, CK_ULONG_PTR pusObjectCount) {
+ static CK_C_FindObjects sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_FindObjects)dlsym(hPK11, "C_FindObjects");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, phObject, usMaxObjectCount, pusObjectCount));
+}
+
+CK_RV
+pkcs_C_FindObjectsFinal(CK_SESSION_HANDLE hSession) {
+ static CK_C_FindObjectsFinal sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_FindObjectsFinal)dlsym(hPK11, "C_FindObjectsFinal");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession));
+}
+
+CK_RV
+pkcs_C_EncryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hKey) {
+ static CK_C_EncryptInit sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_EncryptInit)dlsym(hPK11, "C_EncryptInit");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pMechanism, hKey));
+}
+
+CK_RV
+pkcs_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData,
+ CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData,
+ CK_ULONG_PTR pulEncryptedDataLen) {
+ static CK_C_Encrypt sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_Encrypt)dlsym(hPK11, "C_Encrypt");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pData, ulDataLen, pEncryptedData,
+ pulEncryptedDataLen));
+}
+
+CK_RV
+pkcs_C_DigestInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism) {
+ static CK_C_DigestInit sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_DigestInit)dlsym(hPK11, "C_DigestInit");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pMechanism));
+}
+
+CK_RV
+pkcs_C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
+ CK_ULONG ulPartLen) {
+ static CK_C_DigestUpdate sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_DigestUpdate)dlsym(hPK11, "C_DigestUpdate");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pPart, ulPartLen));
+}
+
+CK_RV
+pkcs_C_DigestFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pDigest,
+ CK_ULONG_PTR pulDigestLen) {
+ static CK_C_DigestFinal sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_DigestFinal)dlsym(hPK11, "C_DigestFinal");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pDigest, pulDigestLen));
+}
+
+CK_RV
+pkcs_C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hKey) {
+ static CK_C_SignInit sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_SignInit)dlsym(hPK11, "C_SignInit");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pMechanism, hKey));
+}
+
+CK_RV
+pkcs_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen,
+ CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen) {
+ static CK_C_Sign sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_Sign)dlsym(hPK11, "C_Sign");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pData, ulDataLen, pSignature,
+ pulSignatureLen));
+}
+
+CK_RV
+pkcs_C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
+ CK_ULONG ulPartLen) {
+ static CK_C_SignUpdate sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_SignUpdate)dlsym(hPK11, "C_SignUpdate");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pPart, ulPartLen));
+}
+
+CK_RV
+pkcs_C_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature,
+ CK_ULONG_PTR pulSignatureLen) {
+ static CK_C_SignFinal sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_SignFinal)dlsym(hPK11, "C_SignFinal");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pSignature, pulSignatureLen));
+}
+
+CK_RV
+pkcs_C_VerifyInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hKey) {
+ static CK_C_VerifyInit sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_VerifyInit)dlsym(hPK11, "C_VerifyInit");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pMechanism, hKey));
+}
+
+CK_RV
+pkcs_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen,
+ CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen) {
+ static CK_C_Verify sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_Verify)dlsym(hPK11, "C_Verify");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pData, ulDataLen, pSignature, ulSignatureLen));
+}
+
+CK_RV
+pkcs_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
+ CK_ULONG ulPartLen) {
+ static CK_C_VerifyUpdate sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_VerifyUpdate)dlsym(hPK11, "C_VerifyUpdate");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pPart, ulPartLen));
+}
+
+CK_RV
+pkcs_C_VerifyFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature,
+ CK_ULONG ulSignatureLen) {
+ static CK_C_VerifyFinal sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_VerifyFinal)dlsym(hPK11, "C_VerifyFinal");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pSignature, ulSignatureLen));
+}
+
+CK_RV
+pkcs_C_GenerateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount,
+ CK_OBJECT_HANDLE_PTR phKey) {
+ static CK_C_GenerateKey sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_GenerateKey)dlsym(hPK11, "C_GenerateKey");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pMechanism, pTemplate, ulCount, phKey));
+}
+
+CK_RV
+pkcs_C_GenerateKeyPair(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_ATTRIBUTE_PTR pPublicKeyTemplate,
+ CK_ULONG usPublicKeyAttributeCount,
+ CK_ATTRIBUTE_PTR pPrivateKeyTemplate,
+ CK_ULONG usPrivateKeyAttributeCount,
+ CK_OBJECT_HANDLE_PTR phPrivateKey,
+ CK_OBJECT_HANDLE_PTR phPublicKey) {
+ static CK_C_GenerateKeyPair sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_GenerateKeyPair)dlsym(hPK11, "C_GenerateKeyPair");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pMechanism, pPublicKeyTemplate,
+ usPublicKeyAttributeCount, pPrivateKeyTemplate,
+ usPrivateKeyAttributeCount, phPrivateKey, phPublicKey));
+}
+
+CK_RV
+pkcs_C_DeriveKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hBaseKey, CK_ATTRIBUTE_PTR pTemplate,
+ CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey) {
+ static CK_C_DeriveKey sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_DeriveKey)dlsym(hPK11, "C_DeriveKey");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pMechanism, hBaseKey, pTemplate,
+ ulAttributeCount, phKey));
+}
+
+CK_RV
+pkcs_C_SeedRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSeed,
+ CK_ULONG ulSeedLen) {
+ static CK_C_SeedRandom sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_SeedRandom)dlsym(hPK11, "C_SeedRandom");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, pSeed, ulSeedLen));
+}
+
+CK_RV
+pkcs_C_GenerateRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR RandomData,
+ CK_ULONG ulRandomLen) {
+ static CK_C_GenerateRandom sym = NULL;
+ static void *pPK11 = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_LOAD_FAILED);
+ }
+ if ((sym == NULL) || (hPK11 != pPK11)) {
+ pPK11 = hPK11;
+ sym = (CK_C_GenerateRandom)dlsym(hPK11, "C_GenerateRandom");
+ }
+ if (sym == NULL) {
+ return (CKR_FUNCTION_NOT_SUPPORTED);
+ }
+ return ((*sym)(hSession, RandomData, ulRandomLen));
+}
diff --git a/lib/isc/unix/resource.c b/lib/isc/unix/resource.c
new file mode 100644
index 0000000..6020b39
--- /dev/null
+++ b/lib/isc/unix/resource.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#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/platform.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/unix/socket.c b/lib/isc/unix/socket.c
new file mode 100644
index 0000000..d6578ea
--- /dev/null
+++ b/lib/isc/unix/socket.c
@@ -0,0 +1,5521 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__)
+#include <sys/sysctl.h>
+#endif /* if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__) */
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H)
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#endif /* if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) \
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <isc/app.h>
+#include <isc/buffer.h>
+#include <isc/condition.h>
+#include <isc/formatcheck.h>
+#include <isc/list.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/net.h>
+#include <isc/once.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/resource.h>
+#include <isc/socket.h>
+#include <isc/stats.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#ifdef ISC_PLATFORM_HAVESYSUNH
+#include <sys/un.h>
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+#ifdef HAVE_KQUEUE
+#include <sys/event.h>
+#endif /* ifdef HAVE_KQUEUE */
+#ifdef HAVE_EPOLL_CREATE1
+#include <sys/epoll.h>
+#endif /* ifdef HAVE_EPOLL_CREATE1 */
+#if defined(HAVE_SYS_DEVPOLL_H)
+#include <sys/devpoll.h>
+#elif defined(HAVE_DEVPOLL_H)
+#include <devpoll.h>
+#endif /* if defined(HAVE_SYS_DEVPOLL_H) */
+
+#include <netinet/tcp.h>
+
+#include "errno2result.h"
+
+#ifdef ENABLE_TCP_FASTOPEN
+#include <netinet/tcp.h>
+#endif /* ifdef ENABLE_TCP_FASTOPEN */
+
+#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 */
+
+/*%
+ * Choose the most preferable multiplex method.
+ */
+#if defined(HAVE_KQUEUE)
+#define USE_KQUEUE
+#elif defined(HAVE_EPOLL_CREATE1)
+#define USE_EPOLL
+#elif defined(HAVE_SYS_DEVPOLL_H) || defined(HAVE_DEVPOLL_H)
+#define USE_DEVPOLL
+typedef struct {
+ unsigned int want_read : 1, want_write : 1;
+} pollinfo_t;
+#else /* if defined(HAVE_KQUEUE) */
+#define USE_SELECT
+#endif /* HAVE_KQUEUE */
+
+/*
+ * Set by the -T dscp option on the command line. If set to a value
+ * other than -1, we check to make sure DSCP values match it, and
+ * assert if not.
+ */
+int isc_dscp_check_value = -1;
+
+/*%
+ * Maximum number of allowable open sockets. This is also the maximum
+ * allowable socket file descriptor.
+ *
+ * Care should be taken before modifying this value for select():
+ * The API standard doesn't ensure select() accept more than (the system default
+ * of) FD_SETSIZE descriptors, and the default size should in fact be fine in
+ * the vast majority of cases. This constant should therefore be increased only
+ * when absolutely necessary and possible, i.e., the server is exhausting all
+ * available file descriptors (up to FD_SETSIZE) and the select() function
+ * and FD_xxx macros support larger values than FD_SETSIZE (which may not
+ * always by true, but we keep using some of them to ensure as much
+ * portability as possible). Note also that overall server performance
+ * may be rather worsened with a larger value of this constant due to
+ * inherent scalability problems of select().
+ *
+ * As a special note, this value shouldn't have to be touched if
+ * this is a build for an authoritative only DNS server.
+ */
+#ifndef ISC_SOCKET_MAXSOCKETS
+#if defined(USE_KQUEUE) || defined(USE_EPOLL) || defined(USE_DEVPOLL)
+#ifdef TUNE_LARGE
+#define ISC_SOCKET_MAXSOCKETS 21000
+#else /* ifdef TUNE_LARGE */
+#define ISC_SOCKET_MAXSOCKETS 4096
+#endif /* TUNE_LARGE */
+#elif defined(USE_SELECT)
+#define ISC_SOCKET_MAXSOCKETS FD_SETSIZE
+#endif /* USE_KQUEUE... */
+#endif /* ISC_SOCKET_MAXSOCKETS */
+
+#ifdef USE_SELECT
+/*%
+ * Mac OS X needs a special definition to support larger values in select().
+ * We always define this because a larger value can be specified run-time.
+ */
+#ifdef __APPLE__
+#define _DARWIN_UNLIMITED_SELECT
+#endif /* __APPLE__ */
+#endif /* USE_SELECT */
+
+#ifdef ISC_SOCKET_USE_POLLWATCH
+/*%
+ * If this macro is defined, enable workaround for a Solaris /dev/poll kernel
+ * bug: DP_POLL ioctl could keep sleeping even if socket I/O is possible for
+ * some of the specified FD. The idea is based on the observation that it's
+ * likely for a busy server to keep receiving packets. It specifically works
+ * as follows: the socket watcher is first initialized with the state of
+ * "poll_idle". While it's in the idle state it keeps sleeping until a socket
+ * event occurs. When it wakes up for a socket I/O event, it moves to the
+ * poll_active state, and sets the poll timeout to a short period
+ * (ISC_SOCKET_POLLWATCH_TIMEOUT msec). If timeout occurs in this state, the
+ * watcher goes to the poll_checking state with the same timeout period.
+ * In this state, the watcher tries to detect whether this is a break
+ * during intermittent events or the kernel bug is triggered. If the next
+ * polling reports an event within the short period, the previous timeout is
+ * likely to be a kernel bug, and so the watcher goes back to the active state.
+ * Otherwise, it moves to the idle state again.
+ *
+ * It's not clear whether this is a thread-related bug, but since we've only
+ * seen this with threads, this workaround is used only when enabling threads.
+ */
+
+typedef enum { poll_idle, poll_active, poll_checking } pollstate_t;
+
+#ifndef ISC_SOCKET_POLLWATCH_TIMEOUT
+#define ISC_SOCKET_POLLWATCH_TIMEOUT 10
+#endif /* ISC_SOCKET_POLLWATCH_TIMEOUT */
+#endif /* ISC_SOCKET_USE_POLLWATCH */
+
+/*%
+ * Per-FD lock buckets, we shuffle them around a bit as FDs come in herds.
+ */
+#define FDLOCK_BITS 10
+#define FDLOCK_COUNT (1 << FDLOCK_BITS)
+#define FDLOCK_ID(fd) \
+ (((fd) % (FDLOCK_COUNT) >> (FDLOCK_BITS / 2)) | \
+ (((fd) << (FDLOCK_BITS / 2)) % (FDLOCK_COUNT)))
+
+/*%
+ * Maximum number of events communicated with the kernel. There should normally
+ * be no need for having a large number.
+ */
+#if defined(USE_KQUEUE) || defined(USE_EPOLL) || defined(USE_DEVPOLL)
+#ifndef ISC_SOCKET_MAXEVENTS
+#ifdef TUNE_LARGE
+#define ISC_SOCKET_MAXEVENTS 2048
+#else /* ifdef TUNE_LARGE */
+#define ISC_SOCKET_MAXEVENTS 64
+#endif /* TUNE_LARGE */
+#endif /* ifndef ISC_SOCKET_MAXEVENTS */
+#endif /* if defined(USE_KQUEUE) || defined(USE_EPOLL) || defined(USE_DEVPOLL) \
+ * */
+
+/*%
+ * Some systems define the socket length argument as an int, some as size_t,
+ * some as socklen_t. This is here so it can be easily changed if needed.
+ */
+#ifndef socklen_t
+#define socklen_t unsigned int
+#endif /* ifndef socklen_t */
+
+/*%
+ * Define what the possible "soft" errors can be. These are non-fatal returns
+ * of various network related functions, like recv() and so on.
+ *
+ * For some reason, BSDI (and perhaps others) will sometimes return <0
+ * from recv() but will have errno==0. This is broken, but we have to
+ * work around it here.
+ */
+#define SOFT_ERROR(e) \
+ ((e) == EAGAIN || (e) == EWOULDBLOCK || (e) == ENOBUFS || \
+ (e) == EINTR || (e) == 0)
+
+#define DLVL(x) ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(x)
+
+/*!<
+ * DLVL(90) -- Function entry/exit and other tracing.
+ * DLVL(70) -- Socket "correctness" -- including returning of events, etc.
+ * DLVL(60) -- Socket data send/receive
+ * DLVL(50) -- Event tracing, including receiving/sending completion events.
+ * DLVL(20) -- Socket creation/destruction.
+ */
+#define TRACE_LEVEL 90
+#define CORRECTNESS_LEVEL 70
+#define IOEVENT_LEVEL 60
+#define EVENT_LEVEL 50
+#define CREATION_LEVEL 20
+
+#define TRACE DLVL(TRACE_LEVEL)
+#define CORRECTNESS DLVL(CORRECTNESS_LEVEL)
+#define IOEVENT DLVL(IOEVENT_LEVEL)
+#define EVENT DLVL(EVENT_LEVEL)
+#define CREATION DLVL(CREATION_LEVEL)
+
+typedef isc_event_t intev_t;
+
+#define SOCKET_MAGIC ISC_MAGIC('I', 'O', 'i', 'o')
+#define VALID_SOCKET(s) ISC_MAGIC_VALID(s, SOCKET_MAGIC)
+
+/*!
+ * IPv6 control information. If the socket is an IPv6 socket we want
+ * to collect the destination address and interface so the client can
+ * set them on outgoing packets.
+ */
+#ifndef USE_CMSG
+#define USE_CMSG 1
+#endif /* ifndef USE_CMSG */
+
+/*%
+ * NetBSD and FreeBSD can timestamp packets. XXXMLG Should we have
+ * a setsockopt() like interface to request timestamps, and if the OS
+ * doesn't do it for us, call gettimeofday() on every UDP receive?
+ */
+#ifdef SO_TIMESTAMP
+#ifndef USE_CMSG
+#define USE_CMSG 1
+#endif /* ifndef USE_CMSG */
+#endif /* ifdef SO_TIMESTAMP */
+
+#if defined(SO_RCVBUF) && defined(ISC_RECV_BUFFER_SIZE)
+#define SET_RCVBUF
+#endif
+
+#if defined(SO_SNDBUF) && defined(ISC_SEND_BUFFER_SIZE)
+#define SET_SNDBUF
+#endif
+
+/*%
+ * Instead of calculating the cmsgbuf lengths every time we take
+ * a rule of thumb approach - sizes are taken from x86_64 linux,
+ * multiplied by 2, everything should fit. Those sizes are not
+ * large enough to cause any concern.
+ */
+#if defined(USE_CMSG)
+#define CMSG_SP_IN6PKT 40
+#else /* if defined(USE_CMSG) */
+#define CMSG_SP_IN6PKT 0
+#endif /* if defined(USE_CMSG) */
+
+#if defined(USE_CMSG) && defined(SO_TIMESTAMP)
+#define CMSG_SP_TIMESTAMP 32
+#else /* if defined(USE_CMSG) && defined(SO_TIMESTAMP) */
+#define CMSG_SP_TIMESTAMP 0
+#endif /* if defined(USE_CMSG) && defined(SO_TIMESTAMP) */
+
+#if defined(USE_CMSG) && (defined(IPV6_TCLASS) || defined(IP_TOS))
+#define CMSG_SP_TCTOS 24
+#else /* if defined(USE_CMSG) && (defined(IPV6_TCLASS) || defined(IP_TOS)) */
+#define CMSG_SP_TCTOS 0
+#endif /* if defined(USE_CMSG) && (defined(IPV6_TCLASS) || defined(IP_TOS)) */
+
+#define CMSG_SP_INT 24
+
+/* Align cmsg buffers to be safe on SPARC etc. */
+#define RECVCMSGBUFLEN \
+ ISC_ALIGN(2 * (CMSG_SP_IN6PKT + CMSG_SP_TIMESTAMP + CMSG_SP_TCTOS) + \
+ 1, \
+ sizeof(void *))
+#define SENDCMSGBUFLEN \
+ ISC_ALIGN(2 * (CMSG_SP_IN6PKT + CMSG_SP_INT + CMSG_SP_TCTOS) + 1, \
+ sizeof(void *))
+
+/*%
+ * The number of times a send operation is repeated if the result is EINTR.
+ */
+#define NRETRIES 10
+
+typedef struct isc__socketthread isc__socketthread_t;
+
+#define NEWCONNSOCK(ev) ((ev)->newsocket)
+
+struct isc_socket {
+ /* Not locked. */
+ unsigned int magic;
+ isc_socketmgr_t *manager;
+ isc_mutex_t lock;
+ isc_sockettype_t type;
+ const isc_statscounter_t *statsindex;
+ isc_refcount_t references;
+
+ /* Locked by socket lock. */
+ ISC_LINK(isc_socket_t) link;
+ int fd;
+ int pf;
+ int threadid;
+ char name[16];
+ void *tag;
+
+ ISC_LIST(isc_socketevent_t) send_list;
+ ISC_LIST(isc_socketevent_t) recv_list;
+ ISC_LIST(isc_socket_newconnev_t) accept_list;
+ ISC_LIST(isc_socket_connev_t) connect_list;
+
+ isc_sockaddr_t peer_address; /* remote address */
+
+ unsigned int listener : 1, /* listener socket */
+ connected : 1, connecting : 1, /* connect pending
+ * */
+ bound : 1, /* bound to local addr */
+ dupped : 1, active : 1, /* currently active */
+ pktdscp : 1; /* per packet dscp */
+
+#ifdef ISC_PLATFORM_RECVOVERFLOW
+ unsigned char overflow; /* used for MSG_TRUNC fake */
+#endif /* ifdef ISC_PLATFORM_RECVOVERFLOW */
+
+ unsigned int dscp;
+};
+
+#define SOCKET_MANAGER_MAGIC ISC_MAGIC('I', 'O', 'm', 'g')
+#define VALID_MANAGER(m) ISC_MAGIC_VALID(m, SOCKET_MANAGER_MAGIC)
+
+struct isc_socketmgr {
+ /* Not locked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ isc_stats_t *stats;
+ int nthreads;
+ isc__socketthread_t *threads;
+ unsigned int maxsocks;
+ /* Locked by manager lock. */
+ ISC_LIST(isc_socket_t) socklist;
+ int reserved; /* unlocked */
+ isc_condition_t shutdown_ok;
+ size_t maxudp;
+};
+
+struct isc__socketthread {
+ isc_socketmgr_t *manager;
+ int threadid;
+ isc_thread_t thread;
+ int pipe_fds[2];
+ isc_mutex_t *fdlock;
+ /* Locked by fdlock. */
+ isc_socket_t **fds;
+ int *fdstate;
+#ifdef USE_KQUEUE
+ int kqueue_fd;
+ int nevents;
+ struct kevent *events;
+#endif /* USE_KQUEUE */
+#ifdef USE_EPOLL
+ int epoll_fd;
+ int nevents;
+ struct epoll_event *events;
+ uint32_t *epoll_events;
+#endif /* USE_EPOLL */
+#ifdef USE_DEVPOLL
+ int devpoll_fd;
+ isc_resourcevalue_t open_max;
+ unsigned int calls;
+ int nevents;
+ struct pollfd *events;
+ pollinfo_t *fdpollinfo;
+#endif /* USE_DEVPOLL */
+#ifdef USE_SELECT
+ int fd_bufsize;
+ fd_set *read_fds;
+ fd_set *read_fds_copy;
+ fd_set *write_fds;
+ fd_set *write_fds_copy;
+ int maxfd;
+#endif /* USE_SELECT */
+};
+
+#define CLOSED 0 /* this one must be zero */
+#define MANAGED 1
+#define CLOSE_PENDING 2
+
+/*
+ * send() and recv() iovec counts
+ */
+#define MAXSCATTERGATHER_SEND (ISC_SOCKET_MAXSCATTERGATHER)
+#ifdef ISC_PLATFORM_RECVOVERFLOW
+#define MAXSCATTERGATHER_RECV (ISC_SOCKET_MAXSCATTERGATHER + 1)
+#else /* ifdef ISC_PLATFORM_RECVOVERFLOW */
+#define MAXSCATTERGATHER_RECV (ISC_SOCKET_MAXSCATTERGATHER)
+#endif /* ifdef ISC_PLATFORM_RECVOVERFLOW */
+
+static isc_result_t
+socket_create(isc_socketmgr_t *manager0, int pf, isc_sockettype_t type,
+ isc_socket_t **socketp, isc_socket_t *dup_socket);
+static void
+send_recvdone_event(isc_socket_t *, isc_socketevent_t **);
+static void
+send_senddone_event(isc_socket_t *, isc_socketevent_t **);
+static void
+send_connectdone_event(isc_socket_t *, isc_socket_connev_t **);
+static void
+free_socket(isc_socket_t **);
+static isc_result_t
+allocate_socket(isc_socketmgr_t *, isc_sockettype_t, isc_socket_t **);
+static void
+destroy(isc_socket_t **);
+static void
+internal_accept(isc_socket_t *);
+static void
+internal_connect(isc_socket_t *);
+static void
+internal_recv(isc_socket_t *);
+static void
+internal_send(isc_socket_t *);
+static void
+process_cmsg(isc_socket_t *, struct msghdr *, isc_socketevent_t *);
+static void
+build_msghdr_send(isc_socket_t *, char *, isc_socketevent_t *, struct msghdr *,
+ struct iovec *, size_t *);
+static void
+build_msghdr_recv(isc_socket_t *, char *, isc_socketevent_t *, struct msghdr *,
+ struct iovec *, size_t *);
+static bool
+process_ctlfd(isc__socketthread_t *thread);
+static void
+setdscp(isc_socket_t *sock, isc_dscp_t dscp);
+
+#define SELECT_POKE_SHUTDOWN (-1)
+#define SELECT_POKE_NOTHING (-2)
+#define SELECT_POKE_READ (-3)
+#define SELECT_POKE_ACCEPT (-3) /*%< Same as _READ */
+#define SELECT_POKE_WRITE (-4)
+#define SELECT_POKE_CONNECT (-4) /*%< Same as _WRITE */
+#define SELECT_POKE_CLOSE (-5)
+
+/*%
+ * Shortcut index arrays to get access to statistics counters.
+ */
+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
+};
+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
+};
+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
+};
+static const isc_statscounter_t rawstatsindex[] = {
+ isc_sockstatscounter_rawopen,
+ isc_sockstatscounter_rawopenfail,
+ isc_sockstatscounter_rawclose,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ isc_sockstatscounter_rawrecvfail,
+ isc_sockstatscounter_rawactive
+};
+
+static int
+gen_threadid(isc_socket_t *sock);
+
+static int
+gen_threadid(isc_socket_t *sock) {
+ return (sock->fd % sock->manager->nthreads);
+}
+
+static void
+manager_log(isc_socketmgr_t *sockmgr, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(5, 6);
+static void
+manager_log(isc_socketmgr_t *sockmgr, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt, ...) {
+ char msgbuf[2048];
+ va_list ap;
+
+ if (!isc_log_wouldlog(isc_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ isc_log_write(isc_lctx, category, module, level, "sockmgr %p: %s",
+ sockmgr, msgbuf);
+}
+
+static void
+thread_log(isc__socketthread_t *thread, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(5, 6);
+static void
+thread_log(isc__socketthread_t *thread, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt, ...) {
+ char msgbuf[2048];
+ va_list ap;
+
+ if (!isc_log_wouldlog(isc_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ isc_log_write(isc_lctx, category, module, level,
+ "sockmgr %p thread %d: %s", thread->manager,
+ thread->threadid, msgbuf);
+}
+
+static void
+socket_log(isc_socket_t *sock, const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module, int level,
+ const char *fmt, ...) ISC_FORMAT_PRINTF(6, 7);
+static void
+socket_log(isc_socket_t *sock, const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module, int level,
+ const char *fmt, ...) {
+ char msgbuf[2048];
+ char peerbuf[ISC_SOCKADDR_FORMATSIZE];
+ va_list ap;
+
+ if (!isc_log_wouldlog(isc_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ if (address == NULL) {
+ isc_log_write(isc_lctx, category, module, level,
+ "socket %p: %s", sock, msgbuf);
+ } else {
+ isc_sockaddr_format(address, peerbuf, sizeof(peerbuf));
+ isc_log_write(isc_lctx, category, module, level,
+ "socket %p %s: %s", sock, peerbuf, msgbuf);
+ }
+}
+
+/*%
+ * Increment socket-related statistics counters.
+ */
+static void
+inc_stats(isc_stats_t *stats, isc_statscounter_t counterid) {
+ REQUIRE(counterid != -1);
+
+ if (stats != NULL) {
+ isc_stats_increment(stats, counterid);
+ }
+}
+
+/*%
+ * Decrement socket-related statistics counters.
+ */
+static void
+dec_stats(isc_stats_t *stats, isc_statscounter_t counterid) {
+ REQUIRE(counterid != -1);
+
+ if (stats != NULL) {
+ isc_stats_decrement(stats, counterid);
+ }
+}
+
+static isc_result_t
+watch_fd(isc__socketthread_t *thread, int fd, int msg) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+#ifdef USE_KQUEUE
+ struct kevent evchange;
+
+ memset(&evchange, 0, sizeof(evchange));
+ if (msg == SELECT_POKE_READ) {
+ evchange.filter = EVFILT_READ;
+ } else {
+ evchange.filter = EVFILT_WRITE;
+ }
+ evchange.flags = EV_ADD;
+ evchange.ident = fd;
+ if (kevent(thread->kqueue_fd, &evchange, 1, NULL, 0, NULL) != 0) {
+ result = isc__errno2result(errno);
+ }
+
+ return (result);
+#elif defined(USE_EPOLL)
+ struct epoll_event event;
+ uint32_t oldevents;
+ int ret;
+ int op;
+
+ oldevents = thread->epoll_events[fd];
+ if (msg == SELECT_POKE_READ) {
+ thread->epoll_events[fd] |= EPOLLIN;
+ } else {
+ thread->epoll_events[fd] |= EPOLLOUT;
+ }
+
+ event.events = thread->epoll_events[fd];
+ memset(&event.data, 0, sizeof(event.data));
+ event.data.fd = fd;
+
+ op = (oldevents == 0U) ? EPOLL_CTL_ADD : EPOLL_CTL_MOD;
+ if (thread->fds[fd] != NULL) {
+ LOCK(&thread->fds[fd]->lock);
+ }
+ ret = epoll_ctl(thread->epoll_fd, op, fd, &event);
+ if (thread->fds[fd] != NULL) {
+ UNLOCK(&thread->fds[fd]->lock);
+ }
+ if (ret == -1) {
+ if (errno == EEXIST) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "epoll_ctl(ADD/MOD) returned "
+ "EEXIST for fd %d",
+ fd);
+ }
+ result = isc__errno2result(errno);
+ }
+
+ return (result);
+#elif defined(USE_DEVPOLL)
+ struct pollfd pfd;
+
+ memset(&pfd, 0, sizeof(pfd));
+ if (msg == SELECT_POKE_READ) {
+ pfd.events = POLLIN;
+ } else {
+ pfd.events = POLLOUT;
+ }
+ pfd.fd = fd;
+ pfd.revents = 0;
+ if (write(thread->devpoll_fd, &pfd, sizeof(pfd)) == -1) {
+ result = isc__errno2result(errno);
+ } else {
+ if (msg == SELECT_POKE_READ) {
+ thread->fdpollinfo[fd].want_read = 1;
+ } else {
+ thread->fdpollinfo[fd].want_write = 1;
+ }
+ }
+
+ return (result);
+#elif defined(USE_SELECT)
+ LOCK(&thread->manager->lock);
+ if (msg == SELECT_POKE_READ) {
+ FD_SET(fd, thread->read_fds);
+ }
+ if (msg == SELECT_POKE_WRITE) {
+ FD_SET(fd, thread->write_fds);
+ }
+ UNLOCK(&thread->manager->lock);
+
+ return (result);
+#endif /* ifdef USE_KQUEUE */
+}
+
+static isc_result_t
+unwatch_fd(isc__socketthread_t *thread, int fd, int msg) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+#ifdef USE_KQUEUE
+ struct kevent evchange;
+
+ memset(&evchange, 0, sizeof(evchange));
+ if (msg == SELECT_POKE_READ) {
+ evchange.filter = EVFILT_READ;
+ } else {
+ evchange.filter = EVFILT_WRITE;
+ }
+ evchange.flags = EV_DELETE;
+ evchange.ident = fd;
+ if (kevent(thread->kqueue_fd, &evchange, 1, NULL, 0, NULL) != 0) {
+ result = isc__errno2result(errno);
+ }
+
+ return (result);
+#elif defined(USE_EPOLL)
+ struct epoll_event event;
+ int ret;
+ int op;
+
+ if (msg == SELECT_POKE_READ) {
+ thread->epoll_events[fd] &= ~(EPOLLIN);
+ } else {
+ thread->epoll_events[fd] &= ~(EPOLLOUT);
+ }
+
+ event.events = thread->epoll_events[fd];
+ memset(&event.data, 0, sizeof(event.data));
+ event.data.fd = fd;
+
+ op = (event.events == 0U) ? EPOLL_CTL_DEL : EPOLL_CTL_MOD;
+ ret = epoll_ctl(thread->epoll_fd, op, fd, &event);
+ if (ret == -1 && errno != ENOENT) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "epoll_ctl(DEL), %d: %s",
+ fd, strbuf);
+ result = ISC_R_UNEXPECTED;
+ }
+ return (result);
+#elif defined(USE_DEVPOLL)
+ struct pollfd pfds[2];
+ size_t writelen = sizeof(pfds[0]);
+
+ memset(pfds, 0, sizeof(pfds));
+ pfds[0].events = POLLREMOVE;
+ pfds[0].fd = fd;
+
+ /*
+ * Canceling read or write polling via /dev/poll is tricky. Since it
+ * only provides a way of canceling per FD, we may need to re-poll the
+ * socket for the other operation.
+ */
+ if (msg == SELECT_POKE_READ && thread->fdpollinfo[fd].want_write == 1) {
+ pfds[1].events = POLLOUT;
+ pfds[1].fd = fd;
+ writelen += sizeof(pfds[1]);
+ }
+ if (msg == SELECT_POKE_WRITE && thread->fdpollinfo[fd].want_read == 1) {
+ pfds[1].events = POLLIN;
+ pfds[1].fd = fd;
+ writelen += sizeof(pfds[1]);
+ }
+
+ if (write(thread->devpoll_fd, pfds, writelen) == -1) {
+ result = isc__errno2result(errno);
+ } else {
+ if (msg == SELECT_POKE_READ) {
+ thread->fdpollinfo[fd].want_read = 0;
+ } else {
+ thread->fdpollinfo[fd].want_write = 0;
+ }
+ }
+
+ return (result);
+#elif defined(USE_SELECT)
+ LOCK(&thread->manager->lock);
+ if (msg == SELECT_POKE_READ) {
+ FD_CLR(fd, thread->read_fds);
+ } else if (msg == SELECT_POKE_WRITE) {
+ FD_CLR(fd, thread->write_fds);
+ }
+ UNLOCK(&thread->manager->lock);
+
+ return (result);
+#endif /* ifdef USE_KQUEUE */
+}
+
+/*
+ * A poke message was received, perform a proper watch/unwatch
+ * on a fd provided
+ */
+static void
+wakeup_socket(isc__socketthread_t *thread, int fd, int msg) {
+ isc_result_t result;
+ int lockid = FDLOCK_ID(fd);
+
+ /*
+ * This is a wakeup on a socket. If the socket is not in the
+ * process of being closed, start watching it for either reads
+ * or writes.
+ */
+
+ INSIST(fd >= 0 && fd < (int)thread->manager->maxsocks);
+
+ if (msg == SELECT_POKE_CLOSE) {
+ LOCK(&thread->fdlock[lockid]);
+ INSIST(thread->fdstate[fd] == CLOSE_PENDING);
+ thread->fdstate[fd] = CLOSED;
+ (void)unwatch_fd(thread, fd, SELECT_POKE_READ);
+ (void)unwatch_fd(thread, fd, SELECT_POKE_WRITE);
+ (void)close(fd);
+ UNLOCK(&thread->fdlock[lockid]);
+ return;
+ }
+
+ LOCK(&thread->fdlock[lockid]);
+ if (thread->fdstate[fd] == CLOSE_PENDING) {
+ /*
+ * We accept (and ignore) any error from unwatch_fd() as we are
+ * closing the socket, hoping it doesn't leave dangling state in
+ * the kernel.
+ * Note that unwatch_fd() must be called after releasing the
+ * fdlock; otherwise it could cause deadlock due to a lock order
+ * reversal.
+ */
+ (void)unwatch_fd(thread, fd, SELECT_POKE_READ);
+ (void)unwatch_fd(thread, fd, SELECT_POKE_WRITE);
+ UNLOCK(&thread->fdlock[lockid]);
+ return;
+ }
+ if (thread->fdstate[fd] != MANAGED) {
+ UNLOCK(&thread->fdlock[lockid]);
+ return;
+ }
+
+ /*
+ * Set requested bit.
+ */
+ result = watch_fd(thread, fd, msg);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * XXXJT: what should we do? Ignoring the failure of watching
+ * a socket will make the application dysfunctional, but there
+ * seems to be no reasonable recovery process.
+ */
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "failed to start watching FD (%d): %s", fd,
+ isc_result_totext(result));
+ }
+ UNLOCK(&thread->fdlock[lockid]);
+}
+
+/*
+ * Poke the select loop when there is something for us to do.
+ * The write is required (by POSIX) to complete. That is, we
+ * will not get partial writes.
+ */
+static void
+select_poke(isc_socketmgr_t *mgr, int threadid, int fd, int msg) {
+ int cc;
+ int buf[2];
+ char strbuf[ISC_STRERRORSIZE];
+
+ buf[0] = fd;
+ buf[1] = msg;
+
+ do {
+ cc = write(mgr->threads[threadid].pipe_fds[1], buf,
+ sizeof(buf));
+#ifdef ENOSR
+ /*
+ * Treat ENOSR as EAGAIN but loop slowly as it is
+ * unlikely to clear fast.
+ */
+ if (cc < 0 && errno == ENOSR) {
+ sleep(1);
+ errno = EAGAIN;
+ }
+#endif /* ifdef ENOSR */
+ } while (cc < 0 && SOFT_ERROR(errno));
+
+ if (cc < 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ FATAL_ERROR(__FILE__, __LINE__,
+ "write() failed during watcher poke: %s", strbuf);
+ }
+
+ INSIST(cc == sizeof(buf));
+}
+
+/*
+ * Read a message on the internal fd.
+ */
+static void
+select_readmsg(isc__socketthread_t *thread, int *fd, int *msg) {
+ int buf[2];
+ int cc;
+ char strbuf[ISC_STRERRORSIZE];
+
+ cc = read(thread->pipe_fds[0], buf, sizeof(buf));
+ if (cc < 0) {
+ *msg = SELECT_POKE_NOTHING;
+ *fd = -1; /* Silence compiler. */
+ if (SOFT_ERROR(errno)) {
+ return;
+ }
+
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ FATAL_ERROR(__FILE__, __LINE__,
+ "read() failed during watcher poke: %s", strbuf);
+ }
+ INSIST(cc == sizeof(buf));
+
+ *fd = buf[0];
+ *msg = buf[1];
+}
+
+/*
+ * Make a fd non-blocking.
+ */
+static isc_result_t
+make_nonblock(int fd) {
+ int ret;
+ char strbuf[ISC_STRERRORSIZE];
+#ifdef USE_FIONBIO_IOCTL
+ int on = 1;
+#else /* ifdef USE_FIONBIO_IOCTL */
+ int flags;
+#endif /* ifdef USE_FIONBIO_IOCTL */
+
+#ifdef USE_FIONBIO_IOCTL
+ ret = ioctl(fd, FIONBIO, (char *)&on);
+#else /* ifdef USE_FIONBIO_IOCTL */
+ flags = fcntl(fd, F_GETFL, 0);
+ flags |= PORT_NONBLOCK;
+ ret = fcntl(fd, F_SETFL, flags);
+#endif /* ifdef USE_FIONBIO_IOCTL */
+
+ if (ret == -1) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+#ifdef USE_FIONBIO_IOCTL
+ "ioctl(%d, FIONBIO, &on): %s", fd,
+#else /* ifdef USE_FIONBIO_IOCTL */
+ "fcntl(%d, F_SETFL, %d): %s", fd, flags,
+#endif /* ifdef USE_FIONBIO_IOCTL */
+ strbuf);
+
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+#ifdef USE_CMSG
+/*
+ * Not all OSes support advanced CMSG macros: CMSG_LEN and CMSG_SPACE.
+ * In order to ensure as much portability as possible, we provide wrapper
+ * functions of these macros.
+ * Note that cmsg_space() could run slow on OSes that do not have
+ * CMSG_SPACE.
+ */
+static socklen_t
+cmsg_len(socklen_t len) {
+#ifdef CMSG_LEN
+ return (CMSG_LEN(len));
+#else /* ifdef CMSG_LEN */
+ socklen_t hdrlen;
+
+ /*
+ * Cast NULL so that any pointer arithmetic performed by CMSG_DATA
+ * is correct.
+ */
+ hdrlen = (socklen_t)CMSG_DATA(((struct cmsghdr *)NULL));
+ return (hdrlen + len);
+#endif /* ifdef CMSG_LEN */
+}
+
+static socklen_t
+cmsg_space(socklen_t len) {
+#ifdef CMSG_SPACE
+ return (CMSG_SPACE(len));
+#else /* ifdef CMSG_SPACE */
+ struct msghdr msg;
+ struct cmsghdr *cmsgp;
+ /*
+ * XXX: The buffer length is an ad-hoc value, but should be enough
+ * in a practical sense.
+ */
+ char dummybuf[sizeof(struct cmsghdr) + 1024];
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_control = dummybuf;
+ msg.msg_controllen = sizeof(dummybuf);
+
+ cmsgp = (struct cmsghdr *)dummybuf;
+ cmsgp->cmsg_len = cmsg_len(len);
+
+ cmsgp = CMSG_NXTHDR(&msg, cmsgp);
+ if (cmsgp != NULL) {
+ return ((char *)cmsgp - (char *)msg.msg_control);
+ } else {
+ return (0);
+ }
+#endif /* ifdef CMSG_SPACE */
+}
+#endif /* USE_CMSG */
+
+/*
+ * Process control messages received on a socket.
+ */
+static void
+process_cmsg(isc_socket_t *sock, struct msghdr *msg, isc_socketevent_t *dev) {
+#ifdef USE_CMSG
+ struct cmsghdr *cmsgp;
+ struct in6_pktinfo *pktinfop;
+#ifdef SO_TIMESTAMP
+ void *timevalp;
+#endif /* ifdef SO_TIMESTAMP */
+#endif /* ifdef USE_CMSG */
+
+ /*
+ * sock is used only when ISC_NET_BSD44MSGHDR and USE_CMSG are defined.
+ * msg and dev are used only when ISC_NET_BSD44MSGHDR is defined.
+ * They are all here, outside of the CPP tests, because it is
+ * more consistent with the usual ISC coding style.
+ */
+ UNUSED(sock);
+ UNUSED(msg);
+ UNUSED(dev);
+
+#ifdef MSG_TRUNC
+ if ((msg->msg_flags & MSG_TRUNC) != 0) {
+ dev->attributes |= ISC_SOCKEVENTATTR_TRUNC;
+ }
+#endif /* ifdef MSG_TRUNC */
+
+#ifdef MSG_CTRUNC
+ if ((msg->msg_flags & MSG_CTRUNC) != 0) {
+ dev->attributes |= ISC_SOCKEVENTATTR_CTRUNC;
+ }
+#endif /* ifdef MSG_CTRUNC */
+
+#ifndef USE_CMSG
+ return;
+#else /* ifndef USE_CMSG */
+ if (msg->msg_controllen == 0U || msg->msg_control == NULL) {
+ return;
+ }
+
+#ifdef SO_TIMESTAMP
+ timevalp = NULL;
+#endif /* ifdef SO_TIMESTAMP */
+ pktinfop = NULL;
+
+ cmsgp = CMSG_FIRSTHDR(msg);
+ while (cmsgp != NULL) {
+ socket_log(sock, NULL, TRACE, "processing cmsg %p", cmsgp);
+
+ if (cmsgp->cmsg_level == IPPROTO_IPV6 &&
+ cmsgp->cmsg_type == IPV6_PKTINFO)
+ {
+ pktinfop = (struct in6_pktinfo *)CMSG_DATA(cmsgp);
+ memmove(&dev->pktinfo, pktinfop,
+ sizeof(struct in6_pktinfo));
+ dev->attributes |= ISC_SOCKEVENTATTR_PKTINFO;
+ socket_log(sock, NULL, TRACE,
+ "interface received on ifindex %u",
+ dev->pktinfo.ipi6_ifindex);
+ if (IN6_IS_ADDR_MULTICAST(&pktinfop->ipi6_addr)) {
+ dev->attributes |= ISC_SOCKEVENTATTR_MULTICAST;
+ }
+ goto next;
+ }
+
+#ifdef SO_TIMESTAMP
+ if (cmsgp->cmsg_level == SOL_SOCKET &&
+ cmsgp->cmsg_type == SCM_TIMESTAMP)
+ {
+ struct timeval tv;
+ timevalp = CMSG_DATA(cmsgp);
+ memmove(&tv, timevalp, sizeof(tv));
+ dev->timestamp.seconds = tv.tv_sec;
+ dev->timestamp.nanoseconds = tv.tv_usec * 1000;
+ dev->attributes |= ISC_SOCKEVENTATTR_TIMESTAMP;
+ goto next;
+ }
+#endif /* ifdef SO_TIMESTAMP */
+
+#ifdef IPV6_TCLASS
+ if (cmsgp->cmsg_level == IPPROTO_IPV6 &&
+ cmsgp->cmsg_type == IPV6_TCLASS)
+ {
+ dev->dscp = *(int *)CMSG_DATA(cmsgp);
+ dev->dscp >>= 2;
+ dev->attributes |= ISC_SOCKEVENTATTR_DSCP;
+ goto next;
+ }
+#endif /* ifdef IPV6_TCLASS */
+
+#ifdef IP_TOS
+ if (cmsgp->cmsg_level == IPPROTO_IP &&
+ (cmsgp->cmsg_type == IP_TOS
+#ifdef IP_RECVTOS
+ || cmsgp->cmsg_type == IP_RECVTOS
+#endif /* ifdef IP_RECVTOS */
+ ))
+ {
+ dev->dscp = (int)*(unsigned char *)CMSG_DATA(cmsgp);
+ dev->dscp >>= 2;
+ dev->attributes |= ISC_SOCKEVENTATTR_DSCP;
+ goto next;
+ }
+#endif /* ifdef IP_TOS */
+ next:
+ cmsgp = CMSG_NXTHDR(msg, cmsgp);
+ }
+#endif /* USE_CMSG */
+}
+
+/*
+ * Construct an iov array and attach it to the msghdr passed in. This is
+ * the SEND constructor, which will use the used region of the buffer
+ * (if using a buffer list) or will use the internal region (if a single
+ * buffer I/O is requested).
+ *
+ * Nothing can be NULL, and the done event must list at least one buffer
+ * on the buffer linked list for this function to be meaningful.
+ *
+ * If write_countp != NULL, *write_countp will hold the number of bytes
+ * this transaction can send.
+ */
+static void
+build_msghdr_send(isc_socket_t *sock, char *cmsgbuf, isc_socketevent_t *dev,
+ struct msghdr *msg, struct iovec *iov, size_t *write_countp) {
+ unsigned int iovcount;
+ size_t write_count;
+ struct cmsghdr *cmsgp;
+
+ memset(msg, 0, sizeof(*msg));
+
+ if (!sock->connected) {
+ msg->msg_name = (void *)&dev->address.type.sa;
+ msg->msg_namelen = dev->address.length;
+ } else {
+ msg->msg_name = NULL;
+ msg->msg_namelen = 0;
+ }
+
+ write_count = dev->region.length - dev->n;
+ iov[0].iov_base = (void *)(dev->region.base + dev->n);
+ iov[0].iov_len = write_count;
+ iovcount = 1;
+
+ msg->msg_iov = iov;
+ msg->msg_iovlen = iovcount;
+ msg->msg_control = NULL;
+ msg->msg_controllen = 0;
+ msg->msg_flags = 0;
+#if defined(USE_CMSG)
+
+ if ((sock->type == isc_sockettype_udp) &&
+ ((dev->attributes & ISC_SOCKEVENTATTR_PKTINFO) != 0))
+ {
+ struct in6_pktinfo *pktinfop;
+
+ socket_log(sock, NULL, TRACE, "sendto pktinfo data, ifindex %u",
+ dev->pktinfo.ipi6_ifindex);
+
+ msg->msg_control = (void *)cmsgbuf;
+ msg->msg_controllen = cmsg_space(sizeof(struct in6_pktinfo));
+ INSIST(msg->msg_controllen <= SENDCMSGBUFLEN);
+
+ cmsgp = (struct cmsghdr *)cmsgbuf;
+ cmsgp->cmsg_level = IPPROTO_IPV6;
+ cmsgp->cmsg_type = IPV6_PKTINFO;
+ cmsgp->cmsg_len = cmsg_len(sizeof(struct in6_pktinfo));
+ pktinfop = (struct in6_pktinfo *)CMSG_DATA(cmsgp);
+ memmove(pktinfop, &dev->pktinfo, sizeof(struct in6_pktinfo));
+ }
+
+#if defined(IPV6_USE_MIN_MTU)
+ if ((sock->type == isc_sockettype_udp) && (sock->pf == AF_INET6) &&
+ ((dev->attributes & ISC_SOCKEVENTATTR_USEMINMTU) != 0))
+ {
+ int use_min_mtu = 1; /* -1, 0, 1 */
+
+ cmsgp = (struct cmsghdr *)(cmsgbuf + msg->msg_controllen);
+ msg->msg_control = (void *)cmsgbuf;
+ msg->msg_controllen += cmsg_space(sizeof(use_min_mtu));
+ INSIST(msg->msg_controllen <= SENDCMSGBUFLEN);
+
+ cmsgp->cmsg_level = IPPROTO_IPV6;
+ cmsgp->cmsg_type = IPV6_USE_MIN_MTU;
+ cmsgp->cmsg_len = cmsg_len(sizeof(use_min_mtu));
+ memmove(CMSG_DATA(cmsgp), &use_min_mtu, sizeof(use_min_mtu));
+ }
+#endif /* if defined(IPV6_USE_MIN_MTU) */
+
+ if (isc_dscp_check_value > -1) {
+ if (sock->type == isc_sockettype_udp) {
+ INSIST((int)dev->dscp == isc_dscp_check_value);
+ } else if (sock->type == isc_sockettype_tcp) {
+ INSIST((int)sock->dscp == isc_dscp_check_value);
+ }
+ }
+
+#if defined(IP_TOS) || (defined(IPPROTO_IPV6) && defined(IPV6_TCLASS))
+ if ((sock->type == isc_sockettype_udp) &&
+ ((dev->attributes & ISC_SOCKEVENTATTR_DSCP) != 0))
+ {
+ int dscp = (dev->dscp << 2) & 0xff;
+
+ INSIST(dev->dscp < 0x40);
+
+#ifdef IP_TOS
+ if (sock->pf == AF_INET && sock->pktdscp) {
+ cmsgp = (struct cmsghdr *)(cmsgbuf +
+ msg->msg_controllen);
+ msg->msg_control = (void *)cmsgbuf;
+ msg->msg_controllen += cmsg_space(sizeof(dscp));
+ INSIST(msg->msg_controllen <= SENDCMSGBUFLEN);
+
+ cmsgp->cmsg_level = IPPROTO_IP;
+ cmsgp->cmsg_type = IP_TOS;
+ cmsgp->cmsg_len = cmsg_len(sizeof(char));
+ *(unsigned char *)CMSG_DATA(cmsgp) = dscp;
+ } else if (sock->pf == AF_INET && sock->dscp != dev->dscp) {
+ if (setsockopt(sock->fd, IPPROTO_IP, IP_TOS,
+ (void *)&dscp, sizeof(int)) < 0)
+ {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IP_TOS, %.02x)"
+ " failed: %s",
+ sock->fd, dscp >> 2, strbuf);
+ } else {
+ sock->dscp = dscp;
+ }
+ }
+#endif /* ifdef IP_TOS */
+#if defined(IPPROTO_IPV6) && defined(IPV6_TCLASS)
+ if (sock->pf == AF_INET6 && sock->pktdscp) {
+ cmsgp = (struct cmsghdr *)(cmsgbuf +
+ msg->msg_controllen);
+ msg->msg_control = (void *)cmsgbuf;
+ msg->msg_controllen += cmsg_space(sizeof(dscp));
+ INSIST(msg->msg_controllen <= SENDCMSGBUFLEN);
+
+ cmsgp->cmsg_level = IPPROTO_IPV6;
+ cmsgp->cmsg_type = IPV6_TCLASS;
+ cmsgp->cmsg_len = cmsg_len(sizeof(dscp));
+ memmove(CMSG_DATA(cmsgp), &dscp, sizeof(dscp));
+ } else if (sock->pf == AF_INET6 && sock->dscp != dev->dscp) {
+ if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_TCLASS,
+ (void *)&dscp, sizeof(int)) < 0)
+ {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IPV6_TCLASS, "
+ "%.02x) failed: %s",
+ sock->fd, dscp >> 2, strbuf);
+ } else {
+ sock->dscp = dscp;
+ }
+ }
+#endif /* if defined(IPPROTO_IPV6) && defined(IPV6_TCLASS) */
+ if (msg->msg_controllen != 0 &&
+ msg->msg_controllen < SENDCMSGBUFLEN)
+ {
+ memset(cmsgbuf + msg->msg_controllen, 0,
+ SENDCMSGBUFLEN - msg->msg_controllen);
+ }
+ }
+#endif /* if defined(IP_TOS) || (defined(IPPROTO_IPV6) && \
+ * defined(IPV6_TCLASS)) \
+ * */
+#endif /* USE_CMSG */
+
+ if (write_countp != NULL) {
+ *write_countp = write_count;
+ }
+}
+
+/*
+ * Construct an iov array and attach it to the msghdr passed in. This is
+ * the RECV constructor, which will use the available region of the buffer
+ * (if using a buffer list) or will use the internal region (if a single
+ * buffer I/O is requested).
+ *
+ * Nothing can be NULL, and the done event must list at least one buffer
+ * on the buffer linked list for this function to be meaningful.
+ *
+ * If read_countp != NULL, *read_countp will hold the number of bytes
+ * this transaction can receive.
+ */
+static void
+build_msghdr_recv(isc_socket_t *sock, char *cmsgbuf, isc_socketevent_t *dev,
+ struct msghdr *msg, struct iovec *iov, size_t *read_countp) {
+ unsigned int iovcount;
+ size_t read_count;
+
+ memset(msg, 0, sizeof(struct msghdr));
+
+ if (sock->type == isc_sockettype_udp) {
+ memset(&dev->address, 0, sizeof(dev->address));
+ msg->msg_name = (void *)&dev->address.type.sa;
+ msg->msg_namelen = sizeof(dev->address.type);
+ } else { /* TCP */
+ msg->msg_name = NULL;
+ msg->msg_namelen = 0;
+ dev->address = sock->peer_address;
+ }
+
+ read_count = dev->region.length - dev->n;
+ iov[0].iov_base = (void *)(dev->region.base + dev->n);
+ iov[0].iov_len = read_count;
+ iovcount = 1;
+
+ /*
+ * If needed, set up to receive that one extra byte.
+ */
+#ifdef ISC_PLATFORM_RECVOVERFLOW
+ if (sock->type == isc_sockettype_udp) {
+ INSIST(iovcount < MAXSCATTERGATHER_RECV);
+ iov[iovcount].iov_base = (void *)(&sock->overflow);
+ iov[iovcount].iov_len = 1;
+ iovcount++;
+ }
+#endif /* ifdef ISC_PLATFORM_RECVOVERFLOW */
+
+ msg->msg_iov = iov;
+ msg->msg_iovlen = iovcount;
+
+#if defined(USE_CMSG)
+ msg->msg_control = cmsgbuf;
+ msg->msg_controllen = RECVCMSGBUFLEN;
+#else /* if defined(USE_CMSG) */
+ msg->msg_control = NULL;
+ msg->msg_controllen = 0;
+#endif /* USE_CMSG */
+ msg->msg_flags = 0;
+
+ if (read_countp != NULL) {
+ *read_countp = read_count;
+ }
+}
+
+static void
+set_dev_address(const isc_sockaddr_t *address, isc_socket_t *sock,
+ isc_socketevent_t *dev) {
+ if (sock->type == isc_sockettype_udp) {
+ if (address != NULL) {
+ dev->address = *address;
+ } else {
+ dev->address = sock->peer_address;
+ }
+ } else if (sock->type == isc_sockettype_tcp) {
+ INSIST(address == NULL);
+ dev->address = sock->peer_address;
+ }
+}
+
+static void
+destroy_socketevent(isc_event_t *event) {
+ isc_socketevent_t *ev = (isc_socketevent_t *)event;
+
+ (ev->destroy)(event);
+}
+
+static isc_socketevent_t *
+allocate_socketevent(isc_mem_t *mctx, void *sender, isc_eventtype_t eventtype,
+ isc_taskaction_t action, void *arg) {
+ isc_socketevent_t *ev;
+
+ ev = (isc_socketevent_t *)isc_event_allocate(mctx, sender, eventtype,
+ action, arg, sizeof(*ev));
+
+ ev->result = ISC_R_UNSET;
+ ISC_LINK_INIT(ev, ev_link);
+ ev->region.base = NULL;
+ ev->n = 0;
+ ev->offset = 0;
+ ev->attributes = 0;
+ ev->destroy = ev->ev_destroy;
+ ev->ev_destroy = destroy_socketevent;
+ ev->dscp = 0;
+
+ return (ev);
+}
+
+#if defined(ISC_SOCKET_DEBUG)
+static void
+dump_msg(struct msghdr *msg) {
+ unsigned int i;
+
+ printf("MSGHDR %p\n", msg);
+ printf("\tname %p, namelen %ld\n", msg->msg_name,
+ (long)msg->msg_namelen);
+ printf("\tiov %p, iovlen %ld\n", msg->msg_iov, (long)msg->msg_iovlen);
+ for (i = 0; i < (unsigned int)msg->msg_iovlen; i++) {
+ printf("\t\t%u\tbase %p, len %ld\n", i,
+ msg->msg_iov[i].iov_base, (long)msg->msg_iov[i].iov_len);
+ }
+ printf("\tcontrol %p, controllen %ld\n", msg->msg_control,
+ (long)msg->msg_controllen);
+}
+#endif /* if defined(ISC_SOCKET_DEBUG) */
+
+#define DOIO_SUCCESS 0 /* i/o ok, event sent */
+#define DOIO_SOFT 1 /* i/o ok, soft error, no event sent */
+#define DOIO_HARD 2 /* i/o error, event sent */
+#define DOIO_EOF 3 /* EOF, no event sent */
+
+static int
+doio_recv(isc_socket_t *sock, isc_socketevent_t *dev) {
+ int cc;
+ struct iovec iov[MAXSCATTERGATHER_RECV];
+ size_t read_count;
+ struct msghdr msghdr;
+ int recv_errno;
+ char strbuf[ISC_STRERRORSIZE];
+ char cmsgbuf[RECVCMSGBUFLEN] = { 0 };
+
+ build_msghdr_recv(sock, cmsgbuf, dev, &msghdr, iov, &read_count);
+
+#if defined(ISC_SOCKET_DEBUG)
+ dump_msg(&msghdr);
+#endif /* if defined(ISC_SOCKET_DEBUG) */
+
+ cc = recvmsg(sock->fd, &msghdr, 0);
+ recv_errno = errno;
+
+#if defined(ISC_SOCKET_DEBUG)
+ dump_msg(&msghdr);
+#endif /* if defined(ISC_SOCKET_DEBUG) */
+
+ if (cc < 0) {
+ if (SOFT_ERROR(recv_errno)) {
+ return (DOIO_SOFT);
+ }
+
+ if (isc_log_wouldlog(isc_lctx, IOEVENT_LEVEL)) {
+ strerror_r(recv_errno, strbuf, sizeof(strbuf));
+ socket_log(sock, NULL, IOEVENT,
+ "doio_recv: recvmsg(%d) %d bytes, err %d/%s",
+ sock->fd, cc, recv_errno, strbuf);
+ }
+
+#define SOFT_OR_HARD(_system, _isc) \
+ if (recv_errno == _system) { \
+ if (sock->connected) { \
+ dev->result = _isc; \
+ inc_stats(sock->manager->stats, \
+ sock->statsindex[STATID_RECVFAIL]); \
+ return (DOIO_HARD); \
+ } \
+ return (DOIO_SOFT); \
+ }
+#define ALWAYS_HARD(_system, _isc) \
+ if (recv_errno == _system) { \
+ dev->result = _isc; \
+ inc_stats(sock->manager->stats, \
+ sock->statsindex[STATID_RECVFAIL]); \
+ return (DOIO_HARD); \
+ }
+
+ SOFT_OR_HARD(ECONNREFUSED, ISC_R_CONNREFUSED);
+ SOFT_OR_HARD(ENETUNREACH, ISC_R_NETUNREACH);
+ SOFT_OR_HARD(EHOSTUNREACH, ISC_R_HOSTUNREACH);
+ SOFT_OR_HARD(EHOSTDOWN, ISC_R_HOSTDOWN);
+ SOFT_OR_HARD(ENOBUFS, ISC_R_NORESOURCES);
+ /*
+ * Older operating systems may still return EPROTO in some
+ * situations, for example when receiving ICMP/ICMPv6 errors.
+ * A real life scenario is when ICMPv6 returns code 5 or 6.
+ * These codes are introduced in RFC 4443 from March 2006,
+ * and the document obsoletes RFC 1885. But unfortunately not
+ * all operating systems have caught up with the new standard
+ * (in 2020) and thus a generic protocol error is returned.
+ */
+ SOFT_OR_HARD(EPROTO, ISC_R_HOSTUNREACH);
+ /* Should never get this one but it was seen. */
+#ifdef ENOPROTOOPT
+ SOFT_OR_HARD(ENOPROTOOPT, ISC_R_HOSTUNREACH);
+#endif /* ifdef ENOPROTOOPT */
+ SOFT_OR_HARD(EINVAL, ISC_R_HOSTUNREACH);
+
+#undef SOFT_OR_HARD
+#undef ALWAYS_HARD
+
+ dev->result = isc__errno2result(recv_errno);
+ inc_stats(sock->manager->stats,
+ sock->statsindex[STATID_RECVFAIL]);
+ return (DOIO_HARD);
+ }
+
+ /*
+ * On TCP and UNIX sockets, zero length reads indicate EOF,
+ * while on UDP sockets, zero length reads are perfectly valid,
+ * although strange.
+ */
+ switch (sock->type) {
+ case isc_sockettype_tcp:
+ case isc_sockettype_unix:
+ if (cc == 0) {
+ return (DOIO_EOF);
+ }
+ break;
+ case isc_sockettype_udp:
+ case isc_sockettype_raw:
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (sock->type == isc_sockettype_udp) {
+ dev->address.length = msghdr.msg_namelen;
+ if (isc_sockaddr_getport(&dev->address) == 0) {
+ if (isc_log_wouldlog(isc_lctx, IOEVENT_LEVEL)) {
+ socket_log(sock, &dev->address, IOEVENT,
+ "dropping source port zero packet");
+ }
+ return (DOIO_SOFT);
+ }
+ /*
+ * Simulate a firewall blocking UDP responses bigger than
+ * 'maxudp' bytes.
+ */
+ if (sock->manager->maxudp != 0 &&
+ cc > (int)sock->manager->maxudp)
+ {
+ return (DOIO_SOFT);
+ }
+ }
+
+ socket_log(sock, &dev->address, IOEVENT, "packet received correctly");
+
+ /*
+ * Overflow bit detection. If we received MORE bytes than we should,
+ * this indicates an overflow situation. Set the flag in the
+ * dev entry and adjust how much we read by one.
+ */
+#ifdef ISC_PLATFORM_RECVOVERFLOW
+ if ((sock->type == isc_sockettype_udp) && ((size_t)cc > read_count)) {
+ dev->attributes |= ISC_SOCKEVENTATTR_TRUNC;
+ cc--;
+ }
+#endif /* ifdef ISC_PLATFORM_RECVOVERFLOW */
+
+ /*
+ * If there are control messages attached, run through them and pull
+ * out the interesting bits.
+ */
+ process_cmsg(sock, &msghdr, dev);
+
+ /*
+ * update the buffers (if any) and the i/o count
+ */
+ dev->n += cc;
+
+ /*
+ * If we read less than we expected, update counters,
+ * and let the upper layer poke the descriptor.
+ */
+ if (((size_t)cc != read_count) && (dev->n < dev->minimum)) {
+ return (DOIO_SOFT);
+ }
+
+ /*
+ * Full reads are posted, or partials if partials are ok.
+ */
+ dev->result = ISC_R_SUCCESS;
+ return (DOIO_SUCCESS);
+}
+
+/*
+ * Returns:
+ * DOIO_SUCCESS The operation succeeded. dev->result contains
+ * ISC_R_SUCCESS.
+ *
+ * DOIO_HARD A hard or unexpected I/O error was encountered.
+ * dev->result contains the appropriate error.
+ *
+ * DOIO_SOFT A soft I/O error was encountered. No senddone
+ * event was sent. The operation should be retried.
+ *
+ * No other return values are possible.
+ */
+static int
+doio_send(isc_socket_t *sock, isc_socketevent_t *dev) {
+ int cc;
+ struct iovec iov[MAXSCATTERGATHER_SEND];
+ size_t write_count;
+ struct msghdr msghdr;
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+ int attempts = 0;
+ int send_errno;
+ char strbuf[ISC_STRERRORSIZE];
+ char cmsgbuf[SENDCMSGBUFLEN] = { 0 };
+
+ build_msghdr_send(sock, cmsgbuf, dev, &msghdr, iov, &write_count);
+
+resend:
+ if (sock->type == isc_sockettype_udp && sock->manager->maxudp != 0 &&
+ write_count > sock->manager->maxudp)
+ {
+ cc = write_count;
+ } else {
+ cc = sendmsg(sock->fd, &msghdr, 0);
+ }
+ send_errno = errno;
+
+ /*
+ * Check for error or block condition.
+ */
+ if (cc < 0) {
+ if (send_errno == EINTR && ++attempts < NRETRIES) {
+ goto resend;
+ }
+
+ if (SOFT_ERROR(send_errno)) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ dev->result = ISC_R_WOULDBLOCK;
+ }
+ return (DOIO_SOFT);
+ }
+
+#define SOFT_OR_HARD(_system, _isc) \
+ if (send_errno == _system) { \
+ if (sock->connected) { \
+ dev->result = _isc; \
+ inc_stats(sock->manager->stats, \
+ sock->statsindex[STATID_SENDFAIL]); \
+ return (DOIO_HARD); \
+ } \
+ return (DOIO_SOFT); \
+ }
+#define ALWAYS_HARD(_system, _isc) \
+ if (send_errno == _system) { \
+ dev->result = _isc; \
+ inc_stats(sock->manager->stats, \
+ sock->statsindex[STATID_SENDFAIL]); \
+ return (DOIO_HARD); \
+ }
+
+ SOFT_OR_HARD(ECONNREFUSED, ISC_R_CONNREFUSED);
+ ALWAYS_HARD(EACCES, ISC_R_NOPERM);
+ ALWAYS_HARD(EAFNOSUPPORT, ISC_R_ADDRNOTAVAIL);
+ ALWAYS_HARD(EADDRNOTAVAIL, ISC_R_ADDRNOTAVAIL);
+ ALWAYS_HARD(EHOSTUNREACH, ISC_R_HOSTUNREACH);
+#ifdef EHOSTDOWN
+ ALWAYS_HARD(EHOSTDOWN, ISC_R_HOSTUNREACH);
+#endif /* ifdef EHOSTDOWN */
+ ALWAYS_HARD(ENETUNREACH, ISC_R_NETUNREACH);
+ SOFT_OR_HARD(ENOBUFS, ISC_R_NORESOURCES);
+ ALWAYS_HARD(EPERM, ISC_R_HOSTUNREACH);
+ ALWAYS_HARD(EPIPE, ISC_R_NOTCONNECTED);
+ ALWAYS_HARD(ECONNRESET, ISC_R_CONNECTIONRESET);
+
+#undef SOFT_OR_HARD
+#undef ALWAYS_HARD
+
+ /*
+ * The other error types depend on whether or not the
+ * socket is UDP or TCP. If it is UDP, some errors
+ * that we expect to be fatal under TCP are merely
+ * annoying, and are really soft errors.
+ *
+ * However, these soft errors are still returned as
+ * a status.
+ */
+ isc_sockaddr_format(&dev->address, addrbuf, sizeof(addrbuf));
+ strerror_r(send_errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "internal_send: %s: %s",
+ addrbuf, strbuf);
+ dev->result = isc__errno2result(send_errno);
+ inc_stats(sock->manager->stats,
+ sock->statsindex[STATID_SENDFAIL]);
+ return (DOIO_HARD);
+ }
+
+ if (cc == 0) {
+ inc_stats(sock->manager->stats,
+ sock->statsindex[STATID_SENDFAIL]);
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "doio_send: send() returned 0");
+ }
+
+ /*
+ * If we write less than we expected, update counters, poke.
+ */
+ dev->n += cc;
+ if ((size_t)cc != write_count) {
+ return (DOIO_SOFT);
+ }
+
+ /*
+ * Exactly what we wanted to write. We're done with this
+ * entry. Post its completion event.
+ */
+ dev->result = ISC_R_SUCCESS;
+ return (DOIO_SUCCESS);
+}
+
+/*
+ * Kill.
+ *
+ * Caller must ensure that the socket is not locked and no external
+ * references exist.
+ */
+static void
+socketclose(isc__socketthread_t *thread, isc_socket_t *sock, int fd) {
+ int lockid = FDLOCK_ID(fd);
+ /*
+ * No one has this socket open, so the watcher doesn't have to be
+ * poked, and the socket doesn't have to be locked.
+ */
+ LOCK(&thread->fdlock[lockid]);
+ thread->fds[fd] = NULL;
+ thread->fdstate[fd] = CLOSE_PENDING;
+ UNLOCK(&thread->fdlock[lockid]);
+ select_poke(thread->manager, thread->threadid, fd, SELECT_POKE_CLOSE);
+
+ inc_stats(thread->manager->stats, sock->statsindex[STATID_CLOSE]);
+
+ LOCK(&sock->lock);
+ if (sock->active == 1) {
+ dec_stats(thread->manager->stats,
+ sock->statsindex[STATID_ACTIVE]);
+ sock->active = 0;
+ }
+ UNLOCK(&sock->lock);
+
+ /*
+ * update manager->maxfd here (XXX: this should be implemented more
+ * efficiently)
+ */
+#ifdef USE_SELECT
+ LOCK(&thread->manager->lock);
+ if (thread->maxfd == fd) {
+ int i;
+
+ thread->maxfd = 0;
+ for (i = fd - 1; i >= 0; i--) {
+ lockid = FDLOCK_ID(i);
+
+ LOCK(&thread->fdlock[lockid]);
+ if (thread->fdstate[i] == MANAGED) {
+ thread->maxfd = i;
+ UNLOCK(&thread->fdlock[lockid]);
+ break;
+ }
+ UNLOCK(&thread->fdlock[lockid]);
+ }
+ if (thread->maxfd < thread->pipe_fds[0]) {
+ thread->maxfd = thread->pipe_fds[0];
+ }
+ }
+
+ UNLOCK(&thread->manager->lock);
+#endif /* USE_SELECT */
+}
+
+static void
+destroy(isc_socket_t **sockp) {
+ int fd = 0;
+ isc_socket_t *sock = *sockp;
+ isc_socketmgr_t *manager = sock->manager;
+ isc__socketthread_t *thread = NULL;
+
+ socket_log(sock, NULL, CREATION, "destroying");
+
+ isc_refcount_destroy(&sock->references);
+
+ LOCK(&sock->lock);
+ INSIST(ISC_LIST_EMPTY(sock->connect_list));
+ INSIST(ISC_LIST_EMPTY(sock->accept_list));
+ INSIST(ISC_LIST_EMPTY(sock->recv_list));
+ INSIST(ISC_LIST_EMPTY(sock->send_list));
+ INSIST(sock->fd >= -1 && sock->fd < (int)manager->maxsocks);
+
+ if (sock->fd >= 0) {
+ fd = sock->fd;
+ thread = &manager->threads[sock->threadid];
+ sock->fd = -1;
+ sock->threadid = -1;
+ }
+ UNLOCK(&sock->lock);
+
+ if (fd > 0) {
+ socketclose(thread, sock, fd);
+ }
+
+ LOCK(&manager->lock);
+
+ ISC_LIST_UNLINK(manager->socklist, sock, link);
+
+ if (ISC_LIST_EMPTY(manager->socklist)) {
+ SIGNAL(&manager->shutdown_ok);
+ }
+
+ /* can't unlock manager as its memory context is still used */
+ free_socket(sockp);
+
+ UNLOCK(&manager->lock);
+}
+
+static isc_result_t
+allocate_socket(isc_socketmgr_t *manager, isc_sockettype_t type,
+ isc_socket_t **socketp) {
+ isc_socket_t *sock;
+
+ sock = isc_mem_get(manager->mctx, sizeof(*sock));
+
+ sock->magic = 0;
+ isc_refcount_init(&sock->references, 0);
+
+ sock->manager = manager;
+ sock->type = type;
+ sock->fd = -1;
+ sock->threadid = -1;
+ sock->dscp = 0; /* TOS/TCLASS is zero until set. */
+ sock->dupped = 0;
+ sock->statsindex = NULL;
+ sock->active = 0;
+
+ ISC_LINK_INIT(sock, link);
+
+ memset(sock->name, 0, sizeof(sock->name));
+ sock->tag = NULL;
+
+ /*
+ * Set up list of readers and writers to be initially empty.
+ */
+ ISC_LIST_INIT(sock->recv_list);
+ ISC_LIST_INIT(sock->send_list);
+ ISC_LIST_INIT(sock->accept_list);
+ ISC_LIST_INIT(sock->connect_list);
+
+ sock->listener = 0;
+ sock->connected = 0;
+ sock->connecting = 0;
+ sock->bound = 0;
+ sock->pktdscp = 0;
+
+ /*
+ * Initialize the lock.
+ */
+ isc_mutex_init(&sock->lock);
+
+ sock->magic = SOCKET_MAGIC;
+ *socketp = sock;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * This event requires that the various lists be empty, that the reference
+ * count be 1, and that the magic number is valid. The other socket bits,
+ * like the lock, must be initialized as well. The fd associated must be
+ * marked as closed, by setting it to -1 on close, or this routine will
+ * also close the socket.
+ */
+static void
+free_socket(isc_socket_t **socketp) {
+ isc_socket_t *sock = *socketp;
+ *socketp = NULL;
+
+ INSIST(VALID_SOCKET(sock));
+ isc_refcount_destroy(&sock->references);
+ LOCK(&sock->lock);
+ INSIST(!sock->connecting);
+ INSIST(ISC_LIST_EMPTY(sock->recv_list));
+ INSIST(ISC_LIST_EMPTY(sock->send_list));
+ INSIST(ISC_LIST_EMPTY(sock->accept_list));
+ INSIST(ISC_LIST_EMPTY(sock->connect_list));
+ INSIST(!ISC_LINK_LINKED(sock, link));
+ UNLOCK(&sock->lock);
+
+ sock->magic = 0;
+
+ isc_mutex_destroy(&sock->lock);
+
+ isc_mem_put(sock->manager->mctx, sock, sizeof(*sock));
+}
+
+#if defined(SET_RCVBUF)
+static isc_once_t rcvbuf_once = ISC_ONCE_INIT;
+static int rcvbuf = ISC_RECV_BUFFER_SIZE;
+
+static void
+set_rcvbuf(void) {
+ int fd;
+ int max = rcvbuf, min;
+ socklen_t len;
+
+ fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd == -1) {
+ switch (errno) {
+ case EPROTONOSUPPORT:
+ case EPFNOSUPPORT:
+ case EAFNOSUPPORT:
+ /*
+ * Linux 2.2 (and maybe others) return EINVAL instead of
+ * EAFNOSUPPORT.
+ */
+ case EINVAL:
+ fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ break;
+ }
+ }
+ if (fd == -1) {
+ return;
+ }
+
+ len = sizeof(min);
+ if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *)&min, &len) == 0 &&
+ min < rcvbuf)
+ {
+ again:
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *)&rcvbuf,
+ sizeof(rcvbuf)) == -1)
+ {
+ if (errno == ENOBUFS && rcvbuf > min) {
+ max = rcvbuf - 1;
+ rcvbuf = (rcvbuf + min) / 2;
+ goto again;
+ } else {
+ rcvbuf = min;
+ goto cleanup;
+ }
+ } else {
+ min = rcvbuf;
+ }
+ if (min != max) {
+ rcvbuf = max;
+ goto again;
+ }
+ }
+cleanup:
+ close(fd);
+}
+#endif /* ifdef SO_RCVBUF */
+
+#if defined(SET_SNDBUF)
+static isc_once_t sndbuf_once = ISC_ONCE_INIT;
+static int sndbuf = ISC_SEND_BUFFER_SIZE;
+
+static void
+set_sndbuf(void) {
+ int fd;
+ int max = sndbuf, min;
+ socklen_t len;
+
+ fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd == -1) {
+ switch (errno) {
+ case EPROTONOSUPPORT:
+ case EPFNOSUPPORT:
+ case EAFNOSUPPORT:
+ /*
+ * Linux 2.2 (and maybe others) return EINVAL instead of
+ * EAFNOSUPPORT.
+ */
+ case EINVAL:
+ fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ break;
+ }
+ }
+ if (fd == -1) {
+ return;
+ }
+
+ len = sizeof(min);
+ if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&min, &len) == 0 &&
+ min < sndbuf)
+ {
+ again:
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&sndbuf,
+ sizeof(sndbuf)) == -1)
+ {
+ if (errno == ENOBUFS && sndbuf > min) {
+ max = sndbuf - 1;
+ sndbuf = (sndbuf + min) / 2;
+ goto again;
+ } else {
+ sndbuf = min;
+ goto cleanup;
+ }
+ } else {
+ min = sndbuf;
+ }
+ if (min != max) {
+ sndbuf = max;
+ goto again;
+ }
+ }
+cleanup:
+ close(fd);
+}
+#endif /* ifdef SO_SNDBUF */
+
+static void
+use_min_mtu(isc_socket_t *sock) {
+#if !defined(IPV6_USE_MIN_MTU) && !defined(IPV6_MTU)
+ UNUSED(sock);
+#endif /* if !defined(IPV6_USE_MIN_MTU) && !defined(IPV6_MTU) */
+#ifdef IPV6_USE_MIN_MTU
+ /* use minimum MTU */
+ if (sock->pf == AF_INET6) {
+ int on = 1;
+ (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_USE_MIN_MTU,
+ (void *)&on, sizeof(on));
+ }
+#endif /* ifdef IPV6_USE_MIN_MTU */
+#if defined(IPV6_MTU)
+ /*
+ * Use minimum MTU on IPv6 sockets.
+ */
+ if (sock->pf == AF_INET6) {
+ int mtu = 1280;
+ (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MTU, &mtu,
+ sizeof(mtu));
+ }
+#endif /* if defined(IPV6_MTU) */
+}
+
+static void
+set_tcp_maxseg(isc_socket_t *sock, int size) {
+#ifdef TCP_MAXSEG
+ if (sock->type == isc_sockettype_tcp) {
+ (void)setsockopt(sock->fd, IPPROTO_TCP, TCP_MAXSEG,
+ (void *)&size, sizeof(size));
+ }
+#endif /* ifdef TCP_MAXSEG */
+}
+
+static void
+set_ip_disable_pmtud(isc_socket_t *sock) {
+ /*
+ * Disable Path MTU Discover on IP packets
+ */
+ if (sock->pf == AF_INET6) {
+#if defined(IPV6_DONTFRAG)
+ (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_DONTFRAG,
+ &(int){ 0 }, sizeof(int));
+#endif
+#if defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_OMIT)
+ (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER,
+ &(int){ IP_PMTUDISC_OMIT }, sizeof(int));
+#endif
+ } else if (sock->pf == AF_INET) {
+#if defined(IP_DONTFRAG)
+ (void)setsockopt(sock->fd, IPPROTO_IP, IP_DONTFRAG, &(int){ 0 },
+ sizeof(int));
+#endif
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_OMIT)
+ (void)setsockopt(sock->fd, IPPROTO_IP, IP_MTU_DISCOVER,
+ &(int){ IP_PMTUDISC_OMIT }, sizeof(int));
+#endif
+ }
+}
+
+static isc_result_t
+opensocket(isc_socketmgr_t *manager, isc_socket_t *sock,
+ isc_socket_t *dup_socket) {
+ isc_result_t result;
+ char strbuf[ISC_STRERRORSIZE];
+ const char *err = "socket";
+ int tries = 0;
+#if defined(USE_CMSG) || defined(SO_NOSIGPIPE)
+ int on = 1;
+#endif /* if defined(USE_CMSG) || defined(SO_NOSIGPIPE) */
+#if defined(SET_RCVBUF) || defined(SET_SNDBUF)
+ socklen_t optlen;
+ int size = 0;
+#endif
+
+again:
+ if (dup_socket == NULL) {
+ switch (sock->type) {
+ case isc_sockettype_udp:
+ sock->fd = socket(sock->pf, SOCK_DGRAM, IPPROTO_UDP);
+ break;
+ case isc_sockettype_tcp:
+ sock->fd = socket(sock->pf, SOCK_STREAM, IPPROTO_TCP);
+ break;
+ case isc_sockettype_unix:
+ sock->fd = socket(sock->pf, SOCK_STREAM, 0);
+ break;
+ case isc_sockettype_raw:
+ errno = EPFNOSUPPORT;
+ /*
+ * PF_ROUTE is a alias for PF_NETLINK on linux.
+ */
+#if defined(PF_ROUTE)
+ if (sock->fd == -1 && sock->pf == PF_ROUTE) {
+#ifdef NETLINK_ROUTE
+ sock->fd = socket(sock->pf, SOCK_RAW,
+ NETLINK_ROUTE);
+#else /* ifdef NETLINK_ROUTE */
+ sock->fd = socket(sock->pf, SOCK_RAW, 0);
+#endif /* ifdef NETLINK_ROUTE */
+ if (sock->fd != -1) {
+#ifdef NETLINK_ROUTE
+ struct sockaddr_nl sa;
+ int n;
+
+ /*
+ * Do an implicit bind.
+ */
+ memset(&sa, 0, sizeof(sa));
+ sa.nl_family = AF_NETLINK;
+ sa.nl_groups = RTMGRP_IPV4_IFADDR |
+ RTMGRP_IPV6_IFADDR;
+ n = bind(sock->fd,
+ (struct sockaddr *)&sa,
+ sizeof(sa));
+ if (n < 0) {
+ close(sock->fd);
+ sock->fd = -1;
+ }
+#endif /* ifdef NETLINK_ROUTE */
+ sock->bound = 1;
+ }
+ }
+#endif /* if defined(PF_ROUTE) */
+ break;
+ }
+ } else {
+ sock->fd = dup(dup_socket->fd);
+ sock->dupped = 1;
+ sock->bound = dup_socket->bound;
+ }
+ if (sock->fd == -1 && errno == EINTR && tries++ < 42) {
+ goto again;
+ }
+
+#ifdef F_DUPFD
+ /*
+ * Leave a space for stdio and TCP to work in.
+ */
+ if (manager->reserved != 0 && sock->type == isc_sockettype_udp &&
+ sock->fd >= 0 && sock->fd < manager->reserved)
+ {
+ int newfd, tmp;
+ newfd = fcntl(sock->fd, F_DUPFD, manager->reserved);
+ tmp = errno;
+ (void)close(sock->fd);
+ errno = tmp;
+ sock->fd = newfd;
+ err = "isc_socket_create: fcntl/reserved";
+ } else if (sock->fd >= 0 && sock->fd < 20) {
+ int newfd, tmp;
+ newfd = fcntl(sock->fd, F_DUPFD, 20);
+ tmp = errno;
+ (void)close(sock->fd);
+ errno = tmp;
+ sock->fd = newfd;
+ err = "isc_socket_create: fcntl";
+ }
+#endif /* ifdef F_DUPFD */
+
+ if (sock->fd >= (int)manager->maxsocks) {
+ (void)close(sock->fd);
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "socket: file descriptor exceeds limit (%d/%u)",
+ sock->fd, manager->maxsocks);
+ inc_stats(manager->stats, sock->statsindex[STATID_OPENFAIL]);
+ return (ISC_R_NORESOURCES);
+ }
+
+ if (sock->fd < 0) {
+ switch (errno) {
+ case EMFILE:
+ case ENFILE:
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "%s: %s", err, strbuf);
+ FALLTHROUGH;
+ case ENOBUFS:
+ inc_stats(manager->stats,
+ sock->statsindex[STATID_OPENFAIL]);
+ return (ISC_R_NORESOURCES);
+
+ case EPROTONOSUPPORT:
+ case EPFNOSUPPORT:
+ case EAFNOSUPPORT:
+ /*
+ * Linux 2.2 (and maybe others) return EINVAL instead of
+ * EAFNOSUPPORT.
+ */
+ case EINVAL:
+ inc_stats(manager->stats,
+ sock->statsindex[STATID_OPENFAIL]);
+ return (ISC_R_FAMILYNOSUPPORT);
+
+ default:
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "%s() failed: %s",
+ err, strbuf);
+ inc_stats(manager->stats,
+ sock->statsindex[STATID_OPENFAIL]);
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+
+ if (dup_socket != NULL) {
+ goto setup_done;
+ }
+
+ result = make_nonblock(sock->fd);
+ if (result != ISC_R_SUCCESS) {
+ (void)close(sock->fd);
+ inc_stats(manager->stats, sock->statsindex[STATID_OPENFAIL]);
+ return (result);
+ }
+
+#ifdef SO_NOSIGPIPE
+ if (setsockopt(sock->fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&on,
+ sizeof(on)) < 0)
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, SO_NOSIGPIPE) failed: %s",
+ sock->fd, strbuf);
+ /* Press on... */
+ }
+#endif /* ifdef SO_NOSIGPIPE */
+
+ /*
+ * Use minimum mtu if possible.
+ */
+ if (sock->type == isc_sockettype_tcp && sock->pf == AF_INET6) {
+ use_min_mtu(sock);
+ set_tcp_maxseg(sock, 1280 - 20 - 40); /* 1280 - TCP - IPV6 */
+ }
+
+#if defined(USE_CMSG) || defined(SET_RCVBUF) || defined(SET_SNDBUF)
+ if (sock->type == isc_sockettype_udp) {
+#if defined(USE_CMSG)
+#if defined(SO_TIMESTAMP)
+ if (setsockopt(sock->fd, SOL_SOCKET, SO_TIMESTAMP, (void *)&on,
+ sizeof(on)) < 0 &&
+ errno != ENOPROTOOPT)
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, SO_TIMESTAMP) failed: "
+ "%s",
+ sock->fd, strbuf);
+ /* Press on... */
+ }
+#endif /* SO_TIMESTAMP */
+
+#ifdef IPV6_RECVPKTINFO
+ /* RFC 3542 */
+ if ((sock->pf == AF_INET6) &&
+ (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ (void *)&on, sizeof(on)) < 0))
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IPV6_RECVPKTINFO) "
+ "failed: %s",
+ sock->fd, strbuf);
+ }
+#else /* ifdef IPV6_RECVPKTINFO */
+ /* RFC 2292 */
+ if ((sock->pf == AF_INET6) &&
+ (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_PKTINFO,
+ (void *)&on, sizeof(on)) < 0))
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IPV6_PKTINFO) failed: "
+ "%s",
+ sock->fd, strbuf);
+ }
+#endif /* IPV6_RECVPKTINFO */
+#endif /* defined(USE_CMSG) */
+
+#if defined(SET_RCVBUF)
+ optlen = sizeof(size);
+ if (getsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF, (void *)&size,
+ &optlen) == 0 &&
+ size < rcvbuf)
+ {
+ RUNTIME_CHECK(isc_once_do(&rcvbuf_once, set_rcvbuf) ==
+ ISC_R_SUCCESS);
+ if (setsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF,
+ (void *)&rcvbuf, sizeof(rcvbuf)) == -1)
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, SO_RCVBUF, "
+ "%d) failed: %s",
+ sock->fd, rcvbuf, strbuf);
+ }
+ }
+#endif /* if defined(SET_RCVBUF) */
+
+#if defined(SET_SNDBUF)
+ optlen = sizeof(size);
+ if (getsockopt(sock->fd, SOL_SOCKET, SO_SNDBUF, (void *)&size,
+ &optlen) == 0 &&
+ size < sndbuf)
+ {
+ RUNTIME_CHECK(isc_once_do(&sndbuf_once, set_sndbuf) ==
+ ISC_R_SUCCESS);
+ if (setsockopt(sock->fd, SOL_SOCKET, SO_SNDBUF,
+ (void *)&sndbuf, sizeof(sndbuf)) == -1)
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, SO_SNDBUF, "
+ "%d) failed: %s",
+ sock->fd, sndbuf, strbuf);
+ }
+ }
+#endif /* if defined(SO_SNDBUF) */
+ }
+#ifdef IPV6_RECVTCLASS
+ if ((sock->pf == AF_INET6) &&
+ (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_RECVTCLASS, (void *)&on,
+ sizeof(on)) < 0))
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IPV6_RECVTCLASS) "
+ "failed: %s",
+ sock->fd, strbuf);
+ }
+#endif /* ifdef IPV6_RECVTCLASS */
+#ifdef IP_RECVTOS
+ if ((sock->pf == AF_INET) &&
+ (setsockopt(sock->fd, IPPROTO_IP, IP_RECVTOS, (void *)&on,
+ sizeof(on)) < 0))
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IP_RECVTOS) "
+ "failed: %s",
+ sock->fd, strbuf);
+ }
+#endif /* ifdef IP_RECVTOS */
+#endif /* defined(USE_CMSG) || defined(SET_RCVBUF) || defined(SET_SNDBUF) */
+
+ set_ip_disable_pmtud(sock);
+
+setup_done:
+ inc_stats(manager->stats, sock->statsindex[STATID_OPEN]);
+ if (sock->active == 0) {
+ inc_stats(manager->stats, sock->statsindex[STATID_ACTIVE]);
+ sock->active = 1;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Create a 'type' socket or duplicate an existing socket, managed
+ * by 'manager'. Events will be posted to 'task' and when dispatched
+ * 'action' will be called with 'arg' as the arg value. The new
+ * socket is returned in 'socketp'.
+ */
+static isc_result_t
+socket_create(isc_socketmgr_t *manager, int pf, isc_sockettype_t type,
+ isc_socket_t **socketp, isc_socket_t *dup_socket) {
+ isc_socket_t *sock = NULL;
+ isc__socketthread_t *thread;
+ isc_result_t result;
+ int lockid;
+
+ REQUIRE(VALID_MANAGER(manager));
+ REQUIRE(socketp != NULL && *socketp == NULL);
+
+ result = allocate_socket(manager, type, &sock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ switch (sock->type) {
+ case isc_sockettype_udp:
+ sock->statsindex = (pf == AF_INET) ? udp4statsindex
+ : udp6statsindex;
+#define DCSPPKT(pf) ((pf == AF_INET) ? ISC_NET_DSCPPKTV4 : ISC_NET_DSCPPKTV6)
+ sock->pktdscp = (isc_net_probedscp() & DCSPPKT(pf)) != 0;
+ break;
+ case isc_sockettype_tcp:
+ sock->statsindex = (pf == AF_INET) ? tcp4statsindex
+ : tcp6statsindex;
+ break;
+ case isc_sockettype_unix:
+ sock->statsindex = unixstatsindex;
+ break;
+ case isc_sockettype_raw:
+ sock->statsindex = rawstatsindex;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ sock->pf = pf;
+
+ result = opensocket(manager, sock, dup_socket);
+ if (result != ISC_R_SUCCESS) {
+ free_socket(&sock);
+ return (result);
+ }
+
+ if (sock->fd == -1) {
+ abort();
+ }
+ sock->threadid = gen_threadid(sock);
+ isc_refcount_increment0(&sock->references);
+ thread = &manager->threads[sock->threadid];
+ *socketp = sock;
+
+ /*
+ * Note we don't have to lock the socket like we normally would because
+ * there are no external references to it yet.
+ */
+
+ lockid = FDLOCK_ID(sock->fd);
+ LOCK(&thread->fdlock[lockid]);
+ thread->fds[sock->fd] = sock;
+ thread->fdstate[sock->fd] = MANAGED;
+#if defined(USE_EPOLL)
+ thread->epoll_events[sock->fd] = 0;
+#endif /* if defined(USE_EPOLL) */
+#ifdef USE_DEVPOLL
+ INSIST(thread->fdpollinfo[sock->fd].want_read == 0 &&
+ thread->fdpollinfo[sock->fd].want_write == 0);
+#endif /* ifdef USE_DEVPOLL */
+ UNLOCK(&thread->fdlock[lockid]);
+
+ LOCK(&manager->lock);
+ ISC_LIST_APPEND(manager->socklist, sock, link);
+#ifdef USE_SELECT
+ if (thread->maxfd < sock->fd) {
+ thread->maxfd = sock->fd;
+ }
+#endif /* ifdef USE_SELECT */
+ UNLOCK(&manager->lock);
+
+ socket_log(sock, NULL, CREATION,
+ dup_socket != NULL ? "dupped" : "created");
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Create a new 'type' socket managed by 'manager'. Events
+ * will be posted to 'task' and when dispatched 'action' will be
+ * called with 'arg' as the arg value. The new socket is returned
+ * in 'socketp'.
+ */
+isc_result_t
+isc_socket_create(isc_socketmgr_t *manager0, int pf, isc_sockettype_t type,
+ isc_socket_t **socketp) {
+ return (socket_create(manager0, pf, type, socketp, NULL));
+}
+
+/*%
+ * Duplicate an existing socket. The new socket is returned
+ * in 'socketp'.
+ */
+isc_result_t
+isc_socket_dup(isc_socket_t *sock, isc_socket_t **socketp) {
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(socketp != NULL && *socketp == NULL);
+
+ return (socket_create(sock->manager, sock->pf, sock->type, socketp,
+ sock));
+}
+
+isc_result_t
+isc_socket_open(isc_socket_t *sock) {
+ isc_result_t result;
+ isc__socketthread_t *thread;
+
+ REQUIRE(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+
+ REQUIRE(isc_refcount_current(&sock->references) >= 1);
+ REQUIRE(sock->fd == -1);
+ REQUIRE(sock->threadid == -1);
+
+ result = opensocket(sock->manager, sock, NULL);
+
+ UNLOCK(&sock->lock);
+
+ if (result != ISC_R_SUCCESS) {
+ sock->fd = -1;
+ } else {
+ sock->threadid = gen_threadid(sock);
+ thread = &sock->manager->threads[sock->threadid];
+ int lockid = FDLOCK_ID(sock->fd);
+
+ LOCK(&thread->fdlock[lockid]);
+ thread->fds[sock->fd] = sock;
+ thread->fdstate[sock->fd] = MANAGED;
+#if defined(USE_EPOLL)
+ thread->epoll_events[sock->fd] = 0;
+#endif /* if defined(USE_EPOLL) */
+#ifdef USE_DEVPOLL
+ INSIST(thread->fdpollinfo[sock->fd].want_read == 0 &&
+ thread->fdpollinfo[sock->fd].want_write == 0);
+#endif /* ifdef USE_DEVPOLL */
+ UNLOCK(&thread->fdlock[lockid]);
+
+#ifdef USE_SELECT
+ LOCK(&sock->manager->lock);
+ if (thread->maxfd < sock->fd) {
+ thread->maxfd = sock->fd;
+ }
+ UNLOCK(&sock->manager->lock);
+#endif /* ifdef USE_SELECT */
+ }
+
+ return (result);
+}
+
+/*
+ * Attach to a socket. Caller must explicitly detach when it is done.
+ */
+void
+isc_socket_attach(isc_socket_t *sock, isc_socket_t **socketp) {
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(socketp != NULL && *socketp == NULL);
+
+ int old_refs = isc_refcount_increment(&sock->references);
+ REQUIRE(old_refs > 0);
+
+ *socketp = sock;
+}
+
+/*
+ * Dereference a socket. If this is the last reference to it, clean things
+ * up by destroying the socket.
+ */
+void
+isc_socket_detach(isc_socket_t **socketp) {
+ isc_socket_t *sock;
+
+ REQUIRE(socketp != NULL);
+ sock = *socketp;
+ REQUIRE(VALID_SOCKET(sock));
+ if (isc_refcount_decrement(&sock->references) == 1) {
+ destroy(&sock);
+ }
+
+ *socketp = NULL;
+}
+
+isc_result_t
+isc_socket_close(isc_socket_t *sock) {
+ int fd;
+ isc_socketmgr_t *manager;
+ isc__socketthread_t *thread;
+ fflush(stdout);
+ REQUIRE(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+
+ REQUIRE(sock->fd >= 0 && sock->fd < (int)sock->manager->maxsocks);
+
+ INSIST(!sock->connecting);
+ INSIST(ISC_LIST_EMPTY(sock->recv_list));
+ INSIST(ISC_LIST_EMPTY(sock->send_list));
+ INSIST(ISC_LIST_EMPTY(sock->accept_list));
+ INSIST(ISC_LIST_EMPTY(sock->connect_list));
+
+ manager = sock->manager;
+ thread = &manager->threads[sock->threadid];
+ fd = sock->fd;
+ sock->fd = -1;
+ sock->threadid = -1;
+
+ sock->dupped = 0;
+ memset(sock->name, 0, sizeof(sock->name));
+ sock->tag = NULL;
+ sock->listener = 0;
+ sock->connected = 0;
+ sock->connecting = 0;
+ sock->bound = 0;
+ isc_sockaddr_any(&sock->peer_address);
+
+ UNLOCK(&sock->lock);
+
+ socketclose(thread, sock, fd);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Dequeue an item off the given socket's read queue, set the result code
+ * in the done event to the one provided, and send it to the task it was
+ * destined for.
+ *
+ * If the event to be sent is on a list, remove it before sending. If
+ * asked to, send and detach from the socket as well.
+ *
+ * Caller must have the socket locked if the event is attached to the socket.
+ */
+static void
+send_recvdone_event(isc_socket_t *sock, isc_socketevent_t **dev) {
+ isc_task_t *task;
+
+ task = (*dev)->ev_sender;
+
+ (*dev)->ev_sender = sock;
+
+ if (ISC_LINK_LINKED(*dev, ev_link)) {
+ ISC_LIST_DEQUEUE(sock->recv_list, *dev, ev_link);
+ }
+
+ if (((*dev)->attributes & ISC_SOCKEVENTATTR_ATTACHED) != 0) {
+ isc_task_sendtoanddetach(&task, (isc_event_t **)dev,
+ sock->threadid);
+ } else {
+ isc_task_sendto(task, (isc_event_t **)dev, sock->threadid);
+ }
+}
+
+/*
+ * See comments for send_recvdone_event() above.
+ *
+ * Caller must have the socket locked if the event is attached to the socket.
+ */
+static void
+send_senddone_event(isc_socket_t *sock, isc_socketevent_t **dev) {
+ isc_task_t *task;
+
+ INSIST(dev != NULL && *dev != NULL);
+
+ task = (*dev)->ev_sender;
+ (*dev)->ev_sender = sock;
+
+ if (ISC_LINK_LINKED(*dev, ev_link)) {
+ ISC_LIST_DEQUEUE(sock->send_list, *dev, ev_link);
+ }
+
+ if (((*dev)->attributes & ISC_SOCKEVENTATTR_ATTACHED) != 0) {
+ isc_task_sendtoanddetach(&task, (isc_event_t **)dev,
+ sock->threadid);
+ } else {
+ isc_task_sendto(task, (isc_event_t **)dev, sock->threadid);
+ }
+}
+
+/*
+ * See comments for send_recvdone_event() above.
+ *
+ * Caller must have the socket locked if the event is attached to the socket.
+ */
+static void
+send_connectdone_event(isc_socket_t *sock, isc_socket_connev_t **dev) {
+ isc_task_t *task;
+
+ INSIST(dev != NULL && *dev != NULL);
+
+ task = (*dev)->ev_sender;
+ (*dev)->ev_sender = sock;
+
+ if (ISC_LINK_LINKED(*dev, ev_link)) {
+ ISC_LIST_DEQUEUE(sock->connect_list, *dev, ev_link);
+ }
+
+ isc_task_sendtoanddetach(&task, (isc_event_t **)dev, sock->threadid);
+}
+
+/*
+ * Call accept() on a socket, to get the new file descriptor. The listen
+ * socket is used as a prototype to create a new isc_socket_t. The new
+ * socket has one outstanding reference. The task receiving the event
+ * will be detached from just after the event is delivered.
+ *
+ * On entry to this function, the event delivered is the internal
+ * readable event, and the first item on the accept_list should be
+ * the done event we want to send. If the list is empty, this is a no-op,
+ * so just unlock and return.
+ */
+static void
+internal_accept(isc_socket_t *sock) {
+ isc_socketmgr_t *manager;
+ isc__socketthread_t *thread, *nthread;
+ isc_socket_newconnev_t *dev;
+ isc_task_t *task;
+ socklen_t addrlen;
+ int fd;
+ isc_result_t result = ISC_R_SUCCESS;
+ char strbuf[ISC_STRERRORSIZE];
+ const char *err = "accept";
+
+ INSIST(VALID_SOCKET(sock));
+ REQUIRE(sock->fd >= 0);
+
+ socket_log(sock, NULL, TRACE, "internal_accept called, locked socket");
+
+ manager = sock->manager;
+ INSIST(VALID_MANAGER(manager));
+ thread = &manager->threads[sock->threadid];
+
+ INSIST(sock->listener);
+
+ /*
+ * Get the first item off the accept list.
+ * If it is empty, unlock the socket and return.
+ */
+ dev = ISC_LIST_HEAD(sock->accept_list);
+ if (dev == NULL) {
+ unwatch_fd(thread, sock->fd, SELECT_POKE_ACCEPT);
+ UNLOCK(&sock->lock);
+ return;
+ }
+
+ /*
+ * Try to accept the new connection. If the accept fails with
+ * EAGAIN or EINTR, simply poke the watcher to watch this socket
+ * again. Also ignore ECONNRESET, which has been reported to
+ * be spuriously returned on Linux 2.2.19 although it is not
+ * a documented error for accept(). ECONNABORTED has been
+ * reported for Solaris 8. The rest are thrown in not because
+ * we have seen them but because they are ignored by other
+ * daemons such as BIND 8 and Apache.
+ */
+
+ addrlen = sizeof(NEWCONNSOCK(dev)->peer_address.type);
+ memset(&NEWCONNSOCK(dev)->peer_address.type, 0, addrlen);
+ fd = accept(sock->fd, &NEWCONNSOCK(dev)->peer_address.type.sa,
+ (void *)&addrlen);
+
+#ifdef F_DUPFD
+ /*
+ * Leave a space for stdio to work in.
+ */
+ if (fd >= 0 && fd < 20) {
+ int newfd, tmp;
+ newfd = fcntl(fd, F_DUPFD, 20);
+ tmp = errno;
+ (void)close(fd);
+ errno = tmp;
+ fd = newfd;
+ err = "accept/fcntl";
+ }
+#endif /* ifdef F_DUPFD */
+
+ if (fd < 0) {
+ if (SOFT_ERROR(errno)) {
+ goto soft_error;
+ }
+ switch (errno) {
+ case ENFILE:
+ case EMFILE:
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "%s: too many open file descriptors",
+ err);
+ goto soft_error;
+
+ case ENOBUFS:
+ case ENOMEM:
+ case ECONNRESET:
+ case ECONNABORTED:
+ case EHOSTUNREACH:
+ case EHOSTDOWN:
+ case ENETUNREACH:
+ case ENETDOWN:
+ case ECONNREFUSED:
+#ifdef EPROTO
+ case EPROTO:
+#endif /* ifdef EPROTO */
+#ifdef ENONET
+ case ENONET:
+#endif /* ifdef ENONET */
+ goto soft_error;
+ default:
+ break;
+ }
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "internal_accept: %s() failed: %s", err,
+ strbuf);
+ fd = -1;
+ result = ISC_R_UNEXPECTED;
+ } else {
+ if (addrlen == 0U) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "internal_accept(): "
+ "accept() failed to return "
+ "remote address");
+
+ (void)close(fd);
+ goto soft_error;
+ } else if (NEWCONNSOCK(dev)->peer_address.type.sa.sa_family !=
+ sock->pf)
+ {
+ UNEXPECTED_ERROR(
+ __FILE__, __LINE__,
+ "internal_accept(): "
+ "accept() returned peer address "
+ "family %u (expected %u)",
+ NEWCONNSOCK(dev)->peer_address.type.sa.sa_family,
+ sock->pf);
+ (void)close(fd);
+ goto soft_error;
+ } else if (fd >= (int)manager->maxsocks) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "accept: file descriptor exceeds limit "
+ "(%d/%u)",
+ fd, manager->maxsocks);
+ (void)close(fd);
+ goto soft_error;
+ }
+ }
+
+ if (fd != -1) {
+ NEWCONNSOCK(dev)->peer_address.length = addrlen;
+ NEWCONNSOCK(dev)->pf = sock->pf;
+ }
+
+ /*
+ * Pull off the done event.
+ */
+ ISC_LIST_UNLINK(sock->accept_list, dev, ev_link);
+
+ /*
+ * Poke watcher if there are more pending accepts.
+ */
+ if (ISC_LIST_EMPTY(sock->accept_list)) {
+ unwatch_fd(thread, sock->fd, SELECT_POKE_ACCEPT);
+ }
+
+ if (fd != -1) {
+ result = make_nonblock(fd);
+ if (result != ISC_R_SUCCESS) {
+ (void)close(fd);
+ fd = -1;
+ }
+ }
+
+ /*
+ * We need to unlock sock->lock now to be able to lock manager->lock
+ * without risking a deadlock with xmlstats.
+ */
+ UNLOCK(&sock->lock);
+
+ /*
+ * -1 means the new socket didn't happen.
+ */
+ if (fd != -1) {
+ int lockid = FDLOCK_ID(fd);
+
+ NEWCONNSOCK(dev)->fd = fd;
+ NEWCONNSOCK(dev)->threadid = gen_threadid(NEWCONNSOCK(dev));
+ NEWCONNSOCK(dev)->bound = 1;
+ NEWCONNSOCK(dev)->connected = 1;
+ nthread = &manager->threads[NEWCONNSOCK(dev)->threadid];
+
+ /*
+ * We already hold a lock on one fdlock in accepting thread,
+ * we need to make sure that we don't double lock.
+ */
+ bool same_bucket = (sock->threadid ==
+ NEWCONNSOCK(dev)->threadid) &&
+ (FDLOCK_ID(sock->fd) == lockid);
+
+ /*
+ * Use minimum mtu if possible.
+ */
+ use_min_mtu(NEWCONNSOCK(dev));
+ set_tcp_maxseg(NEWCONNSOCK(dev), 1280 - 20 - 40);
+
+ /*
+ * Ensure DSCP settings are inherited across accept.
+ */
+ setdscp(NEWCONNSOCK(dev), sock->dscp);
+
+ /*
+ * Save away the remote address
+ */
+ dev->address = NEWCONNSOCK(dev)->peer_address;
+
+ if (NEWCONNSOCK(dev)->active == 0) {
+ inc_stats(manager->stats,
+ NEWCONNSOCK(dev)->statsindex[STATID_ACTIVE]);
+ NEWCONNSOCK(dev)->active = 1;
+ }
+
+ if (!same_bucket) {
+ LOCK(&nthread->fdlock[lockid]);
+ }
+ nthread->fds[fd] = NEWCONNSOCK(dev);
+ nthread->fdstate[fd] = MANAGED;
+#if defined(USE_EPOLL)
+ nthread->epoll_events[fd] = 0;
+#endif /* if defined(USE_EPOLL) */
+ if (!same_bucket) {
+ UNLOCK(&nthread->fdlock[lockid]);
+ }
+
+ LOCK(&manager->lock);
+
+#ifdef USE_SELECT
+ if (nthread->maxfd < fd) {
+ nthread->maxfd = fd;
+ }
+#endif /* ifdef USE_SELECT */
+
+ socket_log(sock, &NEWCONNSOCK(dev)->peer_address, CREATION,
+ "accepted connection, new socket %p",
+ dev->newsocket);
+
+ ISC_LIST_APPEND(manager->socklist, NEWCONNSOCK(dev), link);
+
+ UNLOCK(&manager->lock);
+
+ inc_stats(manager->stats, sock->statsindex[STATID_ACCEPT]);
+ } else {
+ inc_stats(manager->stats, sock->statsindex[STATID_ACCEPTFAIL]);
+ isc_refcount_decrementz(&NEWCONNSOCK(dev)->references);
+ free_socket((isc_socket_t **)&dev->newsocket);
+ }
+
+ /*
+ * Fill in the done event details and send it off.
+ */
+ dev->result = result;
+ task = dev->ev_sender;
+ dev->ev_sender = sock;
+
+ isc_task_sendtoanddetach(&task, ISC_EVENT_PTR(&dev), sock->threadid);
+ return;
+
+soft_error:
+ watch_fd(thread, sock->fd, SELECT_POKE_ACCEPT);
+ UNLOCK(&sock->lock);
+
+ inc_stats(manager->stats, sock->statsindex[STATID_ACCEPTFAIL]);
+ return;
+}
+
+static void
+internal_recv(isc_socket_t *sock) {
+ isc_socketevent_t *dev;
+
+ INSIST(VALID_SOCKET(sock));
+ REQUIRE(sock->fd >= 0);
+
+ dev = ISC_LIST_HEAD(sock->recv_list);
+ if (dev == NULL) {
+ goto finish;
+ }
+
+ socket_log(sock, NULL, IOEVENT, "internal_recv: event %p -> task %p",
+ dev, dev->ev_sender);
+
+ /*
+ * Try to do as much I/O as possible on this socket. There are no
+ * limits here, currently.
+ */
+ while (dev != NULL) {
+ switch (doio_recv(sock, dev)) {
+ case DOIO_SOFT:
+ goto finish;
+
+ case DOIO_EOF:
+ /*
+ * read of 0 means the remote end was closed.
+ * Run through the event queue and dispatch all
+ * the events with an EOF result code.
+ */
+ do {
+ dev->result = ISC_R_EOF;
+ send_recvdone_event(sock, &dev);
+ dev = ISC_LIST_HEAD(sock->recv_list);
+ } while (dev != NULL);
+ goto finish;
+
+ case DOIO_SUCCESS:
+ case DOIO_HARD:
+ send_recvdone_event(sock, &dev);
+ break;
+ }
+
+ dev = ISC_LIST_HEAD(sock->recv_list);
+ }
+
+finish:
+ if (ISC_LIST_EMPTY(sock->recv_list)) {
+ unwatch_fd(&sock->manager->threads[sock->threadid], sock->fd,
+ SELECT_POKE_READ);
+ }
+}
+
+static void
+internal_send(isc_socket_t *sock) {
+ isc_socketevent_t *dev;
+
+ INSIST(VALID_SOCKET(sock));
+ REQUIRE(sock->fd >= 0);
+
+ dev = ISC_LIST_HEAD(sock->send_list);
+ if (dev == NULL) {
+ goto finish;
+ }
+ socket_log(sock, NULL, EVENT, "internal_send: event %p -> task %p", dev,
+ dev->ev_sender);
+
+ /*
+ * Try to do as much I/O as possible on this socket. There are no
+ * limits here, currently.
+ */
+ while (dev != NULL) {
+ switch (doio_send(sock, dev)) {
+ case DOIO_SOFT:
+ goto finish;
+
+ case DOIO_HARD:
+ case DOIO_SUCCESS:
+ send_senddone_event(sock, &dev);
+ break;
+ }
+
+ dev = ISC_LIST_HEAD(sock->send_list);
+ }
+
+finish:
+ if (ISC_LIST_EMPTY(sock->send_list)) {
+ unwatch_fd(&sock->manager->threads[sock->threadid], sock->fd,
+ SELECT_POKE_WRITE);
+ }
+}
+
+/*
+ * Process read/writes on each fd here. Avoid locking
+ * and unlocking twice if both reads and writes are possible.
+ */
+static void
+process_fd(isc__socketthread_t *thread, int fd, bool readable, bool writeable) {
+ isc_socket_t *sock;
+ int lockid = FDLOCK_ID(fd);
+
+ /*
+ * If the socket is going to be closed, don't do more I/O.
+ */
+ LOCK(&thread->fdlock[lockid]);
+ if (thread->fdstate[fd] == CLOSE_PENDING) {
+ UNLOCK(&thread->fdlock[lockid]);
+
+ (void)unwatch_fd(thread, fd, SELECT_POKE_READ);
+ (void)unwatch_fd(thread, fd, SELECT_POKE_WRITE);
+ return;
+ }
+
+ sock = thread->fds[fd];
+ if (sock == NULL) {
+ UNLOCK(&thread->fdlock[lockid]);
+ return;
+ }
+
+ LOCK(&sock->lock);
+
+ if (sock->fd < 0) {
+ /*
+ * Sock is being closed - the final external reference
+ * is gone but it was not yet removed from event loop
+ * and fdstate[]/fds[] as destroy() is waiting on
+ * thread->fdlock[lockid] or sock->lock that we're holding.
+ * Just release the locks and bail.
+ */
+ UNLOCK(&sock->lock);
+ UNLOCK(&thread->fdlock[lockid]);
+ return;
+ }
+
+ REQUIRE(readable || writeable);
+ if (writeable) {
+ if (sock->connecting) {
+ internal_connect(sock);
+ } else {
+ internal_send(sock);
+ }
+ }
+
+ if (readable) {
+ if (sock->listener) {
+ internal_accept(sock); /* unlocks sock */
+ } else {
+ internal_recv(sock);
+ UNLOCK(&sock->lock);
+ }
+ } else {
+ UNLOCK(&sock->lock);
+ }
+
+ UNLOCK(&thread->fdlock[lockid]);
+
+ /*
+ * Socket destruction might be pending, it will resume
+ * after releasing fdlock and sock->lock.
+ */
+}
+
+/*
+ * process_fds is different for different event loops
+ * it takes the events from event loops and for each FD
+ * launches process_fd
+ */
+#ifdef USE_KQUEUE
+static bool
+process_fds(isc__socketthread_t *thread, struct kevent *events, int nevents) {
+ int i;
+ bool readable, writable;
+ bool done = false;
+ bool have_ctlevent = false;
+ if (nevents == thread->nevents) {
+ /*
+ * This is not an error, but something unexpected. If this
+ * happens, it may indicate the need for increasing
+ * ISC_SOCKET_MAXEVENTS.
+ */
+ thread_log(thread, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_INFO,
+ "maximum number of FD events (%d) received",
+ nevents);
+ }
+
+ for (i = 0; i < nevents; i++) {
+ REQUIRE(events[i].ident < thread->manager->maxsocks);
+ if (events[i].ident == (uintptr_t)thread->pipe_fds[0]) {
+ have_ctlevent = true;
+ continue;
+ }
+ readable = (events[i].filter == EVFILT_READ);
+ writable = (events[i].filter == EVFILT_WRITE);
+ process_fd(thread, events[i].ident, readable, writable);
+ }
+
+ if (have_ctlevent) {
+ done = process_ctlfd(thread);
+ }
+
+ return (done);
+}
+#elif defined(USE_EPOLL)
+static bool
+process_fds(isc__socketthread_t *thread, struct epoll_event *events,
+ int nevents) {
+ int i;
+ bool done = false;
+ bool have_ctlevent = false;
+
+ if (nevents == thread->nevents) {
+ thread_log(thread, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_INFO,
+ "maximum number of FD events (%d) received",
+ nevents);
+ }
+
+ for (i = 0; i < nevents; i++) {
+ REQUIRE(events[i].data.fd < (int)thread->manager->maxsocks);
+ if (events[i].data.fd == thread->pipe_fds[0]) {
+ have_ctlevent = true;
+ continue;
+ }
+ if ((events[i].events & EPOLLERR) != 0 ||
+ (events[i].events & EPOLLHUP) != 0)
+ {
+ /*
+ * epoll does not set IN/OUT bits on an erroneous
+ * condition, so we need to try both anyway. This is a
+ * bit inefficient, but should be okay for such rare
+ * events. Note also that the read or write attempt
+ * won't block because we use non-blocking sockets.
+ */
+ int fd = events[i].data.fd;
+ events[i].events |= thread->epoll_events[fd];
+ }
+ process_fd(thread, events[i].data.fd,
+ (events[i].events & EPOLLIN) != 0,
+ (events[i].events & EPOLLOUT) != 0);
+ }
+
+ if (have_ctlevent) {
+ done = process_ctlfd(thread);
+ }
+
+ return (done);
+}
+#elif defined(USE_DEVPOLL)
+static bool
+process_fds(isc__socketthread_t *thread, struct pollfd *events, int nevents) {
+ int i;
+ bool done = false;
+ bool have_ctlevent = false;
+
+ if (nevents == thread->nevents) {
+ thread_log(thread, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_INFO,
+ "maximum number of FD events (%d) received",
+ nevents);
+ }
+
+ for (i = 0; i < nevents; i++) {
+ REQUIRE(events[i].fd < (int)thread->manager->maxsocks);
+ if (events[i].fd == thread->pipe_fds[0]) {
+ have_ctlevent = true;
+ continue;
+ }
+ process_fd(thread, events[i].fd,
+ (events[i].events & POLLIN) != 0,
+ (events[i].events & POLLOUT) != 0);
+ }
+
+ if (have_ctlevent) {
+ done = process_ctlfd(thread);
+ }
+
+ return (done);
+}
+#elif defined(USE_SELECT)
+static void
+process_fds(isc__socketthread_t *thread, int maxfd, fd_set *readfds,
+ fd_set *writefds) {
+ int i;
+
+ REQUIRE(maxfd <= (int)thread->manager->maxsocks);
+
+ for (i = 0; i < maxfd; i++) {
+ if (i == thread->pipe_fds[0] || i == thread->pipe_fds[1]) {
+ continue;
+ }
+ process_fd(thread, i, FD_ISSET(i, readfds),
+ FD_ISSET(i, writefds));
+ }
+}
+#endif /* ifdef USE_KQUEUE */
+
+static bool
+process_ctlfd(isc__socketthread_t *thread) {
+ int msg, fd;
+
+ for (;;) {
+ select_readmsg(thread, &fd, &msg);
+
+ thread_log(thread, IOEVENT,
+ "watcher got message %d for socket %d", msg, fd);
+
+ /*
+ * Nothing to read?
+ */
+ if (msg == SELECT_POKE_NOTHING) {
+ break;
+ }
+
+ /*
+ * Handle shutdown message. We really should
+ * jump out of this loop right away, but
+ * it doesn't matter if we have to do a little
+ * more work first.
+ */
+ if (msg == SELECT_POKE_SHUTDOWN) {
+ return (true);
+ }
+
+ /*
+ * This is a wakeup on a socket. Look
+ * at the event queue for both read and write,
+ * and decide if we need to watch on it now
+ * or not.
+ */
+ wakeup_socket(thread, fd, msg);
+ }
+
+ return (false);
+}
+
+/*
+ * This is the thread that will loop forever, always in a select or poll
+ * call.
+ *
+ * When select returns something to do, do whatever's necessary and post
+ * an event to the task that was requesting the action.
+ */
+static isc_threadresult_t
+netthread(void *uap) {
+ isc__socketthread_t *thread = uap;
+ isc_socketmgr_t *manager = thread->manager;
+ (void)manager;
+ bool done;
+ int cc;
+#ifdef USE_KQUEUE
+ const char *fnname = "kevent()";
+#elif defined(USE_EPOLL)
+ const char *fnname = "epoll_wait()";
+#elif defined(USE_DEVPOLL)
+ isc_result_t result;
+ const char *fnname = "ioctl(DP_POLL)";
+ struct dvpoll dvp;
+ int pass;
+#if defined(ISC_SOCKET_USE_POLLWATCH)
+ pollstate_t pollstate = poll_idle;
+#endif /* if defined(ISC_SOCKET_USE_POLLWATCH) */
+#elif defined(USE_SELECT)
+ const char *fnname = "select()";
+ int maxfd;
+ int ctlfd;
+#endif /* ifdef USE_KQUEUE */
+ char strbuf[ISC_STRERRORSIZE];
+
+#if defined(USE_SELECT)
+ /*
+ * Get the control fd here. This will never change.
+ */
+ ctlfd = thread->pipe_fds[0];
+#endif /* if defined(USE_SELECT) */
+ done = false;
+ while (!done) {
+ do {
+#ifdef USE_KQUEUE
+ cc = kevent(thread->kqueue_fd, NULL, 0, thread->events,
+ thread->nevents, NULL);
+#elif defined(USE_EPOLL)
+ cc = epoll_wait(thread->epoll_fd, thread->events,
+ thread->nevents, -1);
+#elif defined(USE_DEVPOLL)
+ /*
+ * Re-probe every thousand calls.
+ */
+ if (thread->calls++ > 1000U) {
+ result = isc_resource_getcurlimit(
+ isc_resource_openfiles,
+ &thread->open_max);
+ if (result != ISC_R_SUCCESS) {
+ thread->open_max = 64;
+ }
+ thread->calls = 0;
+ }
+ for (pass = 0; pass < 2; pass++) {
+ dvp.dp_fds = thread->events;
+ dvp.dp_nfds = thread->nevents;
+ if (dvp.dp_nfds >= thread->open_max) {
+ dvp.dp_nfds = thread->open_max - 1;
+ }
+#ifndef ISC_SOCKET_USE_POLLWATCH
+ dvp.dp_timeout = -1;
+#else /* ifndef ISC_SOCKET_USE_POLLWATCH */
+ if (pollstate == poll_idle) {
+ dvp.dp_timeout = -1;
+ } else {
+ dvp.dp_timeout =
+ ISC_SOCKET_POLLWATCH_TIMEOUT;
+ }
+#endif /* ISC_SOCKET_USE_POLLWATCH */
+ cc = ioctl(thread->devpoll_fd, DP_POLL, &dvp);
+ if (cc == -1 && errno == EINVAL) {
+ /*
+ * {OPEN_MAX} may have dropped. Look
+ * up the current value and try again.
+ */
+ result = isc_resource_getcurlimit(
+ isc_resource_openfiles,
+ &thread->open_max);
+ if (result != ISC_R_SUCCESS) {
+ thread->open_max = 64;
+ }
+ } else {
+ break;
+ }
+ }
+#elif defined(USE_SELECT)
+ /*
+ * We will have only one thread anyway, we can lock
+ * manager lock and don't care
+ */
+ LOCK(&manager->lock);
+ memmove(thread->read_fds_copy, thread->read_fds,
+ thread->fd_bufsize);
+ memmove(thread->write_fds_copy, thread->write_fds,
+ thread->fd_bufsize);
+ maxfd = thread->maxfd + 1;
+ UNLOCK(&manager->lock);
+
+ cc = select(maxfd, thread->read_fds_copy,
+ thread->write_fds_copy, NULL, NULL);
+#endif /* USE_KQUEUE */
+
+ if (cc < 0 && !SOFT_ERROR(errno)) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ FATAL_ERROR(__FILE__, __LINE__, "%s failed: %s",
+ fnname, strbuf);
+ }
+
+#if defined(USE_DEVPOLL) && defined(ISC_SOCKET_USE_POLLWATCH)
+ if (cc == 0) {
+ if (pollstate == poll_active) {
+ pollstate = poll_checking;
+ } else if (pollstate == poll_checking) {
+ pollstate = poll_idle;
+ }
+ } else if (cc > 0) {
+ if (pollstate == poll_checking) {
+ /*
+ * XXX: We'd like to use a more
+ * verbose log level as it's actually an
+ * unexpected event, but the kernel bug
+ * reportedly happens pretty frequently
+ * (and it can also be a false positive)
+ * so it would be just too noisy.
+ */
+ thread_log(thread,
+ ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET,
+ ISC_LOG_DEBUG(1),
+ "unexpected POLL timeout");
+ }
+ pollstate = poll_active;
+ }
+#endif /* if defined(USE_DEVPOLL) && defined(ISC_SOCKET_USE_POLLWATCH) */
+ } while (cc < 0);
+
+#if defined(USE_KQUEUE) || defined(USE_EPOLL) || defined(USE_DEVPOLL)
+ done = process_fds(thread, thread->events, cc);
+#elif defined(USE_SELECT)
+ process_fds(thread, maxfd, thread->read_fds_copy,
+ thread->write_fds_copy);
+
+ /*
+ * Process reads on internal, control fd.
+ */
+ if (FD_ISSET(ctlfd, thread->read_fds_copy)) {
+ done = process_ctlfd(thread);
+ }
+#endif /* if defined(USE_KQUEUE) || defined(USE_EPOLL) || defined(USE_DEVPOLL) \
+ * */
+ }
+
+ thread_log(thread, TRACE, "watcher exiting");
+ return ((isc_threadresult_t)0);
+}
+
+void
+isc_socketmgr_setreserved(isc_socketmgr_t *manager, uint32_t reserved) {
+ REQUIRE(VALID_MANAGER(manager));
+
+ manager->reserved = reserved;
+}
+
+void
+isc_socketmgr_maxudp(isc_socketmgr_t *manager, unsigned int maxudp) {
+ REQUIRE(VALID_MANAGER(manager));
+
+ manager->maxudp = maxudp;
+}
+
+/*
+ * Setup socket thread, thread->manager and thread->threadid must be filled.
+ */
+
+static isc_result_t
+setup_thread(isc__socketthread_t *thread) {
+ isc_result_t result = ISC_R_SUCCESS;
+ int i;
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(thread != NULL);
+ REQUIRE(VALID_MANAGER(thread->manager));
+ REQUIRE(thread->threadid >= 0 &&
+ thread->threadid < thread->manager->nthreads);
+
+ thread->fds =
+ isc_mem_get(thread->manager->mctx,
+ thread->manager->maxsocks * sizeof(isc_socket_t *));
+
+ memset(thread->fds, 0,
+ thread->manager->maxsocks * sizeof(isc_socket_t *));
+
+ thread->fdstate = isc_mem_get(thread->manager->mctx,
+ thread->manager->maxsocks * sizeof(int));
+
+ memset(thread->fdstate, 0, thread->manager->maxsocks * sizeof(int));
+
+ thread->fdlock = isc_mem_get(thread->manager->mctx,
+ FDLOCK_COUNT * sizeof(isc_mutex_t));
+
+ for (i = 0; i < FDLOCK_COUNT; i++) {
+ isc_mutex_init(&thread->fdlock[i]);
+ }
+
+ if (pipe(thread->pipe_fds) != 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "pipe() failed: %s",
+ strbuf);
+ return (ISC_R_UNEXPECTED);
+ }
+ RUNTIME_CHECK(make_nonblock(thread->pipe_fds[0]) == ISC_R_SUCCESS);
+
+#ifdef USE_KQUEUE
+ thread->nevents = ISC_SOCKET_MAXEVENTS;
+ thread->events = isc_mem_get(thread->manager->mctx,
+ sizeof(struct kevent) * thread->nevents);
+
+ thread->kqueue_fd = kqueue();
+ if (thread->kqueue_fd == -1) {
+ result = isc__errno2result(errno);
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "kqueue failed: %s",
+ strbuf);
+ isc_mem_put(thread->manager->mctx, thread->events,
+ sizeof(struct kevent) * thread->nevents);
+ return (result);
+ }
+
+ result = watch_fd(thread, thread->pipe_fds[0], SELECT_POKE_READ);
+ if (result != ISC_R_SUCCESS) {
+ close(thread->kqueue_fd);
+ isc_mem_put(thread->manager->mctx, thread->events,
+ sizeof(struct kevent) * thread->nevents);
+ }
+ return (result);
+
+#elif defined(USE_EPOLL)
+ thread->nevents = ISC_SOCKET_MAXEVENTS;
+ thread->epoll_events =
+ isc_mem_get(thread->manager->mctx,
+ (thread->manager->maxsocks * sizeof(uint32_t)));
+
+ memset(thread->epoll_events, 0,
+ thread->manager->maxsocks * sizeof(uint32_t));
+
+ thread->events =
+ isc_mem_get(thread->manager->mctx,
+ sizeof(struct epoll_event) * thread->nevents);
+
+ thread->epoll_fd = epoll_create(thread->nevents);
+ if (thread->epoll_fd == -1) {
+ result = isc__errno2result(errno);
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "epoll_create failed: %s",
+ strbuf);
+ return (result);
+ }
+
+ result = watch_fd(thread, thread->pipe_fds[0], SELECT_POKE_READ);
+ return (result);
+
+#elif defined(USE_DEVPOLL)
+ thread->nevents = ISC_SOCKET_MAXEVENTS;
+ result = isc_resource_getcurlimit(isc_resource_openfiles,
+ &thread->open_max);
+ if (result != ISC_R_SUCCESS) {
+ thread->open_max = 64;
+ }
+ thread->calls = 0;
+ thread->events = isc_mem_get(thread->manager->mctx,
+ sizeof(struct pollfd) * thread->nevents);
+
+ /*
+ * Note: fdpollinfo should be able to support all possible FDs, so
+ * it must have maxsocks entries (not nevents).
+ */
+ thread->fdpollinfo =
+ isc_mem_get(thread->manager->mctx,
+ sizeof(pollinfo_t) * thread->manager->maxsocks);
+ memset(thread->fdpollinfo, 0,
+ sizeof(pollinfo_t) * thread->manager->maxsocks);
+ thread->devpoll_fd = open("/dev/poll", O_RDWR);
+ if (thread->devpoll_fd == -1) {
+ result = isc__errno2result(errno);
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "open(/dev/poll) failed: %s", strbuf);
+ isc_mem_put(thread->manager->mctx, thread->events,
+ sizeof(struct pollfd) * thread->nevents);
+ isc_mem_put(thread->manager->mctx, thread->fdpollinfo,
+ sizeof(pollinfo_t) * thread->manager->maxsocks);
+ return (result);
+ }
+ result = watch_fd(thread, thread->pipe_fds[0], SELECT_POKE_READ);
+ if (result != ISC_R_SUCCESS) {
+ close(thread->devpoll_fd);
+ isc_mem_put(thread->manager->mctx, thread->events,
+ sizeof(struct pollfd) * thread->nevents);
+ isc_mem_put(thread->manager->mctx, thread->fdpollinfo,
+ sizeof(pollinfo_t) * thread->manager->maxsocks);
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+#elif defined(USE_SELECT)
+ UNUSED(result);
+
+#if ISC_SOCKET_MAXSOCKETS > FD_SETSIZE
+ /*
+ * Note: this code should also cover the case of MAXSOCKETS <=
+ * FD_SETSIZE, but we separate the cases to avoid possible portability
+ * issues regarding howmany() and the actual representation of fd_set.
+ */
+ thread->fd_bufsize = howmany(manager->maxsocks, NFDBITS) *
+ sizeof(fd_mask);
+#else /* if ISC_SOCKET_MAXSOCKETS > FD_SETSIZE */
+ thread->fd_bufsize = sizeof(fd_set);
+#endif /* if ISC_SOCKET_MAXSOCKETS > FD_SETSIZE */
+
+ thread->read_fds = isc_mem_get(thread->manager->mctx,
+ thread->fd_bufsize);
+ thread->read_fds_copy = isc_mem_get(thread->manager->mctx,
+ thread->fd_bufsize);
+ thread->write_fds = isc_mem_get(thread->manager->mctx,
+ thread->fd_bufsize);
+ thread->write_fds_copy = isc_mem_get(thread->manager->mctx,
+ thread->fd_bufsize);
+ memset(thread->read_fds, 0, thread->fd_bufsize);
+ memset(thread->write_fds, 0, thread->fd_bufsize);
+
+ (void)watch_fd(thread, thread->pipe_fds[0], SELECT_POKE_READ);
+ thread->maxfd = thread->pipe_fds[0];
+
+ return (ISC_R_SUCCESS);
+#endif /* USE_KQUEUE */
+}
+
+static void
+cleanup_thread(isc_mem_t *mctx, isc__socketthread_t *thread) {
+ isc_result_t result;
+ int i;
+
+ result = unwatch_fd(thread, thread->pipe_fds[0], SELECT_POKE_READ);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "epoll_ctl(DEL) failed");
+ }
+#ifdef USE_KQUEUE
+ close(thread->kqueue_fd);
+ isc_mem_put(mctx, thread->events,
+ sizeof(struct kevent) * thread->nevents);
+#elif defined(USE_EPOLL)
+ close(thread->epoll_fd);
+
+ isc_mem_put(mctx, thread->events,
+ sizeof(struct epoll_event) * thread->nevents);
+#elif defined(USE_DEVPOLL)
+ close(thread->devpoll_fd);
+ isc_mem_put(mctx, thread->events,
+ sizeof(struct pollfd) * thread->nevents);
+ isc_mem_put(mctx, thread->fdpollinfo,
+ sizeof(pollinfo_t) * thread->manager->maxsocks);
+#elif defined(USE_SELECT)
+ if (thread->read_fds != NULL) {
+ isc_mem_put(mctx, thread->read_fds, thread->fd_bufsize);
+ }
+ if (thread->read_fds_copy != NULL) {
+ isc_mem_put(mctx, thread->read_fds_copy, thread->fd_bufsize);
+ }
+ if (thread->write_fds != NULL) {
+ isc_mem_put(mctx, thread->write_fds, thread->fd_bufsize);
+ }
+ if (thread->write_fds_copy != NULL) {
+ isc_mem_put(mctx, thread->write_fds_copy, thread->fd_bufsize);
+ }
+#endif /* USE_KQUEUE */
+ for (i = 0; i < (int)thread->manager->maxsocks; i++) {
+ if (thread->fdstate[i] == CLOSE_PENDING) {
+ /* no need to lock */
+ (void)close(i);
+ }
+ }
+
+#if defined(USE_EPOLL)
+ isc_mem_put(thread->manager->mctx, thread->epoll_events,
+ thread->manager->maxsocks * sizeof(uint32_t));
+#endif /* if defined(USE_EPOLL) */
+ isc_mem_put(thread->manager->mctx, thread->fds,
+ thread->manager->maxsocks * sizeof(isc_socket_t *));
+ isc_mem_put(thread->manager->mctx, thread->fdstate,
+ thread->manager->maxsocks * sizeof(int));
+
+ for (i = 0; i < FDLOCK_COUNT; i++) {
+ isc_mutex_destroy(&thread->fdlock[i]);
+ }
+ isc_mem_put(thread->manager->mctx, thread->fdlock,
+ FDLOCK_COUNT * sizeof(isc_mutex_t));
+}
+
+isc_result_t
+isc_socketmgr_create(isc_mem_t *mctx, isc_socketmgr_t **managerp) {
+ return (isc_socketmgr_create2(mctx, managerp, 0, 1));
+}
+
+isc_result_t
+isc_socketmgr_create2(isc_mem_t *mctx, isc_socketmgr_t **managerp,
+ unsigned int maxsocks, int nthreads) {
+ int i;
+ isc_socketmgr_t *manager;
+
+ REQUIRE(managerp != NULL && *managerp == NULL);
+
+ if (maxsocks == 0) {
+ maxsocks = ISC_SOCKET_MAXSOCKETS;
+ }
+
+ manager = isc_mem_get(mctx, sizeof(*manager));
+
+ /* zero-clear so that necessary cleanup on failure will be easy */
+ memset(manager, 0, sizeof(*manager));
+ manager->maxsocks = maxsocks;
+ manager->reserved = 0;
+ manager->maxudp = 0;
+ manager->nthreads = nthreads;
+ manager->stats = NULL;
+
+ manager->magic = SOCKET_MANAGER_MAGIC;
+ manager->mctx = NULL;
+ ISC_LIST_INIT(manager->socklist);
+ isc_mutex_init(&manager->lock);
+ isc_condition_init(&manager->shutdown_ok);
+
+ /*
+ * Start up the select/poll thread.
+ */
+ manager->threads = isc_mem_get(mctx, sizeof(isc__socketthread_t) *
+ manager->nthreads);
+ isc_mem_attach(mctx, &manager->mctx);
+
+ for (i = 0; i < manager->nthreads; i++) {
+ manager->threads[i].manager = manager;
+ manager->threads[i].threadid = i;
+ setup_thread(&manager->threads[i]);
+ isc_thread_create(netthread, &manager->threads[i],
+ &manager->threads[i].thread);
+ char tname[1024];
+ sprintf(tname, "isc-socket-%d", i);
+ isc_thread_setname(manager->threads[i].thread, tname);
+ }
+
+ *managerp = manager;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_socketmgr_getmaxsockets(isc_socketmgr_t *manager, unsigned int *nsockp) {
+ REQUIRE(VALID_MANAGER(manager));
+ REQUIRE(nsockp != NULL);
+
+ *nsockp = manager->maxsocks;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_socketmgr_setstats(isc_socketmgr_t *manager, isc_stats_t *stats) {
+ REQUIRE(VALID_MANAGER(manager));
+ REQUIRE(ISC_LIST_EMPTY(manager->socklist));
+ REQUIRE(manager->stats == NULL);
+ REQUIRE(isc_stats_ncounters(stats) == isc_sockstatscounter_max);
+
+ isc_stats_attach(stats, &manager->stats);
+}
+
+void
+isc_socketmgr_destroy(isc_socketmgr_t **managerp) {
+ isc_socketmgr_t *manager;
+
+ /*
+ * Destroy a socket manager.
+ */
+
+ REQUIRE(managerp != NULL);
+ manager = *managerp;
+ REQUIRE(VALID_MANAGER(manager));
+
+ LOCK(&manager->lock);
+
+ /*
+ * Wait for all sockets to be destroyed.
+ */
+ while (!ISC_LIST_EMPTY(manager->socklist)) {
+ manager_log(manager, CREATION, "sockets exist");
+ WAIT(&manager->shutdown_ok, &manager->lock);
+ }
+
+ UNLOCK(&manager->lock);
+
+ /*
+ * Here, poke our select/poll thread. Do this by closing the write
+ * half of the pipe, which will send EOF to the read half.
+ * This is currently a no-op in the non-threaded case.
+ */
+ for (int i = 0; i < manager->nthreads; i++) {
+ select_poke(manager, i, 0, SELECT_POKE_SHUTDOWN);
+ }
+
+ /*
+ * Wait for thread to exit.
+ */
+ for (int i = 0; i < manager->nthreads; i++) {
+ isc_thread_join(manager->threads[i].thread, NULL);
+ cleanup_thread(manager->mctx, &manager->threads[i]);
+ }
+ /*
+ * Clean up.
+ */
+ isc_mem_put(manager->mctx, manager->threads,
+ sizeof(isc__socketthread_t) * manager->nthreads);
+ (void)isc_condition_destroy(&manager->shutdown_ok);
+
+ if (manager->stats != NULL) {
+ isc_stats_detach(&manager->stats);
+ }
+ isc_mutex_destroy(&manager->lock);
+ manager->magic = 0;
+ isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager));
+
+ *managerp = NULL;
+}
+
+static isc_result_t
+socket_recv(isc_socket_t *sock, isc_socketevent_t *dev, isc_task_t *task,
+ unsigned int flags) {
+ int io_state;
+ bool have_lock = false;
+ isc_task_t *ntask = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ dev->ev_sender = task;
+
+ if (sock->type == isc_sockettype_udp) {
+ io_state = doio_recv(sock, dev);
+ } else {
+ LOCK(&sock->lock);
+ have_lock = true;
+
+ if (ISC_LIST_EMPTY(sock->recv_list)) {
+ io_state = doio_recv(sock, dev);
+ } else {
+ io_state = DOIO_SOFT;
+ }
+ }
+
+ switch (io_state) {
+ case DOIO_SOFT:
+ /*
+ * We couldn't read all or part of the request right now, so
+ * queue it.
+ *
+ * Attach to socket and to task
+ */
+ isc_task_attach(task, &ntask);
+ dev->attributes |= ISC_SOCKEVENTATTR_ATTACHED;
+
+ if (!have_lock) {
+ LOCK(&sock->lock);
+ have_lock = true;
+ }
+
+ /*
+ * Enqueue the request. If the socket was previously not being
+ * watched, poke the watcher to start paying attention to it.
+ */
+ bool do_poke = ISC_LIST_EMPTY(sock->recv_list);
+ ISC_LIST_ENQUEUE(sock->recv_list, dev, ev_link);
+ if (do_poke) {
+ select_poke(sock->manager, sock->threadid, sock->fd,
+ SELECT_POKE_READ);
+ }
+
+ socket_log(sock, NULL, EVENT,
+ "socket_recv: event %p -> task %p", dev, ntask);
+
+ if ((flags & ISC_SOCKFLAG_IMMEDIATE) != 0) {
+ result = ISC_R_INPROGRESS;
+ }
+ break;
+
+ case DOIO_EOF:
+ dev->result = ISC_R_EOF;
+ FALLTHROUGH;
+
+ case DOIO_HARD:
+ case DOIO_SUCCESS:
+ if ((flags & ISC_SOCKFLAG_IMMEDIATE) == 0) {
+ send_recvdone_event(sock, &dev);
+ }
+ break;
+ }
+
+ if (have_lock) {
+ UNLOCK(&sock->lock);
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_socket_recv(isc_socket_t *sock, isc_region_t *region, unsigned int minimum,
+ isc_task_t *task, isc_taskaction_t action, void *arg) {
+ isc_socketevent_t *dev;
+ isc_socketmgr_t *manager;
+
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(action != NULL);
+
+ manager = sock->manager;
+ REQUIRE(VALID_MANAGER(manager));
+
+ INSIST(sock->bound);
+
+ dev = allocate_socketevent(manager->mctx, sock, ISC_SOCKEVENT_RECVDONE,
+ action, arg);
+ if (dev == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ return (isc_socket_recv2(sock, region, minimum, task, dev, 0));
+}
+
+isc_result_t
+isc_socket_recv2(isc_socket_t *sock, isc_region_t *region, unsigned int minimum,
+ isc_task_t *task, isc_socketevent_t *event,
+ unsigned int flags) {
+ event->ev_sender = sock;
+ event->result = ISC_R_UNSET;
+ event->region = *region;
+ event->n = 0;
+ event->offset = 0;
+ event->attributes = 0;
+
+ /*
+ * UDP sockets are always partial read.
+ */
+ if (sock->type == isc_sockettype_udp) {
+ event->minimum = 1;
+ } else {
+ if (minimum == 0) {
+ event->minimum = region->length;
+ } else {
+ event->minimum = minimum;
+ }
+ }
+
+ return (socket_recv(sock, event, task, flags));
+}
+
+static isc_result_t
+socket_send(isc_socket_t *sock, isc_socketevent_t *dev, isc_task_t *task,
+ const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo,
+ unsigned int flags) {
+ int io_state;
+ bool have_lock = false;
+ isc_task_t *ntask = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ dev->ev_sender = task;
+
+ set_dev_address(address, sock, dev);
+ if (pktinfo != NULL) {
+ dev->attributes |= ISC_SOCKEVENTATTR_PKTINFO;
+ dev->pktinfo = *pktinfo;
+
+ if (!isc_sockaddr_issitelocal(&dev->address) &&
+ !isc_sockaddr_islinklocal(&dev->address))
+ {
+ socket_log(sock, NULL, TRACE,
+ "pktinfo structure provided, ifindex %u "
+ "(set to 0)",
+ pktinfo->ipi6_ifindex);
+
+ /*
+ * Set the pktinfo index to 0 here, to let the
+ * kernel decide what interface it should send on.
+ */
+ dev->pktinfo.ipi6_ifindex = 0;
+ }
+ }
+
+ if (sock->type == isc_sockettype_udp) {
+ io_state = doio_send(sock, dev);
+ } else {
+ LOCK(&sock->lock);
+ have_lock = true;
+
+ if (ISC_LIST_EMPTY(sock->send_list)) {
+ io_state = doio_send(sock, dev);
+ } else {
+ io_state = DOIO_SOFT;
+ }
+ }
+
+ switch (io_state) {
+ case DOIO_SOFT:
+ /*
+ * We couldn't send all or part of the request right now, so
+ * queue it unless ISC_SOCKFLAG_NORETRY is set.
+ */
+ if ((flags & ISC_SOCKFLAG_NORETRY) == 0) {
+ isc_task_attach(task, &ntask);
+ dev->attributes |= ISC_SOCKEVENTATTR_ATTACHED;
+
+ if (!have_lock) {
+ LOCK(&sock->lock);
+ have_lock = true;
+ }
+
+ /*
+ * Enqueue the request. If the socket was previously
+ * not being watched, poke the watcher to start
+ * paying attention to it.
+ */
+ bool do_poke = ISC_LIST_EMPTY(sock->send_list);
+ ISC_LIST_ENQUEUE(sock->send_list, dev, ev_link);
+ if (do_poke) {
+ select_poke(sock->manager, sock->threadid,
+ sock->fd, SELECT_POKE_WRITE);
+ }
+ socket_log(sock, NULL, EVENT,
+ "socket_send: event %p -> task %p", dev,
+ ntask);
+
+ if ((flags & ISC_SOCKFLAG_IMMEDIATE) != 0) {
+ result = ISC_R_INPROGRESS;
+ }
+ break;
+ }
+
+ FALLTHROUGH;
+
+ case DOIO_HARD:
+ case DOIO_SUCCESS:
+ if (!have_lock) {
+ LOCK(&sock->lock);
+ have_lock = true;
+ }
+ if ((flags & ISC_SOCKFLAG_IMMEDIATE) == 0) {
+ send_senddone_event(sock, &dev);
+ }
+ break;
+ }
+
+ if (have_lock) {
+ UNLOCK(&sock->lock);
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_socket_send(isc_socket_t *sock, isc_region_t *region, isc_task_t *task,
+ isc_taskaction_t action, void *arg) {
+ /*
+ * REQUIRE() checking is performed in isc_socket_sendto().
+ */
+ return (isc_socket_sendto(sock, region, task, action, arg, NULL, NULL));
+}
+
+isc_result_t
+isc_socket_sendto(isc_socket_t *sock, isc_region_t *region, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo) {
+ isc_socketevent_t *dev;
+ isc_socketmgr_t *manager;
+
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(region != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(action != NULL);
+
+ manager = sock->manager;
+ REQUIRE(VALID_MANAGER(manager));
+
+ INSIST(sock->bound);
+
+ dev = allocate_socketevent(manager->mctx, sock, ISC_SOCKEVENT_SENDDONE,
+ action, arg);
+ if (dev == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ dev->region = *region;
+
+ return (socket_send(sock, dev, task, address, pktinfo, 0));
+}
+
+isc_result_t
+isc_socket_sendto2(isc_socket_t *sock, isc_region_t *region, isc_task_t *task,
+ const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo,
+ isc_socketevent_t *event, unsigned int flags) {
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE((flags & ~(ISC_SOCKFLAG_IMMEDIATE | ISC_SOCKFLAG_NORETRY)) ==
+ 0);
+ if ((flags & ISC_SOCKFLAG_NORETRY) != 0) {
+ REQUIRE(sock->type == isc_sockettype_udp);
+ }
+ event->ev_sender = sock;
+ event->result = ISC_R_UNSET;
+ event->region = *region;
+ event->n = 0;
+ event->offset = 0;
+ event->attributes &= ~ISC_SOCKEVENTATTR_ATTACHED;
+
+ return (socket_send(sock, event, task, address, pktinfo, flags));
+}
+
+void
+isc_socket_cleanunix(const isc_sockaddr_t *sockaddr, bool active) {
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ int s;
+ struct stat sb;
+ char strbuf[ISC_STRERRORSIZE];
+
+ if (sockaddr->type.sa.sa_family != AF_UNIX) {
+ return;
+ }
+
+#ifndef S_ISSOCK
+#if defined(S_IFMT) && defined(S_IFSOCK)
+#define S_ISSOCK(mode) ((mode & S_IFMT) == S_IFSOCK)
+#elif defined(_S_IFMT) && defined(S_IFSOCK)
+#define S_ISSOCK(mode) ((mode & _S_IFMT) == S_IFSOCK)
+#endif /* if defined(S_IFMT) && defined(S_IFSOCK) */
+#endif /* ifndef S_ISSOCK */
+
+#ifndef S_ISFIFO
+#if defined(S_IFMT) && defined(S_IFIFO)
+#define S_ISFIFO(mode) ((mode & S_IFMT) == S_IFIFO)
+#elif defined(_S_IFMT) && defined(S_IFIFO)
+#define S_ISFIFO(mode) ((mode & _S_IFMT) == S_IFIFO)
+#endif /* if defined(S_IFMT) && defined(S_IFIFO) */
+#endif /* ifndef S_ISFIFO */
+
+#if !defined(S_ISFIFO) && !defined(S_ISSOCK)
+/* cppcheck-suppress preprocessorErrorDirective */
+#error \
+ You need to define S_ISFIFO and S_ISSOCK as appropriate for your platform. See <sys/stat.h>.
+#endif /* if !defined(S_ISFIFO) && !defined(S_ISSOCK) */
+
+#ifndef S_ISFIFO
+#define S_ISFIFO(mode) 0
+#endif /* ifndef S_ISFIFO */
+
+#ifndef S_ISSOCK
+#define S_ISSOCK(mode) 0
+#endif /* ifndef S_ISSOCK */
+
+ if (stat(sockaddr->type.sunix.sun_path, &sb) < 0) {
+ switch (errno) {
+ case ENOENT:
+ if (active) { /* We exited cleanly last time */
+ break;
+ }
+ FALLTHROUGH;
+ default:
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET,
+ active ? ISC_LOG_ERROR : ISC_LOG_WARNING,
+ "isc_socket_cleanunix: stat(%s): %s",
+ sockaddr->type.sunix.sun_path, strbuf);
+ return;
+ }
+ } else {
+ if (!(S_ISSOCK(sb.st_mode) || S_ISFIFO(sb.st_mode))) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET,
+ active ? ISC_LOG_ERROR : ISC_LOG_WARNING,
+ "isc_socket_cleanunix: %s: not a socket",
+ sockaddr->type.sunix.sun_path);
+ return;
+ }
+ }
+
+ if (active) {
+ if (unlink(sockaddr->type.sunix.sun_path) < 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "isc_socket_cleanunix: unlink(%s): %s",
+ sockaddr->type.sunix.sun_path, strbuf);
+ }
+ return;
+ }
+
+ s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s < 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_WARNING,
+ "isc_socket_cleanunix: socket(%s): %s",
+ sockaddr->type.sunix.sun_path, strbuf);
+ return;
+ }
+
+ if (connect(s, (const struct sockaddr *)&sockaddr->type.sunix,
+ sizeof(sockaddr->type.sunix)) < 0)
+ {
+ switch (errno) {
+ case ECONNREFUSED:
+ case ECONNRESET:
+ if (unlink(sockaddr->type.sunix.sun_path) < 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(
+ isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_WARNING,
+ "isc_socket_cleanunix: "
+ "unlink(%s): %s",
+ sockaddr->type.sunix.sun_path, strbuf);
+ }
+ break;
+ default:
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_WARNING,
+ "isc_socket_cleanunix: connect(%s): %s",
+ sockaddr->type.sunix.sun_path, strbuf);
+ break;
+ }
+ }
+ close(s);
+#else /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ UNUSED(sockaddr);
+ UNUSED(active);
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+}
+
+isc_result_t
+isc_socket_permunix(const isc_sockaddr_t *sockaddr, uint32_t perm,
+ uint32_t owner, uint32_t group) {
+#ifdef ISC_PLATFORM_HAVESYSUNH
+ isc_result_t result = ISC_R_SUCCESS;
+ char strbuf[ISC_STRERRORSIZE];
+ char path[sizeof(sockaddr->type.sunix.sun_path)];
+#ifdef NEED_SECURE_DIRECTORY
+ char *slash;
+#endif /* ifdef NEED_SECURE_DIRECTORY */
+
+ REQUIRE(sockaddr->type.sa.sa_family == AF_UNIX);
+ INSIST(strlen(sockaddr->type.sunix.sun_path) < sizeof(path));
+ strlcpy(path, sockaddr->type.sunix.sun_path, sizeof(path));
+
+#ifdef NEED_SECURE_DIRECTORY
+ slash = strrchr(path, '/');
+ if (slash != NULL) {
+ if (slash != path) {
+ *slash = '\0';
+ } else {
+ strlcpy(path, "/", sizeof(path));
+ }
+ } else {
+ strlcpy(path, ".", sizeof(path));
+ }
+#endif /* ifdef NEED_SECURE_DIRECTORY */
+
+ if (chmod(path, perm) < 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "isc_socket_permunix: chmod(%s, %d): %s", path,
+ perm, strbuf);
+ result = ISC_R_FAILURE;
+ }
+ if (chown(path, owner, group) < 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "isc_socket_permunix: chown(%s, %d, %d): %s",
+ path, owner, group, strbuf);
+ result = ISC_R_FAILURE;
+ }
+ return (result);
+#else /* ifdef ISC_PLATFORM_HAVESYSUNH */
+ UNUSED(sockaddr);
+ UNUSED(perm);
+ UNUSED(owner);
+ UNUSED(group);
+ return (ISC_R_NOTIMPLEMENTED);
+#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */
+}
+
+isc_result_t
+isc_socket_bind(isc_socket_t *sock, const isc_sockaddr_t *sockaddr,
+ isc_socket_options_t options) {
+ char strbuf[ISC_STRERRORSIZE];
+ int on = 1;
+
+ REQUIRE(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+
+ INSIST(!sock->bound);
+ INSIST(!sock->dupped);
+
+ if (sock->pf != sockaddr->type.sa.sa_family) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_FAMILYMISMATCH);
+ }
+
+ /*
+ * Only set SO_REUSEADDR when we want a specific port.
+ */
+#ifdef AF_UNIX
+ if (sock->pf == AF_UNIX) {
+ goto bind_socket;
+ }
+#endif /* ifdef AF_UNIX */
+ if ((options & ISC_SOCKET_REUSEADDRESS) != 0 &&
+ isc_sockaddr_getport(sockaddr) != (in_port_t)0)
+ {
+ if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on,
+ sizeof(on)) < 0)
+ {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d) failed", sock->fd);
+ }
+#if defined(__FreeBSD_kernel__) && defined(SO_REUSEPORT_LB)
+ if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEPORT_LB,
+ (void *)&on, sizeof(on)) < 0)
+ {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d) failed", sock->fd);
+ }
+#elif defined(__linux__) && defined(SO_REUSEPORT)
+ if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEPORT, (void *)&on,
+ sizeof(on)) < 0)
+ {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d) failed", sock->fd);
+ }
+#endif /* if defined(__FreeBSD_kernel__) && defined(SO_REUSEPORT_LB) */
+ /* Press on... */
+ }
+#ifdef AF_UNIX
+bind_socket:
+#endif /* ifdef AF_UNIX */
+ if (bind(sock->fd, &sockaddr->type.sa, sockaddr->length) < 0) {
+ inc_stats(sock->manager->stats,
+ sock->statsindex[STATID_BINDFAIL]);
+
+ UNLOCK(&sock->lock);
+ switch (errno) {
+ case EACCES:
+ return (ISC_R_NOPERM);
+ case EADDRNOTAVAIL:
+ return (ISC_R_ADDRNOTAVAIL);
+ case EADDRINUSE:
+ return (ISC_R_ADDRINUSE);
+ case EINVAL:
+ return (ISC_R_BOUND);
+ default:
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "bind: %s",
+ strbuf);
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+
+ socket_log(sock, sockaddr, TRACE, "bound");
+ sock->bound = 1;
+
+ UNLOCK(&sock->lock);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Enable this only for specific OS versions, and only when they have repaired
+ * their problems with it. Until then, this is is broken and needs to be
+ * disabled by default. See RT22589 for details.
+ */
+#undef ENABLE_ACCEPTFILTER
+
+isc_result_t
+isc_socket_filter(isc_socket_t *sock, const char *filter) {
+#if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER)
+ char strbuf[ISC_STRERRORSIZE];
+ struct accept_filter_arg afa;
+#else /* if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER) */
+ UNUSED(sock);
+ UNUSED(filter);
+#endif /* if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER) */
+
+ REQUIRE(VALID_SOCKET(sock));
+
+#if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER)
+ bzero(&afa, sizeof(afa));
+ strlcpy(afa.af_name, filter, sizeof(afa.af_name));
+ if (setsockopt(sock->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa,
+ sizeof(afa)) == -1)
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ socket_log(sock, NULL, CREATION,
+ "setsockopt(SO_ACCEPTFILTER): %s", strbuf);
+ return (ISC_R_FAILURE);
+ }
+ return (ISC_R_SUCCESS);
+#else /* if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER) */
+ return (ISC_R_NOTIMPLEMENTED);
+#endif /* if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER) */
+}
+
+/*
+ * Try enabling TCP Fast Open for a given socket if the OS supports it.
+ */
+static void
+set_tcp_fastopen(isc_socket_t *sock, unsigned int backlog) {
+#if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN)
+ char strbuf[ISC_STRERRORSIZE];
+
+/*
+ * FreeBSD, as of versions 10.3 and 11.0, defines TCP_FASTOPEN while also
+ * shipping a default kernel without TFO support, so we special-case it by
+ * performing an additional runtime check for TFO support using sysctl to
+ * prevent setsockopt() errors from being logged.
+ */
+#if defined(__FreeBSD__) && defined(HAVE_SYSCTLBYNAME)
+#define SYSCTL_TFO "net.inet.tcp.fastopen.enabled"
+ unsigned int enabled;
+ size_t enabledlen = sizeof(enabled);
+ static bool tfo_notice_logged = false;
+
+ if (sysctlbyname(SYSCTL_TFO, &enabled, &enabledlen, NULL, 0) < 0) {
+ /*
+ * This kernel does not support TCP Fast Open. There is
+ * nothing more we can do.
+ */
+ return;
+ } else if (enabled == 0) {
+ /*
+ * This kernel does support TCP Fast Open, but it is disabled
+ * by sysctl. Notify the user, but do not nag.
+ */
+ if (!tfo_notice_logged) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_NOTICE,
+ "TCP_FASTOPEN support is disabled by "
+ "sysctl (" SYSCTL_TFO " = 0)");
+ tfo_notice_logged = true;
+ }
+ return;
+ }
+#endif /* if defined(__FreeBSD__) && defined(HAVE_SYSCTLBYNAME) */
+
+#ifdef __APPLE__
+ backlog = 1;
+#else /* ifdef __APPLE__ */
+ backlog = backlog / 2;
+ if (backlog == 0) {
+ backlog = 1;
+ }
+#endif /* ifdef __APPLE__ */
+ if (setsockopt(sock->fd, IPPROTO_TCP, TCP_FASTOPEN, (void *)&backlog,
+ sizeof(backlog)) < 0)
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, TCP_FASTOPEN) failed with %s",
+ sock->fd, strbuf);
+ /* TCP_FASTOPEN is experimental so ignore failures */
+ }
+#else /* if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) */
+ UNUSED(sock);
+ UNUSED(backlog);
+#endif /* if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) */
+}
+
+/*
+ * Set up to listen on a given socket. We do this by creating an internal
+ * event that will be dispatched when the socket has read activity. The
+ * watcher will send the internal event to the task when there is a new
+ * connection.
+ *
+ * Unlike in read, we don't preallocate a done event here. Every time there
+ * is a new connection we'll have to allocate a new one anyway, so we might
+ * as well keep things simple rather than having to track them.
+ */
+isc_result_t
+isc_socket_listen(isc_socket_t *sock, unsigned int backlog) {
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+
+ REQUIRE(!sock->listener);
+ REQUIRE(sock->bound);
+ REQUIRE(sock->type == isc_sockettype_tcp ||
+ sock->type == isc_sockettype_unix);
+
+ if (backlog == 0) {
+ backlog = SOMAXCONN;
+ }
+
+ if (listen(sock->fd, (int)backlog) < 0) {
+ UNLOCK(&sock->lock);
+ strerror_r(errno, strbuf, sizeof(strbuf));
+
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "listen: %s", strbuf);
+
+ return (ISC_R_UNEXPECTED);
+ }
+
+ set_tcp_fastopen(sock, backlog);
+
+ sock->listener = 1;
+
+ UNLOCK(&sock->lock);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * This should try to do aggressive accept() XXXMLG
+ */
+isc_result_t
+isc_socket_accept(isc_socket_t *sock, isc_task_t *task, isc_taskaction_t action,
+ void *arg) {
+ isc_socket_newconnev_t *dev;
+ isc_socketmgr_t *manager;
+ isc_task_t *ntask = NULL;
+ isc_socket_t *nsock;
+ isc_result_t result;
+ bool do_poke = false;
+
+ REQUIRE(VALID_SOCKET(sock));
+ manager = sock->manager;
+ REQUIRE(VALID_MANAGER(manager));
+
+ LOCK(&sock->lock);
+
+ REQUIRE(sock->listener);
+
+ /*
+ * Sender field is overloaded here with the task we will be sending
+ * this event to. Just before the actual event is delivered the
+ * actual ev_sender will be touched up to be the socket.
+ */
+ dev = (isc_socket_newconnev_t *)isc_event_allocate(
+ manager->mctx, task, ISC_SOCKEVENT_NEWCONN, action, arg,
+ sizeof(*dev));
+ ISC_LINK_INIT(dev, ev_link);
+
+ result = allocate_socket(manager, sock->type, &nsock);
+ if (result != ISC_R_SUCCESS) {
+ isc_event_free(ISC_EVENT_PTR(&dev));
+ UNLOCK(&sock->lock);
+ return (result);
+ }
+
+ /*
+ * Attach to socket and to task.
+ */
+ isc_task_attach(task, &ntask);
+ if (isc_task_exiting(ntask)) {
+ free_socket(&nsock);
+ isc_task_detach(&ntask);
+ isc_event_free(ISC_EVENT_PTR(&dev));
+ UNLOCK(&sock->lock);
+ return (ISC_R_SHUTTINGDOWN);
+ }
+ isc_refcount_increment0(&nsock->references);
+ nsock->statsindex = sock->statsindex;
+
+ dev->ev_sender = ntask;
+ dev->newsocket = nsock;
+
+ /*
+ * Poke watcher here. We still have the socket locked, so there
+ * is no race condition. We will keep the lock for such a short
+ * bit of time waking it up now or later won't matter all that much.
+ */
+ do_poke = ISC_LIST_EMPTY(sock->accept_list);
+ ISC_LIST_ENQUEUE(sock->accept_list, dev, ev_link);
+ if (do_poke) {
+ select_poke(manager, sock->threadid, sock->fd,
+ SELECT_POKE_ACCEPT);
+ }
+ UNLOCK(&sock->lock);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_socket_connect(isc_socket_t *sock, const isc_sockaddr_t *addr,
+ isc_task_t *task, isc_taskaction_t action, void *arg) {
+ isc_socket_connev_t *dev;
+ isc_task_t *ntask = NULL;
+ isc_socketmgr_t *manager;
+ int cc;
+ char strbuf[ISC_STRERRORSIZE];
+ char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(addr != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(action != NULL);
+
+ manager = sock->manager;
+ REQUIRE(VALID_MANAGER(manager));
+ REQUIRE(addr != NULL);
+
+ if (isc_sockaddr_ismulticast(addr)) {
+ return (ISC_R_MULTICAST);
+ }
+
+ LOCK(&sock->lock);
+
+ dev = (isc_socket_connev_t *)isc_event_allocate(
+ manager->mctx, sock, ISC_SOCKEVENT_CONNECT, action, arg,
+ sizeof(*dev));
+ ISC_LINK_INIT(dev, ev_link);
+
+ if (sock->connecting) {
+ INSIST(isc_sockaddr_equal(&sock->peer_address, addr));
+ goto queue;
+ }
+
+ if (sock->connected) {
+ INSIST(isc_sockaddr_equal(&sock->peer_address, addr));
+ dev->result = ISC_R_SUCCESS;
+ isc_task_sendto(task, ISC_EVENT_PTR(&dev), sock->threadid);
+
+ UNLOCK(&sock->lock);
+
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Try to do the connect right away, as there can be only one
+ * outstanding, and it might happen to complete.
+ */
+ sock->peer_address = *addr;
+ cc = connect(sock->fd, &addr->type.sa, addr->length);
+ if (cc < 0) {
+ /*
+ * The socket is nonblocking and the connection cannot be
+ * completed immediately. It is possible to select(2) or
+ * poll(2) for completion by selecting the socket for writing.
+ * After select(2) indicates writability, use getsockopt(2) to
+ * read the SO_ERROR option at level SOL_SOCKET to determine
+ * whether connect() completed successfully (SO_ERROR is zero)
+ * or unsuccessfully (SO_ERROR is one of the usual error codes
+ * listed here, explaining the reason for the failure).
+ */
+ if (sock->type == isc_sockettype_udp && errno == EINPROGRESS) {
+ cc = 0;
+ goto success;
+ }
+ if (SOFT_ERROR(errno) || errno == EINPROGRESS) {
+ goto queue;
+ }
+
+ switch (errno) {
+#define ERROR_MATCH(a, b) \
+ case a: \
+ dev->result = b; \
+ goto err_exit;
+ ERROR_MATCH(EACCES, ISC_R_NOPERM);
+ ERROR_MATCH(EADDRNOTAVAIL, ISC_R_ADDRNOTAVAIL);
+ ERROR_MATCH(EAFNOSUPPORT, ISC_R_ADDRNOTAVAIL);
+ ERROR_MATCH(ECONNREFUSED, ISC_R_CONNREFUSED);
+ ERROR_MATCH(EHOSTUNREACH, ISC_R_HOSTUNREACH);
+#ifdef EHOSTDOWN
+ ERROR_MATCH(EHOSTDOWN, ISC_R_HOSTUNREACH);
+#endif /* ifdef EHOSTDOWN */
+ ERROR_MATCH(ENETUNREACH, ISC_R_NETUNREACH);
+ ERROR_MATCH(ENOBUFS, ISC_R_NORESOURCES);
+ ERROR_MATCH(EPERM, ISC_R_HOSTUNREACH);
+ ERROR_MATCH(EPIPE, ISC_R_NOTCONNECTED);
+ ERROR_MATCH(ETIMEDOUT, ISC_R_TIMEDOUT);
+ ERROR_MATCH(ECONNRESET, ISC_R_CONNECTIONRESET);
+#undef ERROR_MATCH
+ }
+
+ sock->connected = 0;
+
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_sockaddr_format(addr, addrbuf, sizeof(addrbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "connect(%s) %d/%s",
+ addrbuf, errno, strbuf);
+
+ UNLOCK(&sock->lock);
+ inc_stats(sock->manager->stats,
+ sock->statsindex[STATID_CONNECTFAIL]);
+ isc_event_free(ISC_EVENT_PTR(&dev));
+ return (ISC_R_UNEXPECTED);
+
+ err_exit:
+ sock->connected = 0;
+ isc_task_sendto(task, ISC_EVENT_PTR(&dev), sock->threadid);
+
+ UNLOCK(&sock->lock);
+ inc_stats(sock->manager->stats,
+ sock->statsindex[STATID_CONNECTFAIL]);
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * If connect completed, fire off the done event.
+ */
+success:
+ if (cc == 0) {
+ sock->connected = 1;
+ sock->bound = 1;
+ dev->result = ISC_R_SUCCESS;
+ isc_task_sendto(task, ISC_EVENT_PTR(&dev), sock->threadid);
+
+ UNLOCK(&sock->lock);
+
+ inc_stats(sock->manager->stats,
+ sock->statsindex[STATID_CONNECT]);
+
+ return (ISC_R_SUCCESS);
+ }
+
+queue:
+
+ /*
+ * Attach to task.
+ */
+ isc_task_attach(task, &ntask);
+
+ dev->ev_sender = ntask;
+
+ /*
+ * Poke watcher here. We still have the socket locked, so there
+ * is no race condition. We will keep the lock for such a short
+ * bit of time waking it up now or later won't matter all that much.
+ */
+ bool do_poke = ISC_LIST_EMPTY(sock->connect_list);
+ ISC_LIST_ENQUEUE(sock->connect_list, dev, ev_link);
+ if (do_poke && !sock->connecting) {
+ sock->connecting = 1;
+ select_poke(manager, sock->threadid, sock->fd,
+ SELECT_POKE_CONNECT);
+ }
+
+ UNLOCK(&sock->lock);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Called when a socket with a pending connect() finishes.
+ */
+static void
+internal_connect(isc_socket_t *sock) {
+ isc_socket_connev_t *dev;
+ int cc;
+ isc_result_t result;
+ socklen_t optlen;
+ char strbuf[ISC_STRERRORSIZE];
+ char peerbuf[ISC_SOCKADDR_FORMATSIZE];
+
+ INSIST(VALID_SOCKET(sock));
+ REQUIRE(sock->fd >= 0);
+
+ /*
+ * Get the first item off the connect list.
+ * If it is empty, unlock the socket and return.
+ */
+ dev = ISC_LIST_HEAD(sock->connect_list);
+ if (dev == NULL) {
+ INSIST(!sock->connecting);
+ goto finish;
+ }
+
+ INSIST(sock->connecting);
+ sock->connecting = 0;
+
+ /*
+ * Get any possible error status here.
+ */
+ optlen = sizeof(cc);
+ if (getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, (void *)&cc,
+ (void *)&optlen) != 0)
+ {
+ cc = errno;
+ } else {
+ errno = cc;
+ }
+
+ if (errno != 0) {
+ /*
+ * If the error is EAGAIN, just re-select on this
+ * fd and pretend nothing strange happened.
+ */
+ if (SOFT_ERROR(errno) || errno == EINPROGRESS) {
+ sock->connecting = 1;
+ return;
+ }
+
+ inc_stats(sock->manager->stats,
+ sock->statsindex[STATID_CONNECTFAIL]);
+
+ /*
+ * Translate other errors into ISC_R_* flavors.
+ */
+ switch (errno) {
+#define ERROR_MATCH(a, b) \
+ case a: \
+ result = b; \
+ break;
+ ERROR_MATCH(EACCES, ISC_R_NOPERM);
+ ERROR_MATCH(EADDRNOTAVAIL, ISC_R_ADDRNOTAVAIL);
+ ERROR_MATCH(EAFNOSUPPORT, ISC_R_ADDRNOTAVAIL);
+ ERROR_MATCH(ECONNREFUSED, ISC_R_CONNREFUSED);
+ ERROR_MATCH(EHOSTUNREACH, ISC_R_HOSTUNREACH);
+#ifdef EHOSTDOWN
+ ERROR_MATCH(EHOSTDOWN, ISC_R_HOSTUNREACH);
+#endif /* ifdef EHOSTDOWN */
+ ERROR_MATCH(ENETUNREACH, ISC_R_NETUNREACH);
+ ERROR_MATCH(ENOBUFS, ISC_R_NORESOURCES);
+ ERROR_MATCH(EPERM, ISC_R_HOSTUNREACH);
+ ERROR_MATCH(EPIPE, ISC_R_NOTCONNECTED);
+ ERROR_MATCH(ETIMEDOUT, ISC_R_TIMEDOUT);
+ ERROR_MATCH(ECONNRESET, ISC_R_CONNECTIONRESET);
+#undef ERROR_MATCH
+ default:
+ result = ISC_R_UNEXPECTED;
+ isc_sockaddr_format(&sock->peer_address, peerbuf,
+ sizeof(peerbuf));
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "internal_connect: connect(%s) %s",
+ peerbuf, strbuf);
+ }
+ } else {
+ inc_stats(sock->manager->stats,
+ sock->statsindex[STATID_CONNECT]);
+ result = ISC_R_SUCCESS;
+ sock->connected = 1;
+ sock->bound = 1;
+ }
+
+ do {
+ dev->result = result;
+ send_connectdone_event(sock, &dev);
+ dev = ISC_LIST_HEAD(sock->connect_list);
+ } while (dev != NULL);
+
+finish:
+ unwatch_fd(&sock->manager->threads[sock->threadid], sock->fd,
+ SELECT_POKE_CONNECT);
+}
+
+isc_result_t
+isc_socket_getpeername(isc_socket_t *sock, isc_sockaddr_t *addressp) {
+ isc_result_t result;
+
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(addressp != NULL);
+
+ LOCK(&sock->lock);
+
+ if (sock->connected) {
+ *addressp = sock->peer_address;
+ result = ISC_R_SUCCESS;
+ } else {
+ result = ISC_R_NOTCONNECTED;
+ }
+
+ UNLOCK(&sock->lock);
+
+ return (result);
+}
+
+isc_result_t
+isc_socket_getsockname(isc_socket_t *sock, isc_sockaddr_t *addressp) {
+ socklen_t len;
+ isc_result_t result;
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(addressp != NULL);
+
+ LOCK(&sock->lock);
+
+ if (!sock->bound) {
+ result = ISC_R_NOTBOUND;
+ goto out;
+ }
+
+ result = ISC_R_SUCCESS;
+
+ len = sizeof(addressp->type);
+ if (getsockname(sock->fd, &addressp->type.sa, (void *)&len) < 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "getsockname: %s", strbuf);
+ result = ISC_R_UNEXPECTED;
+ goto out;
+ }
+ addressp->length = (unsigned int)len;
+
+out:
+ UNLOCK(&sock->lock);
+
+ return (result);
+}
+
+/*
+ * Run through the list of events on this socket, and cancel the ones
+ * queued for task "task" of type "how". "how" is a bitmask.
+ */
+void
+isc_socket_cancel(isc_socket_t *sock, isc_task_t *task, unsigned int how) {
+ REQUIRE(VALID_SOCKET(sock));
+
+ /*
+ * Quick exit if there is nothing to do. Don't even bother locking
+ * in this case.
+ */
+ if (how == 0) {
+ return;
+ }
+
+ LOCK(&sock->lock);
+
+ /*
+ * All of these do the same thing, more or less.
+ * Each will:
+ * o If the internal event is marked as "posted" try to
+ * remove it from the task's queue. If this fails, mark it
+ * as canceled instead, and let the task clean it up later.
+ * o For each I/O request for that task of that type, post
+ * its done event with status of "ISC_R_CANCELED".
+ * o Reset any state needed.
+ */
+ if (((how & ISC_SOCKCANCEL_RECV) != 0) &&
+ !ISC_LIST_EMPTY(sock->recv_list))
+ {
+ isc_socketevent_t *dev;
+ isc_socketevent_t *next;
+ isc_task_t *current_task;
+
+ dev = ISC_LIST_HEAD(sock->recv_list);
+
+ while (dev != NULL) {
+ current_task = dev->ev_sender;
+ next = ISC_LIST_NEXT(dev, ev_link);
+
+ if ((task == NULL) || (task == current_task)) {
+ dev->result = ISC_R_CANCELED;
+ send_recvdone_event(sock, &dev);
+ }
+ dev = next;
+ }
+ }
+
+ if (((how & ISC_SOCKCANCEL_SEND) != 0) &&
+ !ISC_LIST_EMPTY(sock->send_list))
+ {
+ isc_socketevent_t *dev;
+ isc_socketevent_t *next;
+ isc_task_t *current_task;
+
+ dev = ISC_LIST_HEAD(sock->send_list);
+
+ while (dev != NULL) {
+ current_task = dev->ev_sender;
+ next = ISC_LIST_NEXT(dev, ev_link);
+
+ if ((task == NULL) || (task == current_task)) {
+ dev->result = ISC_R_CANCELED;
+ send_senddone_event(sock, &dev);
+ }
+ dev = next;
+ }
+ }
+
+ if (((how & ISC_SOCKCANCEL_ACCEPT) != 0) &&
+ !ISC_LIST_EMPTY(sock->accept_list))
+ {
+ isc_socket_newconnev_t *dev;
+ isc_socket_newconnev_t *next;
+ isc_task_t *current_task;
+
+ dev = ISC_LIST_HEAD(sock->accept_list);
+ while (dev != NULL) {
+ current_task = dev->ev_sender;
+ next = ISC_LIST_NEXT(dev, ev_link);
+
+ if ((task == NULL) || (task == current_task)) {
+ ISC_LIST_UNLINK(sock->accept_list, dev,
+ ev_link);
+
+ isc_refcount_decrementz(
+ &NEWCONNSOCK(dev)->references);
+ free_socket((isc_socket_t **)&dev->newsocket);
+
+ dev->result = ISC_R_CANCELED;
+ dev->ev_sender = sock;
+ isc_task_sendtoanddetach(&current_task,
+ ISC_EVENT_PTR(&dev),
+ sock->threadid);
+ }
+
+ dev = next;
+ }
+ }
+
+ if (((how & ISC_SOCKCANCEL_CONNECT) != 0) &&
+ !ISC_LIST_EMPTY(sock->connect_list))
+ {
+ isc_socket_connev_t *dev;
+ isc_socket_connev_t *next;
+ isc_task_t *current_task;
+
+ INSIST(sock->connecting);
+ sock->connecting = 0;
+
+ dev = ISC_LIST_HEAD(sock->connect_list);
+
+ while (dev != NULL) {
+ current_task = dev->ev_sender;
+ next = ISC_LIST_NEXT(dev, ev_link);
+
+ if ((task == NULL) || (task == current_task)) {
+ dev->result = ISC_R_CANCELED;
+ send_connectdone_event(sock, &dev);
+ }
+ dev = next;
+ }
+ }
+
+ UNLOCK(&sock->lock);
+}
+
+isc_sockettype_t
+isc_socket_gettype(isc_socket_t *sock) {
+ REQUIRE(VALID_SOCKET(sock));
+
+ return (sock->type);
+}
+
+void
+isc_socket_ipv6only(isc_socket_t *sock, bool yes) {
+#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__)
+ int onoff = yes ? 1 : 0;
+#else /* if defined(IPV6_V6ONLY) */
+ UNUSED(yes);
+ UNUSED(sock);
+#endif /* if defined(IPV6_V6ONLY) */
+
+ REQUIRE(VALID_SOCKET(sock));
+ INSIST(!sock->dupped);
+
+#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__)
+ if (sock->pf == AF_INET6) {
+ if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY,
+ (void *)&onoff, sizeof(int)) < 0)
+ {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IPV6_V6ONLY) failed: "
+ "%s",
+ sock->fd, strbuf);
+ }
+ }
+#endif /* ifdef IPV6_V6ONLY */
+}
+
+static void
+setdscp(isc_socket_t *sock, isc_dscp_t dscp) {
+#if defined(IP_TOS) || defined(IPV6_TCLASS)
+ int value = dscp << 2;
+#endif /* if defined(IP_TOS) || defined(IPV6_TCLASS) */
+
+ sock->dscp = dscp;
+
+#ifdef IP_TOS
+ if (sock->pf == AF_INET) {
+ if (setsockopt(sock->fd, IPPROTO_IP, IP_TOS, (void *)&value,
+ sizeof(value)) < 0)
+ {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IP_TOS, %.02x) "
+ "failed: %s",
+ sock->fd, value >> 2, strbuf);
+ }
+ }
+#endif /* ifdef IP_TOS */
+#ifdef IPV6_TCLASS
+ if (sock->pf == AF_INET6) {
+ if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_TCLASS,
+ (void *)&value, sizeof(value)) < 0)
+ {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IPV6_TCLASS, %.02x) "
+ "failed: %s",
+ sock->fd, dscp >> 2, strbuf);
+ }
+ }
+#endif /* ifdef IPV6_TCLASS */
+}
+
+void
+isc_socket_dscp(isc_socket_t *sock, isc_dscp_t dscp) {
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(dscp < 0x40);
+
+#if !defined(IP_TOS) && !defined(IPV6_TCLASS)
+ UNUSED(dscp);
+#else /* if !defined(IP_TOS) && !defined(IPV6_TCLASS) */
+ if (dscp < 0) {
+ return;
+ }
+
+ /* The DSCP value must not be changed once it has been set. */
+ if (isc_dscp_check_value != -1) {
+ INSIST(dscp == isc_dscp_check_value);
+ }
+#endif /* if !defined(IP_TOS) && !defined(IPV6_TCLASS) */
+
+#ifdef notyet
+ REQUIRE(!sock->dupped);
+#endif /* ifdef notyet */
+
+ setdscp(sock, dscp);
+}
+
+isc_socketevent_t *
+isc_socket_socketevent(isc_mem_t *mctx, void *sender, isc_eventtype_t eventtype,
+ isc_taskaction_t action, void *arg) {
+ return (allocate_socketevent(mctx, sender, eventtype, action, arg));
+}
+
+void
+isc_socket_setname(isc_socket_t *sock, const char *name, void *tag) {
+ /*
+ * Name 'sock'.
+ */
+
+ REQUIRE(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+ strlcpy(sock->name, name, sizeof(sock->name));
+ sock->tag = tag;
+ UNLOCK(&sock->lock);
+}
+
+const char *
+isc_socket_getname(isc_socket_t *sock) {
+ return (sock->name);
+}
+
+void *
+isc_socket_gettag(isc_socket_t *sock) {
+ return (sock->tag);
+}
+
+int
+isc_socket_getfd(isc_socket_t *sock) {
+ return ((short)sock->fd);
+}
+
+static isc_once_t hasreuseport_once = ISC_ONCE_INIT;
+static bool hasreuseport = false;
+
+static void
+init_hasreuseport() {
+/*
+ * SO_REUSEPORT works very differently on *BSD and on Linux (because why not).
+ * We only want to use it on Linux, if it's available. On BSD we want to dup()
+ * sockets instead of re-binding them.
+ */
+#if (defined(SO_REUSEPORT) && defined(__linux__)) || \
+ (defined(SO_REUSEPORT_LB) && defined(__FreeBSD_kernel__))
+ int sock, yes = 1;
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ sock = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ return;
+ }
+ }
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&yes,
+ sizeof(yes)) < 0)
+ {
+ close(sock);
+ return;
+#if defined(__FreeBSD_kernel__)
+ } else if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT_LB, (void *)&yes,
+ sizeof(yes)) < 0)
+#else /* if defined(__FreeBSD_kernel__) */
+ } else if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (void *)&yes,
+ sizeof(yes)) < 0)
+#endif /* if defined(__FreeBSD_kernel__) */
+ {
+ close(sock);
+ return;
+ }
+ hasreuseport = true;
+ close(sock);
+#endif /* if (defined(SO_REUSEPORT) && defined(__linux__)) || \
+ * (defined(SO_REUSEPORT_LB) && defined(__FreeBSD_kernel__)) */
+}
+
+bool
+isc_socket_hasreuseport() {
+ RUNTIME_CHECK(isc_once_do(&hasreuseport_once, init_hasreuseport) ==
+ ISC_R_SUCCESS);
+ return (hasreuseport);
+}
+
+#if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C)
+static const char *
+_socktype(isc_sockettype_t type) {
+ switch (type) {
+ case isc_sockettype_udp:
+ return ("udp");
+ case isc_sockettype_tcp:
+ return ("tcp");
+ case isc_sockettype_unix:
+ return ("unix");
+ default:
+ return ("not-initialized");
+ }
+}
+#endif /* if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C) */
+
+#ifdef HAVE_LIBXML2
+#define TRY0(a) \
+ do { \
+ xmlrc = (a); \
+ if (xmlrc < 0) \
+ goto error; \
+ } while (0)
+int
+isc_socketmgr_renderxml(isc_socketmgr_t *mgr, void *writer0) {
+ isc_socket_t *sock = NULL;
+ char peerbuf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_t addr;
+ socklen_t len;
+ int xmlrc;
+ xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0;
+
+ LOCK(&mgr->lock);
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "sockets"));
+ sock = ISC_LIST_HEAD(mgr->socklist);
+ while (sock != NULL) {
+ LOCK(&sock->lock);
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "socket"));
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%p", sock));
+ TRY0(xmlTextWriterEndElement(writer));
+
+ if (sock->name[0] != 0) {
+ TRY0(xmlTextWriterStartElement(writer,
+ ISC_XMLCHAR "name"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%s",
+ sock->name));
+ TRY0(xmlTextWriterEndElement(writer)); /* name */
+ }
+
+ TRY0(xmlTextWriterStartElement(writer,
+ ISC_XMLCHAR "references"));
+ TRY0(xmlTextWriterWriteFormatString(
+ writer, "%d",
+ (int)isc_refcount_current(&sock->references)));
+ TRY0(xmlTextWriterEndElement(writer));
+
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "type",
+ ISC_XMLCHAR _socktype(sock->type)));
+
+ if (sock->connected) {
+ isc_sockaddr_format(&sock->peer_address, peerbuf,
+ sizeof(peerbuf));
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "peer-address",
+ ISC_XMLCHAR peerbuf));
+ }
+
+ len = sizeof(addr);
+ if (getsockname(sock->fd, &addr.type.sa, (void *)&len) == 0) {
+ isc_sockaddr_format(&addr, peerbuf, sizeof(peerbuf));
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "local-address",
+ ISC_XMLCHAR peerbuf));
+ }
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "states"));
+ if (sock->listener) {
+ TRY0(xmlTextWriterWriteElement(writer,
+ ISC_XMLCHAR "state",
+ ISC_XMLCHAR "listener"));
+ }
+ if (sock->connected) {
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "state",
+ ISC_XMLCHAR "connected"));
+ }
+ if (sock->connecting) {
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "state",
+ ISC_XMLCHAR "connecting"));
+ }
+ if (sock->bound) {
+ TRY0(xmlTextWriterWriteElement(writer,
+ ISC_XMLCHAR "state",
+ ISC_XMLCHAR "bound"));
+ }
+
+ TRY0(xmlTextWriterEndElement(writer)); /* states */
+
+ TRY0(xmlTextWriterEndElement(writer)); /* socket */
+
+ UNLOCK(&sock->lock);
+ sock = ISC_LIST_NEXT(sock, link);
+ }
+ TRY0(xmlTextWriterEndElement(writer)); /* sockets */
+
+error:
+ if (sock != NULL) {
+ UNLOCK(&sock->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_socketmgr_renderjson(isc_socketmgr_t *mgr, void *stats0) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_socket_t *sock = NULL;
+ char peerbuf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_t addr;
+ socklen_t len;
+ json_object *obj, *array = json_object_new_array();
+ json_object *stats = (json_object *)stats0;
+
+ CHECKMEM(array);
+
+ LOCK(&mgr->lock);
+
+ sock = ISC_LIST_HEAD(mgr->socklist);
+ while (sock != NULL) {
+ json_object *states, *entry = json_object_new_object();
+ char buf[255];
+
+ CHECKMEM(entry);
+ json_object_array_add(array, entry);
+
+ LOCK(&sock->lock);
+
+ snprintf(buf, sizeof(buf), "%p", sock);
+ obj = json_object_new_string(buf);
+ CHECKMEM(obj);
+ json_object_object_add(entry, "id", obj);
+
+ if (sock->name[0] != 0) {
+ obj = json_object_new_string(sock->name);
+ CHECKMEM(obj);
+ json_object_object_add(entry, "name", obj);
+ }
+
+ obj = json_object_new_int(
+ (int)isc_refcount_current(&sock->references));
+ CHECKMEM(obj);
+ json_object_object_add(entry, "references", obj);
+
+ obj = json_object_new_string(_socktype(sock->type));
+ CHECKMEM(obj);
+ json_object_object_add(entry, "type", obj);
+
+ if (sock->connected) {
+ isc_sockaddr_format(&sock->peer_address, peerbuf,
+ sizeof(peerbuf));
+ obj = json_object_new_string(peerbuf);
+ CHECKMEM(obj);
+ json_object_object_add(entry, "peer-address", obj);
+ }
+
+ len = sizeof(addr);
+ if (getsockname(sock->fd, &addr.type.sa, (void *)&len) == 0) {
+ isc_sockaddr_format(&addr, peerbuf, sizeof(peerbuf));
+ obj = json_object_new_string(peerbuf);
+ CHECKMEM(obj);
+ json_object_object_add(entry, "local-address", obj);
+ }
+
+ states = json_object_new_array();
+ CHECKMEM(states);
+ json_object_object_add(entry, "states", states);
+
+ if (sock->listener) {
+ obj = json_object_new_string("listener");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ if (sock->connected) {
+ obj = json_object_new_string("connected");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ if (sock->connecting) {
+ obj = json_object_new_string("connecting");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ if (sock->bound) {
+ obj = json_object_new_string("bound");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ UNLOCK(&sock->lock);
+ sock = ISC_LIST_NEXT(sock, link);
+ }
+
+ json_object_object_add(stats, "sockets", array);
+ array = NULL;
+ result = ISC_R_SUCCESS;
+
+error:
+ if (array != NULL) {
+ json_object_put(array);
+ }
+
+ if (sock != NULL) {
+ UNLOCK(&sock->lock);
+ }
+
+ UNLOCK(&mgr->lock);
+
+ return (result);
+}
+#endif /* HAVE_JSON_C */
diff --git a/lib/isc/unix/socket_p.h b/lib/isc/unix/socket_p.h
new file mode 100644
index 0000000..3323cab
--- /dev/null
+++ b/lib/isc/unix/socket_p.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_SOCKET_P_H
+#define ISC_SOCKET_P_H
+
+/*! \file */
+
+#include <sys/time.h>
+
+typedef struct isc_socketwait isc_socketwait_t;
+int
+isc__socketmgr_waitevents(isc_socketmgr_t *, struct timeval *,
+ isc_socketwait_t **);
+isc_result_t
+isc__socketmgr_dispatch(isc_socketmgr_t *, isc_socketwait_t *);
+#endif /* ISC_SOCKET_P_H */
diff --git a/lib/isc/unix/stdio.c b/lib/isc/unix/stdio.c
new file mode 100644
index 0000000..12ab678
--- /dev/null
+++ b/lib/isc/unix/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/unix/stdtime.c b/lib/isc/unix/stdtime.c
new file mode 100644
index 0000000..ada19d0
--- /dev/null
+++ b/lib/isc/unix/stdtime.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \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/strerr.h>
+#include <isc/util.h>
+
+#define NS_PER_S 1000000000 /*%< Nanoseconds per second. */
+
+#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) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__, "clock_gettime failed: %s",
+ strbuf);
+ }
+
+ REQUIRE(ts.tv_sec > 0 && ts.tv_nsec >= 0 && ts.tv_nsec < NS_PER_S);
+
+ *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/unix/syslog.c b/lib/isc/unix/syslog.c
new file mode 100644
index 0000000..81b99ca
--- /dev/null
+++ b/lib/isc/unix/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/unix/time.c b/lib/isc/unix/time.c
new file mode 100644
index 0000000..02291e4
--- /dev/null
+++ b/lib/isc/unix/time.c
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/platform.h>
+#include <isc/print.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/tm.h>
+#include <isc/util.h>
+
+#define NS_PER_S 1000000000 /*%< Nanoseconds per second. */
+#define NS_PER_US 1000 /*%< Nanoseconds per microsecond. */
+#define NS_PER_MS 1000000 /*%< Nanoseconds per millisecond. */
+
+#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_S);
+
+ i->seconds = seconds;
+ i->nanoseconds = nanoseconds;
+}
+
+bool
+isc_interval_iszero(const isc_interval_t *i) {
+ REQUIRE(i != NULL);
+ INSIST(i->nanoseconds < NS_PER_S);
+
+ if (i->seconds == 0 && i->nanoseconds == 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+/***
+ *** 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_S);
+
+ 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_S);
+
+ 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;
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(t != NULL);
+
+ if (clock_gettime(clock, &ts) == -1) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "%s", strbuf);
+ return (ISC_R_UNEXPECTED);
+ }
+
+ if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= NS_PER_S) {
+ 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;
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(t != NULL);
+ REQUIRE(i != NULL);
+ INSIST(i->nanoseconds < NS_PER_S);
+
+ if (clock_gettime(CLOCKSOURCE, &ts) == -1) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "%s", strbuf);
+ return (ISC_R_UNEXPECTED);
+ }
+
+ if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= NS_PER_S) {
+ 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_S) {
+ t->seconds++;
+ t->nanoseconds -= NS_PER_S;
+ }
+
+ 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_S && t2->nanoseconds < NS_PER_S);
+
+ 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_S && i->nanoseconds < NS_PER_S);
+
+ /* Seconds */
+ if (t->seconds > UINT_MAX - i->seconds) {
+ return (ISC_R_RANGE);
+ }
+ result->seconds = t->seconds + i->seconds;
+
+ /* Nanoseconds */
+ result->nanoseconds = t->nanoseconds + i->nanoseconds;
+ if (result->nanoseconds >= NS_PER_S) {
+ if (result->seconds == UINT_MAX) {
+ return (ISC_R_RANGE);
+ }
+ result->nanoseconds -= NS_PER_S;
+ 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_S && i->nanoseconds < NS_PER_S);
+
+ /* Seconds */
+ if (t->seconds < i->seconds) {
+ return (ISC_R_RANGE);
+ }
+ result->seconds = t->seconds - i->seconds;
+
+ /* 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_S + 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_S && t2->nanoseconds < NS_PER_S);
+
+ i1 = (uint64_t)t1->seconds * NS_PER_S + t1->nanoseconds;
+ i2 = (uint64_t)t2->seconds * NS_PER_S + 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_S);
+
+ 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_S);
+
+ /*
+ * 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_S);
+
+ 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_S);
+ 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_S);
+ 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_S);
+ 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_S);
+ 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_S);
+ 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_S);
+ 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_S);
+ 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_S);
+ 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_S);
+ 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/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/isc/version.c b/lib/isc/version.c
new file mode 100644
index 0000000..17aa210
--- /dev/null
+++ b/lib/isc/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/version.h>
+
+const char isc_version[] = VERSION;
diff --git a/lib/isc/win32/.dir-locals.el b/lib/isc/win32/.dir-locals.el
new file mode 100644
index 0000000..b16a1be
--- /dev/null
+++ b/lib/isc/win32/.dir-locals.el
@@ -0,0 +1,35 @@
+;;; Directory Local Variables
+;;; For more information see (info "(emacs) Directory Variables")
+
+((c-mode .
+ ((eval .
+ (set (make-local-variable 'directory-of-current-dir-locals-file)
+ (file-name-directory (locate-dominating-file default-directory ".dir-locals.el"))
+ )
+ )
+ (eval .
+ (set (make-local-variable 'include-directories)
+ (list
+ (expand-file-name
+ (concat directory-of-current-dir-locals-file "../../../"))
+ (expand-file-name
+ (concat directory-of-current-dir-locals-file "include"))
+ (expand-file-name
+ (concat directory-of-current-dir-locals-file "../include"))
+ (expand-file-name
+ (concat directory-of-current-dir-locals-file "../"))
+ (expand-file-name
+ (concat directory-of-current-dir-locals-file "./"))
+ (expand-file-name
+ (concat directory-of-current-dir-locals-file "../../dns/include"))
+ (expand-file-name "/usr/local/opt/openssl@1.1/include")
+ (expand-file-name "/usr/local/opt/libxml2/include/libxml2")
+ (expand-file-name "/usr/local/include")
+ )
+ )
+ )
+
+ (eval setq flycheck-clang-include-path include-directories)
+ (eval setq flycheck-cpp-include-path include-directories)
+ )
+ ))
diff --git a/lib/isc/win32/DLLMain.c b/lib/isc/win32/DLLMain.c
new file mode 100644
index 0000000..abaaf40
--- /dev/null
+++ b/lib/isc/win32/DLLMain.c
@@ -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.
+ */
+
+#include <stdio.h>
+#include <windows.h>
+
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include "lib_p.h"
+
+/*
+ * Called when we enter the DLL
+ */
+__declspec(dllexport) BOOL WINAPI
+ DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
+ switch (fdwReason) {
+ /*
+ * The DLL is loading due to process initialization or a call to
+ * LoadLibrary.
+ */
+ case DLL_PROCESS_ATTACH:
+ isc__initialize();
+ break;
+
+ /*
+ * The DLL is unloading from a process due to process
+ * termination or a call to FreeLibrary.
+ */
+ case DLL_PROCESS_DETACH:
+ isc__shutdown();
+ break;
+
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ break;
+
+ default:
+ break;
+ }
+ return (TRUE);
+}
diff --git a/lib/isc/win32/Makefile.in b/lib/isc/win32/Makefile.in
new file mode 100644
index 0000000..e8377ff
--- /dev/null
+++ b/lib/isc/win32/Makefile.in
@@ -0,0 +1,36 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+CINCLUDES = -I${srcdir}/.. \
+ -I./include \
+ -I${srcdir}/include \
+ -I${srcdir}/../include
+CDEFINES =
+CWARNINGS =
+
+# Alphabetically
+OBJS = condition.@O@ dir.@O@ errno.@O@ file.@O@ \
+ meminfo.@O@ fsaccess.@O@ \
+ once.@O@ stdtime.@O@ thread.@O@ time.@O@ pk11_api.@O@
+
+# Alphabetically
+SRCS = condition.c dir.c errno.c file.c \
+ meminfo.c once.c fsaccess.c \
+ stdtime.c thread.c time.c pk11_api.c
+
+SUBDIRS = include
+TARGETS = ${OBJS}
+
+@BIND9_MAKE_RULES@
diff --git a/lib/isc/win32/condition.c b/lib/isc/win32/condition.c
new file mode 100644
index 0000000..ca11bce
--- /dev/null
+++ b/lib/isc/win32/condition.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.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/assertions.h>
+#include <isc/condition.h>
+#include <isc/error.h>
+#include <isc/strerr.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#define LSIGNAL 0
+#define LBROADCAST 1
+
+void
+isc_condition_init(isc_condition_t *cond) {
+ HANDLE h;
+
+ REQUIRE(cond != NULL);
+
+ cond->waiters = 0;
+ /*
+ * This handle is shared across all threads
+ */
+ h = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (h == NULL) {
+ char strbuf[ISC_STRERRORSIZE];
+ DWORD err = GetLastError();
+ strerror_r(err, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__, "CreateEvent failed: %s",
+ strbuf);
+ }
+ cond->events[LSIGNAL] = h;
+
+ /*
+ * The threadlist will hold the actual events needed
+ * for the wait condition
+ */
+ ISC_LIST_INIT(cond->threadlist);
+}
+
+/*
+ * Add the thread to the threadlist along with the required events
+ */
+static isc_result_t
+register_thread(unsigned long thrd, isc_condition_t *gblcond,
+ isc_condition_thread_t **localcond) {
+ HANDLE hc;
+ isc_condition_thread_t *newthread;
+
+ REQUIRE(localcond != NULL && *localcond == NULL);
+
+ newthread = malloc(sizeof(isc_condition_thread_t));
+ if (newthread == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ /*
+ * Create the thread-specific handle
+ */
+ hc = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (hc == NULL) {
+ free(newthread);
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * Add the thread ID and handles to list of threads for broadcast
+ */
+ newthread->handle[LSIGNAL] = gblcond->events[LSIGNAL];
+ newthread->handle[LBROADCAST] = hc;
+ newthread->th = thrd;
+
+ /*
+ * The thread is holding the manager lock so this is safe
+ */
+ ISC_LINK_INIT(newthread, link);
+ ISC_LIST_APPEND(gblcond->threadlist, newthread, link);
+ *localcond = newthread;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+find_thread_condition(uintptr_t thrd, isc_condition_t *cond,
+ isc_condition_thread_t **threadcondp) {
+ isc_condition_thread_t *threadcond;
+
+ REQUIRE(threadcondp != NULL && *threadcondp == NULL);
+
+ /*
+ * Look for the thread ID.
+ */
+ for (threadcond = ISC_LIST_HEAD(cond->threadlist); threadcond != NULL;
+ threadcond = ISC_LIST_NEXT(threadcond, link))
+ {
+ if (threadcond->th == thrd) {
+ *threadcondp = threadcond;
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ /*
+ * Not found, so add it.
+ */
+ return (register_thread(thrd, cond, threadcondp));
+}
+
+isc_result_t
+isc_condition_signal(isc_condition_t *cond) {
+ /*
+ * Unlike pthreads, the caller MUST hold the lock associated with
+ * the condition variable when calling us.
+ */
+ REQUIRE(cond != NULL);
+
+ if (!SetEvent(cond->events[LSIGNAL])) {
+ /* XXX */
+ return (ISC_R_UNEXPECTED);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_condition_broadcast(isc_condition_t *cond) {
+ isc_condition_thread_t *threadcond;
+ bool failed = false;
+
+ /*
+ * Unlike pthreads, the caller MUST hold the lock associated with
+ * the condition variable when calling us.
+ */
+ REQUIRE(cond != NULL);
+
+ /*
+ * Notify every thread registered for this
+ */
+ for (threadcond = ISC_LIST_HEAD(cond->threadlist); threadcond != NULL;
+ threadcond = ISC_LIST_NEXT(threadcond, link))
+ {
+ if (!SetEvent(threadcond->handle[LBROADCAST])) {
+ failed = true;
+ }
+ }
+
+ if (failed) {
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_condition_destroy(isc_condition_t *cond) {
+ isc_condition_thread_t *next, *threadcond;
+
+ REQUIRE(cond != NULL);
+ REQUIRE(cond->waiters == 0);
+
+ (void)CloseHandle(cond->events[LSIGNAL]);
+
+ /*
+ * Delete the threadlist
+ */
+ threadcond = ISC_LIST_HEAD(cond->threadlist);
+
+ while (threadcond != NULL) {
+ next = ISC_LIST_NEXT(threadcond, link);
+ DEQUEUE(cond->threadlist, threadcond, link);
+ (void)CloseHandle(threadcond->handle[LBROADCAST]);
+ free(threadcond);
+ threadcond = next;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * This is always called when the mutex (lock) is held, but because
+ * we are waiting we need to release it and reacquire it as soon as the wait
+ * is over. This allows other threads to make use of the object guarded
+ * by the mutex but it should never try to delete it as long as the
+ * number of waiters > 0. Always reacquire the mutex regardless of the
+ * result of the wait. Note that EnterCriticalSection will wait to acquire
+ * the mutex.
+ */
+static isc_result_t
+wait(isc_condition_t *cond, isc_mutex_t *mutex, DWORD milliseconds) {
+ DWORD result;
+ isc_result_t tresult;
+ isc_condition_thread_t *threadcond = NULL;
+
+ /*
+ * Get the thread events needed for the wait
+ */
+ tresult = find_thread_condition(isc_thread_self(), cond, &threadcond);
+ if (tresult != ISC_R_SUCCESS) {
+ return (tresult);
+ }
+
+ cond->waiters++;
+ LeaveCriticalSection(mutex);
+ result = WaitForMultipleObjects(2, threadcond->handle, FALSE,
+ milliseconds);
+ EnterCriticalSection(mutex);
+ cond->waiters--;
+ if (result == WAIT_FAILED) {
+ /* XXX */
+ return (ISC_R_UNEXPECTED);
+ }
+ if (result == WAIT_TIMEOUT) {
+ return (ISC_R_TIMEDOUT);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_condition_wait(isc_condition_t *cond, isc_mutex_t *mutex) {
+ return (wait(cond, mutex, INFINITE));
+}
+
+isc_result_t
+isc_condition_waituntil(isc_condition_t *cond, isc_mutex_t *mutex,
+ isc_time_t *t) {
+ DWORD milliseconds;
+ uint64_t microseconds;
+ isc_time_t now;
+
+ if (isc_time_now(&now) != ISC_R_SUCCESS) {
+ /* XXX */
+ return (ISC_R_UNEXPECTED);
+ }
+
+ microseconds = isc_time_microdiff(t, &now);
+ if (microseconds > 0xFFFFFFFFi64 * 1000) {
+ milliseconds = 0xFFFFFFFF;
+ } else {
+ milliseconds = (DWORD)(microseconds / 1000);
+ }
+
+ return (wait(cond, mutex, milliseconds));
+}
diff --git a/lib/isc/win32/dir.c b/lib/isc/win32/dir.c
new file mode 100644
index 0000000..9a512da
--- /dev/null
+++ b/lib/isc/win32/dir.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <direct.h>
+#include <io.h>
+#include <process.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <isc/assertions.h>
+#include <isc/dir.h>
+#include <isc/magic.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)
+
+static isc_result_t
+start_directory(isc_dir_t *p);
+
+void
+isc_dir_init(isc_dir_t *dir) {
+ REQUIRE(dir != NULL);
+
+ dir->dirname[0] = '\0';
+
+ dir->entry.name[0] = '\0';
+ dir->entry.length = 0;
+ memset(&(dir->entry.find_data), 0, sizeof(dir->entry.find_data));
+
+ dir->entry_filled = false;
+ dir->search_handle = INVALID_HANDLE_VALUE;
+
+ dir->magic = ISC_DIR_MAGIC;
+}
+
+/*
+ * 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;
+
+ REQUIRE(dirname != NULL);
+ REQUIRE(VALID_DIR(dir) && dir->search_handle == INVALID_HANDLE_VALUE);
+
+ /*
+ * 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 - 1) != ':') {
+ *p++ = '\\';
+ }
+ *p++ = '*';
+ *p = '\0';
+
+ /*
+ * Open stream.
+ */
+ result = start_directory(dir);
+
+ return (result);
+}
+
+/*
+ * 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) {
+ REQUIRE(VALID_DIR(dir) && dir->search_handle != INVALID_HANDLE_VALUE);
+
+ if (dir->entry_filled) {
+ /*
+ * start_directory() already filled in the first entry.
+ */
+ dir->entry_filled = false;
+ } else {
+ /*
+ * Fetch next file in directory.
+ */
+ if (FindNextFile(dir->search_handle, &dir->entry.find_data) ==
+ FALSE)
+ {
+ /*
+ * Either the last file has been processed or
+ * an error has occurred. The former is not
+ * really an error, but the latter is.
+ */
+ if (GetLastError() == ERROR_NO_MORE_FILES) {
+ return (ISC_R_NOMORE);
+ } else {
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+ }
+
+ /*
+ * Make sure that the space for the name is long enough.
+ */
+ strlcpy(dir->entry.name, dir->entry.find_data.cFileName,
+ sizeof(dir->entry.name));
+ dir->entry.length = strlen(dir->entry.name);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Close directory stream.
+ */
+void
+isc_dir_close(isc_dir_t *dir) {
+ REQUIRE(VALID_DIR(dir) && dir->search_handle != INVALID_HANDLE_VALUE);
+
+ FindClose(dir->search_handle);
+ dir->search_handle = INVALID_HANDLE_VALUE;
+}
+
+/*
+ * Reposition directory stream at start.
+ */
+isc_result_t
+isc_dir_reset(isc_dir_t *dir) {
+ isc_result_t result;
+
+ REQUIRE(VALID_DIR(dir) && dir->search_handle != INVALID_HANDLE_VALUE);
+ REQUIRE(dir->dirname != NULL);
+
+ /*
+ * NT cannot reposition the seek pointer to the beginning of the
+ * the directory stream, but rather the directory needs to be
+ * closed and reopened. The latter might fail.
+ */
+
+ isc_dir_close(dir);
+
+ result = start_directory(dir);
+
+ return (result);
+}
+
+/*
+ * Initialize isc_dir_t structure with new directory. The function
+ * returns 0 on failure and nonzero on success.
+ *
+ * Note:
+ * - Be sure to close previous stream before opening new one
+ */
+static isc_result_t
+start_directory(isc_dir_t *dir) {
+ REQUIRE(VALID_DIR(dir));
+ REQUIRE(dir->search_handle == INVALID_HANDLE_VALUE);
+
+ dir->entry_filled = false;
+
+ /*
+ * Open stream and retrieve first file.
+ */
+ dir->search_handle = FindFirstFile(dir->dirname, &dir->entry.find_data);
+
+ if (dir->search_handle == INVALID_HANDLE_VALUE) {
+ /*
+ * Something went wrong but we don't know what. GetLastError()
+ * could give us more information about the error, but the
+ * MSDN documentation is frustratingly thin about what
+ * possible errors could have resulted. (Score one for
+ * the Unix manual pages.) So there is just this lame error
+ * instead of being able to differentiate ISC_R_NOTFOUND
+ * from ISC_R_UNEXPECTED.
+ */
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * Make sure that the space for the name is long enough.
+ */
+ INSIST(sizeof(dir->entry.name) >
+ strlen(dir->entry.find_data.cFileName));
+
+ /*
+ * Fill in the data for the first entry of the directory.
+ */
+ strlcpy(dir->entry.name, dir->entry.find_data.cFileName,
+ sizeof(dir->entry.name));
+ dir->entry.length = strlen(dir->entry.name);
+
+ dir->entry_filled = true;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_dir_chdir(const char *dirname) {
+ /*
+ * 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) {
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+isc_dir_createunique(char *templet) {
+ isc_result_t result;
+ char *x;
+ char *p;
+ int i;
+ int pid;
+
+ REQUIRE(templet != NULL);
+
+ /*
+ * 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);
+ if (i == 0) {
+ i = chmod(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/win32/errno.c b/lib/isc/win32/errno.c
new file mode 100644
index 0000000..fd6899f
--- /dev/null
+++ b/lib/isc/win32/errno.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.
+ */
+
+/*! \file */
+
+#include "errno2result.h"
+
+isc_result_t
+isc_errno_toresult(int err) {
+ return (isc__errno2resultx(err, false, 0, 0));
+}
diff --git a/lib/isc/win32/errno2result.c b/lib/isc/win32/errno2result.c
new file mode 100644
index 0000000..593c27b
--- /dev/null
+++ b/lib/isc/win32/errno2result.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 "errno2result.h"
+#include <stdbool.h>
+#include <winsock2.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__errno2resultx(int posixerrno, bool dolog, const char *file, int line) {
+ char strbuf[ISC_STRERRORSIZE];
+
+ switch (posixerrno) {
+ case ENOTDIR:
+ case WSAELOOP:
+ case WSAEINVAL:
+ case EINVAL: /* XXX sometimes this is not for files */
+ case ENAMETOOLONG:
+ case WSAENAMETOOLONG:
+ case EBADF:
+ case WSAEBADF:
+ return (ISC_R_INVALIDFILE);
+ case ENOENT:
+ return (ISC_R_FILENOTFOUND);
+ case EACCES:
+ case WSAEACCES:
+ case EPERM:
+ return (ISC_R_NOPERM);
+ case EEXIST:
+ return (ISC_R_FILEEXISTS);
+ case EIO:
+ return (ISC_R_IOERROR);
+ case ENOMEM:
+ return (ISC_R_NOMEMORY);
+#ifdef EOVERFLOW
+ case EOVERFLOW:
+ return (ISC_R_RANGE);
+#endif /* ifdef EOVERFLOW */
+ case ENFILE:
+ case EMFILE:
+ case WSAEMFILE:
+ return (ISC_R_TOOMANYOPENFILES);
+ case ENOSPC:
+ return (ISC_R_DISCFULL);
+ case ERROR_CANCELLED:
+ return (ISC_R_CANCELED);
+ case ERROR_CONNECTION_REFUSED:
+ case WSAECONNREFUSED:
+ return (ISC_R_CONNREFUSED);
+ case WSAENOTCONN:
+ case ERROR_CONNECTION_INVALID:
+ return (ISC_R_NOTCONNECTED);
+ case ERROR_HOST_UNREACHABLE:
+ case WSAEHOSTUNREACH:
+ return (ISC_R_HOSTUNREACH);
+ case ERROR_NETWORK_UNREACHABLE:
+ case WSAENETUNREACH:
+ return (ISC_R_NETUNREACH);
+ case ERROR_NO_NETWORK:
+ return (ISC_R_NETUNREACH);
+ case ERROR_PORT_UNREACHABLE:
+ return (ISC_R_HOSTUNREACH);
+ case ERROR_SEM_TIMEOUT:
+ return (ISC_R_TIMEDOUT);
+ case WSAECONNRESET:
+ case WSAENETRESET:
+ case WSAECONNABORTED:
+ case WSAEDISCON:
+ case ERROR_OPERATION_ABORTED:
+ case ERROR_CONNECTION_ABORTED:
+ case ERROR_REQUEST_ABORTED:
+ return (ISC_R_CONNECTIONRESET);
+ case WSAEADDRNOTAVAIL:
+ return (ISC_R_ADDRNOTAVAIL);
+ case ERROR_NETNAME_DELETED:
+ case WSAENETDOWN:
+ return (ISC_R_NETUNREACH);
+ case WSAEHOSTDOWN:
+ return (ISC_R_HOSTUNREACH);
+ case WSAENOBUFS:
+ return (ISC_R_NORESOURCES);
+ 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/win32/errno2result.h b/lib/isc/win32/errno2result.h
new file mode 100644
index 0000000..ce2d72e
--- /dev/null
+++ b/lib/isc/win32/errno2result.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef UNIX_ERRNO2RESULT_H
+#define UNIX_ERRNO2RESULT_H 1
+
+/* 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(posixerrno) \
+ isc__errno2resultx(posixerrno, true, __FILE__, __LINE__)
+
+isc_result_t
+isc__errno2resultx(int posixerrno, bool dolog, const char *file, int line);
+
+ISC_LANG_ENDDECLS
+
+#endif /* UNIX_ERRNO2RESULT_H */
diff --git a/lib/isc/win32/file.c b/lib/isc/win32/file.c
new file mode 100644
index 0000000..32f6a19
--- /dev/null
+++ b/lib/isc/win32/file.c
@@ -0,0 +1,1003 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#undef rename
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <io.h>
+#include <limits.h>
+#include <process.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/utime.h>
+
+#include <isc/file.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/stat.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include "errno2result.h"
+
+static const char alphnum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv"
+ "wxyz0123456789";
+
+/*
+ * Emulate UNIX mkstemp, which returns an open FD to the new file
+ *
+ */
+static int
+gettemp(char *path, bool binary, int *doopen) {
+ char *start, *trv;
+ struct stat sbuf;
+ int flags = O_CREAT | O_EXCL | O_RDWR;
+
+ if (binary) {
+ flags |= _O_BINARY;
+ }
+
+ trv = strrchr(path, 'X');
+ trv++;
+ /* extra X's get set to 0's */
+ while (*--trv == 'X') {
+ uint32_t which = isc_random_uniform(sizeof(alphnum) - 1);
+ *trv = alphnum[which];
+ }
+ /*
+ * check the target directory; if you have six X's and it
+ * doesn't exist this runs for a *very* long time.
+ */
+ for (start = trv + 1;; --trv) {
+ if (trv <= path) {
+ break;
+ }
+ if (*trv == '\\') {
+ *trv = '\0';
+ if (stat(path, &sbuf)) {
+ return (0);
+ }
+ if (!S_ISDIR(sbuf.st_mode)) {
+ errno = ENOTDIR;
+ return (0);
+ }
+ *trv = '\\';
+ break;
+ }
+ }
+
+ for (;;) {
+ if (doopen) {
+ if ((*doopen = open(path, flags,
+ _S_IREAD | _S_IWRITE)) >= 0)
+ {
+ return (1);
+ }
+ if (errno != EEXIST) {
+ return (0);
+ }
+ } else if (stat(path, &sbuf)) {
+ return (errno == ENOENT ? 1 : 0);
+ }
+
+ /* tricky little algorithm for backward compatibility */
+ for (trv = start;;) {
+ if (!*trv) {
+ return (0);
+ }
+ if (*trv == 'z') {
+ *trv++ = 'a';
+ } else {
+ if (isdigit((unsigned char)*trv)) {
+ *trv = 'a';
+ } else {
+ ++*trv;
+ }
+ break;
+ }
+ }
+ }
+ /*NOTREACHED*/
+}
+
+static int
+mkstemp(char *path, bool binary) {
+ int fd;
+
+ return (gettemp(path, binary, &fd) ? fd : -1);
+}
+
+/*
+ * 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_file_safemovefile is needed to be defined here to ensure that
+ * any file with the new name is renamed to a backup name and then the
+ * rename is done. If all goes well then the backup can be deleted,
+ * otherwise it gets renamed back.
+ */
+
+int
+isc_file_safemovefile(const char *oldname, const char *newname) {
+ BOOL filestatus;
+ char buf[512];
+ struct stat sbuf;
+ BOOL exists = FALSE;
+ int tmpfd;
+
+ /*
+ * Make sure we have something to do
+ */
+ if (stat(oldname, &sbuf) != 0) {
+ errno = ENOENT;
+ return (-1);
+ }
+
+ /*
+ * Rename to a backup the new file if it still exists
+ */
+ if (stat(newname, &sbuf) == 0) {
+ exists = TRUE;
+ strlcpy(buf, newname, sizeof(buf));
+ strlcat(buf, ".XXXXX", sizeof(buf));
+ tmpfd = mkstemp(buf, true);
+ if (tmpfd > 0) {
+ _close(tmpfd);
+ }
+ (void)DeleteFile(buf);
+ _chmod(newname, _S_IREAD | _S_IWRITE);
+
+ filestatus = MoveFile(newname, buf);
+ }
+ /* Now rename the file to the new name
+ */
+ _chmod(oldname, _S_IREAD | _S_IWRITE);
+
+ filestatus = MoveFile(oldname, newname);
+ if (filestatus == 0) {
+ /*
+ * Try to rename the backup back to the original name
+ * if the backup got created
+ */
+ if (exists == TRUE) {
+ filestatus = MoveFile(buf, newname);
+ if (filestatus == 0) {
+ errno = EACCES;
+ }
+ }
+ return (-1);
+ }
+
+ /*
+ * Delete the backup file if it got created
+ */
+ if (exists == TRUE) {
+ (void)DeleteFile(buf);
+ }
+ return (0);
+}
+
+isc_result_t
+isc_file_getmodtime(const char *file, isc_time_t *time) {
+ int fh;
+
+ REQUIRE(file != NULL);
+ REQUIRE(time != NULL);
+
+ if ((fh = open(file, _O_RDONLY | _O_BINARY)) < 0) {
+ return (isc__errno2result(errno));
+ }
+
+ if (!GetFileTime((HANDLE)_get_osfhandle(fh), NULL, NULL,
+ &time->absolute))
+ {
+ close(fh);
+ errno = EINVAL;
+ return (isc__errno2result(errno));
+ }
+ close(fh);
+ return (ISC_R_SUCCESS);
+}
+
+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 *time) {
+ int fh;
+
+ REQUIRE(file != NULL && time != NULL);
+
+ if ((fh = open(file, _O_RDWR | _O_BINARY)) < 0) {
+ return (isc__errno2result(errno));
+ }
+
+ /*
+ * Set the date via the filedate system call and return. Failing
+ * this call implies the new file times are not supported by the
+ * underlying file system.
+ */
+ if (!SetFileTime((HANDLE)_get_osfhandle(fh), NULL, &time->absolute,
+ &time->absolute))
+ {
+ close(fh);
+ errno = EINVAL;
+ return (isc__errno2result(errno));
+ }
+
+ close(fh);
+ return (ISC_R_SUCCESS);
+}
+
+#undef TEMPLATE
+#define TEMPLATE "XXXXXXXXXX.tmp" /* 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) {
+ 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);
+}
+
+isc_result_t
+isc_file_renameunique(const char *file, char *templet) {
+ int fd;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(file != NULL);
+ REQUIRE(templet != NULL);
+
+ fd = mkstemp(templet, true);
+ if (fd == -1) {
+ result = isc__errno2result(errno);
+ } else {
+ close(fd);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ int res;
+ res = isc_file_safemovefile(file, templet);
+ if (res != 0) {
+ result = isc__errno2result(errno);
+ (void)unlink(templet);
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+openuniquemode(char *templet, int mode, bool binary, FILE **fp) {
+ int fd;
+ FILE *f;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(templet != NULL);
+ REQUIRE(fp != NULL && *fp == NULL);
+
+ /*
+ * Win32 does not have mkstemp. Using emulation above.
+ */
+ fd = mkstemp(templet, binary);
+
+ if (fd == -1) {
+ result = isc__errno2result(errno);
+ }
+ if (result == ISC_R_SUCCESS) {
+#if 1
+ UNUSED(mode);
+#else /* if 1 */
+ (void)fchmod(fd, mode);
+#endif /* if 1 */
+ f = fdopen(fd, binary ? "wb+" : "w+");
+ if (f == NULL) {
+ result = isc__errno2result(errno);
+ (void)remove(templet);
+ (void)close(fd);
+ } else {
+ *fp = f;
+ }
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_file_openuniqueprivate(char *templet, FILE **fp) {
+ int mode = _S_IREAD | _S_IWRITE;
+ return (openuniquemode(templet, mode, false, fp));
+}
+
+isc_result_t
+isc_file_openunique(char *templet, FILE **fp) {
+ int mode = _S_IREAD | _S_IWRITE;
+ return (openuniquemode(templet, mode, false, fp));
+}
+
+isc_result_t
+isc_file_openuniquemode(char *templet, int mode, FILE **fp) {
+ return (openuniquemode(templet, mode, false, fp));
+}
+
+isc_result_t
+isc_file_bopenuniqueprivate(char *templet, FILE **fp) {
+ int mode = _S_IREAD | _S_IWRITE;
+ return (openuniquemode(templet, mode, true, fp));
+}
+
+isc_result_t
+isc_file_bopenunique(char *templet, FILE **fp) {
+ int mode = _S_IREAD | _S_IWRITE;
+ return (openuniquemode(templet, mode, true, fp));
+}
+
+isc_result_t
+isc_file_bopenuniquemode(char *templet, int mode, FILE **fp) {
+ return (openuniquemode(templet, mode, true, 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 = isc_file_safemovefile(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 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);
+ /*
+ * Look for c:\path\... style, c:/path/... or \\computer\shar\path...
+ * the UNC style file specs
+ */
+ if ((filename[0] == '\\') && (filename[1] == '\\')) {
+ return (true);
+ }
+ if (isalpha(filename[0]) && filename[1] == ':' && filename[2] == '\\') {
+ return (true);
+ }
+ if (isalpha(filename[0]) && filename[1] == ':' && filename[2] == '/') {
+ return (true);
+ }
+ return (false);
+}
+
+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 (filename[0] == '\\') {
+ return (true);
+ }
+ if (filename[0] == '/') {
+ return (true);
+ }
+ if (isc_file_iscurrentdir(filename)) {
+ return (true);
+ }
+ return (false);
+}
+
+const char *
+isc_file_basename(const char *filename) {
+ 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 *progname, size_t namelen) {
+ const char *s;
+ const char *p;
+ size_t len;
+
+ REQUIRE(filename != NULL);
+ REQUIRE(progname != NULL);
+
+ /*
+ * Strip the path from the name
+ */
+ s = isc_file_basename(filename);
+ if (s == NULL) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Strip any and all suffixes
+ */
+ p = strchr(s, '.');
+ if (p == NULL) {
+ if (namelen <= strlen(s)) {
+ return (ISC_R_NOSPACE);
+ }
+
+ strlcpy(progname, s, namelen);
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Copy the result to the buffer
+ */
+ len = p - s;
+ if (len >= namelen) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /* Copy up to 'len' bytes and NUL terminate. */
+ strlcpy(progname, s, ISC_MIN(len + 1, namelen));
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_file_absolutepath(const char *filename, char *path, size_t pathlen) {
+ char *ptrname;
+ DWORD retval;
+
+ REQUIRE(filename != NULL);
+ REQUIRE(path != NULL);
+
+ retval = GetFullPathName(filename, (DWORD)pathlen, path, &ptrname);
+
+ /* Something went wrong in getting the path */
+ if (retval == 0) {
+ return (ISC_R_NOTFOUND);
+ }
+ /* Caller needs to provide a larger buffer to contain the string */
+ if (retval >= pathlen) {
+ return (ISC_R_NOSPACE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_file_truncate(const char *filename, isc_offset_t size) {
+ int fh;
+
+ REQUIRE(filename != NULL && size >= 0);
+
+ if ((fh = open(filename, _O_RDWR | _O_BINARY)) < 0) {
+ return (isc__errno2result(errno));
+ }
+
+ if (_chsize(fh, size) != 0) {
+ close(fh);
+ return (isc__errno2result(errno));
+ }
+ close(fh);
+
+ return (ISC_R_SUCCESS);
+}
+
+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 **basename) {
+ char *dir;
+ const char *file, *slash;
+ char *backslash;
+
+ slash = strrchr(path, '/');
+
+ backslash = strrchr(path, '\\');
+ if ((slash != NULL && backslash != NULL && backslash > slash) ||
+ (slash == NULL && backslash != NULL))
+ {
+ slash = backslash;
+ }
+
+ 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;
+ *basename = file;
+
+ return (ISC_R_SUCCESS);
+}
+
+void *
+isc_file_mmap(void *addr, size_t len, int prot, int flags, int fd,
+ off_t offset) {
+ void *buf;
+ ssize_t ret;
+ off_t end;
+
+ UNUSED(addr);
+ UNUSED(prot);
+ UNUSED(flags);
+
+ end = lseek(fd, 0, SEEK_END);
+ lseek(fd, offset, SEEK_SET);
+ if (end - offset < (off_t)len) {
+ len = end - offset;
+ }
+
+ buf = malloc(len);
+ if (buf == NULL) {
+ return (NULL);
+ }
+
+ ret = read(fd, buf, (unsigned int)len);
+ if (ret != (ssize_t)len) {
+ free(buf);
+ buf = NULL;
+ }
+
+ return (buf);
+}
+
+int
+isc_file_munmap(void *addr, size_t len) {
+ UNUSED(len);
+ free(addr);
+ return (0);
+}
+
+#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 < 65) {
+ l = 65;
+ }
+
+ if (dir != NULL) {
+ l += strlen(dir) + 1;
+ }
+ if (ext != NULL) {
+ l += strlen(ext) + 1;
+ }
+
+ if (l > length || l > 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);
+}
+
+/*
+ * Based on http://blog.aaronballman.com/2011/08/how-to-check-access-rights/
+ */
+bool
+isc_file_isdirwritable(const char *path) {
+ DWORD length = 0;
+ HANDLE hToken = NULL;
+ PSECURITY_DESCRIPTOR security = NULL;
+ bool answer = false;
+
+ if (isc_file_isdirectory(path) != ISC_R_SUCCESS) {
+ return (answer);
+ }
+
+ /*
+ * Figure out buffer size. GetFileSecurity() should not succeed.
+ */
+ if (GetFileSecurity(path,
+ OWNER_SECURITY_INFORMATION |
+ GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION,
+ NULL, 0, &length))
+ {
+ return (answer);
+ }
+
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ return (answer);
+ }
+
+ security = malloc(length);
+ if (security == NULL) {
+ return (answer);
+ }
+
+ /*
+ * GetFileSecurity() should succeed.
+ */
+ if (!GetFileSecurity(path,
+ OWNER_SECURITY_INFORMATION |
+ GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION,
+ security, length, &length))
+ {
+ return (answer);
+ }
+
+ if (OpenProcessToken(GetCurrentProcess(),
+ TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE |
+ STANDARD_RIGHTS_READ,
+ &hToken))
+ {
+ HANDLE hImpersonatedToken = NULL;
+
+ if (DuplicateToken(hToken, SecurityImpersonation,
+ &hImpersonatedToken))
+ {
+ GENERIC_MAPPING mapping;
+ PRIVILEGE_SET privileges = { 0 };
+ DWORD grantedAccess = 0;
+ DWORD privilegesLength = sizeof(privileges);
+ BOOL result = FALSE;
+ DWORD genericAccessRights = GENERIC_WRITE;
+
+ mapping.GenericRead = FILE_GENERIC_READ;
+ mapping.GenericWrite = FILE_GENERIC_WRITE;
+ mapping.GenericExecute = FILE_GENERIC_EXECUTE;
+ mapping.GenericAll = FILE_ALL_ACCESS;
+
+ MapGenericMask(&genericAccessRights, &mapping);
+ if (AccessCheck(security, hImpersonatedToken,
+ genericAccessRights, &mapping,
+ &privileges, &privilegesLength,
+ &grantedAccess, &result))
+ {
+ answer = result;
+ }
+ CloseHandle(hImpersonatedToken);
+ }
+ CloseHandle(hToken);
+ }
+ free(security);
+ return (answer);
+}
diff --git a/lib/isc/win32/fsaccess.c b/lib/isc/win32/fsaccess.c
new file mode 100644
index 0000000..909f9e5
--- /dev/null
+++ b/lib/isc/win32/fsaccess.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.
+ */
+
+/*
+ * Note that Win32 does not have the concept of files having access
+ * and ownership bits. The FAT File system only has a readonly flag
+ * for everyone and that's all. NTFS uses ACL's which is a totally
+ * different concept of controlling access.
+ *
+ * This code needs to be revisited to set up proper access control for
+ * NTFS file systems. Nothing can be done for FAT file systems.
+ */
+
+#include <aclapi.h>
+#include <errno.h>
+#include <io.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <isc/file.h>
+#include <isc/stat.h>
+#include <isc/string.h>
+
+#include "errno2result.h"
+
+/*
+ * The OS-independent part of the API is in lib/isc.
+ */
+#include "../fsaccess.c"
+
+/* Store the user account name locally */
+static char username[255] = "\0";
+static DWORD namelen = 0;
+
+/*
+ * In order to set or retrieve access information, we need to obtain
+ * the File System type. These could be UNC-type shares.
+ */
+
+BOOL
+is_ntfs(const char *file) {
+ char drive[255];
+ char FSType[20];
+ char tmpbuf[256];
+ char *machinename;
+ char *sharename;
+ char filename[1024];
+ char *last;
+
+ REQUIRE(filename != NULL);
+
+ if (isc_file_absolutepath(file, filename, sizeof(filename)) !=
+ ISC_R_SUCCESS)
+ {
+ return (FALSE);
+ }
+
+ /*
+ * Look for c:\path\... style, c:/path/... or \\computer\shar\path...
+ * the UNC style file specs
+ */
+ if (isalpha(filename[0]) && filename[1] == ':' &&
+ (filename[2] == '\\' || filename[2] == '/'))
+ {
+ /* Copy 'c:\' or 'c:/' and NUL terminate. */
+ strlcpy(drive, filename, ISC_MIN(3 + 1, sizeof(drive)));
+ } else if ((filename[0] == '\\') && (filename[1] == '\\')) {
+ /* Find the machine and share name and rebuild the UNC */
+ strlcpy(tmpbuf, filename, sizeof(tmpbuf));
+ machinename = strtok_r(tmpbuf, "\\", &last);
+ sharename = strtok_r(NULL, "\\", &last);
+ strlcpy(drive, "\\\\", sizeof(drive));
+ strlcat(drive, machinename, sizeof(drive));
+ strlcat(drive, "\\", sizeof(drive));
+ strlcat(drive, sharename, sizeof(drive));
+ strlcat(drive, "\\", sizeof(drive));
+ } else { /* Not determinable */
+ return (FALSE);
+ }
+
+ GetVolumeInformation(drive, NULL, 0, NULL, 0, NULL, FSType,
+ sizeof(FSType));
+ if (strcmp(FSType, "NTFS") == 0) {
+ return (TRUE);
+ } else {
+ return (FALSE);
+ }
+}
+
+/*
+ * If it's not NTFS, we assume that it is FAT and proceed
+ * with almost nothing to do. Only the write flag can be set or
+ * cleared.
+ */
+isc_result_t
+FAT_fsaccess_set(const char *path, isc_fsaccess_t access) {
+ int mode;
+ isc_fsaccess_t bits;
+
+ /*
+ * Done with checking bad bits. Set mode_t.
+ */
+ mode = 0;
+
+#define SET_AND_CLEAR1(modebit) \
+ if ((access & bits) != 0) { \
+ mode |= modebit; \
+ access &= ~bits; \
+ }
+#define SET_AND_CLEAR(user, group, other) \
+ SET_AND_CLEAR1(user); \
+ bits <<= STEP; \
+ SET_AND_CLEAR1(group); \
+ bits <<= STEP; \
+ SET_AND_CLEAR1(other);
+
+ bits = ISC_FSACCESS_READ | ISC_FSACCESS_LISTDIRECTORY;
+
+ SET_AND_CLEAR(S_IRUSR, S_IRGRP, S_IROTH);
+
+ bits = ISC_FSACCESS_WRITE | ISC_FSACCESS_CREATECHILD |
+ ISC_FSACCESS_DELETECHILD;
+
+ SET_AND_CLEAR(S_IWUSR, S_IWGRP, S_IWOTH);
+
+ INSIST(access == 0);
+
+ if (_chmod(path, mode) < 0) {
+ return (isc__errno2result(errno));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+NTFS_Access_Control(const char *filename, const char *user, int access,
+ bool isdir) {
+ SECURITY_DESCRIPTOR sd;
+ BYTE aclBuffer[1024];
+ PACL pacl = (PACL)&aclBuffer;
+ BYTE sidBuffer[100];
+ PSID psid = (PSID)&sidBuffer;
+ DWORD sidBufferSize = sizeof(sidBuffer);
+ BYTE adminSidBuffer[100];
+ PSID padminsid = (PSID)&adminSidBuffer;
+ DWORD adminSidBufferSize = sizeof(adminSidBuffer);
+ BYTE otherSidBuffer[100];
+ PSID pothersid = (PSID)&otherSidBuffer;
+ DWORD otherSidBufferSize = sizeof(otherSidBuffer);
+ char domainBuffer[100];
+ DWORD domainBufferSize = sizeof(domainBuffer);
+ SID_NAME_USE snu;
+ DWORD NTFSbits;
+ int caccess;
+
+ /* Initialize an ACL */
+ if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
+ return (ISC_R_NOPERM);
+ }
+ if (!InitializeAcl(pacl, sizeof(aclBuffer), ACL_REVISION)) {
+ return (ISC_R_NOPERM);
+ }
+ if (!LookupAccountName(0, user, psid, &sidBufferSize, domainBuffer,
+ &domainBufferSize, &snu))
+ {
+ return (ISC_R_NOPERM);
+ }
+ domainBufferSize = sizeof(domainBuffer);
+ if (!LookupAccountName(0, "Administrators", padminsid,
+ &adminSidBufferSize, domainBuffer,
+ &domainBufferSize, &snu))
+ {
+ (void)GetLastError();
+ return (ISC_R_NOPERM);
+ }
+ domainBufferSize = sizeof(domainBuffer);
+ if (!LookupAccountName(0, "Everyone", pothersid, &otherSidBufferSize,
+ domainBuffer, &domainBufferSize, &snu))
+ {
+ (void)GetLastError();
+ return (ISC_R_NOPERM);
+ }
+
+ caccess = access;
+ /* Owner check */
+
+ NTFSbits = 0;
+ if ((caccess & ISC_FSACCESS_READ) != 0) {
+ NTFSbits |= FILE_GENERIC_READ;
+ }
+ if ((caccess & ISC_FSACCESS_WRITE) != 0) {
+ NTFSbits |= FILE_GENERIC_WRITE;
+ }
+ if ((caccess & ISC_FSACCESS_EXECUTE) != 0) {
+ NTFSbits |= FILE_GENERIC_EXECUTE;
+ }
+
+ /* For directories check the directory-specific bits */
+ if (isdir) {
+ if ((caccess & ISC_FSACCESS_CREATECHILD) != 0) {
+ NTFSbits |= FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE;
+ }
+ if ((caccess & ISC_FSACCESS_DELETECHILD) != 0) {
+ NTFSbits |= FILE_DELETE_CHILD;
+ }
+ if ((caccess & ISC_FSACCESS_LISTDIRECTORY) != 0) {
+ NTFSbits |= FILE_LIST_DIRECTORY;
+ }
+ if ((caccess & ISC_FSACCESS_ACCESSCHILD) != 0) {
+ NTFSbits |= FILE_TRAVERSE;
+ }
+ }
+
+ if (NTFSbits ==
+ (FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE))
+ {
+ NTFSbits |= FILE_ALL_ACCESS;
+ }
+ /*
+ * Owner and Administrator also get STANDARD_RIGHTS_ALL
+ * to ensure that they have full control
+ */
+
+ NTFSbits |= STANDARD_RIGHTS_ALL;
+
+ /* Add the ACE to the ACL */
+ if (!AddAccessAllowedAce(pacl, ACL_REVISION, NTFSbits, psid)) {
+ return (ISC_R_NOPERM);
+ }
+ if (!AddAccessAllowedAce(pacl, ACL_REVISION, NTFSbits, padminsid)) {
+ return (ISC_R_NOPERM);
+ }
+
+ /*
+ * Group is ignored since we can be in multiple groups or no group
+ * and its meaning is not clear on Win32
+ */
+
+ caccess = caccess >> STEP;
+
+ /*
+ * Other check. We translate this to be the same as Everyone
+ */
+
+ caccess = caccess >> STEP;
+
+ NTFSbits = 0;
+ if ((caccess & ISC_FSACCESS_READ) != 0) {
+ NTFSbits |= FILE_GENERIC_READ;
+ }
+ if ((caccess & ISC_FSACCESS_WRITE) != 0) {
+ NTFSbits |= FILE_GENERIC_WRITE;
+ }
+ if ((caccess & ISC_FSACCESS_EXECUTE) != 0) {
+ NTFSbits |= FILE_GENERIC_EXECUTE;
+ }
+
+ /* For directories check the directory-specific bits */
+ if (isdir == TRUE) {
+ if ((caccess & ISC_FSACCESS_CREATECHILD) != 0) {
+ NTFSbits |= FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE;
+ }
+ if ((caccess & ISC_FSACCESS_DELETECHILD) != 0) {
+ NTFSbits |= FILE_DELETE_CHILD;
+ }
+ if ((caccess & ISC_FSACCESS_LISTDIRECTORY) != 0) {
+ NTFSbits |= FILE_LIST_DIRECTORY;
+ }
+ if ((caccess & ISC_FSACCESS_ACCESSCHILD) != 0) {
+ NTFSbits |= FILE_TRAVERSE;
+ }
+ }
+ /* Add the ACE to the ACL */
+ if (!AddAccessAllowedAce(pacl, ACL_REVISION, NTFSbits, pothersid)) {
+ return (ISC_R_NOPERM);
+ }
+
+ if (!SetSecurityDescriptorDacl(&sd, TRUE, pacl, FALSE)) {
+ return (ISC_R_NOPERM);
+ }
+ if (!SetFileSecurity(filename, DACL_SECURITY_INFORMATION, &sd)) {
+ return (ISC_R_NOPERM);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+NTFS_fsaccess_set(const char *path, isc_fsaccess_t access, bool isdir) {
+ /*
+ * For NTFS we first need to get the name of the account under
+ * which BIND is running
+ */
+ if (namelen == 0) {
+ namelen = sizeof(username);
+ if (GetUserName(username, &namelen) == 0) {
+ return (ISC_R_FAILURE);
+ }
+ }
+ return (NTFS_Access_Control(path, username, access, isdir));
+}
+
+isc_result_t
+isc_fsaccess_set(const char *path, isc_fsaccess_t access) {
+ struct stat statb;
+ bool is_dir = false;
+ isc_result_t result;
+
+ if (stat(path, &statb) != 0) {
+ return (isc__errno2result(errno));
+ }
+
+ if ((statb.st_mode & S_IFDIR) != 0) {
+ is_dir = true;
+ } else if ((statb.st_mode & S_IFREG) == 0) {
+ return (ISC_R_INVALIDFILE);
+ }
+
+ result = check_bad_bits(access, is_dir);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Determine if this is a FAT or NTFS disk and
+ * call the appropriate function to set the permissions
+ */
+ if (is_ntfs(path)) {
+ return (NTFS_fsaccess_set(path, access, is_dir));
+ } else {
+ return (FAT_fsaccess_set(path, access));
+ }
+}
+
+isc_result_t
+isc_fsaccess_changeowner(const char *filename, const char *user) {
+ SECURITY_DESCRIPTOR psd;
+ BYTE sidBuffer[500];
+ BYTE groupBuffer[500];
+ PSID psid = (PSID)&sidBuffer;
+ DWORD sidBufferSize = sizeof(sidBuffer);
+ char domainBuffer[100];
+ DWORD domainBufferSize = sizeof(domainBuffer);
+ SID_NAME_USE snu;
+ PSID pSidGroup = (PSID)&groupBuffer;
+ DWORD groupBufferSize = sizeof(groupBuffer);
+
+ /*
+ * Determine if this is a FAT or NTFS disk and
+ * call the appropriate function to set the ownership
+ * FAT disks do not have ownership attributes so it's
+ * a noop.
+ */
+ if (is_ntfs(filename) == FALSE) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (!InitializeSecurityDescriptor(&psd, SECURITY_DESCRIPTOR_REVISION)) {
+ return (ISC_R_NOPERM);
+ }
+
+ if (!LookupAccountName(0, user, psid, &sidBufferSize, domainBuffer,
+ &domainBufferSize, &snu))
+ {
+ return (ISC_R_NOPERM);
+ }
+
+ /* Make sure administrators can get to it */
+ domainBufferSize = sizeof(domainBuffer);
+ if (!LookupAccountName(0, "Administrators", pSidGroup, &groupBufferSize,
+ domainBuffer, &domainBufferSize, &snu))
+ {
+ return (ISC_R_NOPERM);
+ }
+
+ if (!SetSecurityDescriptorOwner(&psd, psid, FALSE)) {
+ return (ISC_R_NOPERM);
+ }
+
+ if (!SetSecurityDescriptorGroup(&psd, pSidGroup, FALSE)) {
+ return (ISC_R_NOPERM);
+ }
+
+ if (!SetFileSecurity(filename,
+ OWNER_SECURITY_INFORMATION |
+ GROUP_SECURITY_INFORMATION,
+ &psd))
+ {
+ return (ISC_R_NOPERM);
+ }
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/win32/include/.clang-format b/lib/isc/win32/include/.clang-format
new file mode 120000
index 0000000..e919bba
--- /dev/null
+++ b/lib/isc/win32/include/.clang-format
@@ -0,0 +1 @@
+../../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/isc/win32/include/Makefile.in b/lib/isc/win32/include/Makefile.in
new file mode 100644
index 0000000..60a0a67
--- /dev/null
+++ b/lib/isc/win32/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = isc
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/isc/win32/include/isc/Makefile.in b/lib/isc/win32/include/isc/Makefile.in
new file mode 100644
index 0000000..7718caa
--- /dev/null
+++ b/lib/isc/win32/include/isc/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+HEADERS = align.h dir.h mutex.h net.h netdb.h once.h \
+ stat.h stdtime.h thread.h time.h
+
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}\isc
+
+install:: installdirs
+ for i in $(HEADERS); do \
+ $(INSTALL_DATA) $(srcdir)\$$i $(includedir)\isc || exit 1; \
+ done
diff --git a/lib/isc/win32/include/isc/align.h b/lib/isc/win32/include/isc/align.h
new file mode 100644
index 0000000..6e98665
--- /dev/null
+++ b/lib/isc/win32/include/isc/align.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+#define alignas(x) __declspec(align(x))
diff --git a/lib/isc/win32/include/isc/bind_registry.h b/lib/isc/win32/include/isc/bind_registry.h
new file mode 100644
index 0000000..6c997d6
--- /dev/null
+++ b/lib/isc/win32/include/isc/bind_registry.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_BINDREGISTRY_H
+#define ISC_BINDREGISTRY_H
+
+/*
+ * BIND makes use of the following Registry keys in various places, especially
+ * during startup and installation
+ */
+
+#define BIND_SUBKEY "Software\\ISC\\BIND"
+#define BIND_SESSION "CurrentSession"
+#define BIND_SESSION_SUBKEY "Software\\ISC\\BIND\\CurrentSession"
+#define BIND_UNINSTALL_SUBKEY \
+ "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\ISC BIND"
+
+#define EVENTLOG_APP_SUBKEY \
+ "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application"
+#define BIND_MESSAGE_SUBKEY \
+ "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\named"
+#define BIND_MESSAGE_NAME "named"
+
+#define BIND_SERVICE_SUBKEY "SYSTEM\\CurrentControlSet\\Services\\named"
+
+#define BIND_CONFIGFILE 0
+#define BIND_DEBUGLEVEL 1
+#define BIND_QUERYLOG 2
+#define BIND_FOREGROUND 3
+#define BIND_PORT 4
+
+#endif /* ISC_BINDREGISTRY_H */
diff --git a/lib/isc/win32/include/isc/bindevt.h b/lib/isc/win32/include/isc/bindevt.h
new file mode 100644
index 0000000..406efe3
--- /dev/null
+++ b/lib/isc/win32/include/isc/bindevt.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_BINDEVT_H
+#define ISC_BINDEVT_H 1
+
+/*
+ * This is used for the event log for both logging the messages and
+ * later on by the event viewer when looking at the events
+ */
+
+/*
+ * Values are 32 bit values laid out as follows:
+ *
+ * 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
+ * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+ * +---+-+-+-----------------------+-------------------------------+
+ * |Sev|C|R| Facility | Code |
+ * +---+-+-+-----------------------+-------------------------------+
+ *
+ * where
+ *
+ * Sev - is the severity code
+ *
+ * 00 - Success
+ * 01 - Informational
+ * 10 - Warning
+ * 11 - Error
+ *
+ * C - is the Customer code flag
+ *
+ * R - is a reserved bit
+ *
+ * Facility - is the facility code
+ *
+ * Code - is the facility's status code
+ *
+ *
+ * Define the facility codes
+ */
+
+/*
+ * Define the severity codes
+ */
+
+/*
+ * MessageId: BIND_ERR_MSG
+ *
+ * MessageText:
+ *
+ * %1
+ */
+#define BIND_ERR_MSG ((DWORD)0xC0000001L)
+
+/*
+ * MessageId: BIND_WARN_MSG
+ *
+ * MessageText:
+ *
+ * %1
+ */
+#define BIND_WARN_MSG ((DWORD)0x80000002L)
+
+/*
+ * MessageId: BIND_INFO_MSG
+ *
+ * MessageText:
+ *
+ * %1
+ */
+#define BIND_INFO_MSG ((DWORD)0x40000003L)
+
+#endif /* ISC_BINDEVT_H */
diff --git a/lib/isc/win32/include/isc/condition.h b/lib/isc/win32/include/isc/condition.h
new file mode 100644
index 0000000..738419c
--- /dev/null
+++ b/lib/isc/win32/include/isc/condition.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.
+ */
+
+#ifndef ISC_CONDITION_H
+#define ISC_CONDITION_H 1
+
+#include <windows.h>
+
+#include <isc/lang.h>
+#include <isc/mutex.h>
+#include <isc/thread.h>
+#include <isc/types.h>
+
+typedef struct isc_condition_thread isc_condition_thread_t;
+
+struct isc_condition_thread {
+ uintptr_t th;
+ HANDLE handle[2];
+ ISC_LINK(isc_condition_thread_t) link;
+};
+
+typedef struct isc_condition {
+ HANDLE events[2];
+ unsigned int waiters;
+ ISC_LIST(isc_condition_thread_t) threadlist;
+} isc_condition_t;
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_condition_init(isc_condition_t *);
+
+isc_result_t
+isc_condition_wait(isc_condition_t *, isc_mutex_t *);
+
+isc_result_t
+isc_condition_signal(isc_condition_t *);
+
+isc_result_t
+isc_condition_broadcast(isc_condition_t *);
+
+isc_result_t
+isc_condition_destroy(isc_condition_t *);
+
+isc_result_t
+isc_condition_waituntil(isc_condition_t *, isc_mutex_t *, isc_time_t *);
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_CONDITION_H */
diff --git a/lib/isc/win32/include/isc/dir.h b/lib/isc/win32/include/isc/dir.h
new file mode 100644
index 0000000..f0050dd
--- /dev/null
+++ b/lib/isc/win32/include/isc/dir.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_DIR_H
+#define ISC_DIR_H 1
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#include <isc/lang.h>
+#include <isc/platform.h>
+#include <isc/result.h>
+
+typedef struct {
+ char name[NAME_MAX];
+ unsigned int length;
+ WIN32_FIND_DATA find_data;
+} isc_direntry_t;
+
+typedef struct {
+ unsigned int magic;
+ char dirname[PATH_MAX];
+ isc_direntry_t entry;
+ bool entry_filled;
+ HANDLE search_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
+
+#endif /* ISC_DIR_H */
diff --git a/lib/isc/win32/include/isc/ipv6.h b/lib/isc/win32/include/isc/ipv6.h
new file mode 100644
index 0000000..4ab567a
--- /dev/null
+++ b/lib/isc/win32/include/isc/ipv6.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_IPV6_H
+#define ISC_IPV6_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*
+ * IPv6 definitions for systems which do not support IPv6.
+ *
+ * MP:
+ * No impact.
+ *
+ * Reliability:
+ * No anticipated impact.
+ *
+ * Resources:
+ * N/A.
+ *
+ * Security:
+ * No anticipated impact.
+ *
+ * Standards:
+ * RFC2553.
+ */
+
+#if _MSC_VER < 1300
+#define in6_addr in_addr6
+#endif /* if _MSC_VER < 1300 */
+
+#ifndef IN6ADDR_ANY_INIT
+#define IN6ADDR_ANY_INIT \
+ { \
+ { \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \
+ } \
+ }
+#endif /* ifndef IN6ADDR_ANY_INIT */
+#ifndef IN6ADDR_LOOPBACK_INIT
+#define IN6ADDR_LOOPBACK_INIT \
+ { \
+ { \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 \
+ } \
+ }
+#endif /* ifndef IN6ADDR_LOOPBACK_INIT */
+#ifndef IN6ADDR_V4MAPPED_INIT
+#define IN6ADDR_V4MAPPED_INIT \
+ { \
+ { \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0 \
+ } \
+ }
+#endif /* ifndef IN6ADDR_V4MAPPED_INIT */
+
+LIBISC_EXTERNAL_DATA extern const struct in6_addr isc_in6addr_any;
+LIBISC_EXTERNAL_DATA extern const struct in6_addr isc_in6addr_loopback;
+
+/*
+ * Unspecified
+ */
+#ifndef IN6_IS_ADDR_UNSPECIFIED
+#define IN6_IS_ADDR_UNSPECIFIED(a) \
+ (*((u_long *)((a)->s6_addr)) == 0 && \
+ *((u_long *)((a)->s6_addr) + 1) == 0 && \
+ *((u_long *)((a)->s6_addr) + 2) == 0 && \
+ *((u_long *)((a)->s6_addr) + 3) == 0)
+#endif /* ifndef IN6_IS_ADDR_UNSPECIFIED */
+
+/*
+ * Loopback
+ */
+#ifndef IN6_IS_ADDR_LOOPBACK
+#define IN6_IS_ADDR_LOOPBACK(a) \
+ (*((u_long *)((a)->s6_addr)) == 0 && \
+ *((u_long *)((a)->s6_addr) + 1) == 0 && \
+ *((u_long *)((a)->s6_addr) + 2) == 0 && \
+ *((u_long *)((a)->s6_addr) + 3) == htonl(1))
+#endif /* ifndef IN6_IS_ADDR_LOOPBACK */
+
+/*
+ * IPv4 compatible
+ */
+#define IN6_IS_ADDR_V4COMPAT(a) \
+ (*((u_long *)((a)->s6_addr)) == 0 && \
+ *((u_long *)((a)->s6_addr) + 1) == 0 && \
+ *((u_long *)((a)->s6_addr) + 2) == 0 && \
+ *((u_long *)((a)->s6_addr) + 3) != 0 && \
+ *((u_long *)((a)->s6_addr) + 3) != htonl(1))
+
+/*
+ * Mapped
+ */
+#define IN6_IS_ADDR_V4MAPPED(a) \
+ (*((u_long *)((a)->s6_addr)) == 0 && \
+ *((u_long *)((a)->s6_addr) + 1) == 0 && \
+ *((u_long *)((a)->s6_addr) + 2) == htonl(0x0000ffff))
+
+/*
+ * Multicast
+ */
+#define IN6_IS_ADDR_MULTICAST(a) ((a)->s6_addr[0] == 0xffU)
+
+/*
+ * Unicast link / site local.
+ */
+#ifndef IN6_IS_ADDR_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
+#define IN6_IS_ADDR_SITELOCAL(a) \
+ (((a)->s6_addr[0] == 0xfe) && (((a)->s6_addr[1] & 0xc0) == 0xc0))
+#endif /* ifndef IN6_IS_ADDR_SITELOCAL */
+
+#endif /* ISC_IPV6_H */
diff --git a/lib/isc/win32/include/isc/mutex.h b/lib/isc/win32/include/isc/mutex.h
new file mode 100644
index 0000000..3fcc538
--- /dev/null
+++ b/lib/isc/win32/include/isc/mutex.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_MUTEX_H
+#define ISC_MUTEX_H 1
+
+#include <windows.h>
+
+#include <isc/net.h>
+#include <isc/result.h>
+
+typedef CRITICAL_SECTION isc_mutex_t;
+
+/*
+ * This definition is here since some versions of WINBASE.H
+ * omits it for some reason.
+ */
+#if (_WIN32_WINNT < 0x0400)
+WINBASEAPI BOOL WINAPI
+TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
+#endif /* _WIN32_WINNT < 0x0400 */
+
+#define isc_mutex_init(mp) InitializeCriticalSection((mp))
+#define isc_mutex_lock(mp) (EnterCriticalSection((mp)), ISC_R_SUCCESS)
+#define isc_mutex_unlock(mp) (LeaveCriticalSection((mp)), ISC_R_SUCCESS)
+#define isc_mutex_trylock(mp) \
+ (TryEnterCriticalSection((mp)) ? ISC_R_SUCCESS : ISC_R_LOCKBUSY)
+#define isc_mutex_destroy(mp) (DeleteCriticalSection((mp)))
+
+/*
+ * This is a placeholder for now since we are not keeping any mutex stats
+ */
+#define isc_mutex_stats(fp) \
+ do { \
+ } while (0)
+
+#endif /* ISC_MUTEX_H */
diff --git a/lib/isc/win32/include/isc/net.h b/lib/isc/win32/include/isc/net.h
new file mode 100644
index 0000000..391614e
--- /dev/null
+++ b/lib/isc/win32/include/isc/net.h
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_NET_H
+#define ISC_NET_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*
+ * Basic Networking Types
+ *
+ * This module is responsible for defining the following basic networking
+ * types:
+ *
+ * struct in_addr
+ * struct in6_addr
+ * struct in6_pktinfo
+ * struct sockaddr
+ * struct sockaddr_in
+ * struct sockaddr_in6
+ * 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_ANY, IN6ADDR_ANY_INIT, in6addr_any, and
+ * in6addr_loopback are available.
+ *
+ * It ensures that IN_MULTICAST() is available to check for multicast
+ * addresses.
+ *
+ * MP:
+ * No impact.
+ *
+ * Reliability:
+ * No anticipated impact.
+ *
+ * Resources:
+ * N/A.
+ *
+ * Security:
+ * No anticipated impact.
+ *
+ * Standards:
+ * BSD Socket API
+ * RFC2553
+ */
+
+/***
+ *** Imports.
+ ***/
+#include <inttypes.h>
+
+#include <isc/platform.h>
+
+/*
+ * Because of some sort of problem in the MS header files, this cannot
+ * be simple "#include <winsock2.h>", because winsock2.h tries to include
+ * windows.h, which then generates an error out of mswsock.h. _You_
+ * figure it out.
+ */
+#ifndef _WINSOCKAPI_
+#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */
+#endif /* ifndef _WINSOCKAPI_ */
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+#include <isc/ipv6.h>
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#include <sys/types.h>
+
+/*
+ * This is here because named client, interfacemgr.c, etc. use the name as
+ * a variable
+ */
+#undef interface
+
+#ifndef INADDR_ANY
+#define INADDR_ANY 0x00000000UL
+#endif /* ifndef INADDR_ANY */
+
+#ifndef INADDR_LOOPBACK
+#define INADDR_LOOPBACK 0x7f000001UL
+#endif /* ifndef INADDR_LOOPBACK */
+
+#if _MSC_VER < 1300
+#define in6addr_any isc_in6addr_any
+#define in6addr_loopback isc_in6addr_loopback
+#endif /* if _MSC_VER < 1300 */
+
+/*
+ * Ensure type in_port_t is defined.
+ */
+typedef uint16_t in_port_t;
+
+/*
+ * 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.
+ */
+#ifndef MSG_TRUNC
+#define ISC_PLATFORM_RECVOVERFLOW
+#endif /* ifndef MSG_TRUNC */
+
+#define ISC__IPADDR(x) ((uint32_t)htonl((uint32_t)(x)))
+
+#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))
+
+/*
+ * Fix the FD_SET and FD_CLR Macros to properly cast
+ */
+#undef FD_CLR
+#define FD_CLR(fd, set) \
+ do { \
+ u_int __i; \
+ for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \
+ if (((fd_set FAR *)(set))->fd_array[__i] == \
+ (SOCKET)fd) { \
+ while (__i < \
+ ((fd_set FAR *)(set))->fd_count - 1) \
+ { \
+ ((fd_set FAR *)(set))->fd_array[__i] = \
+ ((fd_set FAR *)(set)) \
+ ->fd_array[__i + 1]; \
+ __i++; \
+ } \
+ ((fd_set FAR *)(set))->fd_count--; \
+ break; \
+ } \
+ } \
+ } while (0)
+
+#undef FD_SET
+#define FD_SET(fd, set) \
+ do { \
+ u_int __i; \
+ for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \
+ if (((fd_set FAR *)(set))->fd_array[__i] == \
+ (SOCKET)(fd)) \
+ { \
+ break; \
+ } \
+ } \
+ if (__i == ((fd_set FAR *)(set))->fd_count) { \
+ if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \
+ ((fd_set FAR *)(set))->fd_array[__i] = \
+ (SOCKET)(fd); \
+ ((fd_set FAR *)(set))->fd_count++; \
+ } \
+ } \
+ } while (0)
+
+/*
+ * Windows Sockets errors redefined as regular Berkeley error constants.
+ * These are usually commented out in Windows NT to avoid conflicts with
+ * errno.h. Use the WSA constants instead.
+ */
+
+#include <errno.h>
+
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#endif /* ifndef EWOULDBLOCK */
+#ifndef EINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#endif /* ifndef EINPROGRESS */
+#ifndef EALREADY
+#define EALREADY WSAEALREADY
+#endif /* ifndef EALREADY */
+#ifndef ENOTSOCK
+#define ENOTSOCK WSAENOTSOCK
+#endif /* ifndef ENOTSOCK */
+#ifndef EDESTADDRREQ
+#define EDESTADDRREQ WSAEDESTADDRREQ
+#endif /* ifndef EDESTADDRREQ */
+#ifndef EMSGSIZE
+#define EMSGSIZE WSAEMSGSIZE
+#endif /* ifndef EMSGSIZE */
+#ifndef EPROTOTYPE
+#define EPROTOTYPE WSAEPROTOTYPE
+#endif /* ifndef EPROTOTYPE */
+#ifndef ENOPROTOOPT
+#define ENOPROTOOPT WSAENOPROTOOPT
+#endif /* ifndef ENOPROTOOPT */
+#ifndef EPROTONOSUPPORT
+#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
+#endif /* ifndef EPROTONOSUPPORT */
+#ifndef ESOCKTNOSUPPORT
+#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
+#endif /* ifndef ESOCKTNOSUPPORT */
+#ifndef EOPNOTSUPP
+#define EOPNOTSUPP WSAEOPNOTSUPP
+#endif /* ifndef EOPNOTSUPP */
+#ifndef EPFNOSUPPORT
+#define EPFNOSUPPORT WSAEPFNOSUPPORT
+#endif /* ifndef EPFNOSUPPORT */
+#ifndef EAFNOSUPPORT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#endif /* ifndef EAFNOSUPPORT */
+#ifndef EADDRINUSE
+#define EADDRINUSE WSAEADDRINUSE
+#endif /* ifndef EADDRINUSE */
+#ifndef EADDRNOTAVAIL
+#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#endif /* ifndef EADDRNOTAVAIL */
+#ifndef ENETDOWN
+#define ENETDOWN WSAENETDOWN
+#endif /* ifndef ENETDOWN */
+#ifndef ENETUNREACH
+#define ENETUNREACH WSAENETUNREACH
+#endif /* ifndef ENETUNREACH */
+#ifndef ENETRESET
+#define ENETRESET WSAENETRESET
+#endif /* ifndef ENETRESET */
+#ifndef ECONNABORTED
+#define ECONNABORTED WSAECONNABORTED
+#endif /* ifndef ECONNABORTED */
+#ifndef ECONNRESET
+#define ECONNRESET WSAECONNRESET
+#endif /* ifndef ECONNRESET */
+#ifndef ENOBUFS
+#define ENOBUFS WSAENOBUFS
+#endif /* ifndef ENOBUFS */
+#ifndef EISCONN
+#define EISCONN WSAEISCONN
+#endif /* ifndef EISCONN */
+#ifndef ENOTCONN
+#define ENOTCONN WSAENOTCONN
+#endif /* ifndef ENOTCONN */
+#ifndef ESHUTDOWN
+#define ESHUTDOWN WSAESHUTDOWN
+#endif /* ifndef ESHUTDOWN */
+#ifndef ETOOMANYREFS
+#define ETOOMANYREFS WSAETOOMANYREFS
+#endif /* ifndef ETOOMANYREFS */
+#ifndef ETIMEDOUT
+#define ETIMEDOUT WSAETIMEDOUT
+#endif /* ifndef ETIMEDOUT */
+#ifndef ECONNREFUSED
+#define ECONNREFUSED WSAECONNREFUSED
+#endif /* ifndef ECONNREFUSED */
+#ifndef ELOOP
+#define ELOOP WSAELOOP
+#endif /* ifndef ELOOP */
+#ifndef EHOSTDOWN
+#define EHOSTDOWN WSAEHOSTDOWN
+#endif /* ifndef EHOSTDOWN */
+#ifndef EHOSTUNREACH
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#endif /* ifndef EHOSTUNREACH */
+#ifndef EPROCLIM
+#define EPROCLIM WSAEPROCLIM
+#endif /* ifndef EPROCLIM */
+#ifndef EUSERS
+#define EUSERS WSAEUSERS
+#endif /* ifndef EUSERS */
+#ifndef EDQUOT
+#define EDQUOT WSAEDQUOT
+#endif /* ifndef EDQUOT */
+#ifndef ESTALE
+#define ESTALE WSAESTALE
+#endif /* ifndef ESTALE */
+#ifndef EREMOTE
+#define EREMOTE WSAEREMOTE
+#endif /* ifndef EREMOTE */
+
+/***
+ *** Functions.
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_net_probeipv4(void);
+/*
+ * Check if the system's kernel supports IPv4.
+ *
+ * Returns:
+ *
+ * ISC_R_SUCCESS IPv4 is supported.
+ * ISC_R_NOTFOUND IPv4 is not supported.
+ * ISC_R_DISABLED IPv4 is disabled.
+ * ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_net_probeipv6(void);
+/*
+ * Check if the system's kernel supports IPv6.
+ *
+ * Returns:
+ *
+ * ISC_R_SUCCESS IPv6 is supported.
+ * ISC_R_NOTFOUND IPv6 is not supported.
+ * ISC_R_DISABLED IPv6 is disabled.
+ * ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_net_probeunix(void);
+/*
+ * Check if UNIX domain sockets are supported.
+ *
+ * Returns:
+ *
+ * ISC_R_SUCCESS
+ * ISC_R_NOTFOUND
+ */
+
+#define ISC_NET_DSCPRECVV4 0x01 /* Can receive sent DSCP value IPv4 */
+#define ISC_NET_DSCPRECVV6 0x02 /* Can receive sent DSCP value IPv6 */
+#define ISC_NET_DSCPSETV4 0x04 /* Can set DSCP on socket IPv4 */
+#define ISC_NET_DSCPSETV6 0x08 /* Can set DSCP on socket IPv6 */
+#define ISC_NET_DSCPPKTV4 0x10 /* Can set DSCP on per packet IPv4 */
+#define ISC_NET_DSCPPKTV6 0x20 /* Can set DSCP on per packet IPv6 */
+#define ISC_NET_DSCPALL 0x3f /* All valid flags */
+
+unsigned int
+isc_net_probedscp(void);
+/*%<
+ * Probe the level of DSCP support.
+ */
+
+isc_result_t
+isc_net_probe_ipv6only(void);
+/*
+ * Check if the system's kernel supports the IPV6_V6ONLY socket option.
+ *
+ * Returns:
+ *
+ * ISC_R_SUCCESS the option is supported for both TCP and UDP.
+ * ISC_R_NOTFOUND IPv6 itself or the option is not supported.
+ * 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:
+ *
+ * ISC_R_SUCCESS the option is supported.
+ * ISC_R_NOTFOUND IPv6 itself or the option is not supported.
+ * 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_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
+
+#endif /* ISC_NET_H */
diff --git a/lib/isc/win32/include/isc/netdb.h b/lib/isc/win32/include/isc/netdb.h
new file mode 100644
index 0000000..e37ecb5
--- /dev/null
+++ b/lib/isc/win32/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.
+ */
+
+#ifndef ISC_NETDB_H
+#define ISC_NETDB_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*
+ * Portable netdb.h support.
+ *
+ * This module is responsible for defining the get<x>by<y> APIs.
+ *
+ * MP:
+ * No impact.
+ *
+ * Reliability:
+ * No anticipated impact.
+ *
+ * Resources:
+ * N/A.
+ *
+ * Security:
+ * No anticipated impact.
+ *
+ * Standards:
+ * BSD API
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <isc/net.h>
+
+#endif /* ISC_NETDB_H */
diff --git a/lib/isc/win32/include/isc/ntgroups.h b/lib/isc/win32/include/isc/ntgroups.h
new file mode 100644
index 0000000..496f84a
--- /dev/null
+++ b/lib/isc/win32/include/isc/ntgroups.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_NTGROUPS_H
+#define ISC_NTGROUPS_H 1
+
+#include <isc/lang.h>
+#include <isc/result.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_ntsecurity_getaccountgroups(char *name, char **Groups,
+ unsigned int maxgroups, unsigned int *total);
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_NTGROUPS_H */
diff --git a/lib/isc/win32/include/isc/ntpaths.h b/lib/isc/win32/include/isc/ntpaths.h
new file mode 100644
index 0000000..37c915d
--- /dev/null
+++ b/lib/isc/win32/include/isc/ntpaths.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.
+ */
+
+/*
+ * Windows-specific path definitions
+ * These routines are used to set up and return system-specific path
+ * information about the files enumerated in NtPaths
+ */
+
+#ifndef ISC_NTPATHS_H
+#define ISC_NTPATHS_H
+
+#include <isc/lang.h>
+
+/*
+ * Index of paths needed
+ */
+enum NtPaths {
+ NAMED_CONF_PATH,
+ RESOLV_CONF_PATH,
+ RNDC_CONF_PATH,
+ NAMED_PID_PATH,
+ NAMED_LOCK_PATH,
+ LOCAL_STATE_DIR,
+ SYS_CONF_DIR,
+ RNDC_KEY_PATH,
+ SESSION_KEY_PATH,
+ BIND_KEYS_PATH
+};
+
+/*
+ * Define macros to get the path of the config files
+ */
+#define NAMED_CONFFILE isc_ntpaths_get(NAMED_CONF_PATH)
+#define RNDC_CONFFILE isc_ntpaths_get(RNDC_CONF_PATH)
+#define RNDC_KEYFILE isc_ntpaths_get(RNDC_KEY_PATH)
+#define SESSION_KEYFILE isc_ntpaths_get(SESSION_KEY_PATH)
+#define RESOLV_CONF isc_ntpaths_get(RESOLV_CONF_PATH)
+
+/*
+ * Information about where the files are on disk
+ */
+#define NAMED_LOCALSTATEDIR "/dns/bin"
+#define NAMED_SYSCONFDIR "/dns/etc"
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_ntpaths_init(void);
+
+char *
+isc_ntpaths_get(int);
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_NTPATHS_H */
diff --git a/lib/isc/win32/include/isc/offset.h b/lib/isc/win32/include/isc/offset.h
new file mode 100644
index 0000000..f09cfdb
--- /dev/null
+++ b/lib/isc/win32/include/isc/offset.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_OFFSET_H
+#define ISC_OFFSET_H 1
+
+/*
+ * File offsets are operating-system dependent.
+ */
+#include <limits.h> /* Required for CHAR_BIT. */
+
+#include <sys/types.h>
+
+typedef _off_t isc_offset_t;
+
+#endif /* ISC_OFFSET_H */
diff --git a/lib/isc/win32/include/isc/once.h b/lib/isc/win32/include/isc/once.h
new file mode 100644
index 0000000..d2d841f
--- /dev/null
+++ b/lib/isc/win32/include/isc/once.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_ONCE_H
+#define ISC_ONCE_H 1
+
+#include <isc/lang.h>
+#include <isc/result.h>
+
+ISC_LANG_BEGINDECLS
+
+typedef struct {
+ int status;
+ int counter;
+} isc_once_t;
+
+#define ISC_ONCE_INIT_NEEDED 0
+#define ISC_ONCE_INIT_DONE 1
+
+#define ISC_ONCE_INIT \
+ { \
+ ISC_ONCE_INIT_NEEDED, 1 \
+ }
+
+isc_result_t
+isc_once_do(isc_once_t *controller, void (*function)(void));
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_ONCE_H */
diff --git a/lib/isc/win32/include/isc/platform.h.in b/lib/isc/win32/include/isc/platform.h.in
new file mode 100644
index 0000000..b3fad9c
--- /dev/null
+++ b/lib/isc/win32/include/isc/platform.h.in
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_PLATFORM_H
+#define ISC_PLATFORM_H 1
+
+/*****
+ ***** Platform-dependent defines.
+ *****/
+
+#if _MSC_VER > 1400
+#define HAVE_TLS 1
+#define HAVE___DECLSPEC_THREAD 1
+#endif
+
+/*
+ * Some compatibility cludges
+ */
+
+#if defined(_WIN32) || defined(_WIN64)
+/* We are on Windows */
+# define strtok_r strtok_s
+
+#define ISC_STRERRORSIZE 128
+
+#ifndef strtoull
+#define strtoull _strtoui64
+#endif
+
+#include <stdint.h>
+#if _MSC_VER < 1914
+typedef uint32_t socklen_t;
+#endif
+
+#endif
+
+#define __builtin_unreachable() __assume(0)
+
+/*
+ * Remove __attribute__ ((foo)) on Windows
+ */
+
+#define __attribute__(attribute) /* do nothing */
+
+/*
+ * Limits
+ */
+
+#ifndef NAME_MAX
+#define NAME_MAX _MAX_FNAME
+#endif
+
+#ifndef PATH_MAX
+#define PATH_MAX _MAX_PATH
+#endif
+
+/***
+ *** Network.
+ ***/
+
+#undef MSG_TRUNC
+
+typedef uint16_t sa_family_t;
+
+/*
+ * Define if the platform has <sys/un.h>.
+ */
+#undef ISC_PLATFORM_HAVESYSUNH
+
+/*
+ * Defines for the noreturn attribute.
+ */
+#define ISC_PLATFORM_NORETURN_PRE __declspec(noreturn)
+#define ISC_PLATFORM_NORETURN_POST
+
+/*
+ * Set up a macro for importing and exporting from the DLL
+ */
+
+#ifdef LIBISC_EXPORTS
+#define LIBISC_EXTERNAL_DATA __declspec(dllexport)
+#else
+#define LIBISC_EXTERNAL_DATA __declspec(dllimport)
+#endif
+
+#ifdef LIBDNS_EXPORTS
+#define LIBDNS_EXTERNAL_DATA __declspec(dllexport)
+#else
+#define LIBDNS_EXTERNAL_DATA __declspec(dllimport)
+#endif
+
+#ifdef LIBISCCC_EXPORTS
+#define LIBISCCC_EXTERNAL_DATA __declspec(dllexport)
+#else
+#define LIBISCCC_EXTERNAL_DATA __declspec(dllimport)
+#endif
+
+#ifdef LIBISCCFG_EXPORTS
+#define LIBISCCFG_EXTERNAL_DATA __declspec(dllexport)
+#else
+#define LIBISCCFG_EXTERNAL_DATA __declspec(dllimport)
+#endif
+
+#ifdef LIBNS_EXPORTS
+#define LIBNS_EXTERNAL_DATA __declspec(dllexport)
+#else
+#define LIBNS_EXTERNAL_DATA __declspec(dllimport)
+#endif
+
+#ifdef LIBBIND9_EXPORTS
+#define LIBBIND9_EXTERNAL_DATA __declspec(dllexport)
+#else
+#define LIBBIND9_EXTERNAL_DATA __declspec(dllimport)
+#endif
+
+#ifdef LIBTESTS_EXPORTS
+#define LIBTESTS_EXTERNAL_DATA __declspec(dllexport)
+#else
+#define LIBTESTS_EXTERNAL_DATA __declspec(dllimport)
+#endif
+
+#endif /* ISC_PLATFORM_H */
diff --git a/lib/isc/win32/include/isc/stat.h b/lib/isc/win32/include/isc/stat.h
new file mode 100644
index 0000000..63577f9
--- /dev/null
+++ b/lib/isc/win32/include/isc/stat.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_STAT_H
+#define ISC_STAT_H 1
+
+#include <sys/stat.h>
+
+/*
+ * Windows doesn't typedef this.
+ */
+typedef unsigned short mode_t;
+
+/* open() under unix allows setting of read/write permissions
+ * at the owner, group and other levels. These don't exist in NT
+ * We'll just map them all to the NT equivalent
+ */
+
+#define S_IREAD _S_IREAD /* read permission, owner */
+#define S_IWRITE _S_IWRITE /* write permission, owner */
+#define S_IRUSR _S_IREAD /* Owner read permission */
+#define S_IWUSR _S_IWRITE /* Owner write permission */
+#define S_IRGRP _S_IREAD /* Group read permission */
+#define S_IWGRP _S_IWRITE /* Group write permission */
+#define S_IROTH _S_IREAD /* Other read permission */
+#define S_IWOTH _S_IWRITE /* Other write permission */
+
+#ifndef S_IFMT
+#define S_IFMT _S_IFMT
+#endif /* ifndef S_IFMT */
+#ifndef S_IFDIR
+#define S_IFDIR _S_IFDIR
+#endif /* ifndef S_IFDIR */
+#ifndef S_IFCHR
+#define S_IFCHR _S_IFCHR
+#endif /* ifndef S_IFCHR */
+#ifndef S_IFREG
+#define S_IFREG _S_IFREG
+#endif /* ifndef S_IFREG */
+
+#ifndef S_ISDIR
+#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
+#endif /* ifndef S_ISDIR */
+#ifndef S_ISREG
+#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
+#endif /* ifndef S_ISREG */
+
+#endif /* ISC_STAT_H */
diff --git a/lib/isc/win32/include/isc/stdatomic.h b/lib/isc/win32/include/isc/stdatomic.h
new file mode 100644
index 0000000..dd5293f
--- /dev/null
+++ b/lib/isc/win32/include/isc/stdatomic.h
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 WIN32_LEAN_AND_MEAN
+#include <intrin.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <uchar.h>
+#include <windows.h>
+
+#pragma warning(disable : 4133)
+#pragma warning(disable : 4090)
+
+#define InterlockedExchangeAdd8 _InterlockedExchangeAdd8
+#define InterlockedCompareExchange8 _InterlockedCompareExchange8
+
+#pragma intrinsic(_InterlockedCompareExchange8, _InterlockedExchangeAdd8)
+
+#include <isc/util.h>
+
+#ifndef __ATOMIC_RELAXED
+#define __ATOMIC_RELAXED 0
+#endif /* ifndef __ATOMIC_RELAXED */
+#ifndef __ATOMIC_CONSUME
+#define __ATOMIC_CONSUME 1
+#endif /* ifndef __ATOMIC_CONSUME */
+#ifndef __ATOMIC_ACQUIRE
+#define __ATOMIC_ACQUIRE 2
+#endif /* ifndef __ATOMIC_ACQUIRE */
+#ifndef __ATOMIC_RELEASE
+#define __ATOMIC_RELEASE 3
+#endif /* ifndef __ATOMIC_RELEASE */
+#ifndef __ATOMIC_ACQ_REL
+#define __ATOMIC_ACQ_REL 4
+#endif /* ifndef __ATOMIC_ACQ_REL */
+#ifndef __ATOMIC_SEQ_CST
+#define __ATOMIC_SEQ_CST 5
+#endif /* ifndef __ATOMIC_SEQ_CST */
+
+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
+};
+
+typedef enum memory_order memory_order;
+
+/*
+ * If you add a type with different sizeof() length,
+ * you need to implement atomic_<foo>_explicitNN macros.
+ */
+
+typedef bool volatile atomic_bool;
+typedef char volatile atomic_char;
+typedef signed char volatile atomic_schar;
+typedef unsigned char volatile atomic_uchar;
+typedef short volatile atomic_short;
+typedef unsigned short volatile atomic_ushort;
+typedef int volatile atomic_int;
+typedef unsigned int volatile atomic_uint;
+typedef long volatile atomic_long;
+typedef unsigned long volatile atomic_ulong;
+typedef long long volatile atomic_llong;
+typedef unsigned long long volatile atomic_ullong;
+typedef char16_t volatile atomic_char16_t;
+typedef char32_t volatile atomic_char32_t;
+typedef wchar_t volatile atomic_wchar_t;
+typedef int_least8_t volatile atomic_int_least8_t;
+typedef uint_least8_t volatile atomic_uint_least8_t;
+typedef int_least16_t volatile atomic_int_least16_t;
+typedef uint_least16_t volatile atomic_uint_least16_t;
+typedef int_least32_t volatile atomic_int_least32_t;
+typedef uint_least32_t volatile atomic_uint_least32_t;
+typedef int_least64_t volatile atomic_int_least64_t;
+typedef uint_least64_t volatile atomic_uint_least64_t;
+typedef int_fast8_t volatile atomic_int_fast8_t;
+typedef uint_fast8_t volatile atomic_uint_fast8_t;
+typedef int_fast16_t volatile atomic_int_fast16_t;
+typedef uint_fast16_t volatile atomic_uint_fast16_t;
+typedef int_fast32_t volatile atomic_int_fast32_t;
+typedef uint_fast32_t volatile atomic_uint_fast32_t;
+typedef int_fast64_t volatile atomic_int_fast64_t;
+typedef uint_fast64_t volatile atomic_uint_fast64_t;
+typedef intptr_t volatile atomic_intptr_t;
+typedef uintptr_t volatile atomic_uintptr_t;
+typedef size_t volatile atomic_size_t;
+typedef ptrdiff_t volatile atomic_ptrdiff_t;
+typedef intmax_t volatile atomic_intmax_t;
+typedef uintmax_t volatile atomic_uintmax_t;
+
+#define atomic_init(obj, desired) (*(obj) = (desired))
+
+#define atomic_store_explicit8(obj, desired, order) \
+ (void)InterlockedExchange8((atomic_int_fast8_t *)obj, desired)
+
+#define atomic_store_explicit16(obj, desired, order) \
+ (order == memory_order_relaxed \
+ ? (void)InterlockedExchangeNoFence16((atomic_short *)obj, \
+ desired) \
+ : (order == memory_order_acquire \
+ ? (void)InterlockedExchangeAcquire16( \
+ (atomic_short *)obj, desired) \
+ : (void)InterlockedExchange16((atomic_short *)obj, \
+ desired)))
+
+#define atomic_store_explicit32(obj, desired, order) \
+ (order == memory_order_relaxed \
+ ? (void)InterlockedExchangeNoFence( \
+ (atomic_int_fast32_t *)obj, desired) \
+ : (order == memory_order_acquire \
+ ? (void)InterlockedExchangeAcquire( \
+ (atomic_int_fast32_t *)obj, desired) \
+ : (void)InterlockedExchange( \
+ (atomic_int_fast32_t *)obj, desired)))
+
+#ifdef _WIN64
+#define atomic_store_explicit64(obj, desired, order) \
+ (order == memory_order_relaxed \
+ ? (void)InterlockedExchangeNoFence64( \
+ (atomic_int_fast64_t *)obj, desired) \
+ : (order == memory_order_acquire \
+ ? (void)InterlockedExchangeAcquire64( \
+ (atomic_int_fast64_t *)obj, desired) \
+ : (void)InterlockedExchange64( \
+ (atomic_int_fast64_t *)obj, desired)))
+#else /* ifdef _WIN64 */
+#define atomic_store_explicit64(obj, desired, order) \
+ (void)InterlockedExchange64((atomic_int_fast64_t *)obj, desired)
+#endif /* ifdef _WIN64 */
+
+static inline void
+atomic_store_abort() {
+ UNREACHABLE();
+}
+
+#define atomic_store_explicit(obj, desired, order) \
+ (sizeof(*(obj)) == 8 \
+ ? atomic_store_explicit64(obj, desired, order) \
+ : (sizeof(*(obj)) == 4 \
+ ? atomic_store_explicit32(obj, desired, order) \
+ : (sizeof(*(obj)) == 2 \
+ ? atomic_store_explicit16(obj, desired, \
+ order) \
+ : (sizeof(*(obj)) == 1 \
+ ? atomic_store_explicit8( \
+ obj, desired, \
+ order) \
+ : atomic_store_abort()))))
+
+#define atomic_store(obj, desired) \
+ atomic_store_explicit(obj, desired, memory_order_seq_cst)
+
+#define atomic_load_explicit8(obj, order) \
+ (int8_t) InterlockedOr8((atomic_int_fast8_t *)obj, 0)
+
+#define atomic_load_explicit16(obj, order) \
+ (short)InterlockedOr16((atomic_short *)obj, 0)
+
+#define atomic_load_explicit32(obj, order) \
+ (order == memory_order_relaxed \
+ ? (int32_t)InterlockedOrNoFence((atomic_int_fast32_t *)obj, \
+ 0) \
+ : (order == memory_order_acquire \
+ ? (int32_t)InterlockedOrAcquire( \
+ (atomic_int_fast32_t *)obj, 0) \
+ : (order == memory_order_release \
+ ? (int32_t)InterlockedOrRelease( \
+ (atomic_int_fast32_t *)obj, 0) \
+ : (int32_t)InterlockedOr( \
+ (atomic_int_fast32_t *)obj, \
+ 0))))
+
+#ifdef _WIN64
+#define atomic_load_explicit64(obj, order) \
+ (order == memory_order_relaxed \
+ ? InterlockedOr64NoFence((atomic_int_fast64_t *)obj, 0) \
+ : (order == memory_order_acquire \
+ ? InterlockedOr64Acquire( \
+ (atomic_int_fast64_t *)obj, 0) \
+ : (order == memory_order_release \
+ ? InterlockedOr64Release( \
+ (atomic_int_fast64_t *)obj, 0) \
+ : InterlockedOr64( \
+ (atomic_int_fast64_t *)obj, \
+ 0))))
+#else /* ifdef _WIN64 */
+#define atomic_load_explicit64(obj, order) \
+ InterlockedOr64((atomic_int_fast64_t *)obj, 0)
+#endif /* ifdef _WIN64 */
+
+static inline int8_t
+atomic_load_abort() {
+ UNREACHABLE();
+}
+
+#define atomic_load_explicit(obj, order) \
+ (((sizeof(*(obj)) == 8) \
+ ? atomic_load_explicit64(obj, order) \
+ : ((sizeof(*(obj)) == 4) \
+ ? atomic_load_explicit32(obj, order) \
+ : ((sizeof(*(obj)) == 2) \
+ ? atomic_load_explicit16(obj, order) \
+ : ((sizeof(*(obj)) == 1) \
+ ? atomic_load_explicit8( \
+ obj, order) \
+ : atomic_load_abort())))) & \
+ ((sizeof(*(obj)) == 8) \
+ ? 0xffffffffffffffffULL \
+ : ((sizeof(*(obj)) == 4) \
+ ? 0xffffffffULL \
+ : ((sizeof(*(obj)) == 2) \
+ ? 0xffffULL \
+ : ((sizeof(*(obj)) == 1) \
+ ? 0xffULL \
+ : atomic_load_abort())))))
+
+#define atomic_load(obj) atomic_load_explicit(obj, memory_order_seq_cst)
+
+#define atomic_fetch_add_explicit8(obj, arg, order) \
+ InterlockedExchangeAdd8((atomic_int_fast8_t *)obj, arg)
+
+#define atomic_fetch_add_explicit16(obj, arg, order) \
+ InterlockedExchangeAdd16((atomic_short *)obj, arg)
+
+#define atomic_fetch_add_explicit32(obj, arg, order) \
+ (order == memory_order_relaxed \
+ ? InterlockedExchangeAddNoFence((atomic_int_fast32_t *)obj, \
+ arg) \
+ : (order == memory_order_acquire \
+ ? InterlockedExchangeAddAcquire( \
+ (atomic_int_fast32_t *)obj, arg) \
+ : (order == memory_order_release \
+ ? InterlockedExchangeAddRelease( \
+ (atomic_int_fast32_t *)obj, \
+ arg) \
+ : InterlockedExchangeAdd( \
+ (atomic_int_fast32_t *)obj, \
+ arg))))
+
+#ifdef _WIN64
+#define atomic_fetch_add_explicit64(obj, arg, order) \
+ (order == memory_order_relaxed \
+ ? InterlockedExchangeAddNoFence64((atomic_int_fast64_t *)obj, \
+ arg) \
+ : (order == memory_order_acquire \
+ ? InterlockedExchangeAddAcquire64( \
+ (atomic_int_fast64_t *)obj, arg) \
+ : (order == memory_order_release \
+ ? InterlockedExchangeAddRelease64( \
+ (atomic_int_fast64_t *)obj, \
+ arg) \
+ : InterlockedExchangeAdd64( \
+ (atomic_int_fast64_t *)obj, \
+ arg))))
+#else /* ifdef _WIN64 */
+#define atomic_fetch_add_explicit64(obj, arg, order) \
+ InterlockedExchangeAdd64((atomic_int_fast64_t *)obj, arg)
+#endif /* ifdef _WIN64 */
+
+static inline int8_t
+atomic_add_abort() {
+ UNREACHABLE();
+}
+
+#define atomic_fetch_add_explicit(obj, arg, order) \
+ (sizeof(*(obj)) == 8 \
+ ? atomic_fetch_add_explicit64(obj, arg, order) \
+ : (sizeof(*(obj)) == 4 \
+ ? atomic_fetch_add_explicit32(obj, arg, order) \
+ : (sizeof(*(obj)) == 2 \
+ ? atomic_fetch_add_explicit16(obj, arg, \
+ order) \
+ : (sizeof(*(obj)) == 1 \
+ ? atomic_fetch_add_explicit8( \
+ obj, arg, order) \
+ : atomic_add_abort()))))
+
+#define atomic_fetch_add(obj, arg) \
+ atomic_fetch_add_explicit(obj, arg, memory_order_seq_cst)
+
+#define atomic_fetch_sub_explicit(obj, arg, order) \
+ atomic_fetch_add_explicit(obj, -arg, order)
+
+#define atomic_fetch_sub(obj, arg) \
+ atomic_fetch_sub_explicit(obj, arg, memory_order_seq_cst)
+
+#define atomic_fetch_and_explicit8(obj, arg, order) \
+ InterlockedAnd8((atomic_int_fast8_t *)obj, arg)
+
+#define atomic_fetch_and_explicit16(obj, arg, order) \
+ InterlockedAnd16((atomic_short *)obj, arg)
+
+#define atomic_fetch_and_explicit32(obj, arg, order) \
+ (order == memory_order_relaxed \
+ ? InterlockedAndNoFence((atomic_int_fast32_t *)obj, arg) \
+ : (order == memory_order_acquire \
+ ? InterlockedAndAcquire( \
+ (atomic_int_fast32_t *)obj, arg) \
+ : (order == memory_order_release \
+ ? InterlockedAndRelease( \
+ (atomic_int_fast32_t *)obj, \
+ arg) \
+ : InterlockedAnd( \
+ (atomic_int_fast32_t *)obj, \
+ arg))))
+
+#ifdef _WIN64
+#define atomic_fetch_and_explicit64(obj, arg, order) \
+ (order == memory_order_relaxed \
+ ? InterlockedAnd64NoFence((atomic_int_fast64_t *)obj, arg) \
+ : (order == memory_order_acquire \
+ ? InterlockedAnd64Acquire( \
+ (atomic_int_fast64_t *)obj, arg) \
+ : (order == memory_order_release \
+ ? InterlockedAnd64Release( \
+ (atomic_int_fast64_t *)obj, \
+ arg) \
+ : InterlockedAnd64( \
+ (atomic_int_fast64_t *)obj, \
+ arg))))
+#else /* ifdef _WIN64 */
+#define atomic_fetch_and_explicit64(obj, arg, order) \
+ InterlockedAnd64((atomic_int_fast64_t *)obj, arg)
+#endif /* ifdef _WIN64 */
+
+static inline int8_t
+atomic_and_abort() {
+ UNREACHABLE();
+}
+
+#define atomic_fetch_and_explicit(obj, arg, order) \
+ (sizeof(*(obj)) == 8 \
+ ? atomic_fetch_and_explicit64(obj, arg, order) \
+ : (sizeof(*(obj)) == 4 \
+ ? atomic_fetch_and_explicit32(obj, arg, order) \
+ : (sizeof(*(obj)) == 2 \
+ ? atomic_fetch_and_explicit16(obj, arg, \
+ order) \
+ : (sizeof(*(obj)) == 1 \
+ ? atomic_fetch_and_explicit8( \
+ obj, arg, order) \
+ : atomic_and_abort()))))
+
+#define atomic_fetch_and(obj, arg) \
+ atomic_fetch_and_explicit(obj, arg, memory_order_seq_cst)
+
+#define atomic_fetch_or_explicit8(obj, arg, order) \
+ InterlockedOr8((atomic_int_fast8_t *)obj, arg)
+
+#define atomic_fetch_or_explicit16(obj, arg, order) \
+ InterlockedOr16((atomic_short *)obj, arg)
+
+#define atomic_fetch_or_explicit32(obj, arg, order) \
+ (order == memory_order_relaxed \
+ ? InterlockedOrNoFence((atomic_int_fast32_t *)obj, arg) \
+ : (order == memory_order_acquire \
+ ? InterlockedOrAcquire((atomic_int_fast32_t *)obj, \
+ arg) \
+ : (order == memory_order_release \
+ ? InterlockedOrRelease( \
+ (atomic_int_fast32_t *)obj, \
+ arg) \
+ : InterlockedOr( \
+ (atomic_int_fast32_t *)obj, \
+ arg))))
+
+#ifdef _WIN64
+#define atomic_fetch_or_explicit64(obj, arg, order) \
+ (order == memory_order_relaxed \
+ ? InterlockedOr64NoFence((atomic_int_fast64_t *)obj, arg) \
+ : (order == memory_order_acquire \
+ ? InterlockedOr64Acquire( \
+ (atomic_int_fast64_t *)obj, arg) \
+ : (order == memory_order_release \
+ ? InterlockedOr64Release( \
+ (atomic_int_fast64_t *)obj, \
+ arg) \
+ : InterlockedOr64( \
+ (atomic_int_fast64_t *)obj, \
+ arg))))
+#else /* ifdef _WIN64 */
+#define atomic_fetch_or_explicit64(obj, arg, order) \
+ InterlockedOr64((atomic_int_fast64_t *)obj, arg)
+#endif /* ifdef _WIN64 */
+
+static inline int8_t
+atomic_or_abort() {
+ UNREACHABLE();
+}
+
+#define atomic_fetch_or_explicit(obj, arg, order) \
+ (sizeof(*(obj)) == 8 \
+ ? atomic_fetch_or_explicit64(obj, arg, order) \
+ : (sizeof(*(obj)) == 4 \
+ ? atomic_fetch_or_explicit32(obj, arg, order) \
+ : (sizeof(*(obj)) == 2 \
+ ? atomic_fetch_or_explicit16(obj, arg, \
+ order) \
+ : (sizeof(*(obj)) == 1 \
+ ? atomic_fetch_or_explicit8( \
+ obj, arg, order) \
+ : atomic_or_abort()))))
+
+#define atomic_fetch_or(obj, arg) \
+ atomic_fetch_or_explicit(obj, arg, memory_order_seq_cst)
+
+static inline bool
+atomic_compare_exchange_strong_explicit8(atomic_int_fast8_t *obj,
+ int8_t *expected, int8_t desired,
+ memory_order succ, memory_order fail) {
+ bool __r;
+ int8_t __v;
+
+ UNUSED(succ);
+ UNUSED(fail);
+
+ __v = InterlockedCompareExchange8((atomic_int_fast8_t *)obj, desired,
+ *expected);
+ __r = (*(expected) == __v);
+ if (!__r) {
+ *(expected) = __v;
+ }
+ return (__r);
+}
+
+static inline bool
+atomic_compare_exchange_strong_explicit16(atomic_short *obj, short *expected,
+ short desired, memory_order succ,
+ memory_order fail) {
+ bool __r;
+ short __v;
+
+ UNUSED(succ);
+ UNUSED(fail);
+
+ __v = InterlockedCompareExchange16((atomic_short *)obj, desired,
+ *expected);
+ __r = (*(expected) == __v);
+ if (!__r) {
+ *(expected) = __v;
+ }
+ return (__r);
+}
+
+static inline bool
+atomic_compare_exchange_strong_explicit32(atomic_int_fast32_t *obj,
+ int32_t *expected, int32_t desired,
+ memory_order succ,
+ memory_order fail) {
+ bool __r;
+ int32_t __v;
+
+ UNUSED(succ);
+ UNUSED(fail);
+
+ switch (succ) {
+ case memory_order_relaxed:
+ __v = InterlockedCompareExchangeNoFence(
+ (atomic_int_fast32_t *)obj, desired, *expected);
+ break;
+ case memory_order_acquire:
+ __v = InterlockedCompareExchangeAcquire(
+ (atomic_int_fast32_t *)obj, desired, *expected);
+ break;
+ case memory_order_release:
+ __v = InterlockedCompareExchangeRelease(
+ (atomic_int_fast32_t *)obj, desired, *expected);
+ break;
+ default:
+ __v = InterlockedCompareExchange((atomic_int_fast32_t *)obj,
+ desired, *expected);
+ break;
+ }
+ __r = (*(expected) == __v);
+ if (!__r) {
+ *(expected) = __v;
+ }
+ return (__r);
+}
+
+static inline bool
+atomic_compare_exchange_strong_explicit64(atomic_int_fast64_t *obj,
+ int64_t *expected, int64_t desired,
+ memory_order succ,
+ memory_order fail) {
+ bool __r;
+ int64_t __v;
+
+ UNUSED(succ);
+ UNUSED(fail);
+
+#ifdef _WIN64
+ switch (succ) {
+ case memory_order_relaxed:
+ __v = InterlockedCompareExchangeNoFence64(
+ (atomic_int_fast64_t *)obj, desired, *expected);
+ break;
+ case memory_order_acquire:
+ __v = InterlockedCompareExchangeAcquire64(
+ (atomic_int_fast64_t *)obj, desired, *expected);
+ break;
+ case memory_order_release:
+ __v = InterlockedCompareExchangeRelease64(
+ (atomic_int_fast64_t *)obj, desired, *expected);
+ break;
+ default:
+ __v = InterlockedCompareExchange64((atomic_int_fast64_t *)obj,
+ desired, *expected);
+ break;
+ }
+#else /* ifdef _WIN64 */
+ __v = InterlockedCompareExchange64((atomic_int_fast64_t *)obj, desired,
+ *expected);
+#endif /* ifdef _WIN64 */
+ __r = (*(expected) == __v);
+ if (!__r) {
+ *(expected) = __v;
+ }
+ return (__r);
+}
+
+static inline bool
+atomic_compare_exchange_abort() {
+ UNREACHABLE();
+}
+
+#define atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \
+ fail) \
+ (sizeof(*(obj)) == 8 \
+ ? atomic_compare_exchange_strong_explicit64( \
+ obj, expected, desired, succ, fail) \
+ : (sizeof(*(obj)) == 4 \
+ ? atomic_compare_exchange_strong_explicit32( \
+ obj, expected, desired, succ, fail) \
+ : (sizeof(*(obj)) == 2 \
+ ? atomic_compare_exchange_strong_explicit16( \
+ obj, expected, desired, succ, \
+ fail) \
+ : (sizeof(*(obj)) == 1 \
+ ? atomic_compare_exchange_strong_explicit8( \
+ obj, expected, \
+ desired, succ, \
+ fail) \
+ : atomic_compare_exchange_abort()))))
+
+#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_explicit(obj, expected, desired, succ, \
+ fail) \
+ atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \
+ fail)
+
+#define atomic_compare_exchange_weak(obj, expected, desired) \
+ atomic_compare_exchange_weak_explicit(obj, expected, desired, \
+ memory_order_seq_cst, \
+ memory_order_seq_cst)
+
+static inline bool
+atomic_exchange_abort() {
+ UNREACHABLE();
+}
+
+#define atomic_exchange_explicit(obj, desired, order) \
+ (sizeof(*(obj)) == 8 \
+ ? InterlockedExchange64(obj, desired) \
+ : (sizeof(*(obj)) == 4 \
+ ? InterlockedExchange(obj, desired) \
+ : (sizeof(*(obj)) == 2 \
+ ? InterlockedExchange16(obj, desired) \
+ : (sizeof(*(obj)) == 1 \
+ ? InterlockedExchange8( \
+ obj, desired) \
+ : atomic_exchange_abort()))))
+
+#define atomic_exchange(obj, desired) \
+ atomic_exchange_explicit(obj, desired, memory_order_seq_cst)
diff --git a/lib/isc/win32/include/isc/stdtime.h b/lib/isc/win32/include/isc/stdtime.h
new file mode 100644
index 0000000..12e990c
--- /dev/null
+++ b/lib/isc/win32/include/isc/stdtime.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.
+ */
+
+#ifndef ISC_STDTIME_H
+#define ISC_STDTIME_H 1
+
+#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:
+ *
+ * '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
+
+#endif /* ISC_STDTIME_H */
diff --git a/lib/isc/win32/include/isc/syslog.h b/lib/isc/win32/include/isc/syslog.h
new file mode 100644
index 0000000..2638ad2
--- /dev/null
+++ b/lib/isc/win32/include/isc/syslog.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_SYSLOG_H
+#define ISC_SYSLOG_H 1
+
+#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:
+ *
+ * 'str' is not NULL
+ * 'facilityp' is not NULL
+ *
+ * Returns:
+ * ISC_R_SUCCESS
+ * ISC_R_NOTFOUND
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_SYSLOG_H */
diff --git a/lib/isc/win32/include/isc/thread.h b/lib/isc/win32/include/isc/thread.h
new file mode 100644
index 0000000..5e30c63
--- /dev/null
+++ b/lib/isc/win32/include/isc/thread.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_THREAD_H
+#define ISC_THREAD_H 1
+
+#include <inttypes.h>
+#include <windows.h>
+
+#include <isc/lang.h>
+#include <isc/result.h>
+
+extern __declspec(thread) size_t isc_tid_v;
+
+/*
+ * Inlines to help with wait return checking
+ */
+
+/* check handle for NULL and INVALID_HANDLE */
+inline BOOL
+IsValidHandle(HANDLE hHandle) {
+ return ((hHandle != NULL) && (hHandle != INVALID_HANDLE_VALUE));
+}
+
+/* validate wait return codes... */
+inline BOOL
+WaitSucceeded(DWORD dwWaitResult, DWORD dwHandleCount) {
+ return ((dwWaitResult >= WAIT_OBJECT_0) &&
+ (dwWaitResult < WAIT_OBJECT_0 + dwHandleCount));
+}
+
+inline BOOL
+WaitAbandoned(DWORD dwWaitResult, DWORD dwHandleCount) {
+ return ((dwWaitResult >= WAIT_ABANDONED_0) &&
+ (dwWaitResult < WAIT_ABANDONED_0 + dwHandleCount));
+}
+
+inline BOOL
+WaitTimeout(DWORD dwWaitResult) {
+ return (dwWaitResult == WAIT_TIMEOUT);
+}
+
+inline BOOL
+WaitFailed(DWORD dwWaitResult) {
+ return (dwWaitResult == WAIT_FAILED);
+}
+
+/* compute object indices for waits... */
+inline DWORD
+WaitSucceededIndex(DWORD dwWaitResult) {
+ return (dwWaitResult - WAIT_OBJECT_0);
+}
+
+inline DWORD
+WaitAbandonedIndex(DWORD dwWaitResult) {
+ return (dwWaitResult - WAIT_ABANDONED_0);
+}
+
+typedef HANDLE isc_thread_t;
+typedef DWORD isc_threadresult_t;
+typedef void *isc_threadarg_t;
+typedef isc_threadresult_t(WINAPI *isc_threadfunc_t)(isc_threadarg_t);
+
+#define isc_thread_self (uintptr_t) GetCurrentThreadId
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_thread_create(isc_threadfunc_t, isc_threadarg_t, isc_thread_t *);
+
+void
+isc_thread_join(isc_thread_t, isc_threadresult_t *);
+
+void
+isc_thread_setconcurrency(unsigned int level);
+
+void
+isc_thread_setname(isc_thread_t, const char *);
+
+isc_result_t
+isc_thread_setaffinity(int cpu);
+
+#define isc_thread_yield() Sleep(0)
+
+#if HAVE___DECLSPEC_THREAD
+#define ISC_THREAD_LOCAL static __declspec(thread)
+#else /* if HAVE___DECLSPEC_THREAD */
+#error "Thread-local storage support is required!"
+#endif /* if HAVE___DECLSPEC_THREAD */
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_THREAD_H */
diff --git a/lib/isc/win32/include/isc/time.h b/lib/isc/win32/include/isc/time.h
new file mode 100644
index 0000000..baed464
--- /dev/null
+++ b/lib/isc/win32/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.
+ */
+
+#ifndef ISC_TIME_H
+#define ISC_TIME_H 1
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <time.h>
+#include <windows.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+/***
+ *** POSIX Shims
+ ***/
+
+struct tm *
+gmtime_r(const time_t *clock, struct tm *result);
+
+struct tm *
+localtime_r(const time_t *clock, struct tm *result);
+
+int
+nanosleep(const struct timespec *req, struct timespec *rem);
+
+typedef uint32_t useconds_t;
+
+int
+usleep(useconds_t usec);
+
+/***
+ *** Intervals
+ ***/
+
+/*
+ * 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 {
+ int64_t interval;
+};
+
+LIBISC_EXTERNAL_DATA 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:
+ *
+ * 't' is a valid pointer.
+ * nanoseconds < 1000000000.
+ */
+
+bool
+isc_interval_iszero(const isc_interval_t *i);
+/*
+ * Returns true iff. 'i' is the zero interval.
+ *
+ * Requires:
+ *
+ * '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 {
+ FILETIME absolute;
+};
+
+LIBISC_EXTERNAL_DATA 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.
+ *
+ * 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:
+ * The date of the epoch is platform-dependent.
+ *
+ * Requires:
+ *
+ * 't' is a valid pointer.
+ */
+
+bool
+isc_time_isepoch(const isc_time_t *t);
+/*
+ * Returns true iff. 't' is the epoch ("time zero").
+ *
+ * Requires:
+ *
+ * 't' is a valid pointer.
+ */
+
+isc_result_t
+isc_time_now(isc_time_t *t);
+/*
+ * Set 't' to the current absolute time.
+ *
+ * Requires:
+ *
+ * 't' is a valid pointer.
+ *
+ * Returns:
+ *
+ * Success
+ * Unexpected error
+ * Getting the time from the system failed.
+ * 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:
+ * This call is equivalent to:
+ *
+ * isc_time_now(t);
+ * isc_time_add(t, i, t);
+ *
+ * Requires:
+ *
+ * 't' and 'i' are valid pointers.
+ *
+ * Returns:
+ *
+ * Success
+ * Unexpected error
+ * Getting the time from the system failed.
+ * 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:
+ *
+ * 't1' and 't2' are valid pointers.
+ *
+ * Returns:
+ *
+ * -1 t1 < t2 (comparing times, not pointers)
+ * 0 t1 = t2
+ * 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:
+ *
+ * 't', 'i', and 'result' are valid pointers.
+ *
+ * Returns:
+ * Success
+ * 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:
+ *
+ * 't', 'i', and 'result' are valid pointers.
+ *
+ * Returns:
+ * Success
+ * 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 milliseconds between time t1 and time t2.
+ * t2 is the subtrahend of t1; ie, difference = t1 - t2.
+ *
+ * Requires:
+ *
+ * 't1' and 't2' are valid pointers.
+ *
+ * Returns:
+ * The difference of t1 - t2, or 0 if t1 <= t2.
+ */
+
+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.
+ */
+
+uint32_t
+isc_time_nanoseconds(const isc_time_t *t);
+/*
+ * Return the number of nanoseconds stored in a time structure.
+ *
+ * Notes:
+ * 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:
+ * 't' is a valid pointer.
+ *
+ * Ensures:
+ * 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:
+ * 'len' > 0
+ * '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:
+ * 'len' > 0
+ * '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
+ *
+ */
+
+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
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_TIME_H */
diff --git a/lib/isc/win32/include/isc/win32os.h b/lib/isc/win32/include/isc/win32os.h
new file mode 100644
index 0000000..22def42
--- /dev/null
+++ b/lib/isc/win32/include/isc/win32os.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISC_WIN32OS_H
+#define ISC_WIN32OS_H 1
+
+#include <isc/lang.h>
+
+ISC_LANG_BEGINDECLS
+
+/*
+ * Return the number of CPUs available on the system, or 1 if this cannot
+ * be determined.
+ */
+
+int
+isc_win32os_versioncheck(unsigned int major, unsigned int minor,
+ unsigned int updatemajor, unsigned int updateminor);
+
+/*
+ * Checks the current version of the operating system with the
+ * supplied version information.
+ * Returns:
+ * -1 if less than the version information supplied
+ * 0 if equal to all of the version information supplied
+ * +1 if greater than the version information supplied
+ */
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISC_WIN32OS_H */
diff --git a/lib/isc/win32/interfaceiter.c b/lib/isc/win32/interfaceiter.c
new file mode 100644
index 0000000..db82e20
--- /dev/null
+++ b/lib/isc/win32/interfaceiter.c
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Note that this code will need to be revisited to support IPv6 Interfaces.
+ * For now we just iterate through IPv4 interfaces.
+ */
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+#include <isc/interfaceiter.h>
+#include <isc/mem.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>
+
+void
+InitSockets(void);
+
+/* Common utility functions */
+
+/*
+ * Extract the network address part from a "struct sockaddr".
+ *
+ * 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).
+ */
+
+#define IFITER_MAGIC 0x49464954U /* IFIT. */
+#define VALID_IFITER(t) ((t) != NULL && (t)->magic == IFITER_MAGIC)
+
+struct isc_interfaceiter {
+ unsigned int magic; /* Magic number. */
+ isc_mem_t *mctx;
+ SOCKET socket;
+ INTERFACE_INFO IFData; /* Current Interface Info. */
+ int numIF; /* Current Interface count. */
+ int v4IF; /* Number of IPv4 Interfaces */
+ INTERFACE_INFO *buf4; /* Buffer for WSAIoctl data. */
+ unsigned int buf4size; /* Bytes allocated. */
+ INTERFACE_INFO *pos4; /* Current offset in IF List */
+ SOCKET_ADDRESS_LIST *buf6; /* Buffer for WSAIoctl data. */
+ unsigned int buf6size; /* Bytes allocated. */
+ unsigned int pos6; /* Which entry to process. */
+ bool v6loop; /* See IPv6 loop address. */
+ bool pos6zero; /* Done pos6 == 0. */
+ isc_interface_t current; /* Current interface data. */
+ isc_result_t result; /* Last result code. */
+};
+
+/*
+ * Size of buffer for SIO_GET_INTERFACE_LIST, in number of interfaces.
+ * We assume no sane system will have more than than 1K of IP addresses on
+ * all of its adapters.
+ */
+#define IFCONF_SIZE_INITIAL 16
+#define IFCONF_SIZE_INCREMENT 64
+#define IFCONF_SIZE_MAX 1040
+
+static void
+get_addr(unsigned int family, isc_netaddr_t *dst, struct sockaddr *src) {
+ 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:
+ memmove(&dst->type.in6,
+ &((struct sockaddr_in6 *)src)->sin6_addr,
+ sizeof(struct in6_addr));
+ dst->zone = ((struct sockaddr_in6 *)src)->sin6_scope_id;
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+isc_result_t
+isc_interfaceiter_create(isc_mem_t *mctx, isc_interfaceiter_t **iterp) {
+ char strbuf[ISC_STRERRORSIZE];
+ isc_interfaceiter_t *iter;
+ isc_result_t result;
+ int error;
+ unsigned long bytesReturned = 0;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(iterp != NULL);
+ REQUIRE(*iterp == NULL);
+
+ iter = isc_mem_get(mctx, sizeof(*iter));
+
+ InitSockets();
+
+ iter->mctx = mctx;
+ iter->buf4 = NULL;
+ iter->buf6 = NULL;
+ iter->pos4 = NULL;
+ iter->pos6 = 0;
+ iter->v6loop = true;
+ iter->pos6zero = true;
+ iter->buf6size = 0;
+ iter->buf4size = 0;
+ iter->result = ISC_R_FAILURE;
+ iter->numIF = 0;
+ iter->v4IF = 0;
+
+ /*
+ * Create an unbound datagram socket to do the
+ * SIO_GET_INTERFACE_LIST WSAIoctl on.
+ */
+ iter->socket = socket(AF_INET, SOCK_DGRAM, 0);
+ if (iter->socket == INVALID_SOCKET) {
+ error = WSAGetLastError();
+ if (error == WSAEAFNOSUPPORT) {
+ goto inet6_only;
+ }
+ strerror_r(error, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "making interface scan socket: %s", strbuf);
+ result = ISC_R_UNEXPECTED;
+ goto socket_failure;
+ }
+
+ /*
+ * Get the interface configuration, allocating more memory if
+ * necessary.
+ */
+ iter->buf4size = IFCONF_SIZE_INITIAL * sizeof(INTERFACE_INFO);
+
+ for (;;) {
+ iter->buf4 = isc_mem_get(mctx, iter->buf4size);
+
+ if (WSAIoctl(iter->socket, SIO_GET_INTERFACE_LIST, 0, 0,
+ iter->buf4, iter->buf4size, &bytesReturned, 0,
+ 0) == SOCKET_ERROR)
+ {
+ error = WSAGetLastError();
+ if (error != WSAEFAULT && error != WSAENOBUFS) {
+ errno = error;
+ strerror_r(error, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "get interface configuration: "
+ "%s",
+ strbuf);
+ result = ISC_R_UNEXPECTED;
+ goto ioctl_failure;
+ }
+ /*
+ * EINVAL. Retry with a bigger buffer.
+ */
+ } else {
+ /*
+ * The WSAIoctl succeeded.
+ * If the number of the returned bytes is the same
+ * as the buffer size, we will grow it just in
+ * case and retry.
+ */
+ if (bytesReturned > 0 &&
+ (bytesReturned < iter->buf4size))
+ {
+ break;
+ }
+ }
+ if (iter->buf4size >= IFCONF_SIZE_MAX * sizeof(INTERFACE_INFO))
+ {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "get interface configuration: "
+ "maximum buffer size exceeded");
+ result = ISC_R_UNEXPECTED;
+ goto ioctl_failure;
+ }
+ isc_mem_put(mctx, iter->buf4, iter->buf4size);
+
+ iter->buf4size += IFCONF_SIZE_INCREMENT *
+ sizeof(INTERFACE_INFO);
+ }
+
+ /*
+ * A newly created iterator has an undefined position
+ * until isc_interfaceiter_first() is called.
+ */
+ iter->v4IF = bytesReturned / sizeof(INTERFACE_INFO);
+
+ /* We don't need the socket any more, so close it */
+ closesocket(iter->socket);
+
+inet6_only:
+ /*
+ * Create an unbound datagram socket to do the
+ * SIO_ADDRESS_LIST_QUERY WSAIoctl on.
+ */
+ iter->socket = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (iter->socket == INVALID_SOCKET) {
+ error = WSAGetLastError();
+ if (error == WSAEAFNOSUPPORT) {
+ goto inet_only;
+ }
+ strerror_r(error, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "making interface scan socket: %s", strbuf);
+ result = ISC_R_UNEXPECTED;
+ goto ioctl_failure;
+ }
+
+ /*
+ * Get the interface configuration, allocating more memory if
+ * necessary.
+ */
+ iter->buf6size = sizeof(SOCKET_ADDRESS_LIST) +
+ IFCONF_SIZE_INITIAL * sizeof(SOCKET_ADDRESS);
+
+ for (;;) {
+ iter->buf6 = isc_mem_get(mctx, iter->buf6size);
+
+ if (WSAIoctl(iter->socket, SIO_ADDRESS_LIST_QUERY, 0, 0,
+ iter->buf6, iter->buf6size, &bytesReturned, 0,
+ 0) == SOCKET_ERROR)
+ {
+ error = WSAGetLastError();
+ if (error != WSAEFAULT && error != WSAENOBUFS) {
+ errno = error;
+ strerror_r(error, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "sio address list query: %s",
+ strbuf);
+ result = ISC_R_UNEXPECTED;
+ goto ioctl6_failure;
+ }
+ /*
+ * EINVAL. Retry with a bigger buffer.
+ */
+ } else {
+ break;
+ }
+
+ if (iter->buf6size >= IFCONF_SIZE_MAX * sizeof(SOCKET_ADDRESS))
+ {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "get interface configuration: "
+ "maximum buffer size exceeded");
+ result = ISC_R_UNEXPECTED;
+ goto ioctl6_failure;
+ }
+ isc_mem_put(mctx, iter->buf6, iter->buf6size);
+
+ iter->buf6size += IFCONF_SIZE_INCREMENT *
+ sizeof(SOCKET_ADDRESS);
+ }
+
+ closesocket(iter->socket);
+
+inet_only:
+ iter->magic = IFITER_MAGIC;
+ *iterp = iter;
+ return (ISC_R_SUCCESS);
+
+ioctl6_failure:
+ isc_mem_put(mctx, iter->buf6, iter->buf6size);
+
+ioctl_failure:
+ if (iter->buf4 != NULL) {
+ isc_mem_put(mctx, iter->buf4, iter->buf4size);
+ }
+ if (iter->socket != INVALID_SOCKET) {
+ (void)closesocket(iter->socket);
+ }
+
+socket_failure:
+ 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, or if
+ * some operation on it fails, return ISC_R_IGNORE to make
+ * the higher-level iterator code ignore it.
+ */
+
+static isc_result_t
+internal_current(isc_interfaceiter_t *iter) {
+ BOOL ifNamed = FALSE;
+ unsigned long flags;
+
+ REQUIRE(VALID_IFITER(iter));
+ REQUIRE(iter->numIF >= 0);
+
+ memset(&iter->current, 0, sizeof(iter->current));
+ iter->current.af = AF_INET;
+
+ get_addr(AF_INET, &iter->current.address,
+ (struct sockaddr *)&(iter->IFData.iiAddress));
+
+ /*
+ * Get interface flags.
+ */
+
+ iter->current.flags = 0;
+ flags = iter->IFData.iiFlags;
+
+ if ((flags & IFF_UP) != 0) {
+ iter->current.flags |= INTERFACE_F_UP;
+ }
+
+ if ((flags & IFF_POINTTOPOINT) != 0) {
+ iter->current.flags |= INTERFACE_F_POINTTOPOINT;
+ snprintf(iter->current.name, sizeof(iter->current.name),
+ "PPP Interface %d", iter->numIF);
+ ifNamed = TRUE;
+ }
+
+ if ((flags & IFF_LOOPBACK) != 0) {
+ iter->current.flags |= INTERFACE_F_LOOPBACK;
+ snprintf(iter->current.name, sizeof(iter->current.name),
+ "Loopback Interface %d", iter->numIF);
+ ifNamed = TRUE;
+ }
+
+ /*
+ * If the interface is point-to-point, get the destination address.
+ */
+ if ((iter->current.flags & INTERFACE_F_POINTTOPOINT) != 0) {
+ get_addr(AF_INET, &iter->current.dstaddress,
+ (struct sockaddr *)&(iter->IFData.iiBroadcastAddress));
+ }
+
+ if (ifNamed == FALSE) {
+ snprintf(iter->current.name, sizeof(iter->current.name),
+ "TCP/IP Interface %d", iter->numIF);
+ }
+
+ /*
+ * Get the network mask.
+ */
+ get_addr(AF_INET, &iter->current.netmask,
+ (struct sockaddr *)&(iter->IFData.iiNetmask));
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+internal_current6(isc_interfaceiter_t *iter) {
+ SOCKET fd;
+ int i;
+
+ REQUIRE(VALID_IFITER(iter));
+ REQUIRE(iter->buf6 != NULL);
+
+ memset(&iter->current, 0, sizeof(iter->current));
+ iter->current.af = AF_INET6;
+
+ if (!iter->pos6zero) {
+ if (iter->pos6 == 0U) {
+ iter->pos6zero = true;
+ }
+ get_addr(AF_INET6, &iter->current.address,
+ iter->buf6->Address[iter->pos6].lpSockaddr);
+
+ /*
+ * Set interface flags.
+ */
+
+ iter->current.flags = INTERFACE_F_UP;
+
+ snprintf(iter->current.name, sizeof(iter->current.name),
+ "TCP/IPv6 Interface %u", iter->pos6 + 1);
+
+ for (i = 0; i < 16; i++) {
+ iter->current.netmask.type.in6.s6_addr[i] = 0xff;
+ }
+ iter->current.netmask.family = AF_INET6;
+ if (IN6_IS_ADDR_LOOPBACK(&iter->current.address.type.in6)) {
+ iter->v6loop = true;
+ }
+ } else {
+ /*
+ * See if we can bind to the ::1 and if so return ::1.
+ */
+ struct sockaddr_in6 sin6;
+
+ iter->v6loop = true; /* So we don't loop forever. */
+
+ fd = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (fd == INVALID_SOCKET) {
+ return (ISC_R_IGNORE);
+ }
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_addr.s6_addr[15] = 1;
+ if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) < 0) {
+ closesocket(fd);
+ return (ISC_R_IGNORE);
+ }
+ closesocket(fd);
+
+ iter->current.flags = INTERFACE_F_UP | INTERFACE_F_LOOPBACK;
+ snprintf(iter->current.name, sizeof(iter->current.name),
+ "TCP/IPv6 Loopback Interface");
+ for (i = 0; i < 16; i++) {
+ if (i != 15) {
+ iter->current.address.type.in6.s6_addr[i] = 0;
+ } else {
+ iter->current.address.type.in6.s6_addr[i] = 1;
+ }
+ iter->current.netmask.type.in6.s6_addr[i] = 0xff;
+ }
+ iter->current.address.family = AF_INET6;
+ iter->current.netmask.family = AF_INET6;
+ }
+ 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->numIF >= iter->v4IF) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * The first one needs to be set up to point to the last
+ * Element of the array. Go to the end and back up
+ * Microsoft's implementation is peculiar for returning
+ * the list in reverse order
+ */
+
+ if (iter->numIF == 0) {
+ iter->pos4 = (INTERFACE_INFO *)(iter->buf4 + (iter->v4IF));
+ }
+
+ iter->pos4--;
+ if (&(iter->pos4) < &(iter->buf4)) {
+ return (ISC_R_NOMORE);
+ }
+
+ memset(&(iter->IFData), 0, sizeof(INTERFACE_INFO));
+ memmove(&(iter->IFData), iter->pos4, sizeof(INTERFACE_INFO));
+ iter->numIF++;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+internal_next6(isc_interfaceiter_t *iter) {
+ if (iter->pos6 == 0U && iter->v6loop) {
+ return (ISC_R_NOMORE);
+ }
+ if (iter->pos6 != 0U) {
+ iter->pos6--;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+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) {
+ REQUIRE(VALID_IFITER(iter));
+
+ if (iter->buf6 != NULL) {
+ iter->pos6 = iter->buf6->iAddressCount;
+ iter->v6loop = false;
+ iter->pos6zero = (iter->pos6 == 0U);
+ }
+ iter->result = ISC_R_SUCCESS;
+ return (isc_interfaceiter_next(iter));
+}
+
+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_NOMORE) {
+ result = internal_next6(iter);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ result = internal_current6(iter);
+ if (result == ISC_R_IGNORE) {
+ continue;
+ }
+ break;
+ } else 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));
+
+ if (iter->buf4 != NULL) {
+ isc_mem_put(iter->mctx, iter->buf4, iter->buf4size);
+ }
+ if (iter->buf6 != NULL) {
+ isc_mem_put(iter->mctx, iter->buf6, iter->buf6size);
+ }
+
+ iter->magic = 0;
+ isc_mem_put(iter->mctx, iter, sizeof(*iter));
+}
diff --git a/lib/isc/win32/ipv6.c b/lib/isc/win32/ipv6.c
new file mode 100644
index 0000000..fecec98
--- /dev/null
+++ b/lib/isc/win32/ipv6.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/net.h>
+#include <isc/platform.h>
+
+LIBISC_EXTERNAL_DATA const struct in6_addr isc_in6addr_loopback =
+ IN6ADDR_LOOPBACK_INIT;
diff --git a/lib/isc/win32/libgen.h b/lib/isc/win32/libgen.h
new file mode 100644
index 0000000..b04ac37
--- /dev/null
+++ b/lib/isc/win32/libgen.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef LIBGEN_H
+#define LIBGEN_H 1
+
+char *
+basename(const char *);
+char *
+dirname(const char *);
+
+#endif /* ifndef LIBGEN_H */
diff --git a/lib/isc/win32/libisc.def.exclude b/lib/isc/win32/libisc.def.exclude
new file mode 100644
index 0000000..d2fe4ec
--- /dev/null
+++ b/lib/isc/win32/libisc.def.exclude
@@ -0,0 +1,42 @@
+; These symbols are not needed by the WIN32 build, but build-tarballs
+; will complain if they aren't present here.
+isc_socket_accept
+isc_socket_attach
+isc_socket_bind
+isc_socket_cancel
+isc_socket_cleanunix
+isc_socket_close
+isc_socket_connect
+isc_socket_create
+isc_socket_detach
+isc_socket_dscp
+isc_socket_dup
+isc_socket_fdwatchcreate
+isc_socket_fdwatchpoke
+isc_socket_filter
+isc_socket_getpeername
+isc_socket_getsockname
+isc_socket_gettype
+isc_socket_ipv6only
+isc_socket_listen
+isc_socket_open
+isc_socket_permunix
+isc_socket_recv
+isc_socket_recv2
+isc_socket_recvv
+isc_socket_register
+isc_socket_send
+isc_socket_sendto
+isc_socket_sendto2
+isc_socket_sendtov
+isc_socket_sendtov2
+isc_socket_sendv
+isc_socketmgr_create
+isc_socketmgr_create2
+isc_socketmgr_destroy
+isc_socketmgr_setstats
+isc_print_fprintf
+isc_print_printf
+isc_print_snprintf
+isc_print_sprintf
+isc_print_vsnprintf
diff --git a/lib/isc/win32/libisc.def.in b/lib/isc/win32/libisc.def.in
new file mode 100644
index 0000000..0ee055d
--- /dev/null
+++ b/lib/isc/win32/libisc.def.in
@@ -0,0 +1,805 @@
+LIBRARY libisc
+
+; Exported Functions
+EXPORTS
+
+NTReportError
+closelog
+@IF PKCS11
+getpassphrase
+@END PKCS11
+isc_app_block
+isc_app_ctxfinish
+isc_app_ctxonrun
+isc_app_ctxrun
+isc_app_ctxshutdown
+isc_app_ctxstart
+isc_app_ctxsuspend
+isc_app_finish
+isc_app_onrun
+isc_app_reload
+isc_app_run
+isc_app_shutdown
+isc_app_start
+isc_app_unblock
+isc_appctx_create
+isc_appctx_destroy
+isc_astack_destroy
+isc_astack_new
+isc_astack_pop
+isc_astack_trypush
+isc__buffer_activeregion
+isc__buffer_add
+isc__buffer_availableregion
+isc__buffer_back
+isc__buffer_clear
+isc__buffer_consumedregion
+isc__buffer_first
+isc__buffer_forward
+isc__buffer_init
+isc__buffer_initnull
+isc__buffer_invalidate
+isc__buffer_putmem
+isc__buffer_putstr
+isc__buffer_putuint16
+isc__buffer_putuint24
+isc__buffer_putuint32
+isc__buffer_putuint48
+isc__buffer_putuint8
+isc__buffer_region
+isc__buffer_remainingregion
+isc__buffer_setactive
+isc__buffer_subtract
+isc__buffer_usedregion
+isc__mem_allocate
+isc__mem_free
+isc__mem_get
+isc__mem_printactive
+isc__mem_put
+isc__mem_putanddetach
+isc__mem_reallocate
+isc__mem_strdup
+isc__mem_strndup
+isc__mempool_get
+isc__mempool_put
+isc__md_md5
+isc__md_sha1
+isc__md_sha224
+isc__md_sha256
+isc__md_sha384
+isc__md_sha512
+isc_socket_accept
+isc_socket_attach
+isc_socket_bind
+isc_socket_cancel
+isc_socket_cleanunix
+isc_socket_close
+isc_socket_connect
+isc_socket_create
+isc_socket_detach
+isc_socket_dscp
+isc_socket_dup
+isc_socket_filter
+isc_socket_getfd
+isc_socket_getname
+isc_socket_getpeername
+isc_socket_getsockname
+isc_socket_gettag
+isc_socket_gettype
+isc_socket_hasreuseport
+isc_socket_ipv6only
+isc_socket_listen
+isc_socket_open
+isc_socket_permunix
+isc_socket_recv
+isc_socket_recv2
+isc_socket_send
+isc_socket_sendto
+isc_socket_sendto2
+isc_socket_setname
+isc_socketmgr_create
+isc_socketmgr_create2
+isc_socketmgr_destroy
+isc_socketmgr_getmaxsockets
+isc_socketmgr_setreserved
+isc_socketmgr_setstats
+isc_aes128_crypt
+isc_aes192_crypt
+isc_aes256_crypt
+isc_app_block
+isc_app_ctxfinish
+isc_app_ctxonrun
+isc_app_ctxrun
+isc_app_ctxshutdown
+isc_app_ctxstart
+isc_app_ctxsuspend
+isc_app_finish
+isc_app_isrunning
+isc_app_onrun
+isc_app_reload
+isc_app_run
+isc_app_shutdown
+isc_app_start
+isc_app_unblock
+isc_appctx_create
+isc_appctx_destroy
+isc_assertion_failed
+isc_assertion_setcallback
+isc_assertion_typetotext
+isc_backtrace_getsymbol
+isc_backtrace_getsymbolfromindex
+isc_backtrace_gettrace
+isc_base32_decoderegion
+isc_base32_decodestring
+isc_base32_tobuffer
+isc_base32_totext
+isc_base32hex_decoderegion
+isc_base32hex_decodestring
+isc_base32hex_tobuffer
+isc_base32hex_totext
+isc_base32hexnp_decoderegion
+isc_base32hexnp_decodestring
+isc_base32hexnp_tobuffer
+isc_base32hexnp_totext
+isc_base64_decodestring
+isc_base64_tobuffer
+isc_base64_totext
+isc_buffer_allocate
+isc_buffer_compact
+isc_buffer_copyregion
+isc_buffer_dup
+isc_buffer_free
+isc_buffer_getuint16
+isc_buffer_getuint32
+isc_buffer_getuint48
+isc_buffer_getuint8
+isc_buffer_printf
+isc_buffer_putdecint
+isc_buffer_reinit
+isc_buffer_reserve
+isc_buffer_setautorealloc
+isc_bufferlist_availablecount
+isc_bufferlist_usedcount
+isc_commandline_parse
+isc_commandline_strtoargv
+isc_condition_broadcast
+isc_condition_destroy
+isc_condition_init
+isc_condition_signal
+isc_condition_wait
+isc_condition_waituntil
+isc_counter_attach
+isc_counter_create
+isc_counter_detach
+isc_counter_increment
+isc_counter_setlimit
+isc_counter_used
+isc_crc64_final
+isc_crc64_init
+isc_crc64_update
+isc_dir_chdir
+isc_dir_chroot
+isc_dir_close
+isc_dir_createunique
+isc_dir_init
+isc_dir_open
+isc_dir_read
+isc_dir_reset
+isc_enable_constructors
+isc_entropy_get
+isc_errno_toresult
+isc_error_fatal
+isc_error_runtimecheck
+isc_error_setfatal
+isc_error_setunexpected
+isc_error_unexpected
+isc_event_allocate
+isc_event_constallocate
+isc_event_free
+isc_file_absolutepath
+isc_file_basename
+isc_file_bopenunique
+isc_file_bopenuniquemode
+isc_file_bopenuniqueprivate
+isc_file_exists
+isc_file_getmodtime
+isc_file_getsize
+isc_file_getsizefd
+isc_file_isabsolute
+isc_file_ischdiridempotent
+isc_file_iscurrentdir
+isc_file_isdirectory
+isc_file_isdirwritable
+isc_file_isplainfile
+isc_file_isplainfilefd
+isc_file_mktemplate
+isc_file_mmap
+isc_file_mode
+isc_file_munmap
+isc_file_openunique
+isc_file_openuniquemode
+isc_file_openuniqueprivate
+isc_file_progname
+isc_file_remove
+isc_file_rename
+isc_file_renameunique
+isc_file_safecreate
+isc_file_safemovefile
+isc_file_sanitize
+isc_file_settime
+isc_file_splitpath
+isc_file_template
+isc_file_truncate
+isc_fsaccess_add
+isc_fsaccess_changeowner
+isc_fsaccess_remove
+isc_fsaccess_set
+isc_hash32
+isc_hash64
+isc_hash_get_initializer
+isc_hash_set_initializer
+isc_heap_create
+isc_heap_decreased
+isc_heap_delete
+isc_heap_destroy
+isc_heap_foreach
+isc_heap_element
+isc_heap_increased
+isc_heap_insert
+isc_hex_decodestring
+isc_hex_tobuffer
+isc_hex_totext
+isc_hmac
+isc_hmac_new
+isc_hmac_free
+isc_hmac_init
+isc_hmac_reset
+isc_hmac_update
+isc_hmac_final
+isc_hmac_get_md_type
+isc_hmac_get_size
+isc_hmac_get_block_size
+isc_ht_add
+isc_ht_count
+isc_ht_delete
+isc_ht_destroy
+isc_ht_find
+isc_ht_init
+isc_ht_iter_create
+isc_ht_iter_current
+isc_ht_iter_currentkey
+isc_ht_iter_delcurrent_next
+isc_ht_iter_destroy
+isc_ht_iter_first
+isc_ht_iter_next
+isc_httpd_addheader
+isc_httpd_addheaderuint
+isc_httpd_endheaders
+isc_httpd_response
+isc_httpd_setfinishhook
+isc_httpdmgr_addurl
+isc_httpdmgr_addurl2
+isc_httpdmgr_create
+isc_httpdmgr_shutdown
+isc_interfaceiter_create
+isc_interfaceiter_current
+isc_interfaceiter_destroy
+isc_interfaceiter_first
+isc_interfaceiter_next
+isc_interval_iszero
+isc_interval_set
+isc_iterated_hash
+isc_lex_close
+isc_lex_create
+isc_lex_destroy
+isc_lex_getcomments
+isc_lex_getlasttokentext
+isc_lex_getmastertoken
+isc_lex_getoctaltoken
+isc_lex_getsourceline
+isc_lex_getsourcename
+isc_lex_getspecials
+isc_lex_gettoken
+isc_lex_isfile
+isc_lex_openbuffer
+isc_lex_openfile
+isc_lex_openstream
+isc_lex_setcomments
+isc_lex_setsourceline
+isc_lex_setsourcename
+isc_lex_setspecials
+isc_lex_ungettoken
+isc_lfsr_generate
+isc_lfsr_generate32
+isc_lfsr_init
+isc_lfsr_skip
+isc_lib_ntservice
+isc_lib_register
+isc_log_categorybyname
+isc_log_closefilelogs
+isc_log_create
+isc_log_createchannel
+isc_log_destroy
+isc_log_getdebuglevel
+isc_log_getduplicateinterval
+isc_log_gettag
+isc_log_modulebyname
+isc_log_opensyslog
+isc_log_registercategories
+isc_log_registermodules
+isc_log_setcontext
+isc_log_setdebuglevel
+isc_log_setduplicateinterval
+isc_log_settag
+isc_log_usechannel
+isc_log_vwrite
+isc_log_vwrite1
+isc_log_wouldlog
+isc_log_write
+isc_log_write1
+isc_logconfig_create
+isc_logconfig_destroy
+isc_logconfig_use
+isc_logfile_roll
+isc_managers_create
+isc_managers_destroy
+isc_md_new
+isc_md_init
+isc_md_reset
+isc_md_update
+isc_md_final
+isc_md_free
+isc_md_get_md_type
+isc_md_get_size
+isc_md_get_block_size
+isc_md_type_get_size
+isc_md_type_get_block_size
+isc_md
+isc__mem_checkdestroyed
+isc__mem_initialize
+isc__mem_shutdown
+isc_mem_attach
+isc_mem_checkdestroyed
+isc_mem_create
+isc_mem_destroy
+isc_mem_detach
+isc_mem_getname
+isc_mem_gettag
+isc_mem_inuse
+isc_mem_isovermem
+isc_mem_maxinuse
+isc_mem_references
+@IF NOTYET
+isc_mem_renderjson
+@END NOTYET
+@IF LIBXML2
+isc_mem_renderxml
+@END LIBXML2
+isc_mem_setdestroycheck
+isc_mem_setname
+isc_mem_setwater
+isc_mem_stats
+isc_mem_total
+isc_mem_waterack
+isc_meminfo_totalphys
+isc_mempool_create
+isc_mempool_destroy
+isc_mempool_getallocated
+isc_mempool_getfillcount
+isc_mempool_getfreecount
+isc_mempool_getfreemax
+isc_mempool_getmaxalloc
+isc_mempool_setfillcount
+isc_mempool_setfreemax
+isc_mempool_setmaxalloc
+isc_mempool_setname
+isc_mutexblock_destroy
+isc_mutexblock_init
+isc_net_disableipv4
+isc_net_disableipv6
+isc_net_enableipv4
+isc_net_enableipv6
+isc_net_getudpportrange
+isc_net_probe_ipv6only
+isc_net_probe_ipv6pktinfo
+isc_net_probedscp
+isc_net_probeipv4
+isc_net_probeipv6
+isc_net_probeunix
+isc_netaddr_any
+isc_netaddr_any6
+isc_netaddr_eqprefix
+isc_netaddr_equal
+isc_netaddr_format
+isc_netaddr_fromin
+isc_netaddr_fromin6
+isc_netaddr_frompath
+isc_netaddr_fromsockaddr
+isc_netaddr_fromv4mapped
+isc_netaddr_getzone
+isc_netaddr_isexperimental
+isc_netaddr_islinklocal
+isc_netaddr_isloopback
+isc_netaddr_ismulticast
+isc_netaddr_isnetzero
+isc_netaddr_issitelocal
+isc_netaddr_masktoprefixlen
+isc_netaddr_prefixok
+isc_netaddr_setzone
+isc_netaddr_totext
+isc_netaddr_unspec
+isc_netscope_pton
+isc__nmhandle_attach
+isc__nmhandle_detach
+isc_nmhandle_cleartimeout
+isc_nmhandle_getdata
+isc_nmhandle_getextra
+isc_nmhandle_is_stream
+isc_nmhandle_keepalive
+isc_nmhandle_netmgr
+isc_nmhandle_localaddr
+isc_nmhandle_peeraddr
+isc_nmhandle_setdata
+isc_nmhandle_settimeout
+isc_nmhandle_setwritetimeout
+isc_nm_attach
+isc_nm_cancelread
+isc_nm_detach
+isc_nm_getloadbalancesockets
+isc_nm_gettimeouts
+isc_nm_listentcp
+isc_nm_listentcpdns
+isc_nm_listenudp
+isc_nm_maxudp
+isc_nm_pause
+isc_nm_pauseread
+isc_nm_read
+isc_nm_resume
+isc_nm_resumeread
+isc_nm_send
+isc_nm_setloadbalancesockets
+isc_nm_setstats
+isc_nm_settimeouts
+isc_nm_stoplistening
+isc_nm_task_enqueue
+isc_nm_tcpconnect
+isc_nm_tcpdnsconnect
+isc_nm_tcpdns_sequential
+isc_nm_tid
+isc_nm_timer_create
+isc_nm_timer_attach
+isc_nm_timer_detach
+isc_nm_timer_start
+isc_nm_timer_stop
+isc_nm_udpconnect
+isc_nm_work_offload
+isc_nmsocket_close
+isc__nm_acquire_interlocked
+isc__nm_drop_interlocked
+isc__nm_acquire_interlocked_force
+isc_nonce_buf
+isc_ntpaths_get
+isc_ntpaths_init
+isc_once_do
+isc_os_ncpus
+isc_parse_uint16
+isc_parse_uint32
+isc_parse_uint8
+isc_pool_count
+isc_pool_create
+isc_pool_destroy
+isc_pool_expand
+isc_pool_get
+isc_portset_add
+isc_portset_addrange
+isc_portset_create
+isc_portset_destroy
+isc_portset_isset
+isc_portset_nports
+isc_portset_remove
+isc_portset_removerange
+isc_quota_attach
+isc_quota_attach_cb
+isc_quota_cb_init
+isc_quota_destroy
+isc_quota_detach
+isc_quota_getmax
+isc_quota_getsoft
+isc_quota_getused
+isc_quota_init
+isc_quota_max
+isc_quota_soft
+isc_radix_create
+isc_radix_destroy
+isc_radix_insert
+isc_radix_process
+isc_radix_remove
+isc_radix_search
+isc_random8
+isc_random16
+isc_random32
+isc_random_buf
+isc_random_uniform
+isc_ratelimiter_attach
+isc_ratelimiter_create
+isc_ratelimiter_dequeue
+isc_ratelimiter_detach
+isc_ratelimiter_enqueue
+isc_ratelimiter_release
+isc_ratelimiter_setinterval
+isc_ratelimiter_setpertic
+isc_ratelimiter_setpushpop
+isc_ratelimiter_shutdown
+isc_ratelimiter_stall
+isc_regex_validate
+isc_region_compare
+isc_resource_getcurlimit
+isc_resource_getlimit
+isc_resource_setlimit
+isc_result_register
+isc_result_registerids
+isc_result_toid
+isc_result_totext
+isc_rwlock_destroy
+isc_rwlock_downgrade
+isc_rwlock_init
+isc_rwlock_lock
+isc_rwlock_trylock
+isc_rwlock_tryupgrade
+isc_rwlock_unlock
+isc_safe_memequal
+isc_safe_memwipe
+isc_serial_eq
+isc_serial_ge
+isc_serial_gt
+isc_serial_le
+isc_serial_lt
+isc_serial_ne
+isc_halfsiphash24
+isc_siphash24
+isc_sockaddr_any
+isc_sockaddr_any6
+isc_sockaddr_anyofpf
+isc_sockaddr_compare
+isc_sockaddr_eqaddr
+isc_sockaddr_eqaddrprefix
+isc_sockaddr_equal
+isc_sockaddr_format
+isc_sockaddr_fromin
+isc_sockaddr_fromin6
+isc_sockaddr_fromnetaddr
+isc_sockaddr_frompath
+isc_sockaddr_fromsockaddr
+isc_sockaddr_getport
+isc_sockaddr_hash
+isc_sockaddr_isexperimental
+isc_sockaddr_islinklocal
+isc_sockaddr_ismulticast
+isc_sockaddr_isnetzero
+isc_sockaddr_issitelocal
+isc_sockaddr_pf
+isc_sockaddr_setport
+isc_sockaddr_totext
+isc_sockaddr_v6fromin
+isc_socket_socketevent
+isc_socketmgr_maxudp
+@IF NOTYET
+isc_socketmgr_renderjson
+@END NOTYET
+@IF LIBXML2
+isc_socketmgr_renderxml
+@END LIBXML2
+isc_stats_attach
+isc_stats_create
+isc_stats_decrement
+isc_stats_detach
+isc_stats_dump
+isc_stats_get_counter
+isc_stats_increment
+isc_stats_ncounters
+isc_stats_resize
+isc_stats_set
+isc_stats_update_if_greater
+isc_stdio_close
+isc_stdio_flush
+isc_stdio_open
+isc_stdio_read
+isc_stdio_seek
+isc_stdio_sync
+isc_stdio_tell
+isc_stdio_write
+isc_stdtime_get
+isc_stdtime_tostring
+isc_string_strerror_r
+isc_symtab_count
+isc_symtab_create
+isc_symtab_define
+isc_symtab_destroy
+isc_symtab_lookup
+isc_symtab_undefine
+isc_syslog_facilityfromstring
+isc_task_attach
+isc_task_beginexclusive
+isc_task_create
+isc_task_create_bound
+isc_task_destroy
+isc_task_detach
+isc_task_endexclusive
+isc_task_exiting
+isc_task_getname
+isc_task_unsendrange
+isc_task_getcurrenttime
+isc_task_getcurrenttimex
+isc_task_getnetmgr
+isc_task_getprivilege
+isc_task_gettag
+isc_task_onshutdown
+isc_task_pause
+isc_task_privileged
+isc_task_purge
+isc_task_purgeevent
+isc_task_purgerange
+isc_task_ready
+isc_task_run
+isc_task_send
+isc_task_sendanddetach
+isc_task_sendto
+isc_task_sendtoanddetach
+isc_task_setname
+isc_task_setprivilege
+isc_task_setquantum
+isc_task_shutdown
+isc_task_unpause
+isc_task_unsend
+isc_taskmgr_attach
+isc_taskmgr_detach
+isc_taskmgr_excltask
+isc_taskmgr_mode
+@IF NOTYET
+isc_taskmgr_renderjson
+@END NOTYET
+@IF LIBXML2
+isc_taskmgr_renderxml
+@END LIBXML2
+isc_taskmgr_setexcltask
+isc_taskmgr_setmode
+isc_taskpool_create
+isc_taskpool_destroy
+isc_taskpool_expand
+isc_taskpool_gettask
+isc_taskpool_size
+isc_thread_create
+isc_thread_join
+isc_thread_setaffinity
+isc_thread_setconcurrency
+isc_thread_setname
+gmtime_r
+localtime_r
+nanosleep
+usleep
+isc_time_add
+isc_time_compare
+isc_time_formatISO8601
+isc_time_formatISO8601L
+isc_time_formatISO8601Lms
+isc_time_formatISO8601Lus
+isc_time_formatISO8601ms
+isc_time_formatISO8601us
+isc_time_formathttptimestamp
+isc_time_formatshorttimestamp
+isc_time_formattimestamp
+isc_time_isepoch
+isc_time_microdiff
+isc_time_nanoseconds
+isc_time_now
+isc_time_now_hires
+isc_time_nowplusinterval
+isc_time_parsehttptimestamp
+isc_time_secondsastimet
+isc_time_seconds
+isc_time_set
+isc_time_settoepoch
+isc_time_subtract
+isc_timer_create
+isc_timer_destroy
+isc_timer_gettype
+isc_timer_reset
+isc_timer_touch
+isc_timermgr_create
+isc_timermgr_destroy
+isc_timermgr_poke
+isc__tls_initialize
+isc__tls_shutdown
+isc__trampoline_initialize
+isc__trampoline_shutdown
+isc__trampoline_get
+isc__trampoline_run
+isc_tm_timegm
+isc_tm_strptime
+isc_url_parse
+isc_utf8_bom
+isc_utf8_valid
+isc_win32os_versioncheck
+openlog
+@IF PKCS11
+pk11_attribute_bytype
+pk11_attribute_first
+pk11_attribute_next
+pk11_dump_tokens
+pk11_error_fatalcheck
+pk11_finalize
+pk11_get_best_token
+pk11_get_lib_name
+pk11_get_load_error_message
+pk11_get_session
+pk11_initialize
+pk11_mem_get
+pk11_mem_put
+pk11_numbits
+pk11_parse_uri
+pk11_result_register
+pk11_result_totext
+pk11_return_session
+pk11_set_lib_name
+pkcs_C_CloseSession
+pkcs_C_CreateObject
+pkcs_C_DeriveKey
+pkcs_C_DestroyObject
+pkcs_C_DigestFinal
+pkcs_C_DigestInit
+pkcs_C_DigestUpdate
+pkcs_C_Encrypt
+pkcs_C_EncryptInit
+pkcs_C_Finalize
+pkcs_C_FindObjects
+pkcs_C_FindObjectsFinal
+pkcs_C_FindObjectsInit
+pkcs_C_GenerateKey
+pkcs_C_GenerateKeyPair
+pkcs_C_GenerateRandom
+pkcs_C_GetAttributeValue
+pkcs_C_GetMechanismInfo
+pkcs_C_GetSlotList
+pkcs_C_GetTokenInfo
+pkcs_C_Initialize
+pkcs_C_Login
+pkcs_C_Logout
+pkcs_C_OpenSession
+pkcs_C_SeedRandom
+pkcs_C_SetAttributeValue
+pkcs_C_Sign
+pkcs_C_SignFinal
+pkcs_C_SignInit
+pkcs_C_SignUpdate
+pkcs_C_Verify
+pkcs_C_VerifyFinal
+pkcs_C_VerifyInit
+pkcs_C_VerifyUpdate
+@END PKCS11
+strlcat
+strlcpy
+strnstr
+syslog
+
+@IF NOLONGER
+; Exported Data
+
+EXPORTS
+
+isc__backtrace_nsymbols DATA
+isc__backtrace_symtable DATA
+isc_bind9 DATA
+isc_commandline_argument DATA
+isc_commandline_errprint DATA
+isc_commandline_index DATA
+isc_commandline_option DATA
+isc_commandline_progname DATA
+isc_commandline_reset DATA
+isc_dscp_check_value DATA
+isc_hashctx DATA
+isc_mem_debugging DATA
+isc_tid_v DATA
+@IF PKCS11
+pk11_verbose_init DATA
+@END PKCS11
+@END NOLONGER
diff --git a/lib/isc/win32/libisc.vcxproj.filters.in b/lib/isc/win32/libisc.vcxproj.filters.in
new file mode 100644
index 0000000..75a4c80
--- /dev/null
+++ b/lib/isc/win32/libisc.vcxproj.filters.in
@@ -0,0 +1,671 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Library Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Win32 Source Files">
+ <UniqueIdentifier>{289562c2-1bdd-4582-b6bd-3f598ee23cbd}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Win32 Header Files">
+ <UniqueIdentifier>{d03c3e6a-e78e-4a01-bd77-64c839b1adfe}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Library Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="libisc.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\isc\aes.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\app.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\assertions.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\astack.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\atomic.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\backtrace.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\barrier.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\base32.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\base64.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\bind9.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\boolean.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\buffer.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\bufferlist.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\commandline.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\counter.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\crc64.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\endian.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\errno.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\error.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\event.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\eventclass.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\file.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\formatcheck.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\fsaccess.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\hash.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\heap.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\hex.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\hmac.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\ht.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\httpd.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\interfaceiter.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\iterated_hash.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\json.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\lang.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\lex.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\lfsr.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\lib.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\list.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\log.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\magic.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\managers.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\mem.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\meminfo.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\mutexblock.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\netaddr.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\netscope.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\nonce.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\os.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\parseint.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\pool.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\portset.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\print.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\quota.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\radix.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\random.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\ratelimiter.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\refcount.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\regex.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\region.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\resource.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\result.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\resultclass.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\rwlock.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\safe.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\serial.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\siphash.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\sockaddr.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\socket.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\stats.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\stdio.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\stdlib.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\strerr.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\string.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\symtab.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\task.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\taskpool.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\timer.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\tm.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\types.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\url.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\utf8.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\util.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isc\version.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+@IF PKCS11
+ <ClInclude Include="..\include\pk11\constants.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\pk11\internal.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\pk11\pk11.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\pk11\result.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\pkcs11\pkcs11.h">
+ <Filter>Pkcs11 Header Files</Filter>
+ </ClInclude>
+@END PKCS11
+ <ClInclude Include="include\isc\bind_registry.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\bindevt.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\condition.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\dir.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\ipv6.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\mutex.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\net.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\netdb.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\ntgroups.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\ntpaths.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\offset.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\once.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\platform.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\stat.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\stdatomic.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\stdtime.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\strerror.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\syslog.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\thread.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\time.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="include\isc\win32os.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\entropy_private.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\netmgr_p.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\openssl_shim.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\trampoline_p.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="errno2result.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="syslog.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="unistd.h">
+ <Filter>Win32 Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\config.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\versions.h">
+ <Filter>Library Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="condition.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="dir.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DLLMain.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="errno.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="errno2result.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="file.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="fsaccess.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="interfaceiter.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ipv6.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="meminfo.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="net.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ntpaths.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="once.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="os.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="resource.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="socket.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="stdio.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="stdtime.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="syslog.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="thread.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="time.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="version.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win32os.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+@IF PKCS11
+ <ClCompile Include="pk11_api.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+@END PKCS11
+ <ClCompile Include="..\aes.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\app.c">
+ <Filter>Win32 Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\assertions.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\astack.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\backtrace.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\backtrace-emptytbl.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\base32.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\base64.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\bind9.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\buffer.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\bufferlist.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\commandline.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\counter.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\crc64.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\error.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\event.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\entropy.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\hash.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\heap.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\hex.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\hmac.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ht.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\httpd.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\iterated_hash.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\lex.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\lfsr.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\lib.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\log.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\managers.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\mem.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\mutexblock.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\netaddr.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\netmgr\netmgr.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\netmgr\tcp.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\netmgr\udp.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\netmgr\uverr2result.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\netmgr\uv-compat.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\netmgr\tcpdns.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\netscope.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nonce.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\openssl_shim.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\parseint.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\pool.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\portset.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\quota.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\radix.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\random.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ratelimiter.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\regex.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\region.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\result.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\rwlock.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\safe.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\serial.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\siphash.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\sockaddr.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\stats.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\string.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\symtab.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\task.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\taskpool.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\timer.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\tls.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\trampoline.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\tm.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\url.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\utf8.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+@IF PKCS11
+ <ClCompile Include="..\pk11.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\pk11_result.c">
+ <Filter>Library Source Files</Filter>
+ </ClCompile>
+@END PKCS11
+ </ItemGroup>
+</Project>
diff --git a/lib/isc/win32/libisc.vcxproj.in b/lib/isc/win32/libisc.vcxproj.in
new file mode 100644
index 0000000..8fd9381
--- /dev/null
+++ b/lib/isc/win32/libisc.vcxproj.in
@@ -0,0 +1,483 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="@TOOLS_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|@PLATFORM@">
+ <Configuration>Debug</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|@PLATFORM@">
+ <Configuration>Release</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{3840E563-D180-4761-AA9C-E6155F02EAFF}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>libisc</RootNamespace>
+ @WINDOWS_TARGET_PLATFORM_VERSION@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <Optimization>Disabled</Optimization>
+@IF PKCS11
+ <PreprocessorDefinitions>BIND9;@PK11_LIB_LOCATION@WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;..\..\dns\win32\include;..\..\dns\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+@ELSE PKCS11
+ <PreprocessorDefinitions>BIND9;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+@END PKCS11
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BrowseInformation>true</BrowseInformation>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@@LIBUV_LIB@@LIBXML2_LIB@@ZLIB_LIB@ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ </Link>
+ <PreBuildEvent>
+ <Command>cd ..\..\..\win32utils
+
+if NOT Exist ..\Build mkdir ..\Build
+if NOT Exist ..\Build\Debug mkdir ..\Build\Debug
+
+echo Copying documentation.
+
+copy ..\*.md ..\Build\Debug
+copy ..\CHANGES* ..\Build\Debug
+copy ..\COPYRIGHT ..\Build\Debug
+copy ..\LICENSE ..\Build\Debug
+
+echo Copying COPYRIGHT notice.
+
+copy ..\COPYRIGHT ..\Build\Debug
+
+echo Copying the OpenSSL DLL and LICENSE.
+
+copy @OPENSSL_DLLCRYPTO@ ..\Build\Debug\
+copy @OPENSSL_DLLSSL@ ..\Build\Debug\
+copy @OPENSSL_PATH@\LICENSE ..\Build\Debug\OpenSSL-LICENSE
+
+echo Copying libuv DLL.
+copy @LIBUV_DLL@ ..\Build\Debug\
+
+@IF LIBXML2
+echo Copying the libxml DLL.
+
+copy @LIBXML2_DLL@ ..\Build\Debug\
+@END LIBXML2
+
+@IF GSSAPI
+echo Copying the GSSAPI and KRB5 DLLs.
+
+copy @GSSAPI_DLL@ ..\Build\Debug\
+copy @KRB5_DLL@ ..\Build\Debug\
+copy @COMERR_DLL@ ..\Build\Debug\
+copy @K5SPRT_DLL@ ..\Build\Debug\
+copy @WSHELP_DLL@ ..\Build\Debug\
+@END GSSAPI
+
+@IF IDNKIT
+echo Copying the IDN kit DLL.
+
+copy @IDN_DLL@ ..\Build\Debug\
+copy @ICONV_DLL@ ..\Build\Debug\
+@END IDNKIT
+
+@IF ZLIB
+echo Copying the zlib DLL.
+
+copy @ZLIB_DLL@ ..\Build\Debug\
+@END ZLIB
+
+echo Copying Visual C x86 Redistributable Installer.
+
+copy /Y @VCREDIST_PATH@ ..\Build\Debug\
+
+echo Copying install files (flags and file list).
+
+copy InstallFlags ..\Build\Debug\
+copy InstallFiles ..\Build\Debug\
+
+</Command>
+ </PreBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>@INTRINSIC@</IntrinsicFunctions>
+@IF PKCS11
+ <PreprocessorDefinitions>BIND9;@PK11_LIB_LOCATION@WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;..\..\dns\win32\include;..\..\dns\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+@ELSE PKCS11
+ <PreprocessorDefinitions>BIND9;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+@END PKCS11
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <WholeProgramOptimization>false</WholeProgramOptimization>
+ <StringPooling>true</StringPooling>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@@LIBUV_LIB@@LIBXML2_LIB@@ZLIB_LIB@ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
+ </Link>
+ <PreBuildEvent>
+ <Command>cd ..\..\..\win32utils
+
+if NOT Exist ..\Build mkdir ..\Build
+if NOT Exist ..\Build\Release mkdir ..\Build\Release
+
+echo Copying documentation.
+
+copy ..\*.md ..\Build\Release
+copy ..\CHANGES* ..\Build\Release
+copy ..\COPYRIGHT ..\Build\Release
+copy ..\LICENSE ..\Build\Release
+
+echo Copying the OpenSSL DLL and LICENSE.
+
+copy @OPENSSL_DLLCRYPTO@ ..\Build\Release\
+copy @OPENSSL_DLLSSL@ ..\Build\Release\
+copy @OPENSSL_PATH@\LICENSE ..\Build\Release\OpenSSL-LICENSE
+
+echo Copying libuv DLL.
+copy @LIBUV_DLL@ ..\Build\Release\
+
+@IF LIBXML2
+echo Copying the libxml DLL.
+
+copy @LIBXML2_DLL@ ..\Build\Release\
+@END LIBXML2
+
+@IF GSSAPI
+echo Copying the GSSAPI and KRB5 DLLs.
+
+copy @GSSAPI_DLL@ ..\Build\Release\
+copy @KRB5_DLL@ ..\Build\Release\
+copy @COMERR_DLL@ ..\Build\Release\
+copy @K5SPRT_DLL@ ..\Build\Release\
+copy @WSHELP_DLL@ ..\Build\Release\
+@END GSSAPI
+
+@IF IDNKIT
+echo Copying the IDN kit DLL.
+
+copy @IDN_DLL@ ..\Build\Release\
+copy @ICONV_DLL@ ..\Build\Release\
+@END IDNKIT
+
+@IF ZLIB
+echo Copying the zlib DLL.
+
+copy @ZLIB_DLL@ ..\Build\Release\
+@END ZLIB
+
+echo Copying Visual C x86 Redistributable Installer.
+
+copy /Y @VCREDIST_PATH@ ..\Build\Release\
+
+echo Copying install files (flags and file list).
+
+copy InstallFlags ..\Build\Release\
+copy InstallFiles ..\Build\Release\
+
+</Command>
+ </PreBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <None Include="libisc.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\config.h" />
+ <ClInclude Include="..\include\isc\aes.h" />
+ <ClInclude Include="..\include\isc\app.h" />
+ <ClInclude Include="..\include\isc\assertions.h" />
+ <ClInclude Include="..\include\isc\astack.h" />
+ <ClInclude Include="..\include\isc\atomic.h" />
+ <ClInclude Include="..\include\isc\backtrace.h" />
+ <ClInclude Include="..\include\isc\barrier.h" />
+ <ClInclude Include="..\include\isc\base32.h" />
+ <ClInclude Include="..\include\isc\base64.h" />
+ <ClInclude Include="..\include\isc\bind9.h" />
+ <ClInclude Include="..\include\isc\boolean.h" />
+ <ClInclude Include="..\include\isc\buffer.h" />
+ <ClInclude Include="..\include\isc\bufferlist.h" />
+ <ClInclude Include="..\include\isc\commandline.h" />
+ <ClInclude Include="..\include\isc\counter.h" />
+ <ClInclude Include="..\include\isc\crc64.h" />
+ <ClInclude Include="..\include\isc\endian.h" />
+ <ClInclude Include="..\include\isc\errno.h" />
+ <ClInclude Include="..\include\isc\error.h" />
+ <ClInclude Include="..\include\isc\event.h" />
+ <ClInclude Include="..\include\isc\eventclass.h" />
+ <ClInclude Include="..\include\isc\file.h" />
+ <ClInclude Include="..\include\isc\formatcheck.h" />
+ <ClInclude Include="..\include\isc\fsaccess.h" />
+ <ClInclude Include="..\include\isc\hash.h" />
+ <ClInclude Include="..\include\isc\heap.h" />
+ <ClInclude Include="..\include\isc\hex.h" />
+ <ClInclude Include="..\include\isc\hmac.h" />
+ <ClInclude Include="..\include\isc\ht.h" />
+ <ClInclude Include="..\include\isc\httpd.h" />
+ <ClInclude Include="..\include\isc\interfaceiter.h" />
+ <ClInclude Include="..\include\isc\iterated_hash.h" />
+ <ClInclude Include="..\include\isc\json.h" />
+ <ClInclude Include="..\include\isc\lang.h" />
+ <ClInclude Include="..\include\isc\lex.h" />
+ <ClInclude Include="..\include\isc\lfsr.h" />
+ <ClInclude Include="..\include\isc\lib.h" />
+ <ClInclude Include="..\include\isc\list.h" />
+ <ClInclude Include="..\include\isc\log.h" />
+ <ClInclude Include="..\include\isc\magic.h" />
+ <ClInclude Include="..\include\isc\managers.h" />
+ <ClInclude Include="..\include\isc\md.h" />
+ <ClInclude Include="..\include\isc\mem.h" />
+ <ClInclude Include="..\include\isc\meminfo.h" />
+ <ClInclude Include="..\include\isc\mutexblock.h" />
+ <ClInclude Include="..\include\isc\netaddr.h" />
+ <ClInclude Include="..\include\isc\netscope.h" />
+ <ClInclude Include="..\include\isc\nonce.h" />
+ <ClInclude Include="..\include\isc\os.h" />
+ <ClInclude Include="..\include\isc\parseint.h" />
+ <ClInclude Include="..\include\isc\pool.h" />
+ <ClInclude Include="..\include\isc\portset.h" />
+ <ClInclude Include="..\include\isc\print.h" />
+ <ClInclude Include="..\include\isc\quota.h" />
+ <ClInclude Include="..\include\isc\radix.h" />
+ <ClInclude Include="..\include\isc\random.h" />
+ <ClInclude Include="..\include\isc\ratelimiter.h" />
+ <ClInclude Include="..\include\isc\refcount.h" />
+ <ClInclude Include="..\include\isc\regex.h" />
+ <ClInclude Include="..\include\isc\region.h" />
+ <ClInclude Include="..\include\isc\resource.h" />
+ <ClInclude Include="..\include\isc\result.h" />
+ <ClInclude Include="..\include\isc\resultclass.h" />
+ <ClInclude Include="..\include\isc\rwlock.h" />
+ <ClInclude Include="..\include\isc\safe.h" />
+ <ClInclude Include="..\include\isc\serial.h" />
+ <ClInclude Include="..\include\isc\siphash.h" />
+ <ClInclude Include="..\include\isc\sockaddr.h" />
+ <ClInclude Include="..\include\isc\socket.h" />
+ <ClInclude Include="..\include\isc\stats.h" />
+ <ClInclude Include="..\include\isc\stdio.h" />
+ <ClInclude Include="..\include\isc\stdlib.h" />
+ <ClInclude Include="..\include\isc\strerr.h" />
+ <ClInclude Include="..\include\isc\string.h" />
+ <ClInclude Include="..\include\isc\symtab.h" />
+ <ClInclude Include="..\include\isc\task.h" />
+ <ClInclude Include="..\include\isc\taskpool.h" />
+ <ClInclude Include="..\include\isc\timer.h" />
+ <ClInclude Include="..\include\isc\tm.h" />
+ <ClInclude Include="..\include\isc\types.h" />
+ <ClInclude Include="..\include\isc\utf8.h" />
+ <ClInclude Include="..\include\isc\util.h" />
+ <ClInclude Include="..\include\isc\version.h" />
+@IF PKCS11
+ <ClInclude Include="..\include\pk11\constants.h" />
+ <ClInclude Include="..\include\pk11\internal.h" />
+ <ClInclude Include="..\include\pk11\pk11.h" />
+ <ClInclude Include="..\include\pk11\result.h" />
+ <ClInclude Include="..\include\pkcs11\pkcs11.h" />
+@END PKCS11
+ <ClInclude Include="errno2result.h" />
+ <ClInclude Include="include\isc\bindevt.h" />
+ <ClInclude Include="include\isc\bind_registry.h" />
+ <ClInclude Include="include\isc\condition.h" />
+ <ClInclude Include="include\isc\dir.h" />
+ <ClInclude Include="include\isc\ipv6.h" />
+ <ClInclude Include="include\isc\mutex.h" />
+ <ClInclude Include="include\isc\net.h" />
+ <ClInclude Include="include\isc\netdb.h" />
+ <ClInclude Include="include\isc\ntgroups.h" />
+ <ClInclude Include="include\isc\ntpaths.h" />
+ <ClInclude Include="include\isc\offset.h" />
+ <ClInclude Include="include\isc\once.h" />
+ <ClInclude Include="include\isc\platform.h" />
+ <ClInclude Include="include\isc\stat.h" />
+ <ClInclude Include="include\isc\stdatomic.h" />
+ <ClInclude Include="include\isc\stdtime.h" />
+ <ClInclude Include="include\isc\strerror.h" />
+ <ClInclude Include="include\isc\syslog.h" />
+ <ClInclude Include="include\isc\thread.h" />
+ <ClInclude Include="include\isc\time.h" />
+ <ClInclude Include="include\isc\win32os.h" />
+ <ClInclude Include="..\entropy_private.h" />
+ <ClInclude Include="..\netmgr_p.h" />
+ <ClInclude Include="..\openssl_shim.h" />
+ <ClInclude Include="..\trampoline_p.h" />
+ <ClInclude Include="syslog.h" />
+ <ClInclude Include="unistd.h" />
+ <ClInclude Include="..\..\versions.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\aes.c" />
+ <ClCompile Include="..\app.c" />
+ <ClCompile Include="..\assertions.c" />
+ <ClCompile Include="..\astack.c" />
+ <ClCompile Include="..\backtrace-emptytbl.c" />
+ <ClCompile Include="..\backtrace.c" />
+ <ClCompile Include="..\base32.c" />
+ <ClCompile Include="..\base64.c" />
+ <ClCompile Include="..\bind9.c" />
+ <ClCompile Include="..\buffer.c" />
+ <ClCompile Include="..\bufferlist.c" />
+ <ClCompile Include="..\commandline.c" />
+ <ClCompile Include="..\counter.c" />
+ <ClCompile Include="..\crc64.c" />
+ <ClCompile Include="..\entropy.c" />
+ <ClCompile Include="..\error.c" />
+ <ClCompile Include="..\event.c" />
+ <ClCompile Include="..\hash.c" />
+ <ClCompile Include="..\heap.c" />
+ <ClCompile Include="..\hex.c" />
+ <ClCompile Include="..\hmac.c" />
+ <ClCompile Include="..\ht.c" />
+ <ClCompile Include="..\httpd.c" />
+ <ClCompile Include="..\iterated_hash.c" />
+ <ClCompile Include="..\lex.c" />
+ <ClCompile Include="..\lfsr.c" />
+ <ClCompile Include="..\lib.c" />
+ <ClCompile Include="..\log.c" />
+ <ClCompile Include="..\managers.c" />
+ <ClCompile Include="..\md.c" />
+ <ClCompile Include="..\mem.c" />
+ <ClCompile Include="..\mutexblock.c" />
+ <ClCompile Include="..\netaddr.c" />
+ <ClCompile Include="..\netmgr\netmgr.c" />
+ <ClCompile Include="..\netmgr\tcp.c" />
+ <ClCompile Include="..\netmgr\udp.c" />
+ <ClCompile Include="..\netmgr\uverr2result.c" />
+ <ClCompile Include="..\netmgr\uv-compat.c" />
+ <ClCompile Include="..\netmgr\tcpdns.c" />
+ <ClCompile Include="..\netscope.c" />
+ <ClCompile Include="..\nonce.c" />
+ <ClCompile Include="..\openssl_shim.c" />
+ <ClCompile Include="..\parseint.c" />
+ <ClCompile Include="..\pool.c" />
+ <ClCompile Include="..\portset.c" />
+ <ClCompile Include="..\quota.c" />
+ <ClCompile Include="..\radix.c" />
+ <ClCompile Include="..\random.c" />
+ <ClCompile Include="..\ratelimiter.c" />
+ <ClCompile Include="..\regex.c" />
+ <ClCompile Include="..\region.c" />
+ <ClCompile Include="..\result.c" />
+ <ClCompile Include="..\rwlock.c" />
+ <ClCompile Include="..\safe.c" />
+ <ClCompile Include="..\serial.c" />
+ <ClCompile Include="..\siphash.c" />
+ <ClCompile Include="..\sockaddr.c" />
+ <ClCompile Include="..\stats.c" />
+ <ClCompile Include="..\string.c" />
+ <ClCompile Include="..\symtab.c" />
+ <ClCompile Include="..\task.c" />
+ <ClCompile Include="..\taskpool.c" />
+ <ClCompile Include="..\timer.c" />
+ <ClCompile Include="..\trampoline.c" />
+ <ClCompile Include="..\tls.c" />
+ <ClCompile Include="..\tm.c" />
+ <ClCompile Include="..\url.c" />
+ <ClCompile Include="..\utf8.c" />
+@IF PKCS11
+ <ClCompile Include="..\pk11.c" />
+ <ClCompile Include="..\pk11_result.c" />
+@END PKCS11
+ <ClCompile Include="condition.c" />
+ <ClCompile Include="dir.c" />
+ <ClCompile Include="DLLMain.c" />
+ <ClCompile Include="errno.c" />
+ <ClCompile Include="errno2result.c" />
+ <ClCompile Include="file.c" />
+ <ClCompile Include="fsaccess.c" />
+ <ClCompile Include="interfaceiter.c" />
+ <ClCompile Include="ipv6.c" />
+ <ClCompile Include="meminfo.c" />
+ <ClCompile Include="net.c" />
+ <ClCompile Include="ntpaths.c" />
+ <ClCompile Include="once.c" />
+ <ClCompile Include="os.c" />
+ <ClCompile Include="resource.c" />
+ <ClCompile Include="socket.c" />
+ <ClCompile Include="stdio.c" />
+ <ClCompile Include="stdtime.c" />
+ <ClCompile Include="syslog.c" />
+ <ClCompile Include="thread.c" />
+ <ClCompile Include="time.c" />
+ <ClCompile Include="version.c" />
+ <ClCompile Include="win32os.c" />
+@IF PKCS11
+ <ClCompile Include="pk11_api.c" />
+@END PKCS11
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/lib/isc/win32/libisc.vcxproj.user b/lib/isc/win32/libisc.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/lib/isc/win32/libisc.vcxproj.user
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+</Project> \ No newline at end of file
diff --git a/lib/isc/win32/meminfo.c b/lib/isc/win32/meminfo.c
new file mode 100644
index 0000000..ab10b89
--- /dev/null
+++ b/lib/isc/win32/meminfo.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 <inttypes.h>
+#include <windows.h>
+
+#include <isc/meminfo.h>
+
+uint64_t
+isc_meminfo_totalphys(void) {
+ MEMORYSTATUSEX statex;
+
+ statex.dwLength = sizeof(statex);
+ GlobalMemoryStatusEx(&statex);
+ return ((uint64_t)statex.ullTotalPhys);
+}
diff --git a/lib/isc/win32/net.c b/lib/isc/win32/net.c
new file mode 100644
index 0000000..ea4656d
--- /dev/null
+++ b/lib/isc/win32/net.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/log.h>
+#include <isc/net.h>
+#include <isc/once.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+/*%
+ * 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 32768
+#endif /* ISC_NET_PORTRANGELOW */
+#ifndef ISC_NET_PORTRANGEHIGH
+#define ISC_NET_PORTRANGEHIGH 65535
+#endif /* ISC_NET_PORTRANGEHIGH */
+
+static isc_once_t once = ISC_ONCE_INIT;
+static isc_once_t once_ipv6only = ISC_ONCE_INIT;
+static isc_once_t once_ipv6pktinfo = 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 ipv6only_result = ISC_R_NOTFOUND;
+static isc_result_t ipv6pktinfo_result = ISC_R_NOTFOUND;
+
+void
+InitSockets(void);
+
+static isc_result_t
+try_proto(int domain) {
+ SOCKET s;
+ char strbuf[ISC_STRERRORSIZE];
+ int errval;
+
+ s = socket(domain, SOCK_STREAM, IPPROTO_TCP);
+ if (s == INVALID_SOCKET) {
+ errval = WSAGetLastError();
+ switch (errval) {
+ case WSAEAFNOSUPPORT:
+ case WSAEPROTONOSUPPORT:
+ case WSAEINVAL:
+ return (ISC_R_NOTFOUND);
+ default:
+ strerror_r(errval, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "socket() failed: %s", strbuf);
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+
+ closesocket(s);
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+initialize_action(void) {
+ InitSockets();
+ ipv4_result = try_proto(PF_INET);
+ ipv6_result = try_proto(PF_INET6);
+}
+
+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) {
+ return (ISC_R_NOTFOUND);
+}
+
+static void
+try_ipv6only(void) {
+#ifdef IPV6_V6ONLY
+ SOCKET s;
+ int on;
+ char strbuf[ISC_STRERRORSIZE];
+#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 == INVALID_SOCKET) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s",
+ strbuf);
+ ipv6only_result = ISC_R_UNEXPECTED;
+ return;
+ }
+
+ on = 1;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&on,
+ sizeof(on)) < 0)
+ {
+ ipv6only_result = ISC_R_NOTFOUND;
+ goto close;
+ }
+
+ closesocket(s);
+
+ /* check for UDP sockets */
+ s = socket(PF_INET6, SOCK_DGRAM, 0);
+ if (s == INVALID_SOCKET) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s",
+ strbuf);
+ ipv6only_result = ISC_R_UNEXPECTED;
+ return;
+ }
+
+ on = 1;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&on,
+ sizeof(on)) < 0)
+ {
+ ipv6only_result = ISC_R_NOTFOUND;
+ goto close;
+ }
+
+ ipv6only_result = ISC_R_SUCCESS;
+
+close:
+ closesocket(s);
+ return;
+#endif /* IPV6_V6ONLY */
+}
+
+static void
+initialize_ipv6only(void) {
+ RUNTIME_CHECK(isc_once_do(&once_ipv6only, try_ipv6only) ==
+ ISC_R_SUCCESS);
+}
+
+#ifdef __notyet__
+/*
+ * XXXMPA requires win32/socket.c to be updated to support
+ * WSASendMsg and WSARecvMsg which are themselves Winsock
+ * and compiler version dependent.
+ */
+static void
+try_ipv6pktinfo(void) {
+ SOCKET s;
+ int on;
+ char strbuf[ISC_STRERRORSIZE];
+ 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 == INVALID_SOCKET) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s",
+ strbuf);
+ 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, (const char *)&on,
+ sizeof(on)) < 0)
+ {
+ ipv6pktinfo_result = ISC_R_NOTFOUND;
+ goto close;
+ }
+
+ ipv6pktinfo_result = ISC_R_SUCCESS;
+
+close:
+ closesocket(s);
+ return;
+}
+
+static void
+initialize_ipv6pktinfo(void) {
+ RUNTIME_CHECK(isc_once_do(&once_ipv6pktinfo, try_ipv6pktinfo) ==
+ ISC_R_SUCCESS);
+}
+#endif /* __notyet__ */
+
+isc_result_t
+isc_net_probe_ipv6only(void) {
+ initialize_ipv6only();
+ return (ipv6only_result);
+}
+
+isc_result_t
+isc_net_probe_ipv6pktinfo(void) {
+#ifdef __notyet__
+ initialize_ipv6pktinfo();
+#endif /* __notyet__ */
+ return (ipv6pktinfo_result);
+}
+
+isc_result_t
+isc_net_getudpportrange(int af, in_port_t *low, in_port_t *high) {
+ int result = ISC_R_FAILURE;
+
+ REQUIRE(low != NULL && high != NULL);
+
+ UNUSED(af);
+
+ 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;
+ }
+}
+
+unsigned int
+isc_net_probedscp(void) {
+ return (0);
+}
diff --git a/lib/isc/win32/netdb.h b/lib/isc/win32/netdb.h
new file mode 100644
index 0000000..5a5c122
--- /dev/null
+++ b/lib/isc/win32/netdb.h
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef NETDB_H
+#define NETDB_H 1
+
+#include <stddef.h>
+#include <winsock2.h>
+
+/*
+ * Define if <netdb.h> does not declare struct addrinfo.
+ */
+
+#if _MSC_VER < 1600
+struct addrinfo {
+ int ai_flags; /* AI_PASSIVE, AI_CANONNAME */
+ int ai_family; /* PF_xxx */
+ int ai_socktype; /* SOCK_xxx */
+ int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and
+ * IPv6 */
+ size_t ai_addrlen; /* Length of ai_addr */
+ char *ai_canonname; /* Canonical name for hostname */
+ struct sockaddr *ai_addr; /* Binary address */
+ struct addrinfo *ai_next; /* Next structure in linked list */
+};
+#endif /* if _MSC_VER < 1600 */
+
+/*
+ * Undefine all \#defines we are interested in as <netdb.h> may or may not have
+ * defined them.
+ */
+
+/*
+ * Error return codes from gethostbyname() and gethostbyaddr()
+ * (left in extern int h_errno).
+ */
+
+#undef NETDB_INTERNAL
+#undef NETDB_SUCCESS
+#undef HOST_NOT_FOUND
+#undef TRY_AGAIN
+#undef NO_RECOVERY
+#undef NO_DATA
+#undef NO_ADDRESS
+
+#define NETDB_INTERNAL -1 /* see errno */
+#define NETDB_SUCCESS 0 /* no problem */
+#define HOST_NOT_FOUND 1 /* Authoritative Answer Host not found */
+#define TRY_AGAIN 2 /* Non-Authoritative Host not found, or SERVERFAIL */
+#define NO_RECOVERY 3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
+#define NO_DATA 4 /* Valid name, no data record of requested type */
+#define NO_ADDRESS NO_DATA /* no address, look for MX record */
+
+/*
+ * Error return codes from getaddrinfo()
+ */
+
+#undef EAI_ADDRFAMILY
+#undef EAI_AGAIN
+#undef EAI_BADFLAGS
+#undef EAI_FAIL
+#undef EAI_FAMILY
+#undef EAI_MEMORY
+#undef EAI_NODATA
+#undef EAI_NONAME
+#undef EAI_SERVICE
+#undef EAI_SOCKTYPE
+#undef EAI_SYSTEM
+#undef EAI_BADHINTS
+#undef EAI_PROTOCOL
+#undef EAI_MAX
+
+#define EAI_ADDRFAMILY 1 /* address family for hostname not supported */
+#define EAI_AGAIN 2 /* temporary failure in name resolution */
+#define EAI_BADFLAGS 3 /* invalid value for ai_flags */
+#define EAI_FAIL 4 /* non-recoverable failure in name resolution */
+#define EAI_FAMILY 5 /* ai_family not supported */
+#define EAI_MEMORY 6 /* memory allocation failure */
+#define EAI_NODATA 7 /* no address associated with hostname */
+#define EAI_NONAME 8 /* hostname nor servname provided, or not known */
+#define EAI_SERVICE 9 /* servname not supported for ai_socktype */
+#define EAI_SOCKTYPE 10 /* ai_socktype not supported */
+#define EAI_SYSTEM 11 /* system error returned in errno */
+#define EAI_BADHINTS 12
+#define EAI_PROTOCOL 13
+#define EAI_MAX 14
+
+/*
+ * Flag values for getaddrinfo()
+ */
+#undef AI_PASSIVE
+#undef AI_CANONNAME
+#undef AI_NUMERICHOST
+
+#define AI_PASSIVE 0x00000001
+#define AI_CANONNAME 0x00000002
+#define AI_NUMERICHOST 0x00000004
+
+/*
+ * Flag values for getipnodebyname()
+ */
+#undef AI_V4MAPPED
+#undef AI_ALL
+#undef AI_ADDRCONFIG
+#undef AI_DEFAULT
+
+#define AI_V4MAPPED 0x00000008
+#define AI_ALL 0x00000010
+#define AI_ADDRCONFIG 0x00000020
+#define AI_DEFAULT (AI_V4MAPPED | AI_ADDRCONFIG)
+
+/*
+ * Constants for getnameinfo()
+ */
+#undef NI_MAXHOST
+#undef NI_MAXSERV
+
+#define NI_MAXHOST 1025
+#define NI_MAXSERV 32
+
+/*
+ * Flag values for getnameinfo()
+ */
+#undef NI_NOFQDN
+#undef NI_NUMERICHOST
+#undef NI_NAMEREQD
+#undef NI_NUMERICSERV
+#undef NI_DGRAM
+#undef NI_NUMERICSCOPE
+
+#define NI_NOFQDN 0x00000001
+#define NI_NUMERICHOST 0x00000002
+#define NI_NAMEREQD 0x00000004
+#define NI_NUMERICSERV 0x00000008
+#define NI_DGRAM 0x00000010
+#define NI_NUMERICSCOPE 0x00000020 /*2553bis-00*/
+
+/*
+ * Structures for getrrsetbyname()
+ */
+struct rdatainfo {
+ unsigned int rdi_length;
+ unsigned char *rdi_data;
+};
+
+struct rrsetinfo {
+ unsigned int rri_flags;
+ int rri_rdclass;
+ int rri_rdtype;
+ unsigned int rri_ttl;
+ unsigned int rri_nrdatas;
+ unsigned int rri_nsigs;
+ char *rri_name;
+ struct rdatainfo *rri_rdatas;
+ struct rdatainfo *rri_sigs;
+};
+
+/*
+ * Flags for getrrsetbyname()
+ */
+#define RRSET_VALIDATED 0x00000001
+/* Set was dnssec validated */
+
+/*
+ * Return codes for getrrsetbyname()
+ */
+#define ERRSET_SUCCESS 0
+#define ERRSET_NOMEMORY 1
+#define ERRSET_FAIL 2
+#define ERRSET_INVAL 3
+
+#endif /* NETDB_H */
diff --git a/lib/isc/win32/ntgroups.c b/lib/isc/win32/ntgroups.c
new file mode 100644
index 0000000..0584d48
--- /dev/null
+++ b/lib/isc/win32/ntgroups.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.
+ */
+
+/*
+ * The NT Groups have two groups that are not well documented and are
+ * not normally seen: None and Everyone. A user account belongs to
+ * any number of groups, but if it is not a member of any group then
+ * it is a member of the None Group. The None group is not listed
+ * anywhere. You cannot remove an account from the none group except
+ * by making it a member of some other group, The second group is the
+ * Everyone group. All accounts, no matter how many groups that they
+ * belong to, also belong to the Everyone group. You cannot remove an
+ * account from the Everyone group.
+ */
+
+#ifndef UNICODE
+#define UNICODE
+#endif /* UNICODE */
+
+/*
+ * Silence warnings.
+ */
+#define _CRT_SECURE_NO_DEPRECATE 1
+
+/* clang-format off */
+#include <assert.h>
+#include <windows.h>
+#include <lm.h>
+/* clang-format on */
+
+#include <isc/ntgroups.h>
+#include <isc/result.h>
+
+#define MAX_NAME_LENGTH 256
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) { \
+ goto cleanup; \
+ } \
+ } while (0)
+
+isc_result_t
+isc_ntsecurity_getaccountgroups(char *username, char **GroupList,
+ unsigned int maxgroups,
+ unsigned int *totalGroups) {
+ LPGROUP_USERS_INFO_0 pTmpBuf;
+ LPLOCALGROUP_USERS_INFO_0 pTmpLBuf;
+ DWORD i;
+ LPLOCALGROUP_USERS_INFO_0 pBuf = NULL;
+ LPGROUP_USERS_INFO_0 pgrpBuf = NULL;
+ DWORD dwLevel = 0;
+ DWORD dwFlags = LG_INCLUDE_INDIRECT;
+ DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH;
+ DWORD dwEntriesRead = 0;
+ DWORD dwTotalEntries = 0;
+ NET_API_STATUS nStatus;
+ size_t retlen;
+ wchar_t user[MAX_NAME_LENGTH];
+ isc_result_t result;
+
+ *totalGroups = 0;
+
+ retlen = mbstowcs(user, username, MAX_NAME_LENGTH);
+ if (retlen == (size_t)(-1)) {
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * Call the NetUserGetLocalGroups function
+ * specifying information level 0.
+ *
+ * The LG_INCLUDE_INDIRECT flag specifies that the
+ * function should also return the names of the local
+ * groups in which the user is indirectly a member.
+ */
+ nStatus = NetUserGetLocalGroups(NULL, user, dwLevel, dwFlags,
+ (LPBYTE *)&pBuf, dwPrefMaxLen,
+ &dwEntriesRead, &dwTotalEntries);
+ /*
+ * See if the call succeeds,
+ */
+ if (nStatus != NERR_Success) {
+ if (nStatus == ERROR_ACCESS_DENIED) {
+ return (ISC_R_NOPERM);
+ }
+ if (nStatus == ERROR_MORE_DATA) {
+ return (ISC_R_NOSPACE);
+ }
+ if (nStatus == NERR_UserNotFound) {
+ dwEntriesRead = 0;
+ }
+ }
+
+ if (pBuf != NULL) {
+ pTmpLBuf = pBuf;
+ /*
+ * Loop through the entries
+ */
+ for (i = 0; (i < dwEntriesRead && *totalGroups < maxgroups);
+ i++)
+ {
+ assert(pTmpLBuf != NULL);
+ if (pTmpLBuf == NULL) {
+ break;
+ }
+ retlen = wcslen(pTmpLBuf->lgrui0_name);
+ GroupList[*totalGroups] = (char *)malloc(retlen + 1);
+ if (GroupList[*totalGroups] == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ retlen = wcstombs(GroupList[*totalGroups],
+ pTmpLBuf->lgrui0_name, retlen);
+ if (retlen == (size_t)(-1)) {
+ free(GroupList[*totalGroups]);
+ CHECK(ISC_R_FAILURE);
+ }
+ GroupList[*totalGroups][retlen] = '\0';
+ if (strcmp(GroupList[*totalGroups], "None") == 0) {
+ free(GroupList[*totalGroups]);
+ } else {
+ (*totalGroups)++;
+ }
+ pTmpLBuf++;
+ }
+ }
+ /* Free the allocated memory. */
+ /* cppcheck-suppress duplicateCondition */
+ if (pBuf != NULL) {
+ NetApiBufferFree(pBuf);
+ }
+
+ /*
+ * Call the NetUserGetGroups function, specifying level 0.
+ */
+ nStatus = NetUserGetGroups(NULL, user, dwLevel, (LPBYTE *)&pgrpBuf,
+ dwPrefMaxLen, &dwEntriesRead,
+ &dwTotalEntries);
+ /*
+ * See if the call succeeds,
+ */
+ if (nStatus != NERR_Success) {
+ if (nStatus == ERROR_ACCESS_DENIED) {
+ return (ISC_R_NOPERM);
+ }
+ if (nStatus == ERROR_MORE_DATA) {
+ return (ISC_R_NOSPACE);
+ }
+ if (nStatus == NERR_UserNotFound) {
+ dwEntriesRead = 0;
+ }
+ }
+
+ if (pgrpBuf != NULL) {
+ pTmpBuf = pgrpBuf;
+ /*
+ * Loop through the entries
+ */
+ for (i = 0; (i < dwEntriesRead && *totalGroups < maxgroups);
+ i++)
+ {
+ assert(pTmpBuf != NULL);
+
+ if (pTmpBuf == NULL) {
+ break;
+ }
+ retlen = wcslen(pTmpBuf->grui0_name);
+ GroupList[*totalGroups] = (char *)malloc(retlen + 1);
+ if (GroupList[*totalGroups] == NULL) {
+ CHECK(ISC_R_NOMEMORY);
+ }
+
+ retlen = wcstombs(GroupList[*totalGroups],
+ pTmpBuf->grui0_name, retlen);
+ if (retlen == (size_t)(-1)) {
+ free(GroupList[*totalGroups]);
+ CHECK(ISC_R_FAILURE);
+ }
+ GroupList[*totalGroups][retlen] = '\0';
+ if (strcmp(GroupList[*totalGroups], "None") == 0) {
+ free(GroupList[*totalGroups]);
+ } else {
+ (*totalGroups)++;
+ }
+ pTmpBuf++;
+ }
+ }
+ /*
+ * Free the allocated memory.
+ */
+ /* cppcheck-suppress duplicateCondition */
+ if (pgrpBuf != NULL) {
+ NetApiBufferFree(pgrpBuf);
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ while (--(*totalGroups) > 0) {
+ free(GroupList[*totalGroups]);
+ }
+ return (result);
+}
diff --git a/lib/isc/win32/ntpaths.c b/lib/isc/win32/ntpaths.c
new file mode 100644
index 0000000..793b72a
--- /dev/null
+++ b/lib/isc/win32/ntpaths.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 module fetches the required path information that is specific
+ * to NT systems which can have its configuration and system files
+ * almost anywhere. It can be used to override whatever the application
+ * had previously assigned to the pointer. Basic information about the
+ * file locations are stored in the registry.
+ */
+
+#include <isc/bind_registry.h>
+#include <isc/ntpaths.h>
+#include <isc/string.h>
+
+/*
+ * Module Variables
+ */
+
+static char systemDir[MAX_PATH];
+static char namedBase[MAX_PATH];
+static char ns_confFile[MAX_PATH];
+static char rndc_confFile[MAX_PATH];
+static char ns_defaultpidfile[MAX_PATH];
+static char ns_lockfile[MAX_PATH];
+static char local_state_dir[MAX_PATH];
+static char sys_conf_dir[MAX_PATH];
+static char rndc_keyFile[MAX_PATH];
+static char session_keyFile[MAX_PATH];
+static char resolv_confFile[MAX_PATH];
+static char bind_keysFile[MAX_PATH];
+
+static DWORD baseLen = MAX_PATH;
+static BOOL Initialized = FALSE;
+
+void
+isc_ntpaths_init(void) {
+ HKEY hKey;
+ BOOL keyFound = TRUE;
+
+ memset(namedBase, 0, sizeof(namedBase));
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, BIND_SUBKEY, 0, KEY_READ, &hKey) !=
+ ERROR_SUCCESS)
+ {
+ keyFound = FALSE;
+ }
+
+ if (keyFound == TRUE) {
+ /* Get the named directory */
+ if (RegQueryValueEx(hKey, "InstallDir", NULL, NULL,
+ (LPBYTE)namedBase,
+ &baseLen) != ERROR_SUCCESS)
+ {
+ keyFound = FALSE;
+ }
+ RegCloseKey(hKey);
+ }
+
+ GetSystemDirectory(systemDir, MAX_PATH);
+
+ if (keyFound == FALSE) {
+ /* Use the System Directory as a default */
+ strlcpy(namedBase, systemDir, sizeof(namedBase));
+ }
+
+ strlcpy(ns_confFile, namedBase, sizeof(ns_confFile));
+ strlcat(ns_confFile, "\\etc\\named.conf", sizeof(ns_confFile));
+
+ strlcpy(rndc_keyFile, namedBase, sizeof(rndc_keyFile));
+ strlcat(rndc_keyFile, "\\etc\\rndc.key", sizeof(rndc_keyFile));
+
+ strlcpy(session_keyFile, namedBase, sizeof(session_keyFile));
+ strlcat(session_keyFile, "\\etc\\session.key", sizeof(session_keyFile));
+
+ strlcpy(rndc_confFile, namedBase, sizeof(rndc_confFile));
+ strlcat(rndc_confFile, "\\etc\\rndc.conf", sizeof(rndc_confFile));
+
+ strlcpy(ns_defaultpidfile, namedBase, sizeof(ns_defaultpidfile));
+ strlcat(ns_defaultpidfile, "\\etc\\named.pid",
+ sizeof(ns_defaultpidfile));
+
+ strlcpy(ns_lockfile, namedBase, sizeof(ns_lockfile));
+ strlcat(ns_lockfile, "\\etc\\named.lock", sizeof(ns_lockfile));
+
+ strlcpy(local_state_dir, namedBase, sizeof(local_state_dir));
+ strlcat(local_state_dir, "\\bin", sizeof(local_state_dir));
+
+ strlcpy(sys_conf_dir, namedBase, sizeof(sys_conf_dir));
+ strlcat(sys_conf_dir, "\\etc", sizeof(sys_conf_dir));
+
+ /* Added to avoid an assert on NULL value */
+ strlcpy(resolv_confFile, namedBase, sizeof(resolv_confFile));
+ strlcat(resolv_confFile, "\\etc\\resolv.conf", sizeof(resolv_confFile));
+
+ strlcpy(bind_keysFile, namedBase, sizeof(bind_keysFile));
+ strlcat(bind_keysFile, "\\etc\\bind.keys", sizeof(bind_keysFile));
+
+ Initialized = TRUE;
+}
+
+char *
+isc_ntpaths_get(int ind) {
+ if (!Initialized) {
+ isc_ntpaths_init();
+ }
+
+ switch (ind) {
+ case NAMED_CONF_PATH:
+ return (ns_confFile);
+ break;
+ case RESOLV_CONF_PATH:
+ return (resolv_confFile);
+ break;
+ case RNDC_CONF_PATH:
+ return (rndc_confFile);
+ break;
+ case NAMED_PID_PATH:
+ return (ns_defaultpidfile);
+ break;
+ case NAMED_LOCK_PATH:
+ return (ns_lockfile);
+ break;
+ case LOCAL_STATE_DIR:
+ return (local_state_dir);
+ break;
+ case SYS_CONF_DIR:
+ return (sys_conf_dir);
+ break;
+ case RNDC_KEY_PATH:
+ return (rndc_keyFile);
+ break;
+ case SESSION_KEY_PATH:
+ return (session_keyFile);
+ break;
+ case BIND_KEYS_PATH:
+ return (bind_keysFile);
+ break;
+ default:
+ return (NULL);
+ }
+}
diff --git a/lib/isc/win32/once.c b/lib/isc/win32/once.c
new file mode 100644
index 0000000..23966cb
--- /dev/null
+++ b/lib/isc/win32/once.c
@@ -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.
+ */
+
+#include <windows.h>
+
+#include <isc/assertions.h>
+#include <isc/once.h>
+#include <isc/util.h>
+
+isc_result_t
+isc_once_do(isc_once_t *controller, void (*function)(void)) {
+ REQUIRE(controller != NULL && function != NULL);
+
+ if (controller->status == ISC_ONCE_INIT_NEEDED) {
+ if (InterlockedDecrement(&controller->counter) == 0) {
+ if (controller->status == ISC_ONCE_INIT_NEEDED) {
+ function();
+ controller->status = ISC_ONCE_INIT_DONE;
+ }
+ } else {
+ while (controller->status == ISC_ONCE_INIT_NEEDED) {
+ /*
+ * Sleep(0) indicates that this thread
+ * should be suspended to allow other
+ * waiting threads to execute.
+ */
+ Sleep(0);
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/win32/os.c b/lib/isc/win32/os.c
new file mode 100644
index 0000000..19a992e
--- /dev/null
+++ b/lib/isc/win32/os.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <windows.h>
+
+#include <isc/os.h>
+
+static BOOL bInit = FALSE;
+static SYSTEM_INFO SystemInfo;
+
+static void
+initialize_action(void) {
+ if (bInit) {
+ return;
+ }
+
+ GetSystemInfo(&SystemInfo);
+ bInit = TRUE;
+}
+
+unsigned int
+isc_os_ncpus(void) {
+ long ncpus;
+ initialize_action();
+ ncpus = SystemInfo.dwNumberOfProcessors;
+ if (ncpus <= 0) {
+ ncpus = 1;
+ }
+
+ return ((unsigned int)ncpus);
+}
diff --git a/lib/isc/win32/pk11_api.c b/lib/isc/win32/pk11_api.c
new file mode 100644
index 0000000..5ee8e0d
--- /dev/null
+++ b/lib/isc/win32/pk11_api.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.
+ */
+
+/*! \file */
+
+/* missing code for WIN32 */
+
+#include <string.h>
+#include <windows.h>
+
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/stdio.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <pk11/internal.h>
+#include <pk11/pk11.h>
+
+char *
+getpass(const char *prompt) {
+ static char buf[128];
+ HANDLE h;
+ DWORD cc, mode;
+ int cnt;
+
+ h = GetStdHandle(STD_INPUT_HANDLE);
+ fputs(prompt, stderr);
+ fflush(stderr);
+ fflush(stdout);
+ FlushConsoleInputBuffer(h);
+ GetConsoleMode(h, &mode);
+ SetConsoleMode(h, ENABLE_PROCESSED_INPUT);
+
+ for (cnt = 0; cnt < sizeof(buf) - 1; cnt++) {
+ ReadFile(h, buf + cnt, 1, &cc, NULL);
+ if (buf[cnt] == '\r') {
+ break;
+ }
+ fputc('*', stdout);
+ fflush(stderr);
+ fflush(stdout);
+ }
+
+ SetConsoleMode(h, mode);
+ buf[cnt] = '\0';
+ fputs("\n", stderr);
+ return (buf);
+}
+
+/* load PKCS11 DLL */
+
+static HINSTANCE hPK11 = NULL;
+static char loaderrmsg[1024];
+
+CK_RV
+pkcs_C_Initialize(CK_VOID_PTR pReserved) {
+ CK_C_Initialize sym;
+ const char *lib_name = pk11_get_lib_name();
+
+ if (hPK11 != NULL) {
+ return (CKR_LIBRARY_ALREADY_INITIALIZED);
+ }
+
+ if (lib_name == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ /* Visual Studio conversion issue... */
+ if (*lib_name == ' ') {
+ lib_name++;
+ }
+
+ hPK11 = LoadLibraryA(lib_name);
+
+ if (hPK11 == NULL) {
+ const DWORD err = GetLastError();
+ snprintf(loaderrmsg, sizeof(loaderrmsg),
+ "LoadLibraryA(\"%s\") failed with 0x%X\n", lib_name,
+ err);
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ sym = (CK_C_Initialize)GetProcAddress(hPK11, "C_Initialize");
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(pReserved));
+}
+
+char *
+pk11_get_load_error_message(void) {
+ return (loaderrmsg);
+}
+
+CK_RV
+pkcs_C_Finalize(CK_VOID_PTR pReserved) {
+ CK_C_Finalize sym;
+ CK_RV rv;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ sym = (CK_C_Finalize)GetProcAddress(hPK11, "C_Finalize");
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ rv = (*sym)(pReserved);
+ if ((rv == CKR_OK) && (FreeLibrary(hPK11) == 0)) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ hPK11 = NULL;
+ return (rv);
+}
+
+CK_RV
+pkcs_C_GetSlotList(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR pSlotList,
+ CK_ULONG_PTR pulCount) {
+ static CK_C_GetSlotList sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_GetSlotList)GetProcAddress(hPK11, "C_GetSlotList");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(tokenPresent, pSlotList, pulCount));
+}
+
+CK_RV
+pkcs_C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) {
+ static CK_C_GetTokenInfo sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_GetTokenInfo)GetProcAddress(hPK11, "C_"
+ "GetTokenInfo");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(slotID, pInfo));
+}
+
+CK_RV
+pkcs_C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type,
+ CK_MECHANISM_INFO_PTR pInfo) {
+ static CK_C_GetMechanismInfo sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_GetMechanismInfo)GetProcAddress(hPK11, "C_"
+ "GetMechanis"
+ "mInfo");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(slotID, type, pInfo));
+}
+
+CK_RV
+pkcs_C_OpenSession(CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication,
+ CK_RV (*Notify)(CK_SESSION_HANDLE hSession,
+ CK_NOTIFICATION event,
+ CK_VOID_PTR pApplication),
+ CK_SESSION_HANDLE_PTR phSession) {
+ static CK_C_OpenSession sym = NULL;
+
+ if (hPK11 == NULL) {
+ hPK11 = LoadLibraryA(pk11_get_lib_name());
+ }
+ if (hPK11 == NULL) {
+ const DWORD err = GetLastError();
+ snprintf(loaderrmsg, sizeof(loaderrmsg),
+ "LoadLibraryA(\"%s\") failed with 0x%X\n",
+ pk11_get_lib_name(), err);
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_OpenSession)GetProcAddress(hPK11, "C_OpenSession");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(slotID, flags, pApplication, Notify, phSession));
+}
+
+CK_RV
+pkcs_C_CloseSession(CK_SESSION_HANDLE hSession) {
+ static CK_C_CloseSession sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_CloseSession)GetProcAddress(hPK11, "C_"
+ "CloseSession");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession));
+}
+
+CK_RV
+pkcs_C_Login(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType,
+ CK_CHAR_PTR pPin, CK_ULONG usPinLen) {
+ static CK_C_Login sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_Login)GetProcAddress(hPK11, "C_Login");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, userType, pPin, usPinLen));
+}
+
+CK_RV
+pkcs_C_Logout(CK_SESSION_HANDLE hSession) {
+ static CK_C_Logout sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_Logout)GetProcAddress(hPK11, "C_Logout");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession));
+}
+
+CK_RV
+pkcs_C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
+ CK_ULONG usCount, CK_OBJECT_HANDLE_PTR phObject) {
+ static CK_C_CreateObject sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_CreateObject)GetProcAddress(hPK11, "C_"
+ "CreateObject");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pTemplate, usCount, phObject));
+}
+
+CK_RV
+pkcs_C_DestroyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject) {
+ static CK_C_DestroyObject sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_DestroyObject)GetProcAddress(hPK11, "C_"
+ "DestroyObjec"
+ "t");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, hObject));
+}
+
+CK_RV
+pkcs_C_GetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
+ CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount) {
+ static CK_C_GetAttributeValue sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_GetAttributeValue)GetProcAddress(hPK11, "C_"
+ "GetAttribu"
+ "teValue");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, hObject, pTemplate, usCount));
+}
+
+CK_RV
+pkcs_C_SetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
+ CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount) {
+ static CK_C_SetAttributeValue sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_SetAttributeValue)GetProcAddress(hPK11, "C_"
+ "SetAttribu"
+ "teValue");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, hObject, pTemplate, usCount));
+}
+
+CK_RV
+pkcs_C_FindObjectsInit(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
+ CK_ULONG usCount) {
+ static CK_C_FindObjectsInit sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_FindObjectsInit)GetProcAddress(hPK11, "C_"
+ "FindObjectsI"
+ "nit");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pTemplate, usCount));
+}
+
+CK_RV
+pkcs_C_FindObjects(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE_PTR phObject,
+ CK_ULONG usMaxObjectCount, CK_ULONG_PTR pusObjectCount) {
+ static CK_C_FindObjects sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_FindObjects)GetProcAddress(hPK11, "C_FindObjects");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, phObject, usMaxObjectCount, pusObjectCount));
+}
+
+CK_RV
+pkcs_C_FindObjectsFinal(CK_SESSION_HANDLE hSession) {
+ static CK_C_FindObjectsFinal sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_FindObjectsFinal)GetProcAddress(hPK11, "C_"
+ "FindObjects"
+ "Final");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession));
+}
+
+CK_RV
+pkcs_C_EncryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hKey) {
+ static CK_C_EncryptInit sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_EncryptInit)GetProcAddress(hPK11, "C_EncryptInit");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pMechanism, hKey));
+}
+
+CK_RV
+pkcs_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData,
+ CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData,
+ CK_ULONG_PTR pulEncryptedDataLen) {
+ static CK_C_Encrypt sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_Encrypt)GetProcAddress(hPK11, "C_Encrypt");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pData, ulDataLen, pEncryptedData,
+ pulEncryptedDataLen));
+}
+
+CK_RV
+pkcs_C_DigestInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism) {
+ static CK_C_DigestInit sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_DigestInit)GetProcAddress(hPK11, "C_DigestInit");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pMechanism));
+}
+
+CK_RV
+pkcs_C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
+ CK_ULONG ulPartLen) {
+ static CK_C_DigestUpdate sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_DigestUpdate)GetProcAddress(hPK11, "C_"
+ "DigestUpdate");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pPart, ulPartLen));
+}
+
+CK_RV
+pkcs_C_DigestFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pDigest,
+ CK_ULONG_PTR pulDigestLen) {
+ static CK_C_DigestFinal sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_DigestFinal)GetProcAddress(hPK11, "C_DigestFinal");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pDigest, pulDigestLen));
+}
+
+CK_RV
+pkcs_C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hKey) {
+ static CK_C_SignInit sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_SignInit)GetProcAddress(hPK11, "C_SignInit");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pMechanism, hKey));
+}
+
+CK_RV
+pkcs_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen,
+ CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen) {
+ static CK_C_Sign sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_Sign)GetProcAddress(hPK11, "C_Sign");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pData, ulDataLen, pSignature,
+ pulSignatureLen));
+}
+
+CK_RV
+pkcs_C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
+ CK_ULONG ulPartLen) {
+ static CK_C_SignUpdate sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_SignUpdate)GetProcAddress(hPK11, "C_SignUpdate");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pPart, ulPartLen));
+}
+
+CK_RV
+pkcs_C_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature,
+ CK_ULONG_PTR pulSignatureLen) {
+ static CK_C_SignFinal sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_SignFinal)GetProcAddress(hPK11, "C_SignFinal");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pSignature, pulSignatureLen));
+}
+
+CK_RV
+pkcs_C_VerifyInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hKey) {
+ static CK_C_VerifyInit sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_VerifyInit)GetProcAddress(hPK11, "C_VerifyInit");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pMechanism, hKey));
+}
+
+CK_RV
+pkcs_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen,
+ CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen) {
+ static CK_C_Verify sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_Verify)GetProcAddress(hPK11, "C_Verify");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pData, ulDataLen, pSignature, ulSignatureLen));
+}
+
+CK_RV
+pkcs_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
+ CK_ULONG ulPartLen) {
+ static CK_C_VerifyUpdate sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_VerifyUpdate)GetProcAddress(hPK11, "C_"
+ "VerifyUpdate");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pPart, ulPartLen));
+}
+
+CK_RV
+pkcs_C_VerifyFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature,
+ CK_ULONG ulSignatureLen) {
+ static CK_C_VerifyFinal sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_VerifyFinal)GetProcAddress(hPK11, "C_VerifyFinal");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pSignature, ulSignatureLen));
+}
+
+CK_RV
+pkcs_C_GenerateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount,
+ CK_OBJECT_HANDLE_PTR phKey) {
+ static CK_C_GenerateKey sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_GenerateKey)GetProcAddress(hPK11, "C_GenerateKey");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pMechanism, pTemplate, ulCount, phKey));
+}
+
+CK_RV
+pkcs_C_GenerateKeyPair(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_ATTRIBUTE_PTR pPublicKeyTemplate,
+ CK_ULONG usPublicKeyAttributeCount,
+ CK_ATTRIBUTE_PTR pPrivateKeyTemplate,
+ CK_ULONG usPrivateKeyAttributeCount,
+ CK_OBJECT_HANDLE_PTR phPrivateKey,
+ CK_OBJECT_HANDLE_PTR phPublicKey) {
+ static CK_C_GenerateKeyPair sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_GenerateKeyPair)GetProcAddress(hPK11, "C_"
+ "GenerateKeyP"
+ "air");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pMechanism, pPublicKeyTemplate,
+ usPublicKeyAttributeCount, pPrivateKeyTemplate,
+ usPrivateKeyAttributeCount, phPrivateKey, phPublicKey));
+}
+
+CK_RV
+pkcs_C_DeriveKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
+ CK_OBJECT_HANDLE hBaseKey, CK_ATTRIBUTE_PTR pTemplate,
+ CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey) {
+ static CK_C_DeriveKey sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_DeriveKey)GetProcAddress(hPK11, "C_DeriveKey");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pMechanism, hBaseKey, pTemplate,
+ ulAttributeCount, phKey));
+}
+
+CK_RV
+pkcs_C_SeedRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSeed,
+ CK_ULONG ulSeedLen) {
+ static CK_C_SeedRandom sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_SeedRandom)GetProcAddress(hPK11, "C_SeedRandom");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, pSeed, ulSeedLen));
+}
+
+CK_RV
+pkcs_C_GenerateRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR RandomData,
+ CK_ULONG ulRandomLen) {
+ static CK_C_GenerateRandom sym = NULL;
+
+ if (hPK11 == NULL) {
+ return (CKR_LIBRARY_FAILED_TO_LOAD);
+ }
+ if (sym == NULL) {
+ sym = (CK_C_GenerateRandom)GetProcAddress(hPK11, "C_"
+ "GenerateRando"
+ "m");
+ }
+ if (sym == NULL) {
+ return (CKR_SYMBOL_RESOLUTION_FAILED);
+ }
+ return ((*sym)(hSession, RandomData, ulRandomLen));
+}
diff --git a/lib/isc/win32/resource.c b/lib/isc/win32/resource.c
new file mode 100644
index 0000000..b44b322
--- /dev/null
+++ b/lib/isc/win32/resource.c
@@ -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 <stdio.h>
+
+#include <isc/platform.h>
+#include <isc/resource.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include "errno2result.h"
+
+/*
+ * Windows limits the maximum number of open files to 2048
+ */
+
+#define WIN32_MAX_OPEN_FILES 2048
+
+isc_result_t
+isc_resource_setlimit(isc_resource_t resource, isc_resourcevalue_t value) {
+ isc_resourcevalue_t rlim_value;
+ int wresult;
+
+ if (resource != isc_resource_openfiles) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (value == ISC_RESOURCE_UNLIMITED) {
+ rlim_value = WIN32_MAX_OPEN_FILES;
+ } else {
+ rlim_value = min(value, WIN32_MAX_OPEN_FILES);
+ }
+
+ wresult = _setmaxstdio((int)rlim_value);
+
+ if (wresult > 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (isc__errno2result(errno));
+ }
+}
+
+isc_result_t
+isc_resource_getlimit(isc_resource_t resource, isc_resourcevalue_t *value) {
+ if (resource != isc_resource_openfiles) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ *value = WIN32_MAX_OPEN_FILES;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_resource_getcurlimit(isc_resource_t resource, isc_resourcevalue_t *value) {
+ return (isc_resource_getlimit(resource, value));
+}
diff --git a/lib/isc/win32/socket.c b/lib/isc/win32/socket.c
new file mode 100644
index 0000000..c154175
--- /dev/null
+++ b/lib/isc/win32/socket.c
@@ -0,0 +1,3965 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 code uses functions which are only available on Server 2003 and
+ * higher, and Windows XP and higher.
+ *
+ * This code is by nature multithreaded and takes advantage of various
+ * features to pass on information through the completion port for
+ * when I/O is completed. All sends, receives, accepts, and connects are
+ * completed through the completion port.
+ *
+ * The number of Completion Port Worker threads used is the total number
+ * of CPU's + 1. This increases the likelihood that a Worker Thread is
+ * available for processing a completed request.
+ *
+ * XXXPDM 5 August, 2002
+ */
+
+#define MAKE_EXTERNAL 1
+
+#include <sys/types.h>
+
+#ifndef _WINSOCKAPI_
+#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */
+#endif /* ifndef _WINSOCKAPI_ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <io.h>
+#include <process.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <isc/app.h>
+#include <isc/buffer.h>
+#include <isc/condition.h>
+#include <isc/list.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/net.h>
+#include <isc/once.h>
+#include <isc/os.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/socket.h>
+#include <isc/stats.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/syslog.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+#include <isc/win32os.h>
+
+/* clang-format off */
+/* U Can't Touch This */
+#include <mswsock.h>
+/* clang-format on */
+
+#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 "errno2result.h"
+
+/*
+ * Set by the -T dscp option on the command line. If set to a value
+ * other than -1, we check to make sure DSCP values match it, and
+ * assert if not.
+ */
+LIBISC_EXTERNAL_DATA int isc_dscp_check_value = -1;
+
+/*
+ * How in the world can Microsoft exist with APIs like this?
+ * We can't actually call this directly, because it turns out
+ * no library exports this function. Instead, we need to
+ * issue a runtime call to get the address.
+ */
+LPFN_CONNECTEX ISCConnectEx;
+LPFN_ACCEPTEX ISCAcceptEx;
+LPFN_GETACCEPTEXSOCKADDRS ISCGetAcceptExSockaddrs;
+
+/*
+ * Run expensive internal consistency checks.
+ */
+#ifdef ISC_SOCKET_CONSISTENCY_CHECKS
+#define CONSISTENT(sock) consistent(sock)
+#else /* ifdef ISC_SOCKET_CONSISTENCY_CHECKS */
+#define CONSISTENT(sock) \
+ do { \
+ } while (0)
+#endif /* ifdef ISC_SOCKET_CONSISTENCY_CHECKS */
+static void
+consistent(isc_socket_t *sock);
+
+/*
+ * Define this macro to control the behavior of connection
+ * resets on UDP sockets. See Microsoft KnowledgeBase Article Q263823
+ * for details.
+ * NOTE: This requires that Windows 2000 systems install Service Pack 2
+ * or later.
+ */
+#ifndef SIO_UDP_CONNRESET
+#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)
+#endif /* ifndef SIO_UDP_CONNRESET */
+
+/*
+ * Define what the possible "soft" errors can be. These are non-fatal returns
+ * of various network related functions, like recv() and so on.
+ */
+#define SOFT_ERROR(e) \
+ ((e) == WSAEINTR || (e) == WSAEWOULDBLOCK || (e) == EWOULDBLOCK || \
+ (e) == EINTR || (e) == EAGAIN || (e) == 0)
+
+/*
+ * Pending errors are not really errors and should be
+ * kept separate
+ */
+#define PENDING_ERROR(e) ((e) == WSA_IO_PENDING || (e) == 0)
+
+#define DOIO_SUCCESS 0 /* i/o ok, event sent */
+#define DOIO_SOFT 1 /* i/o ok, soft error, no event sent */
+#define DOIO_HARD 2 /* i/o error, event sent */
+#define DOIO_EOF 3 /* EOF, no event sent */
+#define DOIO_PENDING 4 /* status when i/o is in process */
+#define DOIO_NEEDMORE \
+ 5 /* IO was processed, but we need more due to minimum \
+ */
+
+#define DLVL(x) ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(x)
+
+/*
+ * DLVL(90) -- Function entry/exit and other tracing.
+ * DLVL(70) -- Socket "correctness" -- including returning of events, etc.
+ * DLVL(60) -- Socket data send/receive
+ * DLVL(50) -- Event tracing, including receiving/sending completion events.
+ * DLVL(20) -- Socket creation/destruction.
+ */
+#define TRACE_LEVEL 90
+#define CORRECTNESS_LEVEL 70
+#define IOEVENT_LEVEL 60
+#define EVENT_LEVEL 50
+#define CREATION_LEVEL 20
+
+#define TRACE DLVL(TRACE_LEVEL)
+#define CORRECTNESS DLVL(CORRECTNESS_LEVEL)
+#define IOEVENT DLVL(IOEVENT_LEVEL)
+#define EVENT DLVL(EVENT_LEVEL)
+#define CREATION DLVL(CREATION_LEVEL)
+
+typedef isc_event_t intev_t;
+
+/*
+ * Socket State
+ */
+enum {
+ SOCK_INITIALIZED, /* Socket Initialized */
+ SOCK_OPEN, /* Socket opened but nothing yet to do */
+ SOCK_DATA, /* Socket sending or receiving data */
+ SOCK_LISTEN, /* TCP Socket listening for connects */
+ SOCK_ACCEPT, /* TCP socket is waiting to accept */
+ SOCK_CONNECT, /* TCP Socket connecting */
+ SOCK_CLOSED, /* Socket has been closed */
+};
+
+#define SOCKET_MAGIC ISC_MAGIC('I', 'O', 'i', 'o')
+#define VALID_SOCKET(t) ISC_MAGIC_VALID(t, SOCKET_MAGIC)
+
+/*
+ * IPv6 control information. If the socket is an IPv6 socket we want
+ * to collect the destination address and interface so the client can
+ * set them on outgoing packets.
+ */
+#ifndef USE_CMSG
+#define USE_CMSG 1
+#endif /* ifndef USE_CMSG */
+
+/*
+ * We really don't want to try and use these control messages. Win32
+ * doesn't have this mechanism before XP.
+ */
+#undef USE_CMSG
+
+/*
+ * Message header for recvmsg and sendmsg calls.
+ * Used value-result for recvmsg, value only for sendmsg.
+ */
+struct msghdr {
+ SOCKADDR_STORAGE to_addr; /* UDP send/recv address */
+ int to_addr_len; /* length of the address */
+ WSABUF *msg_iov; /* scatter/gather array */
+ u_int msg_iovlen; /* # elements in msg_iov */
+ void *msg_control; /* ancillary data, see below */
+ u_int msg_controllen; /* ancillary data buffer len */
+ u_int msg_totallen; /* total length of this message */
+} msghdr;
+
+/*
+ * The size to raise the receive buffer to.
+ */
+#define RCVBUFSIZE (32 * 1024)
+
+/*
+ * The number of times a send operation is repeated if the result
+ * is WSAEINTR.
+ */
+#define NRETRIES 10
+
+struct isc_socket {
+ /* Not locked. */
+ unsigned int magic;
+ isc_socketmgr_t *manager;
+ isc_mutex_t lock;
+ isc_sockettype_t type;
+
+ /* Pointers to scatter/gather buffers */
+ WSABUF iov[ISC_SOCKET_MAXSCATTERGATHER];
+
+ /* Locked by socket lock. */
+ ISC_LINK(isc_socket_t) link;
+ isc_refcount_t references; /* EXTERNAL references */
+ SOCKET fd; /* file handle */
+ int pf; /* protocol family */
+ char name[16];
+ void *tag;
+
+ /*
+ * Each recv() call uses this buffer. It is a per-socket receive
+ * buffer that allows us to decouple the system recv() from the
+ * recv_list done events. This means the items on the recv_list
+ * can be removed without having to cancel pending system recv()
+ * calls. It also allows us to read-ahead in some cases.
+ */
+ struct {
+ SOCKADDR_STORAGE from_addr; /* UDP send/recv address */
+ int from_addr_len; /* length of the address */
+ char *base; /* the base of the buffer */
+ char *consume_position; /* where to start
+ * copying data from
+ * next */
+ unsigned int len; /* the actual size of this buffer */
+ unsigned int remaining; /* the number of bytes
+ * remaining */
+ } recvbuf;
+
+ ISC_LIST(isc_socketevent_t) send_list;
+ ISC_LIST(isc_socketevent_t) recv_list;
+ ISC_LIST(isc_socket_newconnev_t) accept_list;
+ ISC_LIST(isc_socket_connev_t) connect_list;
+
+ isc_sockaddr_t address; /* remote address */
+
+ unsigned int listener : 1, /* listener socket */
+ connected : 1, pending_connect : 1, /* connect
+ * pending */
+ bound : 1, /* bound to local addr */
+ dupped : 1; /* created by isc_socket_dup() */
+ unsigned int pending_iocp; /* Should equal the counters below.
+ * Debug. */
+ unsigned int pending_recv; /* Number of outstanding recv() calls.
+ * */
+ unsigned int pending_send; /* Number of outstanding send() calls.
+ * */
+ unsigned int pending_accept; /* Number of outstanding accept()
+ * calls. */
+ unsigned int state; /* Socket state. Debugging and consistency
+ * checking.
+ */
+ int state_lineno; /* line which last touched state */
+};
+
+#define _set_state(sock, _state) \
+ do { \
+ (sock)->state = (_state); \
+ (sock)->state_lineno = __LINE__; \
+ } while (0)
+
+/*
+ * I/O Completion ports Info structures
+ */
+
+static HANDLE hHeapHandle = NULL;
+typedef struct IoCompletionInfo {
+ OVERLAPPED overlapped;
+ isc_socketevent_t *dev; /* send()/recv() done event */
+ isc_socket_connev_t *cdev; /* connect() done event */
+ isc_socket_newconnev_t *adev; /* accept() done event */
+ void *acceptbuffer;
+ DWORD received_bytes;
+ int request_type;
+ struct msghdr messagehdr;
+ void *buf;
+ unsigned int buflen;
+} IoCompletionInfo;
+
+/*
+ * Define a maximum number of I/O Completion Port worker threads
+ * to handle the load on the Completion Port. The actual number
+ * used is the number of CPU's + 1.
+ */
+#define MAX_IOCPTHREADS 20
+
+#define SOCKET_MANAGER_MAGIC ISC_MAGIC('I', 'O', 'm', 'g')
+#define VALID_MANAGER(m) ISC_MAGIC_VALID(m, SOCKET_MANAGER_MAGIC)
+
+struct isc_socketmgr {
+ /* Not locked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ isc_stats_t *stats;
+
+ /* Locked by manager lock. */
+ ISC_LIST(isc_socket_t) socklist;
+ bool bShutdown;
+ isc_condition_t shutdown_ok;
+ HANDLE hIoCompletionPort;
+ int maxIOCPThreads;
+ HANDLE hIOCPThreads[MAX_IOCPTHREADS];
+ size_t maxudp;
+
+ /*
+ * Debugging.
+ * Modified by InterlockedIncrement() and InterlockedDecrement()
+ */
+ LONG totalSockets;
+ LONG iocp_total;
+};
+
+enum { SOCKET_RECV, SOCKET_SEND, SOCKET_ACCEPT, SOCKET_CONNECT };
+
+/*
+ * send() and recv() iovec counts
+ */
+#define MAXSCATTERGATHER_SEND (ISC_SOCKET_MAXSCATTERGATHER)
+#define MAXSCATTERGATHER_RECV (ISC_SOCKET_MAXSCATTERGATHER)
+
+static isc_result_t
+socket_create(isc_socketmgr_t *manager0, int pf, isc_sockettype_t type,
+ isc_socket_t **socketp, isc_socket_t *dup_socket);
+static isc_threadresult_t WINAPI
+SocketIoThread(LPVOID ThreadContext);
+static void
+maybe_free_socket(isc_socket_t **, int);
+static void
+free_socket(isc_socket_t **, int);
+static bool
+senddone_is_active(isc_socket_t *sock, isc_socketevent_t *dev);
+static bool
+acceptdone_is_active(isc_socket_t *sock, isc_socket_newconnev_t *dev);
+static bool
+connectdone_is_active(isc_socket_t *sock, isc_socket_connev_t *dev);
+static void
+send_recvdone_event(isc_socket_t *sock, isc_socketevent_t **dev);
+static void
+send_senddone_event(isc_socket_t *sock, isc_socketevent_t **dev);
+static void
+send_acceptdone_event(isc_socket_t *sock, isc_socket_newconnev_t **adev);
+static void
+send_connectdone_event(isc_socket_t *sock, isc_socket_connev_t **cdev);
+static void
+send_recvdone_abort(isc_socket_t *sock, isc_result_t result);
+static void
+send_connectdone_abort(isc_socket_t *sock, isc_result_t result);
+static void
+queue_receive_event(isc_socket_t *sock, isc_task_t *task,
+ isc_socketevent_t *dev);
+static void
+queue_receive_request(isc_socket_t *sock);
+
+/*
+ * This is used to dump the contents of the sock structure
+ * You should make sure that the sock is locked before
+ * dumping it. Since the code uses simple printf() statements
+ * it should only be used interactively.
+ */
+void
+sock_dump(isc_socket_t *sock) {
+ isc_socketevent_t *ldev;
+ isc_socket_newconnev_t *ndev;
+ isc_socket_connev_t *cdev;
+
+#if 0
+ isc_sockaddr_t addr;
+ char socktext[ISC_SOCKADDR_FORMATSIZE];
+ isc_result_t result;
+
+ result = isc_socket_getpeername(sock,&addr);
+ if (result == ISC_R_SUCCESS) {
+ isc_sockaddr_format(&addr,socktext,sizeof(socktext));
+ printf("Remote Socket: %s\n",socktext);
+ }
+ result = isc_socket_getsockname(sock,&addr);
+ if (result == ISC_R_SUCCESS) {
+ isc_sockaddr_format(&addr,socktext,sizeof(socktext));
+ printf("This Socket: %s\n",socktext);
+ }
+#endif /* if 0 */
+
+ printf("\n\t\tSock Dump\n");
+ printf("\t\tfd: %Iu\n", sock->fd);
+ printf("\t\treferences: %" PRIuFAST32 "\n",
+ isc_refcount_current(&sock->references));
+ printf("\t\tpending_accept: %u\n", sock->pending_accept);
+ printf("\t\tconnecting: %u\n", sock->pending_connect);
+ printf("\t\tconnected: %u\n", sock->connected);
+ printf("\t\tbound: %u\n", sock->bound);
+ printf("\t\tpending_iocp: %u\n", sock->pending_iocp);
+ printf("\t\tsocket type: %d\n", sock->type);
+
+ printf("\n\t\tSock Recv List\n");
+ ldev = ISC_LIST_HEAD(sock->recv_list);
+ while (ldev != NULL) {
+ printf("\t\tdev: %p\n", ldev);
+ ldev = ISC_LIST_NEXT(ldev, ev_link);
+ }
+
+ printf("\n\t\tSock Send List\n");
+ ldev = ISC_LIST_HEAD(sock->send_list);
+ while (ldev != NULL) {
+ printf("\t\tdev: %p\n", ldev);
+ ldev = ISC_LIST_NEXT(ldev, ev_link);
+ }
+
+ printf("\n\t\tSock Accept List\n");
+ ndev = ISC_LIST_HEAD(sock->accept_list);
+ while (ndev != NULL) {
+ printf("\t\tdev: %p\n", ldev);
+ ndev = ISC_LIST_NEXT(ndev, ev_link);
+ }
+
+ printf("\n\t\tSock Connect List\n");
+ cdev = ISC_LIST_HEAD(sock->connect_list);
+ while (cdev != NULL) {
+ printf("\t\tdev: %p\n", cdev);
+ cdev = ISC_LIST_NEXT(cdev, ev_link);
+ }
+}
+
+static void
+socket_log(int lineno, isc_socket_t *sock, const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module, int level,
+ const char *fmt, ...) ISC_FORMAT_PRINTF(10, 11);
+
+/* This function will add an entry to the I/O completion port
+ * that will signal the I/O thread to exit (gracefully)
+ */
+static void
+signal_iocompletionport_exit(isc_socketmgr_t *manager) {
+ int i;
+ int errval;
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(VALID_MANAGER(manager));
+ for (i = 0; i < manager->maxIOCPThreads; i++) {
+ if (!PostQueuedCompletionStatus(manager->hIoCompletionPort, 0,
+ 0, 0))
+ {
+ errval = GetLastError();
+ strerror_r(errval, strbuf, sizeof(strbuf));
+ FATAL_ERROR(__FILE__, __LINE__,
+ "Can't request service thread to exit: %s",
+ strbuf);
+ }
+ }
+}
+
+/*
+ * Create the worker threads for the I/O Completion Port
+ */
+void
+iocompletionport_createthreads(int total_threads, isc_socketmgr_t *manager) {
+ int errval;
+ char strbuf[ISC_STRERRORSIZE];
+ int i;
+
+ INSIST(total_threads > 0);
+ REQUIRE(VALID_MANAGER(manager));
+ /*
+ * We need at least one
+ */
+ for (i = 0; i < total_threads; i++) {
+ isc_thread_create(SocketIoThread, manager,
+ &manager->hIOCPThreads[i]);
+ }
+}
+
+/*
+ * Create/initialise the I/O completion port
+ */
+void
+iocompletionport_init(isc_socketmgr_t *manager) {
+ int errval;
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(VALID_MANAGER(manager));
+ /*
+ * Create a private heap to handle the socket overlapped structure
+ * The minimum number of structures is 10, there is no maximum
+ */
+ hHeapHandle = HeapCreate(0, 10 * sizeof(IoCompletionInfo), 0);
+ if (hHeapHandle == NULL) {
+ errval = GetLastError();
+ strerror_r(errval, strbuf, sizeof(strbuf));
+ FATAL_ERROR(__FILE__, __LINE__,
+ "HeapCreate() failed during initialization: %s",
+ strbuf);
+ }
+
+ /* Now Create the Completion Port */
+ manager->hIoCompletionPort = CreateIoCompletionPort(
+ INVALID_HANDLE_VALUE, NULL, 0, manager->maxIOCPThreads);
+ if (manager->hIoCompletionPort == NULL) {
+ errval = GetLastError();
+ strerror_r(errval, strbuf, sizeof(strbuf));
+ FATAL_ERROR(__FILE__, __LINE__,
+ "CreateIoCompletionPort() failed during "
+ "initialization: %s",
+ strbuf);
+ }
+
+ /*
+ * Worker threads for servicing the I/O
+ */
+ iocompletionport_createthreads(manager->maxIOCPThreads, manager);
+}
+
+/*
+ * Associate a socket with an IO Completion Port. This allows us to queue
+ * events for it and have our worker pool of threads process them.
+ */
+void
+iocompletionport_update(isc_socket_t *sock) {
+ HANDLE hiocp;
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(VALID_SOCKET(sock));
+
+ hiocp = CreateIoCompletionPort((HANDLE)sock->fd,
+ sock->manager->hIoCompletionPort,
+ (ULONG_PTR)sock, 0);
+
+ if (hiocp == NULL) {
+ DWORD errval = GetLastError();
+ strerror_r(errval, strbuf, sizeof(strbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "iocompletionport_update: failed to open io "
+ "completion port: %s",
+ strbuf);
+
+ /* XXXMLG temporary hack to make failures detected.
+ * This function should return errors to the caller, not
+ * exit here.
+ */
+ FATAL_ERROR(__FILE__, __LINE__,
+ "CreateIoCompletionPort() failed during "
+ "initialization: %s",
+ strbuf);
+ }
+
+ InterlockedIncrement(&sock->manager->iocp_total);
+}
+
+/*
+ * Routine to cleanup and then close the socket.
+ * Only close the socket here if it is NOT associated
+ * with an event, otherwise the WSAWaitForMultipleEvents
+ * may fail due to the fact that the Wait should not
+ * be running while closing an event or a socket.
+ * The socket is locked before calling this function
+ */
+void
+socket_close(isc_socket_t *sock) {
+ REQUIRE(sock != NULL);
+
+ if (sock->fd != INVALID_SOCKET) {
+ closesocket(sock->fd);
+ sock->fd = INVALID_SOCKET;
+ _set_state(sock, SOCK_CLOSED);
+ InterlockedDecrement(&sock->manager->totalSockets);
+ }
+}
+
+static isc_once_t initialise_once = ISC_ONCE_INIT;
+static bool initialised = false;
+
+static void
+initialise(void) {
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ int err;
+ SOCKET sock;
+ GUID GUIDConnectEx = WSAID_CONNECTEX;
+ GUID GUIDAcceptEx = WSAID_ACCEPTEX;
+ GUID GUIDGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
+ DWORD dwBytes;
+
+ /* Need Winsock 2.2 or better */
+ wVersionRequested = MAKEWORD(2, 2);
+
+ err = WSAStartup(wVersionRequested, &wsaData);
+ if (err != 0) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(err, strbuf, sizeof(strbuf));
+ FATAL_ERROR(__FILE__, __LINE__, "WSAStartup() failed: %s",
+ strbuf);
+ }
+ /*
+ * The following APIs do not exist as functions in a library, but
+ * we must ask winsock for them. They are "extensions" -- but why
+ * they cannot be actual functions is beyond me. So, ask winsock
+ * for the pointers to the functions we need.
+ */
+ sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ INSIST(sock != INVALID_SOCKET);
+ err = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &GUIDConnectEx,
+ sizeof(GUIDConnectEx), &ISCConnectEx,
+ sizeof(ISCConnectEx), &dwBytes, NULL, NULL);
+ INSIST(err == 0);
+
+ err = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &GUIDAcceptEx,
+ sizeof(GUIDAcceptEx), &ISCAcceptEx, sizeof(ISCAcceptEx),
+ &dwBytes, NULL, NULL);
+ INSIST(err == 0);
+
+ err = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &GUIDGetAcceptExSockaddrs,
+ sizeof(GUIDGetAcceptExSockaddrs),
+ &ISCGetAcceptExSockaddrs,
+ sizeof(ISCGetAcceptExSockaddrs), &dwBytes, NULL, NULL);
+ INSIST(err == 0);
+
+ closesocket(sock);
+
+ initialised = true;
+}
+
+/*
+ * Initialize socket services
+ */
+void
+InitSockets(void) {
+ RUNTIME_CHECK(isc_once_do(&initialise_once, initialise) ==
+ ISC_R_SUCCESS);
+ if (!initialised) {
+ exit(1);
+ }
+}
+
+int
+internal_sendmsg(isc_socket_t *sock, IoCompletionInfo *lpo,
+ struct msghdr *messagehdr, int flags, int *Error) {
+ int Result;
+ DWORD BytesSent;
+ DWORD Flags = flags;
+ int total_sent;
+
+ *Error = 0;
+ Result = WSASendTo(sock->fd, messagehdr->msg_iov,
+ messagehdr->msg_iovlen, &BytesSent, Flags,
+ (SOCKADDR *)&messagehdr->to_addr,
+ messagehdr->to_addr_len, (LPWSAOVERLAPPED)lpo, NULL);
+
+ total_sent = (int)BytesSent;
+
+ /* Check for errors.*/
+ if (Result == SOCKET_ERROR) {
+ *Error = WSAGetLastError();
+
+ switch (*Error) {
+ case WSA_IO_INCOMPLETE:
+ case WSA_WAIT_IO_COMPLETION:
+ case WSA_IO_PENDING:
+ case NO_ERROR: /* Strange, but okay */
+ sock->pending_iocp++;
+ sock->pending_send++;
+ break;
+
+ default:
+ return (-1);
+ break;
+ }
+ } else {
+ sock->pending_iocp++;
+ sock->pending_send++;
+ }
+
+ if (lpo != NULL) {
+ return (0);
+ } else {
+ return (total_sent);
+ }
+}
+
+static void
+queue_receive_request(isc_socket_t *sock) {
+ DWORD Flags = 0;
+ DWORD NumBytes = 0;
+ int Result;
+ int Error;
+ int need_retry;
+ WSABUF iov[1];
+ IoCompletionInfo *lpo = NULL;
+ isc_result_t isc_result;
+
+retry:
+ need_retry = false;
+
+ /*
+ * If we already have a receive pending, do nothing.
+ */
+ if (sock->pending_recv > 0) {
+ if (lpo != NULL) {
+ HeapFree(hHeapHandle, 0, lpo);
+ }
+ return;
+ }
+
+ /*
+ * If no one is waiting, do nothing.
+ */
+ if (ISC_LIST_EMPTY(sock->recv_list)) {
+ if (lpo != NULL) {
+ HeapFree(hHeapHandle, 0, lpo);
+ }
+ return;
+ }
+
+ INSIST(sock->recvbuf.remaining == 0);
+ INSIST(sock->fd != INVALID_SOCKET);
+
+ iov[0].len = sock->recvbuf.len;
+ iov[0].buf = sock->recvbuf.base;
+
+ if (lpo == NULL) {
+ lpo = (IoCompletionInfo *)HeapAlloc(hHeapHandle,
+ HEAP_ZERO_MEMORY,
+ sizeof(IoCompletionInfo));
+ RUNTIME_CHECK(lpo != NULL);
+ } else {
+ ZeroMemory(lpo, sizeof(IoCompletionInfo));
+ }
+ lpo->request_type = SOCKET_RECV;
+
+ sock->recvbuf.from_addr_len = sizeof(sock->recvbuf.from_addr);
+
+ Error = 0;
+ Result = WSARecvFrom((SOCKET)sock->fd, iov, 1, &NumBytes, &Flags,
+ (SOCKADDR *)&sock->recvbuf.from_addr,
+ &sock->recvbuf.from_addr_len, (LPWSAOVERLAPPED)lpo,
+ NULL);
+
+ /* Check for errors. */
+ if (Result == SOCKET_ERROR) {
+ Error = WSAGetLastError();
+
+ switch (Error) {
+ case WSA_IO_PENDING:
+ sock->pending_iocp++;
+ sock->pending_recv++;
+ break;
+
+ /* direct error: no completion event */
+ case ERROR_HOST_UNREACHABLE:
+ case WSAENETRESET:
+ case WSAECONNRESET:
+ if (!sock->connected) {
+ /* soft error */
+ need_retry = true;
+ break;
+ }
+ FALLTHROUGH;
+
+ default:
+ isc_result = isc__errno2result(Error);
+ if (isc_result == ISC_R_UNEXPECTED) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "WSARecvFrom: Windows error "
+ "code: %d, isc result %d",
+ Error, isc_result);
+ }
+ send_recvdone_abort(sock, isc_result);
+ HeapFree(hHeapHandle, 0, lpo);
+ lpo = NULL;
+ break;
+ }
+ } else {
+ /*
+ * The recv() finished immediately, but we will still get
+ * a completion event. Rather than duplicate code, let
+ * that thread handle sending the data along its way.
+ */
+ sock->pending_iocp++;
+ sock->pending_recv++;
+ }
+
+ socket_log(__LINE__, sock, NULL, IOEVENT,
+ "queue_io_request: fd %d result %d error %d", sock->fd,
+ Result, Error);
+
+ CONSISTENT(sock);
+
+ if (need_retry) {
+ goto retry;
+ }
+}
+
+static void
+manager_log(isc_socketmgr_t *sockmgr, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt, ...) {
+ char msgbuf[2048];
+ va_list ap;
+
+ if (!isc_log_wouldlog(isc_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ isc_log_write(isc_lctx, category, module, level, "sockmgr %p: %s",
+ sockmgr, msgbuf);
+}
+
+static void
+socket_log(int lineno, isc_socket_t *sock, const isc_sockaddr_t *address,
+ isc_logcategory_t *category, isc_logmodule_t *module, int level,
+ const char *fmt, ...) {
+ char msgbuf[2048];
+ char peerbuf[256];
+ va_list ap;
+
+ if (!isc_log_wouldlog(isc_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ if (address == NULL) {
+ isc_log_write(isc_lctx, category, module, level,
+ "socket %p line %d: %s", sock, lineno, msgbuf);
+ } else {
+ isc_sockaddr_format(address, peerbuf, sizeof(peerbuf));
+ isc_log_write(isc_lctx, category, module, level,
+ "socket %p line %d %s: %s", sock, lineno, peerbuf,
+ msgbuf);
+ }
+}
+
+/*
+ * Make an fd SOCKET non-blocking.
+ */
+static isc_result_t
+make_nonblock(SOCKET fd) {
+ int ret;
+ unsigned long flags = 1;
+ char strbuf[ISC_STRERRORSIZE];
+
+ /* Set the socket to non-blocking */
+ ret = ioctlsocket(fd, FIONBIO, &flags);
+
+ if (ret == -1) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "ioctlsocket(%d, FIOBIO, %d): %s", fd, flags,
+ strbuf);
+
+ return (ISC_R_UNEXPECTED);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Windows 2000 systems incorrectly cause UDP sockets using WSARecvFrom
+ * to not work correctly, returning a WSACONNRESET error when a WSASendTo
+ * fails with an "ICMP port unreachable" response and preventing the
+ * socket from using the WSARecvFrom in subsequent operations.
+ * The function below fixes this, but requires that Windows 2000
+ * Service Pack 2 or later be installed on the system. NT 4.0
+ * systems are not affected by this and work correctly.
+ * See Microsoft Knowledge Base Article Q263823 for details of this.
+ */
+isc_result_t
+connection_reset_fix(SOCKET fd) {
+ DWORD dwBytesReturned = 0;
+ BOOL bNewBehavior = FALSE;
+ DWORD status;
+
+ if (isc_win32os_versioncheck(5, 0, 0, 0) < 0) {
+ return (ISC_R_SUCCESS); /* NT 4.0 has no problem */
+ }
+ /* disable bad behavior using IOCTL: SIO_UDP_CONNRESET */
+ status = WSAIoctl(fd, SIO_UDP_CONNRESET, &bNewBehavior,
+ sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL,
+ NULL);
+ if (status != SOCKET_ERROR) {
+ return (ISC_R_SUCCESS);
+ } else {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "WSAIoctl(SIO_UDP_CONNRESET, oldBehaviour) "
+ "failed");
+ return (ISC_R_UNEXPECTED);
+ }
+}
+
+/*
+ * Construct an iov array and attach it to the msghdr passed in. This is
+ * the SEND constructor, which will use the used region of the buffer
+ * (if using a buffer list) or will use the internal region (if a single
+ * buffer I/O is requested).
+ *
+ * Nothing can be NULL, and the done event must list at least one buffer
+ * on the buffer linked list for this function to be meaningful.
+ */
+static void
+build_msghdr_send(isc_socket_t *sock, isc_socketevent_t *dev,
+ struct msghdr *msg, char *cmsg, WSABUF *iov,
+ IoCompletionInfo *lpo) {
+ unsigned int iovcount;
+ size_t write_count;
+
+ memset(msg, 0, sizeof(*msg));
+
+ memmove(&msg->to_addr, &dev->address.type, dev->address.length);
+ msg->to_addr_len = dev->address.length;
+
+ write_count = 0;
+ iovcount = 0;
+
+ /*
+ * Single buffer I/O? Skip what we've done so far in this region.
+ */
+ write_count = dev->region.length - dev->n;
+ lpo->buf = HeapAlloc(hHeapHandle, HEAP_ZERO_MEMORY, write_count);
+ RUNTIME_CHECK(lpo->buf != NULL);
+
+ socket_log(__LINE__, sock, NULL, TRACE, "alloc_buffer %p %d", lpo->buf,
+ write_count);
+
+ memmove(lpo->buf, (dev->region.base + dev->n), write_count);
+ lpo->buflen = (unsigned int)write_count;
+ iov[0].buf = lpo->buf;
+ iov[0].len = (u_long)write_count;
+ iovcount = 1;
+
+ msg->msg_iov = iov;
+ msg->msg_iovlen = iovcount;
+ msg->msg_totallen = (u_int)write_count;
+}
+
+static void
+set_dev_address(const isc_sockaddr_t *address, isc_socket_t *sock,
+ isc_socketevent_t *dev) {
+ if (sock->type == isc_sockettype_udp) {
+ if (address != NULL) {
+ dev->address = *address;
+ } else {
+ dev->address = sock->address;
+ }
+ } else if (sock->type == isc_sockettype_tcp) {
+ INSIST(address == NULL);
+ dev->address = sock->address;
+ }
+}
+
+static void
+destroy_socketevent(isc_event_t *event) {
+ isc_socketevent_t *ev = (isc_socketevent_t *)event;
+
+ (ev->destroy)(event);
+}
+
+static isc_socketevent_t *
+allocate_socketevent(isc_mem_t *mctx, isc_socket_t *sock,
+ isc_eventtype_t eventtype, isc_taskaction_t action,
+ void *arg) {
+ isc_socketevent_t *ev;
+
+ ev = (isc_socketevent_t *)isc_event_allocate(mctx, sock, eventtype,
+ action, arg, sizeof(*ev));
+
+ ev->result = ISC_R_IOERROR; /* XXXMLG temporary change to detect failure
+ */
+ /* to set */
+ ISC_LINK_INIT(ev, ev_link);
+ ev->region.base = NULL;
+ ev->n = 0;
+ ev->offset = 0;
+ ev->attributes = 0;
+ ev->destroy = ev->ev_destroy;
+ ev->ev_destroy = destroy_socketevent;
+ ev->dscp = 0;
+
+ return (ev);
+}
+
+#if defined(ISC_SOCKET_DEBUG)
+static void
+dump_msg(struct msghdr *msg, isc_socket_t *sock) {
+ unsigned int i;
+
+ printf("MSGHDR %p, Socket #: %Iu\n", msg, sock->fd);
+ printf("\tname %p, namelen %d\n", msg->msg_name, msg->msg_namelen);
+ printf("\tiov %p, iovlen %d\n", msg->msg_iov, msg->msg_iovlen);
+ for (i = 0; i < (unsigned int)msg->msg_iovlen; i++) {
+ printf("\t\t%u\tbase %p, len %u\n", i, msg->msg_iov[i].buf,
+ msg->msg_iov[i].len);
+ }
+}
+#endif /* if defined(ISC_SOCKET_DEBUG) */
+
+/*
+ * map the error code
+ */
+int
+map_socket_error(isc_socket_t *sock, int windows_errno, int *isc_errno,
+ char *errorstring, size_t bufsize) {
+ int doreturn;
+ switch (windows_errno) {
+ case WSAECONNREFUSED:
+ *isc_errno = ISC_R_CONNREFUSED;
+ if (sock->connected) {
+ doreturn = DOIO_HARD;
+ } else {
+ doreturn = DOIO_SOFT;
+ }
+ break;
+ case WSAENETUNREACH:
+ case ERROR_NETWORK_UNREACHABLE:
+ *isc_errno = ISC_R_NETUNREACH;
+ if (sock->connected) {
+ doreturn = DOIO_HARD;
+ } else {
+ doreturn = DOIO_SOFT;
+ }
+ break;
+ case ERROR_PORT_UNREACHABLE:
+ case ERROR_HOST_UNREACHABLE:
+ case WSAEHOSTUNREACH:
+ *isc_errno = ISC_R_HOSTUNREACH;
+ if (sock->connected) {
+ doreturn = DOIO_HARD;
+ } else {
+ doreturn = DOIO_SOFT;
+ }
+ break;
+ case WSAENETDOWN:
+ *isc_errno = ISC_R_NETDOWN;
+ if (sock->connected) {
+ doreturn = DOIO_HARD;
+ } else {
+ doreturn = DOIO_SOFT;
+ }
+ break;
+ case WSAEHOSTDOWN:
+ *isc_errno = ISC_R_HOSTDOWN;
+ if (sock->connected) {
+ doreturn = DOIO_HARD;
+ } else {
+ doreturn = DOIO_SOFT;
+ }
+ break;
+ case WSAEACCES:
+ *isc_errno = ISC_R_NOPERM;
+ if (sock->connected) {
+ doreturn = DOIO_HARD;
+ } else {
+ doreturn = DOIO_SOFT;
+ }
+ break;
+ case WSAECONNRESET:
+ case WSAENETRESET:
+ case WSAECONNABORTED:
+ case WSAEDISCON:
+ *isc_errno = ISC_R_CONNECTIONRESET;
+ if (sock->connected) {
+ doreturn = DOIO_HARD;
+ } else {
+ doreturn = DOIO_SOFT;
+ }
+ break;
+ case WSAENOTCONN:
+ *isc_errno = ISC_R_NOTCONNECTED;
+ if (sock->connected) {
+ doreturn = DOIO_HARD;
+ } else {
+ doreturn = DOIO_SOFT;
+ }
+ break;
+ case ERROR_OPERATION_ABORTED:
+ case ERROR_CONNECTION_ABORTED:
+ case ERROR_REQUEST_ABORTED:
+ *isc_errno = ISC_R_CONNECTIONRESET;
+ doreturn = DOIO_HARD;
+ break;
+ case WSAENOBUFS:
+ *isc_errno = ISC_R_NORESOURCES;
+ doreturn = DOIO_HARD;
+ break;
+ case WSAEAFNOSUPPORT:
+ *isc_errno = ISC_R_FAMILYNOSUPPORT;
+ doreturn = DOIO_HARD;
+ break;
+ case WSAEADDRNOTAVAIL:
+ *isc_errno = ISC_R_ADDRNOTAVAIL;
+ doreturn = DOIO_HARD;
+ break;
+ case WSAEDESTADDRREQ:
+ *isc_errno = ISC_R_BADADDRESSFORM;
+ doreturn = DOIO_HARD;
+ break;
+ case ERROR_NETNAME_DELETED:
+ *isc_errno = ISC_R_NETDOWN;
+ doreturn = DOIO_HARD;
+ break;
+ default:
+ *isc_errno = ISC_R_IOERROR;
+ doreturn = DOIO_HARD;
+ break;
+ }
+ if (doreturn == DOIO_HARD) {
+ strerror_r(windows_errno, errorstring, bufsize);
+ }
+ return (doreturn);
+}
+
+static void
+fill_recv(isc_socket_t *sock, isc_socketevent_t *dev) {
+ int copylen;
+
+ INSIST(dev->n < dev->minimum);
+ INSIST(sock->recvbuf.remaining > 0);
+ INSIST(sock->pending_recv == 0);
+
+ if (sock->type == isc_sockettype_udp) {
+ dev->address.length = sock->recvbuf.from_addr_len;
+ memmove(&dev->address.type, &sock->recvbuf.from_addr,
+ sock->recvbuf.from_addr_len);
+ if (isc_sockaddr_getport(&dev->address) == 0) {
+ if (isc_log_wouldlog(isc_lctx, IOEVENT_LEVEL)) {
+ socket_log(__LINE__, sock, &dev->address,
+ IOEVENT,
+ "dropping source port zero packet");
+ }
+ sock->recvbuf.remaining = 0;
+ return;
+ }
+ /*
+ * Simulate a firewall blocking UDP responses bigger than
+ * 'maxudp' bytes.
+ */
+ if (sock->manager->maxudp != 0 &&
+ sock->recvbuf.remaining > sock->manager->maxudp)
+ {
+ sock->recvbuf.remaining = 0;
+ return;
+ }
+ } else if (sock->type == isc_sockettype_tcp) {
+ dev->address = sock->address;
+ }
+
+ copylen = min(dev->region.length - dev->n, sock->recvbuf.remaining);
+ memmove(dev->region.base + dev->n, sock->recvbuf.consume_position,
+ copylen);
+ sock->recvbuf.consume_position += copylen;
+ sock->recvbuf.remaining -= copylen;
+ dev->n += copylen;
+
+ /*
+ * UDP receives are all-consuming. That is, if we have 4k worth of
+ * data in our receive buffer, and the caller only gave us
+ * 1k of space, we will toss the remaining 3k of data. TCP
+ * will keep the extra data around and use it for later requests.
+ */
+ if (sock->type == isc_sockettype_udp) {
+ sock->recvbuf.remaining = 0;
+ }
+}
+
+/*
+ * Copy out as much data from the internal buffer to done events.
+ * As each done event is filled, send it along its way.
+ */
+static void
+completeio_recv(isc_socket_t *sock) {
+ isc_socketevent_t *dev;
+
+ /*
+ * If we are in the process of filling our buffer, we cannot
+ * touch it yet, so don't.
+ */
+ if (sock->pending_recv > 0) {
+ return;
+ }
+
+ while (sock->recvbuf.remaining > 0 && !ISC_LIST_EMPTY(sock->recv_list))
+ {
+ dev = ISC_LIST_HEAD(sock->recv_list);
+
+ /*
+ * See if we have sufficient data in our receive buffer
+ * to handle this. If we do, copy out the data.
+ */
+ fill_recv(sock, dev);
+
+ /*
+ * Did we satisfy it?
+ */
+ if (dev->n >= dev->minimum) {
+ dev->result = ISC_R_SUCCESS;
+ send_recvdone_event(sock, &dev);
+ }
+ }
+}
+
+/*
+ * Returns:
+ * DOIO_SUCCESS The operation succeeded. dev->result contains
+ * ISC_R_SUCCESS.
+ *
+ * DOIO_HARD A hard or unexpected I/O error was encountered.
+ * dev->result contains the appropriate error.
+ *
+ * DOIO_SOFT A soft I/O error was encountered. No senddone
+ * event was sent. The operation should be retried.
+ *
+ * No other return values are possible.
+ */
+static int
+completeio_send(isc_socket_t *sock, isc_socketevent_t *dev,
+ struct msghdr *messagehdr, int cc, int send_errno) {
+ char strbuf[ISC_STRERRORSIZE];
+
+ if (send_errno != 0) {
+ if (SOFT_ERROR(send_errno)) {
+ return (DOIO_SOFT);
+ }
+
+ return (map_socket_error(sock, send_errno, &dev->result, strbuf,
+ sizeof(strbuf)));
+ }
+
+ /*
+ * If we write less than we expected, update counters, poke.
+ */
+ dev->n += cc;
+ if (cc != messagehdr->msg_totallen) {
+ return (DOIO_SOFT);
+ }
+
+ /*
+ * Exactly what we wanted to write. We're done with this
+ * entry. Post its completion event.
+ */
+ dev->result = ISC_R_SUCCESS;
+ return (DOIO_SUCCESS);
+}
+
+static int
+startio_send(isc_socket_t *sock, isc_socketevent_t *dev, int *nbytes,
+ int *send_errno) {
+ char *cmsg = NULL;
+ char strbuf[ISC_STRERRORSIZE];
+ IoCompletionInfo *lpo;
+ int status;
+ struct msghdr *mh;
+
+ /*
+ * Simulate a firewall blocking UDP responses bigger than
+ * 'maxudp' bytes.
+ */
+ if (sock->type == isc_sockettype_udp && sock->manager->maxudp != 0 &&
+ dev->region.length - dev->n > sock->manager->maxudp)
+ {
+ *nbytes = dev->region.length - dev->n;
+ return (DOIO_SUCCESS);
+ }
+
+ lpo = (IoCompletionInfo *)HeapAlloc(hHeapHandle, HEAP_ZERO_MEMORY,
+ sizeof(IoCompletionInfo));
+ RUNTIME_CHECK(lpo != NULL);
+ lpo->request_type = SOCKET_SEND;
+ lpo->dev = dev;
+ mh = &lpo->messagehdr;
+ memset(mh, 0, sizeof(struct msghdr));
+
+ build_msghdr_send(sock, dev, mh, cmsg, sock->iov, lpo);
+
+ *nbytes = internal_sendmsg(sock, lpo, mh, 0, send_errno);
+
+ if (*nbytes <= 0) {
+ /*
+ * I/O has been initiated
+ * completion will be through the completion port
+ */
+ if (PENDING_ERROR(*send_errno)) {
+ status = DOIO_PENDING;
+ goto done;
+ }
+
+ if (SOFT_ERROR(*send_errno)) {
+ status = DOIO_SOFT;
+ goto done;
+ }
+
+ /*
+ * If we got this far then something is wrong
+ */
+ if (isc_log_wouldlog(isc_lctx, IOEVENT_LEVEL)) {
+ strerror_r(*send_errno, strbuf, sizeof(strbuf));
+ socket_log(__LINE__, sock, NULL, IOEVENT,
+ "startio_send: internal_sendmsg(%d) %d "
+ "bytes, err %d/%s",
+ sock->fd, *nbytes, *send_errno, strbuf);
+ }
+ status = DOIO_HARD;
+ goto done;
+ }
+ dev->result = ISC_R_SUCCESS;
+ status = DOIO_SOFT;
+done:
+ _set_state(sock, SOCK_DATA);
+ return (status);
+}
+
+static void
+use_min_mtu(isc_socket_t *sock) {
+#ifdef IPV6_USE_MIN_MTU
+ /* use minimum MTU */
+ if (sock->pf == AF_INET6) {
+ int on = 1;
+ (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_USE_MIN_MTU,
+ (void *)&on, sizeof(on));
+ }
+#else /* ifdef IPV6_USE_MIN_MTU */
+ UNUSED(sock);
+#endif /* ifdef IPV6_USE_MIN_MTU */
+}
+
+static isc_result_t
+allocate_socket(isc_socketmgr_t *manager, isc_sockettype_t type,
+ isc_socket_t **socketp) {
+ isc_socket_t *sock;
+
+ sock = isc_mem_get(manager->mctx, sizeof(*sock));
+
+ sock->magic = 0;
+ isc_refcount_init(&sock->references, 0);
+
+ sock->manager = manager;
+ sock->type = type;
+ sock->fd = INVALID_SOCKET;
+
+ ISC_LINK_INIT(sock, link);
+
+ /*
+ * Set up list of readers and writers to be initially empty.
+ */
+ ISC_LIST_INIT(sock->recv_list);
+ ISC_LIST_INIT(sock->send_list);
+ ISC_LIST_INIT(sock->accept_list);
+ ISC_LIST_INIT(sock->connect_list);
+ sock->pending_accept = 0;
+ sock->pending_recv = 0;
+ sock->pending_send = 0;
+ sock->pending_iocp = 0;
+ sock->listener = 0;
+ sock->connected = 0;
+ sock->pending_connect = 0;
+ sock->bound = 0;
+ sock->dupped = 0;
+ memset(sock->name, 0, sizeof(sock->name)); /* zero the name field */
+ _set_state(sock, SOCK_INITIALIZED);
+
+ sock->recvbuf.len = 65536;
+ sock->recvbuf.consume_position = sock->recvbuf.base;
+ sock->recvbuf.remaining = 0;
+ sock->recvbuf.base = isc_mem_get(manager->mctx,
+ sock->recvbuf.len); /* max buffer */
+ /* size */
+
+ /*
+ * Initialize the lock.
+ */
+ isc_mutex_init(&sock->lock);
+
+ socket_log(__LINE__, sock, NULL, EVENT, "allocated");
+
+ sock->magic = SOCKET_MAGIC;
+ *socketp = sock;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Verify that the socket state is consistent.
+ */
+static void
+consistent(isc_socket_t *sock) {
+ isc_socketevent_t *dev;
+ isc_socket_newconnev_t *nev;
+ unsigned int count;
+ char *crash_reason;
+ bool crash = false;
+
+ REQUIRE(sock->pending_iocp == sock->pending_recv + sock->pending_send +
+ sock->pending_accept +
+ sock->pending_connect);
+
+ dev = ISC_LIST_HEAD(sock->send_list);
+ count = 0;
+ while (dev != NULL) {
+ count++;
+ dev = ISC_LIST_NEXT(dev, ev_link);
+ }
+ if (count > sock->pending_send) {
+ crash = true;
+ crash_reason = "send_list > sock->pending_send";
+ }
+
+ nev = ISC_LIST_HEAD(sock->accept_list);
+ count = 0;
+ while (nev != NULL) {
+ count++;
+ nev = ISC_LIST_NEXT(nev, ev_link);
+ }
+ if (count > sock->pending_accept) {
+ crash = true;
+ crash_reason = "accept_list > sock->pending_accept";
+ }
+
+ if (crash) {
+ socket_log(__LINE__, sock, NULL, CREATION,
+ "SOCKET INCONSISTENT: %s", crash_reason);
+ sock_dump(sock);
+ INSIST(!crash);
+ }
+}
+
+/*
+ * Maybe free the socket.
+ *
+ * This function will verify that the socket is no longer in use in any way,
+ * either internally or externally. This is the only place where this
+ * check is to be made; if some bit of code believes that IT is done with
+ * the socket (e.g., some reference counter reaches zero), it should call
+ * this function.
+ *
+ * When calling this function, the socket must be locked, and the manager
+ * must be unlocked.
+ *
+ * When this function returns, *socketp will be NULL. No tricks to try
+ * to hold on to this pointer are allowed.
+ */
+static void
+maybe_free_socket(isc_socket_t **socketp, int lineno) {
+ isc_socket_t *sock = *socketp;
+ *socketp = NULL;
+
+ INSIST(VALID_SOCKET(sock));
+ CONSISTENT(sock);
+
+ if (sock->pending_iocp > 0 || sock->pending_recv > 0 ||
+ sock->pending_send > 0 || sock->pending_accept > 0 ||
+ isc_refcount_current(&sock->references) > 0 ||
+ sock->pending_connect == 1 || !ISC_LIST_EMPTY(sock->recv_list) ||
+ !ISC_LIST_EMPTY(sock->send_list) ||
+ !ISC_LIST_EMPTY(sock->accept_list) ||
+ !ISC_LIST_EMPTY(sock->connect_list) || sock->fd != INVALID_SOCKET)
+ {
+ UNLOCK(&sock->lock);
+ return;
+ }
+ UNLOCK(&sock->lock);
+
+ free_socket(&sock, lineno);
+}
+
+void
+free_socket(isc_socket_t **sockp, int lineno) {
+ isc_socketmgr_t *manager;
+ isc_socket_t *sock = *sockp;
+ *sockp = NULL;
+
+ /*
+ * Seems we can free the socket after all.
+ */
+ manager = sock->manager;
+ socket_log(__LINE__, sock, NULL, CREATION,
+ "freeing socket line %d fd %d lock %p semaphore %p", lineno,
+ sock->fd, &sock->lock, sock->lock.LockSemaphore);
+
+ sock->magic = 0;
+ isc_mutex_destroy(&sock->lock);
+
+ if (sock->recvbuf.base != NULL) {
+ isc_mem_put(manager->mctx, sock->recvbuf.base,
+ sock->recvbuf.len);
+ }
+
+ LOCK(&manager->lock);
+ if (ISC_LINK_LINKED(sock, link)) {
+ ISC_LIST_UNLINK(manager->socklist, sock, link);
+ }
+ isc_mem_put(manager->mctx, sock, sizeof(*sock));
+
+ if (ISC_LIST_EMPTY(manager->socklist)) {
+ SIGNAL(&manager->shutdown_ok);
+ }
+ UNLOCK(&manager->lock);
+}
+
+/*
+ * Create a new 'type' socket managed by 'manager'. Events
+ * will be posted to 'task' and when dispatched 'action' will be
+ * called with 'arg' as the arg value. The new socket is returned
+ * in 'socketp'.
+ */
+static isc_result_t
+socket_create(isc_socketmgr_t *manager, int pf, isc_sockettype_t type,
+ isc_socket_t **socketp, isc_socket_t *dup_socket) {
+ isc_socket_t *sock = NULL;
+ isc_result_t result;
+#if defined(USE_CMSG)
+ int on = 1;
+#endif /* if defined(USE_CMSG) */
+#if defined(SO_RCVBUF)
+ socklen_t optlen;
+ int size;
+#endif /* if defined(SO_RCVBUF) */
+ int socket_errno;
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(VALID_MANAGER(manager));
+ REQUIRE(socketp != NULL && *socketp == NULL);
+
+#ifndef SOCK_RAW
+ if (type == isc_sockettype_raw) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+#endif /* ifndef SOCK_RAW */
+
+ result = allocate_socket(manager, type, &sock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ sock->pf = pf;
+ switch (type) {
+ case isc_sockettype_udp:
+ sock->fd = socket(pf, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock->fd != INVALID_SOCKET) {
+ result = connection_reset_fix(sock->fd);
+ if (result != ISC_R_SUCCESS) {
+ socket_log(__LINE__, sock, NULL, EVENT,
+ "closed %d %d %" PRIuFAST32 " "
+ "con_reset_fix_failed",
+ sock->pending_recv,
+ sock->pending_send,
+ isc_refcount_current(
+ &sock->references));
+ closesocket(sock->fd);
+ _set_state(sock, SOCK_CLOSED);
+ sock->fd = INVALID_SOCKET;
+ free_socket(&sock, __LINE__);
+ return (result);
+ }
+ }
+ break;
+ case isc_sockettype_tcp:
+ sock->fd = socket(pf, SOCK_STREAM, IPPROTO_TCP);
+ break;
+#ifdef SOCK_RAW
+ case isc_sockettype_raw:
+ sock->fd = socket(pf, SOCK_RAW, 0);
+#ifdef PF_ROUTE
+ if (pf == PF_ROUTE) {
+ sock->bound = 1;
+ }
+#endif /* ifdef PF_ROUTE */
+ break;
+#endif /* ifdef SOCK_RAW */
+ }
+
+ if (sock->fd == INVALID_SOCKET) {
+ socket_errno = WSAGetLastError();
+ free_socket(&sock, __LINE__);
+
+ switch (socket_errno) {
+ case WSAEMFILE:
+ case WSAENOBUFS:
+ return (ISC_R_NORESOURCES);
+
+ case WSAEPROTONOSUPPORT:
+ case WSAEPFNOSUPPORT:
+ case WSAEAFNOSUPPORT:
+ return (ISC_R_FAMILYNOSUPPORT);
+
+ default:
+ strerror_r(socket_errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "socket() failed: %s", strbuf);
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+
+ result = make_nonblock(sock->fd);
+ if (result != ISC_R_SUCCESS) {
+ socket_log(__LINE__, sock, NULL, EVENT,
+ "closed %d %d %" PRIuFAST32 " make_nonblock_failed",
+ sock->pending_recv, sock->pending_send,
+ isc_refcount_current(&sock->references));
+ closesocket(sock->fd);
+ sock->fd = INVALID_SOCKET;
+ free_socket(&sock, __LINE__);
+ return (result);
+ }
+
+ /*
+ * Use minimum mtu if possible.
+ */
+ use_min_mtu(sock);
+
+#if defined(USE_CMSG) || defined(SO_RCVBUF)
+ if (type == isc_sockettype_udp) {
+#if defined(USE_CMSG)
+#ifdef IPV6_RECVPKTINFO
+ /* 2292bis */
+ if ((pf == AF_INET6) &&
+ (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ (char *)&on, sizeof(on)) < 0))
+ {
+ strerror_r(WSAGetLastError(), strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IPV6_RECVPKTINFO) "
+ "failed: %s",
+ sock->fd, strbuf);
+ }
+#else /* ifdef IPV6_RECVPKTINFO */
+ /* 2292 */
+ if ((pf == AF_INET6) &&
+ (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_PKTINFO,
+ (char *)&on, sizeof(on)) < 0))
+ {
+ strerror_r(WSAGetLastError(), strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, IPV6_PKTINFO) %s: %s",
+ sock->fd, strbuf);
+ }
+#endif /* IPV6_RECVPKTINFO */
+#endif /* defined(USE_CMSG) */
+
+#if defined(SO_RCVBUF)
+ optlen = sizeof(size);
+ if (getsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF, (char *)&size,
+ &optlen) >= 0 &&
+ size < RCVBUFSIZE)
+ {
+ size = RCVBUFSIZE;
+ (void)setsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF,
+ (char *)&size, sizeof(size));
+ }
+#endif /* if defined(SO_RCVBUF) */
+ }
+#endif /* defined(USE_CMSG) || defined(SO_RCVBUF) */
+
+ _set_state(sock, SOCK_OPEN);
+ isc_refcount_init(&sock->references, 1);
+ *socketp = sock;
+
+ iocompletionport_update(sock);
+
+ if (dup_socket) {
+#ifndef ISC_ALLOW_MAPPED
+ isc_socket_ipv6only(sock, true);
+#endif /* ifndef ISC_ALLOW_MAPPED */
+
+ if (dup_socket->bound) {
+ isc_sockaddr_t local;
+
+ result = isc_socket_getsockname(dup_socket, &local);
+ if (result != ISC_R_SUCCESS) {
+ isc_socket_close(sock);
+ return (result);
+ }
+ result = isc_socket_bind(sock, &local,
+ ISC_SOCKET_REUSEADDRESS);
+ if (result != ISC_R_SUCCESS) {
+ isc_socket_close(sock);
+ return (result);
+ }
+ }
+ sock->dupped = 1;
+ }
+
+ /*
+ * Note we don't have to lock the socket like we normally would because
+ * there are no external references to it yet.
+ */
+ LOCK(&manager->lock);
+ ISC_LIST_APPEND(manager->socklist, sock, link);
+ InterlockedIncrement(&manager->totalSockets);
+ UNLOCK(&manager->lock);
+
+ socket_log(__LINE__, sock, NULL, CREATION, "created %u type %u",
+ sock->fd, type);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_socket_create(isc_socketmgr_t *manager, int pf, isc_sockettype_t type,
+ isc_socket_t **socketp) {
+ return (socket_create(manager, pf, type, socketp, NULL));
+}
+
+isc_result_t
+isc_socket_dup(isc_socket_t *sock, isc_socket_t **socketp) {
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(socketp != NULL && *socketp == NULL);
+
+ return (socket_create(sock->manager, sock->pf, sock->type, socketp,
+ sock));
+}
+
+isc_result_t
+isc_socket_open(isc_socket_t *sock) {
+ REQUIRE(VALID_SOCKET(sock));
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+/*
+ * Attach to a socket. Caller must explicitly detach when it is done.
+ */
+void
+isc_socket_attach(isc_socket_t *sock, isc_socket_t **socketp) {
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(socketp != NULL && *socketp == NULL);
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+ UNLOCK(&sock->lock);
+
+ isc_refcount_increment0(&sock->references);
+
+ *socketp = sock;
+}
+
+/*
+ * Dereference a socket. If this is the last reference to it, clean things
+ * up by destroying the socket.
+ */
+void
+isc_socket_detach(isc_socket_t **socketp) {
+ isc_socket_t *sock;
+ uint32_t references;
+
+ REQUIRE(socketp != NULL);
+ sock = *socketp;
+ *socketp = NULL;
+ REQUIRE(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ references = isc_refcount_decrement(&sock->references);
+
+ socket_log(__LINE__, sock, NULL, EVENT,
+ "detach_socket %d %d %" PRIuFAST32, sock->pending_recv,
+ sock->pending_send, isc_refcount_current(&sock->references));
+
+ if (references == 1 && sock->fd != INVALID_SOCKET) {
+ closesocket(sock->fd);
+ sock->fd = INVALID_SOCKET;
+ _set_state(sock, SOCK_CLOSED);
+ }
+
+ maybe_free_socket(&sock, __LINE__); /* Also unlocks the socket lock */
+}
+
+isc_result_t
+isc_socket_close(isc_socket_t *sock) {
+ REQUIRE(VALID_SOCKET(sock));
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+/*
+ * Dequeue an item off the given socket's read queue, set the result code
+ * in the done event to the one provided, and send it to the task it was
+ * destined for.
+ *
+ * If the event to be sent is on a list, remove it before sending. If
+ * asked to, send and detach from the task as well.
+ *
+ * Caller must have the socket locked if the event is attached to the socket.
+ */
+static void
+send_recvdone_event(isc_socket_t *sock, isc_socketevent_t **dev) {
+ isc_task_t *task;
+
+ task = (*dev)->ev_sender;
+ (*dev)->ev_sender = sock;
+
+ if (ISC_LINK_LINKED(*dev, ev_link)) {
+ ISC_LIST_DEQUEUE(sock->recv_list, *dev, ev_link);
+ }
+
+ if (((*dev)->attributes & ISC_SOCKEVENTATTR_ATTACHED) != 0) {
+ isc_task_sendanddetach(&task, (isc_event_t **)dev);
+ } else {
+ isc_task_send(task, (isc_event_t **)dev);
+ }
+
+ CONSISTENT(sock);
+}
+
+/*
+ * See comments for send_recvdone_event() above.
+ */
+static void
+send_senddone_event(isc_socket_t *sock, isc_socketevent_t **dev) {
+ isc_task_t *task;
+
+ INSIST(dev != NULL && *dev != NULL);
+
+ task = (*dev)->ev_sender;
+ (*dev)->ev_sender = sock;
+
+ if (ISC_LINK_LINKED(*dev, ev_link)) {
+ ISC_LIST_DEQUEUE(sock->send_list, *dev, ev_link);
+ }
+
+ if (((*dev)->attributes & ISC_SOCKEVENTATTR_ATTACHED) != 0) {
+ isc_task_sendanddetach(&task, (isc_event_t **)dev);
+ } else {
+ isc_task_send(task, (isc_event_t **)dev);
+ }
+
+ CONSISTENT(sock);
+}
+
+/*
+ * See comments for send_recvdone_event() above.
+ */
+static void
+send_acceptdone_event(isc_socket_t *sock, isc_socket_newconnev_t **adev) {
+ isc_task_t *task;
+
+ INSIST(adev != NULL && *adev != NULL);
+
+ task = (*adev)->ev_sender;
+ (*adev)->ev_sender = sock;
+
+ if (ISC_LINK_LINKED(*adev, ev_link)) {
+ ISC_LIST_DEQUEUE(sock->accept_list, *adev, ev_link);
+ }
+
+ isc_task_sendanddetach(&task, (isc_event_t **)adev);
+
+ CONSISTENT(sock);
+}
+
+/*
+ * See comments for send_recvdone_event() above.
+ */
+static void
+send_connectdone_event(isc_socket_t *sock, isc_socket_connev_t **cdev) {
+ isc_task_t *task;
+
+ INSIST(cdev != NULL && *cdev != NULL);
+
+ task = (*cdev)->ev_sender;
+ (*cdev)->ev_sender = sock;
+
+ if (ISC_LINK_LINKED(*cdev, ev_link)) {
+ ISC_LIST_DEQUEUE(sock->connect_list, *cdev, ev_link);
+ }
+
+ isc_task_sendanddetach(&task, (isc_event_t **)cdev);
+
+ CONSISTENT(sock);
+}
+
+/*
+ * On entry to this function, the event delivered is the internal
+ * readable event, and the first item on the accept_list should be
+ * the done event we want to send. If the list is empty, this is a no-op,
+ * so just close the new connection, unlock, and return.
+ *
+ * Note the socket is locked before entering here
+ */
+static void
+internal_accept(isc_socket_t *sock, IoCompletionInfo *lpo, int accept_errno) {
+ isc_socket_newconnev_t *adev;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_socket_t *nsock;
+ struct sockaddr *localaddr;
+ int localaddr_len = sizeof(*localaddr);
+ struct sockaddr *remoteaddr;
+ int remoteaddr_len = sizeof(*remoteaddr);
+
+ INSIST(VALID_SOCKET(sock));
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ socket_log(__LINE__, sock, NULL, TRACE, "internal_accept called");
+
+ INSIST(sock->listener);
+
+ INSIST(sock->pending_iocp > 0);
+ sock->pending_iocp--;
+ INSIST(sock->pending_accept > 0);
+ sock->pending_accept--;
+
+ adev = lpo->adev;
+
+ /*
+ * If the event is no longer in the list we can just return.
+ */
+ if (!acceptdone_is_active(sock, adev)) {
+ goto done;
+ }
+
+ nsock = adev->newsocket;
+
+ /*
+ * Pull off the done event.
+ */
+ ISC_LIST_UNLINK(sock->accept_list, adev, ev_link);
+
+ /*
+ * Extract the addresses from the socket, copy them into the structure,
+ * and return the new socket.
+ */
+ ISCGetAcceptExSockaddrs(
+ lpo->acceptbuffer, 0, sizeof(SOCKADDR_STORAGE) + 16,
+ sizeof(SOCKADDR_STORAGE) + 16, (LPSOCKADDR *)&localaddr,
+ &localaddr_len, (LPSOCKADDR *)&remoteaddr, &remoteaddr_len);
+ memmove(&adev->address.type, remoteaddr, remoteaddr_len);
+ adev->address.length = remoteaddr_len;
+ nsock->address = adev->address;
+ nsock->pf = adev->address.type.sa.sa_family;
+
+ socket_log(__LINE__, nsock, &nsock->address, TRACE,
+ "internal_accept parent %p", sock);
+
+ result = make_nonblock(adev->newsocket->fd);
+ INSIST(result == ISC_R_SUCCESS);
+
+ /*
+ * Use minimum mtu if possible.
+ */
+ use_min_mtu(adev->newsocket);
+
+ INSIST(setsockopt(nsock->fd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
+ (char *)&sock->fd, sizeof(sock->fd)) == 0);
+
+ /*
+ * Hook it up into the manager.
+ */
+ nsock->bound = 1;
+ nsock->connected = 1;
+ _set_state(nsock, SOCK_OPEN);
+
+ LOCK(&nsock->manager->lock);
+ ISC_LIST_APPEND(nsock->manager->socklist, nsock, link);
+ InterlockedIncrement(&nsock->manager->totalSockets);
+ UNLOCK(&nsock->manager->lock);
+
+ socket_log(__LINE__, sock, &nsock->address, CREATION,
+ "accepted_connection new_socket %p fd %d", nsock, nsock->fd);
+
+ adev->result = result;
+ send_acceptdone_event(sock, &adev);
+
+done:
+ CONSISTENT(sock);
+ UNLOCK(&sock->lock);
+
+ HeapFree(hHeapHandle, 0, lpo->acceptbuffer);
+ lpo->acceptbuffer = NULL;
+}
+
+/*
+ * Called when a socket with a pending connect() finishes.
+ * Note that the socket is locked before entering.
+ */
+static void
+internal_connect(isc_socket_t *sock, IoCompletionInfo *lpo, int connect_errno) {
+ isc_socket_connev_t *cdev;
+ isc_result_t result;
+ char strbuf[ISC_STRERRORSIZE];
+
+ INSIST(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+
+ INSIST(sock->pending_iocp > 0);
+ sock->pending_iocp--;
+ INSIST(sock->pending_connect == 1);
+ sock->pending_connect = 0;
+
+ /*
+ * If the event is no longer in the list we can just close and return.
+ */
+ cdev = lpo->cdev;
+ if (!connectdone_is_active(sock, cdev)) {
+ sock->pending_connect = 0;
+ if (sock->fd != INVALID_SOCKET) {
+ closesocket(sock->fd);
+ sock->fd = INVALID_SOCKET;
+ _set_state(sock, SOCK_CLOSED);
+ }
+ CONSISTENT(sock);
+ UNLOCK(&sock->lock);
+ return;
+ }
+
+ /*
+ * Check possible Windows network event error status here.
+ */
+ if (connect_errno != 0) {
+ /*
+ * If the error is SOFT, just try again on this
+ * fd and pretend nothing strange happened.
+ */
+ if (SOFT_ERROR(connect_errno) ||
+ connect_errno == WSAEINPROGRESS)
+ {
+ sock->pending_connect = 1;
+ CONSISTENT(sock);
+ UNLOCK(&sock->lock);
+ return;
+ }
+
+ /*
+ * Translate other errors into ISC_R_* flavors.
+ */
+ switch (connect_errno) {
+#define ERROR_MATCH(a, b) \
+ case a: \
+ result = b; \
+ break;
+ ERROR_MATCH(WSAEACCES, ISC_R_NOPERM);
+ ERROR_MATCH(WSAEADDRNOTAVAIL, ISC_R_ADDRNOTAVAIL);
+ ERROR_MATCH(WSAEAFNOSUPPORT, ISC_R_ADDRNOTAVAIL);
+ ERROR_MATCH(WSAECONNREFUSED, ISC_R_CONNREFUSED);
+ ERROR_MATCH(WSAEHOSTUNREACH, ISC_R_HOSTUNREACH);
+ ERROR_MATCH(WSAEHOSTDOWN, ISC_R_HOSTDOWN);
+ ERROR_MATCH(WSAENETUNREACH, ISC_R_NETUNREACH);
+ ERROR_MATCH(WSAENETDOWN, ISC_R_NETDOWN);
+ ERROR_MATCH(WSAENOBUFS, ISC_R_NORESOURCES);
+ ERROR_MATCH(WSAECONNRESET, ISC_R_CONNECTIONRESET);
+ ERROR_MATCH(WSAECONNABORTED, ISC_R_CONNECTIONRESET);
+ ERROR_MATCH(WSAETIMEDOUT, ISC_R_TIMEDOUT);
+#undef ERROR_MATCH
+ default:
+ result = ISC_R_UNEXPECTED;
+ strerror_r(connect_errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "internal_connect: connect() %s",
+ strbuf);
+ }
+ } else {
+ INSIST(setsockopt(sock->fd, SOL_SOCKET,
+ SO_UPDATE_CONNECT_CONTEXT, NULL, 0) == 0);
+ result = ISC_R_SUCCESS;
+ sock->connected = 1;
+ socket_log(__LINE__, sock, &sock->address, IOEVENT,
+ "internal_connect: success");
+ }
+
+ do {
+ cdev->result = result;
+ send_connectdone_event(sock, &cdev);
+ cdev = ISC_LIST_HEAD(sock->connect_list);
+ } while (cdev != NULL);
+
+ UNLOCK(&sock->lock);
+}
+
+/*
+ * Loop through the socket, returning ISC_R_EOF for each done event pending.
+ */
+static void
+send_recvdone_abort(isc_socket_t *sock, isc_result_t result) {
+ isc_socketevent_t *dev;
+
+ while (!ISC_LIST_EMPTY(sock->recv_list)) {
+ dev = ISC_LIST_HEAD(sock->recv_list);
+ dev->result = result;
+ send_recvdone_event(sock, &dev);
+ }
+}
+
+/*
+ * Loop through the socket, returning result for each done event pending.
+ */
+static void
+send_connectdone_abort(isc_socket_t *sock, isc_result_t result) {
+ isc_socket_connev_t *dev;
+
+ while (!ISC_LIST_EMPTY(sock->connect_list)) {
+ dev = ISC_LIST_HEAD(sock->connect_list);
+ dev->result = result;
+ send_connectdone_event(sock, &dev);
+ }
+}
+
+/*
+ * Take the data we received in our private buffer, and if any recv() calls on
+ * our list are satisfied, send the corresponding done event.
+ *
+ * If we need more data (there are still items on the recv_list after we consume
+ * all our data) then arrange for another system recv() call to fill our
+ * buffers.
+ */
+static void
+internal_recv(isc_socket_t *sock, int nbytes) {
+ INSIST(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ socket_log(__LINE__, sock, NULL, IOEVENT,
+ "internal_recv: %d bytes received", nbytes);
+
+ /*
+ * If we got here, the I/O operation succeeded. However, we might
+ * still have removed this event from our notification list (or never
+ * placed it on it due to immediate completion.)
+ * Handle the reference counting here, and handle the cancellation
+ * event just after.
+ */
+ INSIST(sock->pending_iocp > 0);
+ sock->pending_iocp--;
+ INSIST(sock->pending_recv > 0);
+ sock->pending_recv--;
+
+ /*
+ * The only way we could have gotten here is that our I/O has
+ * successfully completed. Update our pointers, and move on.
+ * The only odd case here is that we might not have received
+ * enough data on a TCP stream to satisfy the minimum requirements.
+ * If this is the case, we will re-issue the recv() call for what
+ * we need.
+ *
+ * We do check for a recv() of 0 bytes on a TCP stream. This
+ * means the remote end has closed.
+ */
+ if (nbytes == 0 && sock->type == isc_sockettype_tcp) {
+ send_recvdone_abort(sock, ISC_R_EOF);
+ maybe_free_socket(&sock, __LINE__);
+ return;
+ }
+ sock->recvbuf.remaining = nbytes;
+ sock->recvbuf.consume_position = sock->recvbuf.base;
+ completeio_recv(sock);
+
+ /*
+ * If there are more receivers waiting for data, queue another receive
+ * here.
+ */
+ queue_receive_request(sock);
+
+ /*
+ * Unlock and/or destroy if we are the last thing this socket has left
+ * to do.
+ */
+ maybe_free_socket(&sock, __LINE__);
+}
+
+static void
+internal_send(isc_socket_t *sock, isc_socketevent_t *dev,
+ struct msghdr *messagehdr, int nbytes, int send_errno,
+ IoCompletionInfo *lpo) {
+ /*
+ * Find out what socket this is and lock it.
+ */
+ INSIST(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ socket_log(__LINE__, sock, NULL, IOEVENT,
+ "internal_send: task got socket event %p", dev);
+
+ if (lpo->buf != NULL) {
+ socket_log(__LINE__, sock, NULL, TRACE, "free_buffer %p",
+ lpo->buf);
+
+ HeapFree(hHeapHandle, 0, lpo->buf);
+ lpo->buf = NULL;
+ lpo->buflen = 0;
+ }
+
+ INSIST(sock->pending_iocp > 0);
+ sock->pending_iocp--;
+ INSIST(sock->pending_send > 0);
+ sock->pending_send--;
+
+ /* If the event is no longer in the list we can just return */
+ if (!senddone_is_active(sock, dev)) {
+ goto done;
+ }
+
+ /*
+ * Set the error code and send things on its way.
+ */
+ switch (completeio_send(sock, dev, messagehdr, nbytes, send_errno)) {
+ case DOIO_SOFT:
+ break;
+ case DOIO_HARD:
+ case DOIO_SUCCESS:
+ send_senddone_event(sock, &dev);
+ break;
+ }
+
+done:
+ maybe_free_socket(&sock, __LINE__);
+}
+
+/*
+ * These return if the done event passed in is on the list.
+ * Using these ensures we will not double-send an event.
+ */
+static bool
+senddone_is_active(isc_socket_t *sock, isc_socketevent_t *dev) {
+ isc_socketevent_t *ldev;
+
+ ldev = ISC_LIST_HEAD(sock->send_list);
+ while (ldev != NULL && ldev != dev) {
+ ldev = ISC_LIST_NEXT(ldev, ev_link);
+ }
+
+ return (ldev == NULL ? false : true);
+}
+
+static bool
+acceptdone_is_active(isc_socket_t *sock, isc_socket_newconnev_t *dev) {
+ isc_socket_newconnev_t *ldev;
+
+ ldev = ISC_LIST_HEAD(sock->accept_list);
+ while (ldev != NULL && ldev != dev) {
+ ldev = ISC_LIST_NEXT(ldev, ev_link);
+ }
+
+ return (ldev == NULL ? false : true);
+}
+
+static bool
+connectdone_is_active(isc_socket_t *sock, isc_socket_connev_t *dev) {
+ isc_socket_connev_t *cdev;
+
+ cdev = ISC_LIST_HEAD(sock->connect_list);
+ while (cdev != NULL && cdev != dev) {
+ cdev = ISC_LIST_NEXT(cdev, ev_link);
+ }
+
+ return (cdev == NULL ? false : true);
+}
+
+/* */
+/* The Windows network stack seems to have two very distinct paths depending */
+/* on what is installed. Specifically, if something is looking at network */
+/* connections (like an anti-virus or anti-malware application, such as */
+/* McAfee products) Windows may return additional error conditions which */
+/* were not previously returned. */
+/* */
+/* One specific one is when a TCP SYN scan is used. In this situation, */
+/* Windows responds with the SYN-ACK, but the scanner never responds with */
+/* the 3rd packet, the ACK. Windows considers this a partially open connection.
+ */
+/* Most Unix networking stacks, and Windows without McAfee installed, will */
+/* not return this to the caller. However, with this product installed, */
+/* Windows returns this as a failed status on the Accept() call. Here, we */
+/* will just re-issue the ISCAcceptEx() call as if nothing had happened. */
+/* */
+/* This code should only be called when the listening socket has received */
+/* such an error. Additionally, the "parent" socket must be locked. */
+/* Additionally, the lpo argument is re-used here, and must not be freed */
+/* by the caller. */
+/* */
+static isc_result_t
+restart_accept(isc_socket_t *parent, IoCompletionInfo *lpo) {
+ isc_socket_t *nsock = lpo->adev->newsocket;
+ SOCKET new_fd;
+
+ /*
+ * AcceptEx() requires we pass in a socket. Note that we carefully
+ * do not close the previous socket in case of an error message returned
+ * by our new socket() call. If we return an error here, our caller
+ * will clean up.
+ */
+ new_fd = socket(parent->pf, SOCK_STREAM, IPPROTO_TCP);
+ if (nsock->fd == INVALID_SOCKET) {
+ return (ISC_R_FAILURE); /* parent will ask windows for error */
+ /* message */
+ }
+ closesocket(nsock->fd);
+ nsock->fd = new_fd;
+
+ memset(&lpo->overlapped, 0, sizeof(lpo->overlapped));
+
+ ISCAcceptEx(parent->fd, nsock->fd, /* Accepted Socket */
+ lpo->acceptbuffer, /* Buffer for initial Recv */
+ 0, /* Length of Buffer */
+ sizeof(SOCKADDR_STORAGE) + 16, /* Local address length + 16
+ */
+ sizeof(SOCKADDR_STORAGE) + 16, /* Remote address length + 16
+ */
+ (LPDWORD)&lpo->received_bytes, /* Bytes Recved */
+ (LPOVERLAPPED)lpo /* Overlapped structure */
+ );
+
+ InterlockedDecrement(&nsock->manager->iocp_total);
+ iocompletionport_update(nsock);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * This is the I/O Completion Port Worker Function. It loops forever
+ * waiting for I/O to complete and then forwards them for further
+ * processing. There are a number of these in separate threads.
+ */
+static isc_threadresult_t WINAPI
+SocketIoThread(LPVOID ThreadContext) {
+ isc_socketmgr_t *manager = ThreadContext;
+ DWORD nbytes;
+ IoCompletionInfo *lpo = NULL;
+ isc_socket_t *sock = NULL;
+ int request;
+ struct msghdr *messagehdr = NULL;
+ int errval;
+ char strbuf[ISC_STRERRORSIZE];
+ int errstatus;
+
+ REQUIRE(VALID_MANAGER(manager));
+
+ /*
+ * Set the thread priority high enough so I/O will
+ * preempt normal recv packet processing, but not
+ * higher than the timer sync thread.
+ */
+ if (!SetThreadPriority(GetCurrentThread(),
+ THREAD_PRIORITY_ABOVE_NORMAL))
+ {
+ errval = GetLastError();
+ strerror_r(errval, strbuf, sizeof(strbuf));
+ FATAL_ERROR(__FILE__, __LINE__, "Can't set thread priority: %s",
+ strbuf);
+ }
+
+ /*
+ * Loop forever waiting on I/O Completions and then processing them
+ */
+ while (TRUE) {
+ BOOL bSuccess;
+
+ wait_again:
+ bSuccess = GetQueuedCompletionStatus(
+ manager->hIoCompletionPort, &nbytes, (PULONG_PTR)&sock,
+ (LPWSAOVERLAPPED *)&lpo, INFINITE);
+ if (lpo == NULL) { /* Received request to exit */
+ break;
+ }
+
+ REQUIRE(VALID_SOCKET(sock));
+
+ request = lpo->request_type;
+
+ if (!bSuccess) {
+ errstatus = GetLastError();
+ } else {
+ errstatus = 0;
+ }
+ if (!bSuccess && errstatus != ERROR_MORE_DATA) {
+ isc_result_t isc_result;
+
+ /*
+ * Did the I/O operation complete?
+ */
+ isc_result = isc__errno2result(errstatus);
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+ switch (request) {
+ case SOCKET_RECV:
+ INSIST(sock->pending_iocp > 0);
+ sock->pending_iocp--;
+ INSIST(sock->pending_recv > 0);
+ sock->pending_recv--;
+ if (!sock->connected &&
+ ((errstatus == ERROR_HOST_UNREACHABLE) ||
+ (errstatus == WSAENETRESET) ||
+ (errstatus == WSAECONNRESET)))
+ {
+ /* ignore soft errors */
+ queue_receive_request(sock);
+ break;
+ }
+ send_recvdone_abort(sock, isc_result);
+ if (isc_result == ISC_R_UNEXPECTED) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "SOCKET_RECV: Windows "
+ "error code: %d, "
+ "returning ISC error "
+ "%d",
+ errstatus, isc_result);
+ }
+ break;
+
+ case SOCKET_SEND:
+ INSIST(sock->pending_iocp > 0);
+ sock->pending_iocp--;
+ INSIST(sock->pending_send > 0);
+ sock->pending_send--;
+ if (senddone_is_active(sock, lpo->dev)) {
+ lpo->dev->result = isc_result;
+ socket_log(__LINE__, sock, NULL, EVENT,
+ "canceled_send");
+ send_senddone_event(sock, &lpo->dev);
+ }
+ break;
+
+ case SOCKET_ACCEPT:
+ INSIST(sock->pending_iocp > 0);
+ INSIST(sock->pending_accept > 0);
+
+ socket_log(__LINE__, sock, NULL, EVENT,
+ "Accept: errstatus=%d isc_result=%d",
+ errstatus, isc_result);
+
+ if (acceptdone_is_active(sock, lpo->adev)) {
+ if (restart_accept(sock, lpo) ==
+ ISC_R_SUCCESS)
+ {
+ UNLOCK(&sock->lock);
+ goto wait_again;
+ } else {
+ errstatus = GetLastError();
+ isc_result = isc__errno2result(
+ errstatus);
+ socket_log(__LINE__, sock, NULL,
+ EVENT,
+ "restart_accept() "
+ "failed: "
+ "errstatus=%d "
+ "isc_result=%d",
+ errstatus,
+ isc_result);
+ }
+ }
+
+ sock->pending_iocp--;
+ sock->pending_accept--;
+ if (acceptdone_is_active(sock, lpo->adev)) {
+ closesocket(lpo->adev->newsocket->fd);
+ lpo->adev->newsocket->fd =
+ INVALID_SOCKET;
+ isc_refcount_decrementz(
+ &lpo->adev->newsocket
+ ->references);
+ free_socket(&lpo->adev->newsocket,
+ __LINE__);
+ lpo->adev->result = isc_result;
+ socket_log(__LINE__, sock, NULL, EVENT,
+ "canceled_accept");
+ send_acceptdone_event(sock, &lpo->adev);
+ }
+ break;
+
+ case SOCKET_CONNECT:
+ INSIST(sock->pending_iocp > 0);
+ sock->pending_iocp--;
+ INSIST(sock->pending_connect == 1);
+ sock->pending_connect = 0;
+ if (connectdone_is_active(sock, lpo->cdev)) {
+ socket_log(__LINE__, sock, NULL, EVENT,
+ "canceled_connect");
+ send_connectdone_abort(sock,
+ isc_result);
+ }
+ break;
+ }
+ maybe_free_socket(&sock, __LINE__);
+
+ if (lpo != NULL) {
+ HeapFree(hHeapHandle, 0, lpo);
+ }
+ continue;
+ }
+
+ messagehdr = &lpo->messagehdr;
+
+ switch (request) {
+ case SOCKET_RECV:
+ internal_recv(sock, nbytes);
+ break;
+ case SOCKET_SEND:
+ internal_send(sock, lpo->dev, messagehdr, nbytes,
+ errstatus, lpo);
+ break;
+ case SOCKET_ACCEPT:
+ internal_accept(sock, lpo, errstatus);
+ break;
+ case SOCKET_CONNECT:
+ internal_connect(sock, lpo, errstatus);
+ break;
+ }
+
+ if (lpo != NULL) {
+ HeapFree(hHeapHandle, 0, lpo);
+ }
+ }
+
+ /*
+ * Exit Completion Port Thread
+ */
+ manager_log(manager, TRACE, "SocketIoThread exiting");
+ return ((isc_threadresult_t)0);
+}
+
+/*
+ * Create a new socket manager.
+ */
+isc_result_t
+isc_socketmgr_create(isc_mem_t *mctx, isc_socketmgr_t **managerp) {
+ return (isc_socketmgr_create2(mctx, managerp, 0, 1));
+}
+
+isc_result_t
+isc_socketmgr_create2(isc_mem_t *mctx, isc_socketmgr_t **managerp,
+ unsigned int maxsocks, int nthreads) {
+ isc_socketmgr_t *manager;
+
+ REQUIRE(managerp != NULL && *managerp == NULL);
+
+ if (maxsocks != 0) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ manager = isc_mem_get(mctx, sizeof(*manager));
+
+ InitSockets();
+
+ manager->magic = SOCKET_MANAGER_MAGIC;
+ manager->mctx = NULL;
+ manager->stats = NULL;
+ ISC_LIST_INIT(manager->socklist);
+ isc_mutex_init(&manager->lock);
+ isc_condition_init(&manager->shutdown_ok);
+
+ isc_mem_attach(mctx, &manager->mctx);
+ if (nthreads == 0) {
+ nthreads = isc_os_ncpus() + 1;
+ }
+ manager->maxIOCPThreads = min(nthreads, MAX_IOCPTHREADS);
+
+ iocompletionport_init(manager); /* Create the Completion Ports */
+
+ manager->bShutdown = false;
+ manager->totalSockets = 0;
+ manager->iocp_total = 0;
+ manager->maxudp = 0;
+
+ *managerp = manager;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_socketmgr_getmaxsockets(isc_socketmgr_t *manager, unsigned int *nsockp) {
+ REQUIRE(VALID_MANAGER(manager));
+ REQUIRE(nsockp != NULL);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+void
+isc_socketmgr_setstats(isc_socketmgr_t *manager, isc_stats_t *stats) {
+ REQUIRE(VALID_MANAGER(manager));
+ REQUIRE(ISC_LIST_EMPTY(manager->socklist));
+ REQUIRE(manager->stats == NULL);
+ REQUIRE(isc_stats_ncounters(stats) == isc_sockstatscounter_max);
+
+ isc_stats_attach(stats, &manager->stats);
+}
+
+void
+isc_socketmgr_destroy(isc_socketmgr_t **managerp) {
+ isc_socketmgr_t *manager;
+
+ /*
+ * Destroy a socket manager.
+ */
+
+ REQUIRE(managerp != NULL);
+ manager = *managerp;
+ *managerp = NULL;
+ REQUIRE(VALID_MANAGER(manager));
+
+ LOCK(&manager->lock);
+
+ /*
+ * Wait for all sockets to be destroyed.
+ */
+ while (!ISC_LIST_EMPTY(manager->socklist)) {
+ manager_log(manager, CREATION, "sockets exist");
+ WAIT(&manager->shutdown_ok, &manager->lock);
+ }
+
+ UNLOCK(&manager->lock);
+
+ /*
+ * Here, we need to had some wait code for the completion port
+ * thread.
+ */
+ signal_iocompletionport_exit(manager);
+ manager->bShutdown = true;
+
+ /*
+ * Wait for threads to exit.
+ */
+ for (int i = 0; i < manager->maxIOCPThreads; i++) {
+ isc_thread_join((isc_thread_t)manager->hIOCPThreads[i], NULL);
+ }
+ /*
+ * Clean up.
+ */
+
+ CloseHandle(manager->hIoCompletionPort);
+
+ (void)isc_condition_destroy(&manager->shutdown_ok);
+
+ isc_mutex_destroy(&manager->lock);
+ if (manager->stats != NULL) {
+ isc_stats_detach(&manager->stats);
+ }
+ manager->magic = 0;
+ isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager));
+}
+
+static void
+queue_receive_event(isc_socket_t *sock, isc_task_t *task,
+ isc_socketevent_t *dev) {
+ isc_task_t *ntask = NULL;
+
+ isc_task_attach(task, &ntask);
+ dev->attributes |= ISC_SOCKEVENTATTR_ATTACHED;
+
+ /*
+ * Enqueue the request.
+ */
+ INSIST(!ISC_LINK_LINKED(dev, ev_link));
+ ISC_LIST_ENQUEUE(sock->recv_list, dev, ev_link);
+
+ socket_log(__LINE__, sock, NULL, EVENT,
+ "queue_receive_event: event %p -> task %p", dev, ntask);
+}
+
+/*
+ * Check the pending receive queue, and if we have data pending, give it to this
+ * caller. If we have none, queue an I/O request. If this caller is not the
+ * first on the list, then we will just queue this event and return.
+ *
+ * Caller must have the socket locked.
+ */
+static isc_result_t
+socket_recv(isc_socket_t *sock, isc_socketevent_t *dev, isc_task_t *task,
+ unsigned int flags) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ dev->ev_sender = task;
+
+ if (sock->fd == INVALID_SOCKET) {
+ return (ISC_R_EOF);
+ }
+
+ /*
+ * Queue our event on the list of things to do. Call our function to
+ * attempt to fill buffers as much as possible, and return done events.
+ * We are going to lie about our handling of the ISC_SOCKFLAG_IMMEDIATE
+ * here and tell our caller that we could not satisfy it immediately.
+ */
+ queue_receive_event(sock, task, dev);
+ if ((flags & ISC_SOCKFLAG_IMMEDIATE) != 0) {
+ result = ISC_R_INPROGRESS;
+ }
+
+ completeio_recv(sock);
+
+ /*
+ * If there are more receivers waiting for data, queue another receive
+ * here. If the
+ */
+ queue_receive_request(sock);
+
+ return (result);
+}
+
+isc_result_t
+isc_socket_recv(isc_socket_t *sock, isc_region_t *region, unsigned int minimum,
+ isc_task_t *task, isc_taskaction_t action, void *arg) {
+ isc_socketevent_t *dev;
+ isc_socketmgr_t *manager;
+ isc_result_t ret;
+
+ REQUIRE(VALID_SOCKET(sock));
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+ REQUIRE(action != NULL);
+
+ manager = sock->manager;
+ REQUIRE(VALID_MANAGER(manager));
+
+ INSIST(sock->bound);
+
+ dev = allocate_socketevent(manager->mctx, sock, ISC_SOCKEVENT_RECVDONE,
+ action, arg);
+ if (dev == NULL) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_NOMEMORY);
+ }
+
+ ret = isc_socket_recv2(sock, region, minimum, task, dev, 0);
+ UNLOCK(&sock->lock);
+ return (ret);
+}
+
+isc_result_t
+isc_socket_recv2(isc_socket_t *sock, isc_region_t *region, unsigned int minimum,
+ isc_task_t *task, isc_socketevent_t *event,
+ unsigned int flags) {
+ isc_result_t ret;
+
+ REQUIRE(VALID_SOCKET(sock));
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ event->result = ISC_R_UNEXPECTED;
+ event->ev_sender = sock;
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+
+ event->region = *region;
+ event->n = 0;
+ event->offset = 0;
+ event->attributes = 0;
+
+ /*
+ * UDP sockets are always partial read.
+ */
+ if (sock->type == isc_sockettype_udp) {
+ event->minimum = 1;
+ } else {
+ if (minimum == 0) {
+ event->minimum = region->length;
+ } else {
+ event->minimum = minimum;
+ }
+ }
+
+ ret = socket_recv(sock, event, task, flags);
+ UNLOCK(&sock->lock);
+ return (ret);
+}
+
+/*
+ * Caller must have the socket locked.
+ */
+static isc_result_t
+socket_send(isc_socket_t *sock, isc_socketevent_t *dev, isc_task_t *task,
+ const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo,
+ unsigned int flags) {
+ int io_state;
+ int send_errno = 0;
+ int cc = 0;
+ isc_task_t *ntask = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ dev->ev_sender = task;
+
+ set_dev_address(address, sock, dev);
+ if (pktinfo != NULL) {
+ socket_log(__LINE__, sock, NULL, TRACE,
+ "pktinfo structure provided, ifindex %u (set to 0)",
+ pktinfo->ipi6_ifindex);
+
+ dev->attributes |= ISC_SOCKEVENTATTR_PKTINFO;
+ dev->pktinfo = *pktinfo;
+ /*
+ * Set the pktinfo index to 0 here, to let the kernel decide
+ * what interface it should send on.
+ */
+ dev->pktinfo.ipi6_ifindex = 0;
+ }
+
+ io_state = startio_send(sock, dev, &cc, &send_errno);
+ switch (io_state) {
+ case DOIO_PENDING: /* I/O started. Enqueue completion event. */
+ case DOIO_SOFT:
+ /*
+ * We couldn't send all or part of the request right now, so
+ * queue it unless ISC_SOCKFLAG_NORETRY is set.
+ */
+ if ((flags & ISC_SOCKFLAG_NORETRY) == 0 ||
+ io_state == DOIO_PENDING)
+ {
+ isc_task_attach(task, &ntask);
+ dev->attributes |= ISC_SOCKEVENTATTR_ATTACHED;
+
+ /*
+ * Enqueue the request.
+ */
+ INSIST(!ISC_LINK_LINKED(dev, ev_link));
+ ISC_LIST_ENQUEUE(sock->send_list, dev, ev_link);
+
+ socket_log(__LINE__, sock, NULL, EVENT,
+ "socket_send: event %p -> task %p", dev,
+ ntask);
+
+ if ((flags & ISC_SOCKFLAG_IMMEDIATE) != 0) {
+ result = ISC_R_INPROGRESS;
+ }
+ break;
+ }
+
+ case DOIO_SUCCESS:
+ break;
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_socket_send(isc_socket_t *sock, isc_region_t *region, isc_task_t *task,
+ isc_taskaction_t action, void *arg) {
+ /*
+ * REQUIRE() checking is performed in isc_socket_sendto().
+ */
+ return (isc_socket_sendto(sock, region, task, action, arg, NULL, NULL));
+}
+
+isc_result_t
+isc_socket_sendto(isc_socket_t *sock, isc_region_t *region, isc_task_t *task,
+ isc_taskaction_t action, void *arg,
+ const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo) {
+ isc_socketevent_t *dev;
+ isc_socketmgr_t *manager;
+ isc_result_t ret;
+
+ REQUIRE(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+ REQUIRE(region != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(action != NULL);
+
+ manager = sock->manager;
+ REQUIRE(VALID_MANAGER(manager));
+
+ INSIST(sock->bound);
+
+ dev = allocate_socketevent(manager->mctx, sock, ISC_SOCKEVENT_SENDDONE,
+ action, arg);
+ if (dev == NULL) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_NOMEMORY);
+ }
+ dev->region = *region;
+
+ ret = socket_send(sock, dev, task, address, pktinfo, 0);
+ UNLOCK(&sock->lock);
+ return (ret);
+}
+
+isc_result_t
+isc_socket_sendto2(isc_socket_t *sock, isc_region_t *region, isc_task_t *task,
+ const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo,
+ isc_socketevent_t *event, unsigned int flags) {
+ isc_result_t ret;
+
+ REQUIRE(VALID_SOCKET(sock));
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ REQUIRE((flags & ~(ISC_SOCKFLAG_IMMEDIATE | ISC_SOCKFLAG_NORETRY)) ==
+ 0);
+ if ((flags & ISC_SOCKFLAG_NORETRY) != 0) {
+ REQUIRE(sock->type == isc_sockettype_udp);
+ }
+ event->ev_sender = sock;
+ event->result = ISC_R_UNEXPECTED;
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+ event->region = *region;
+ event->n = 0;
+ event->offset = 0;
+ event->attributes = 0;
+
+ ret = socket_send(sock, event, task, address, pktinfo, flags);
+ UNLOCK(&sock->lock);
+ return (ret);
+}
+
+isc_result_t
+isc_socket_bind(isc_socket_t *sock, const isc_sockaddr_t *sockaddr,
+ isc_socket_options_t options) {
+ int bind_errno;
+ char strbuf[ISC_STRERRORSIZE];
+ int on = 1;
+
+ REQUIRE(VALID_SOCKET(sock));
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+
+ INSIST(!sock->bound);
+ INSIST(!sock->dupped);
+
+ if (sock->pf != sockaddr->type.sa.sa_family) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_FAMILYMISMATCH);
+ }
+ /*
+ * Only set SO_REUSEADDR when we want a specific port.
+ */
+ if ((options & ISC_SOCKET_REUSEADDRESS) != 0 &&
+ isc_sockaddr_getport(sockaddr) != (in_port_t)0 &&
+ setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on,
+ sizeof(on)) < 0)
+ {
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "setsockopt(%d) failed",
+ sock->fd);
+ /* Press on... */
+ }
+ if (bind(sock->fd, &sockaddr->type.sa, sockaddr->length) < 0) {
+ bind_errno = WSAGetLastError();
+ UNLOCK(&sock->lock);
+ switch (bind_errno) {
+ case WSAEACCES:
+ return (ISC_R_NOPERM);
+ case WSAEADDRNOTAVAIL:
+ return (ISC_R_ADDRNOTAVAIL);
+ case WSAEADDRINUSE:
+ return (ISC_R_ADDRINUSE);
+ case WSAEINVAL:
+ return (ISC_R_BOUND);
+ default:
+ strerror_r(bind_errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "bind: %s",
+ strbuf);
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+
+ socket_log(__LINE__, sock, sockaddr, TRACE, "bound");
+ sock->bound = 1;
+
+ UNLOCK(&sock->lock);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_socket_filter(isc_socket_t *sock, const char *filter) {
+ UNUSED(sock);
+ UNUSED(filter);
+
+ REQUIRE(VALID_SOCKET(sock));
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+/*
+ * Set up to listen on a given socket. We do this by creating an internal
+ * event that will be dispatched when the socket has read activity. The
+ * watcher will send the internal event to the task when there is a new
+ * connection.
+ *
+ * Unlike in read, we don't preallocate a done event here. Every time there
+ * is a new connection we'll have to allocate a new one anyway, so we might
+ * as well keep things simple rather than having to track them.
+ */
+isc_result_t
+isc_socket_listen(isc_socket_t *sock, unsigned int backlog) {
+ char strbuf[ISC_STRERRORSIZE];
+#if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN)
+ char on = 1;
+#endif /* if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) */
+
+ REQUIRE(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+
+ REQUIRE(!sock->listener);
+ REQUIRE(sock->bound);
+ REQUIRE(sock->type == isc_sockettype_tcp);
+
+ if (backlog == 0) {
+ backlog = SOMAXCONN;
+ }
+
+ if (listen(sock->fd, (int)backlog) < 0) {
+ UNLOCK(&sock->lock);
+ strerror_r(WSAGetLastError(), strbuf, sizeof(strbuf));
+
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "listen: %s", strbuf);
+
+ return (ISC_R_UNEXPECTED);
+ }
+
+#if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN)
+ if (setsockopt(sock->fd, IPPROTO_TCP, TCP_FASTOPEN, &on, sizeof(on)) <
+ 0)
+ {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "setsockopt(%d, TCP_FASTOPEN) failed with %s",
+ sock->fd, strbuf);
+ /* TCP_FASTOPEN is experimental so ignore failures */
+ }
+#endif /* if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) */
+
+ socket_log(__LINE__, sock, NULL, TRACE, "listening");
+ sock->listener = 1;
+ _set_state(sock, SOCK_LISTEN);
+
+ UNLOCK(&sock->lock);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * This should try to do aggressive accept() XXXMLG
+ */
+isc_result_t
+isc_socket_accept(isc_socket_t *sock, isc_task_t *task, isc_taskaction_t action,
+ void *arg) {
+ isc_socket_newconnev_t *adev;
+ isc_socketmgr_t *manager;
+ isc_task_t *ntask = NULL;
+ isc_socket_t *nsock;
+ isc_result_t result;
+ IoCompletionInfo *lpo;
+
+ REQUIRE(VALID_SOCKET(sock));
+
+ manager = sock->manager;
+ REQUIRE(VALID_MANAGER(manager));
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+
+ REQUIRE(sock->listener);
+
+ /*
+ * Sender field is overloaded here with the task we will be sending
+ * this event to. Just before the actual event is delivered the
+ * actual ev_sender will be touched up to be the socket.
+ */
+ adev = (isc_socket_newconnev_t *)isc_event_allocate(
+ manager->mctx, task, ISC_SOCKEVENT_NEWCONN, action, arg,
+ sizeof(*adev));
+ ISC_LINK_INIT(adev, ev_link);
+
+ result = allocate_socket(manager, sock->type, &nsock);
+ if (result != ISC_R_SUCCESS) {
+ isc_event_free((isc_event_t **)&adev);
+ UNLOCK(&sock->lock);
+ return (result);
+ }
+
+ /*
+ * AcceptEx() requires we pass in a socket.
+ */
+ nsock->fd = socket(sock->pf, SOCK_STREAM, IPPROTO_TCP);
+ if (nsock->fd == INVALID_SOCKET) {
+ free_socket(&nsock, __LINE__);
+ isc_event_free((isc_event_t **)&adev);
+ UNLOCK(&sock->lock);
+ return (ISC_R_FAILURE); /* XXXMLG need real error message */
+ }
+
+ /*
+ * Attach to socket and to task.
+ */
+ isc_task_attach(task, &ntask);
+ if (isc_task_exiting(ntask)) {
+ free_socket(&nsock, __LINE__);
+ isc_task_detach(&ntask);
+ isc_event_free(ISC_EVENT_PTR(&adev));
+ UNLOCK(&sock->lock);
+ return (ISC_R_SHUTTINGDOWN);
+ }
+ isc_refcount_increment0(&nsock->references);
+
+ adev->ev_sender = ntask;
+ adev->newsocket = nsock;
+ _set_state(nsock, SOCK_ACCEPT);
+
+ /*
+ * Queue io completion for an accept().
+ */
+ lpo = (IoCompletionInfo *)HeapAlloc(hHeapHandle, HEAP_ZERO_MEMORY,
+ sizeof(IoCompletionInfo));
+ RUNTIME_CHECK(lpo != NULL);
+ lpo->acceptbuffer =
+ (void *)HeapAlloc(hHeapHandle, HEAP_ZERO_MEMORY,
+ (sizeof(SOCKADDR_STORAGE) + 16) * 2);
+ RUNTIME_CHECK(lpo->acceptbuffer != NULL);
+
+ lpo->adev = adev;
+ lpo->request_type = SOCKET_ACCEPT;
+
+ ISCAcceptEx(sock->fd, nsock->fd, /* Accepted Socket */
+ lpo->acceptbuffer, /* Buffer for initial Recv */
+ 0, /* Length of Buffer */
+ sizeof(SOCKADDR_STORAGE) + 16, /* Local address length + 16
+ */
+ sizeof(SOCKADDR_STORAGE) + 16, /* Remote address length + 16
+ */
+ (LPDWORD)&lpo->received_bytes, /* Bytes Recved */
+ (LPOVERLAPPED)lpo /* Overlapped structure */
+ );
+ iocompletionport_update(nsock);
+
+ socket_log(__LINE__, sock, NULL, TRACE, "accepting for nsock %p fd %d",
+ nsock, nsock->fd);
+
+ /*
+ * Enqueue the event
+ */
+ ISC_LIST_ENQUEUE(sock->accept_list, adev, ev_link);
+ sock->pending_accept++;
+ sock->pending_iocp++;
+
+ UNLOCK(&sock->lock);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_socket_connect(isc_socket_t *sock, const isc_sockaddr_t *addr,
+ isc_task_t *task, isc_taskaction_t action, void *arg) {
+ char strbuf[ISC_STRERRORSIZE];
+ isc_socket_connev_t *cdev;
+ isc_task_t *ntask = NULL;
+ isc_socketmgr_t *manager;
+ IoCompletionInfo *lpo;
+ int bind_errno;
+
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(addr != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(action != NULL);
+
+ manager = sock->manager;
+ REQUIRE(VALID_MANAGER(manager));
+ REQUIRE(addr != NULL);
+
+ if (isc_sockaddr_ismulticast(addr)) {
+ return (ISC_R_MULTICAST);
+ }
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+
+ /*
+ * Windows sockets won't connect unless the socket is bound.
+ */
+ if (!sock->bound) {
+ isc_sockaddr_t any;
+
+ isc_sockaddr_anyofpf(&any, isc_sockaddr_pf(addr));
+ if (bind(sock->fd, &any.type.sa, any.length) < 0) {
+ bind_errno = WSAGetLastError();
+ UNLOCK(&sock->lock);
+ switch (bind_errno) {
+ case WSAEACCES:
+ return (ISC_R_NOPERM);
+ case WSAEADDRNOTAVAIL:
+ return (ISC_R_ADDRNOTAVAIL);
+ case WSAEADDRINUSE:
+ return (ISC_R_ADDRINUSE);
+ case WSAEINVAL:
+ return (ISC_R_BOUND);
+ default:
+ strerror_r(bind_errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "bind: %s",
+ strbuf);
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+ sock->bound = 1;
+ }
+
+ cdev = (isc_socket_connev_t *)isc_event_allocate(
+ manager->mctx, sock, ISC_SOCKEVENT_CONNECT, action, arg,
+ sizeof(*cdev));
+ ISC_LINK_INIT(cdev, ev_link);
+
+ if (sock->connected) {
+ INSIST(isc_sockaddr_equal(&sock->address, addr));
+ cdev->result = ISC_R_SUCCESS;
+ isc_task_send(task, ISC_EVENT_PTR(&cdev));
+
+ UNLOCK(&sock->lock);
+ return (ISC_R_SUCCESS);
+ }
+
+ if ((sock->type == isc_sockettype_tcp) && !sock->pending_connect) {
+ /*
+ * Queue io completion for an accept().
+ */
+ lpo = (IoCompletionInfo *)HeapAlloc(hHeapHandle,
+ HEAP_ZERO_MEMORY,
+ sizeof(IoCompletionInfo));
+ lpo->cdev = cdev;
+ lpo->request_type = SOCKET_CONNECT;
+
+ sock->address = *addr;
+ ISCConnectEx(sock->fd, &addr->type.sa, addr->length, NULL, 0,
+ NULL, (LPOVERLAPPED)lpo);
+
+ /*
+ * Attach to task.
+ */
+ isc_task_attach(task, &ntask);
+ cdev->ev_sender = ntask;
+
+ sock->pending_connect = 1;
+ _set_state(sock, SOCK_CONNECT);
+
+ /*
+ * Enqueue the request.
+ */
+ INSIST(!ISC_LINK_LINKED(cdev, ev_link));
+ ISC_LIST_ENQUEUE(sock->connect_list, cdev, ev_link);
+ sock->pending_iocp++;
+ } else if (sock->type == isc_sockettype_tcp) {
+ INSIST(sock->pending_connect);
+ INSIST(isc_sockaddr_equal(&sock->address, addr));
+ isc_task_attach(task, &ntask);
+ cdev->ev_sender = ntask;
+ INSIST(!ISC_LINK_LINKED(cdev, ev_link));
+ ISC_LIST_ENQUEUE(sock->connect_list, cdev, ev_link);
+ } else {
+ REQUIRE(!sock->pending_connect);
+ WSAConnect(sock->fd, &addr->type.sa, addr->length, NULL, NULL,
+ NULL, NULL);
+ cdev->result = ISC_R_SUCCESS;
+ isc_task_send(task, (isc_event_t **)&cdev);
+ }
+ CONSISTENT(sock);
+ UNLOCK(&sock->lock);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_socket_getpeername(isc_socket_t *sock, isc_sockaddr_t *addressp) {
+ isc_result_t result;
+
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(addressp != NULL);
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+
+ if (sock->connected) {
+ *addressp = sock->address;
+ result = ISC_R_SUCCESS;
+ } else {
+ result = ISC_R_NOTCONNECTED;
+ }
+
+ UNLOCK(&sock->lock);
+
+ return (result);
+}
+
+isc_result_t
+isc_socket_getsockname(isc_socket_t *sock, isc_sockaddr_t *addressp) {
+ socklen_t len;
+ isc_result_t result;
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(VALID_SOCKET(sock));
+ REQUIRE(addressp != NULL);
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+
+ if (!sock->bound) {
+ result = ISC_R_NOTBOUND;
+ goto out;
+ }
+
+ result = ISC_R_SUCCESS;
+
+ len = sizeof(addressp->type);
+ if (getsockname(sock->fd, &addressp->type.sa, (void *)&len) < 0) {
+ strerror_r(WSAGetLastError(), strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(__FILE__, __LINE__, "getsockname: %s", strbuf);
+ result = ISC_R_UNEXPECTED;
+ goto out;
+ }
+ addressp->length = (unsigned int)len;
+
+out:
+ UNLOCK(&sock->lock);
+
+ return (result);
+}
+
+/*
+ * Run through the list of events on this socket, and cancel the ones
+ * queued for task "task" of type "how". "how" is a bitmask.
+ */
+void
+isc_socket_cancel(isc_socket_t *sock, isc_task_t *task, unsigned int how) {
+ REQUIRE(VALID_SOCKET(sock));
+
+ /*
+ * Quick exit if there is nothing to do. Don't even bother locking
+ * in this case.
+ */
+ if (how == 0) {
+ return;
+ }
+
+ LOCK(&sock->lock);
+ CONSISTENT(sock);
+
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return;
+ }
+
+ /*
+ * All of these do the same thing, more or less.
+ * Each will:
+ * o If the internal event is marked as "posted" try to
+ * remove it from the task's queue. If this fails, mark it
+ * as canceled instead, and let the task clean it up later.
+ * o For each I/O request for that task of that type, post
+ * its done event with status of "ISC_R_CANCELED".
+ * o Reset any state needed.
+ */
+
+ if ((how & ISC_SOCKCANCEL_RECV) != 0) {
+ isc_socketevent_t *dev;
+ isc_socketevent_t *next;
+ isc_task_t *current_task;
+
+ dev = ISC_LIST_HEAD(sock->recv_list);
+ while (dev != NULL) {
+ current_task = dev->ev_sender;
+ next = ISC_LIST_NEXT(dev, ev_link);
+ if ((task == NULL) || (task == current_task)) {
+ dev->result = ISC_R_CANCELED;
+ send_recvdone_event(sock, &dev);
+ }
+ dev = next;
+ }
+ }
+ how &= ~ISC_SOCKCANCEL_RECV;
+
+ if ((how & ISC_SOCKCANCEL_SEND) != 0) {
+ isc_socketevent_t *dev;
+ isc_socketevent_t *next;
+ isc_task_t *current_task;
+
+ dev = ISC_LIST_HEAD(sock->send_list);
+
+ while (dev != NULL) {
+ current_task = dev->ev_sender;
+ next = ISC_LIST_NEXT(dev, ev_link);
+ if ((task == NULL) || (task == current_task)) {
+ dev->result = ISC_R_CANCELED;
+ send_senddone_event(sock, &dev);
+ }
+ dev = next;
+ }
+ }
+ how &= ~ISC_SOCKCANCEL_SEND;
+
+ if (((how & ISC_SOCKCANCEL_ACCEPT) != 0) &&
+ !ISC_LIST_EMPTY(sock->accept_list))
+ {
+ isc_socket_newconnev_t *dev;
+ isc_socket_newconnev_t *next;
+ isc_task_t *current_task;
+
+ dev = ISC_LIST_HEAD(sock->accept_list);
+ while (dev != NULL) {
+ current_task = dev->ev_sender;
+ next = ISC_LIST_NEXT(dev, ev_link);
+
+ if ((task == NULL) || (task == current_task)) {
+ isc_refcount_decrementz(
+ &dev->newsocket->references);
+ closesocket(dev->newsocket->fd);
+ dev->newsocket->fd = INVALID_SOCKET;
+ free_socket(&dev->newsocket, __LINE__);
+
+ dev->result = ISC_R_CANCELED;
+ send_acceptdone_event(sock, &dev);
+ }
+
+ dev = next;
+ }
+ }
+ how &= ~ISC_SOCKCANCEL_ACCEPT;
+
+ if (((how & ISC_SOCKCANCEL_CONNECT) != 0) &&
+ !ISC_LIST_EMPTY(sock->connect_list))
+ {
+ isc_socket_connev_t *dev;
+ isc_socket_connev_t *next;
+ isc_task_t *current_task;
+
+ INSIST(sock->pending_connect);
+
+ dev = ISC_LIST_HEAD(sock->connect_list);
+
+ while (dev != NULL) {
+ current_task = dev->ev_sender;
+ next = ISC_LIST_NEXT(dev, ev_link);
+ if ((task == NULL) || (task == current_task)) {
+ dev->result = ISC_R_CANCELED;
+ send_connectdone_event(sock, &dev);
+ }
+ dev = next;
+ }
+ closesocket(sock->fd);
+ sock->fd = INVALID_SOCKET;
+ _set_state(sock, SOCK_CLOSED);
+ }
+ how &= ~ISC_SOCKCANCEL_CONNECT;
+ UNUSED(how);
+
+ maybe_free_socket(&sock, __LINE__);
+}
+
+isc_sockettype_t
+isc_socket_gettype(isc_socket_t *sock) {
+ isc_sockettype_t type;
+
+ REQUIRE(VALID_SOCKET(sock));
+
+ LOCK(&sock->lock);
+
+ /*
+ * make sure that the socket's not closed
+ */
+ if (sock->fd == INVALID_SOCKET) {
+ UNLOCK(&sock->lock);
+ return (ISC_R_CONNREFUSED);
+ }
+
+ type = sock->type;
+ UNLOCK(&sock->lock);
+ return (type);
+}
+
+void
+isc_socket_ipv6only(isc_socket_t *sock, bool yes) {
+#if defined(IPV6_V6ONLY)
+ int onoff = yes ? 1 : 0;
+#else /* if defined(IPV6_V6ONLY) */
+ UNUSED(yes);
+#endif /* if defined(IPV6_V6ONLY) */
+
+ REQUIRE(VALID_SOCKET(sock));
+
+#ifdef IPV6_V6ONLY
+ if (sock->pf == AF_INET6) {
+ (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&onoff, sizeof(onoff));
+ }
+#endif /* ifdef IPV6_V6ONLY */
+}
+
+void
+isc_socket_dscp(isc_socket_t *sock, isc_dscp_t dscp) {
+#if !defined(IP_TOS) && !defined(IPV6_TCLASS)
+ UNUSED(dscp);
+#else /* if !defined(IP_TOS) && !defined(IPV6_TCLASS) */
+ if (dscp < 0) {
+ return;
+ }
+
+ dscp <<= 2;
+ dscp &= 0xff;
+#endif /* if !defined(IP_TOS) && !defined(IPV6_TCLASS) */
+
+ REQUIRE(VALID_SOCKET(sock));
+
+#ifdef IP_TOS
+ if (sock->pf == AF_INET) {
+ (void)setsockopt(sock->fd, IPPROTO_IP, IP_TOS, (char *)&dscp,
+ sizeof(dscp));
+ }
+#endif /* ifdef IP_TOS */
+#ifdef IPV6_TCLASS
+ if (sock->pf == AF_INET6) {
+ (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_TCLASS,
+ (char *)&dscp, sizeof(dscp));
+ }
+#endif /* ifdef IPV6_TCLASS */
+}
+
+void
+isc_socket_cleanunix(const isc_sockaddr_t *addr, bool active) {
+ UNUSED(addr);
+ UNUSED(active);
+}
+
+isc_result_t
+isc_socket_permunix(const isc_sockaddr_t *addr, uint32_t perm, uint32_t owner,
+ uint32_t group) {
+ UNUSED(addr);
+ UNUSED(perm);
+ UNUSED(owner);
+ UNUSED(group);
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+void
+isc_socket_setname(isc_socket_t *socket, const char *name, void *tag) {
+ /*
+ * Name 'socket'.
+ */
+
+ REQUIRE(VALID_SOCKET(socket));
+
+ LOCK(&socket->lock);
+ strlcpy(socket->name, name, sizeof(socket->name));
+ socket->tag = tag;
+ UNLOCK(&socket->lock);
+}
+
+const char *
+isc_socket_getname(isc_socket_t *socket) {
+ return (socket->name);
+}
+
+void *
+isc_socket_gettag(isc_socket_t *socket) {
+ return (socket->tag);
+}
+
+int
+isc_socket_getfd(isc_socket_t *socket) {
+ return ((short)socket->fd);
+}
+
+void
+isc_socketmgr_setreserved(isc_socketmgr_t *manager, uint32_t reserved) {
+ UNUSED(manager);
+ UNUSED(reserved);
+}
+
+isc_socketevent_t *
+isc_socket_socketevent(isc_mem_t *mctx, void *sender, isc_eventtype_t eventtype,
+ isc_taskaction_t action, void *arg) {
+ return (allocate_socketevent(mctx, sender, eventtype, action, arg));
+}
+
+bool
+isc_socket_hasreuseport() {
+ return (false);
+}
+
+#ifdef HAVE_LIBXML2
+
+static const char *
+_socktype(isc_sockettype_t type) {
+ switch (type) {
+ case isc_sockettype_udp:
+ return ("udp");
+ case isc_sockettype_tcp:
+ return ("tcp");
+ case isc_sockettype_unix:
+ return ("unix");
+ default:
+ return ("not-initialized");
+ }
+}
+
+#define TRY0(a) \
+ do { \
+ xmlrc = (a); \
+ if (xmlrc < 0) \
+ goto error; \
+ } while (0)
+int
+isc_socketmgr_renderxml(isc_socketmgr_t *mgr, void *writer0) {
+ isc_socket_t *sock = NULL;
+ char peerbuf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_t addr;
+ socklen_t len;
+ int xmlrc;
+ xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0;
+
+ LOCK(&mgr->lock);
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "sockets"));
+ sock = ISC_LIST_HEAD(mgr->socklist);
+ while (sock != NULL) {
+ LOCK(&sock->lock);
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "socket"));
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%p", sock));
+ TRY0(xmlTextWriterEndElement(writer));
+
+ if (sock->name[0] != 0) {
+ TRY0(xmlTextWriterStartElement(writer,
+ ISC_XMLCHAR "name"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%s",
+ sock->name));
+ TRY0(xmlTextWriterEndElement(writer)); /* name */
+ }
+
+ TRY0(xmlTextWriterStartElement(writer,
+ ISC_XMLCHAR "references"));
+ TRY0(xmlTextWriterWriteFormatString(
+ writer, "%" PRIuFAST32,
+ isc_refcount_current(&sock->references)));
+ TRY0(xmlTextWriterEndElement(writer));
+
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "type",
+ ISC_XMLCHAR _socktype(sock->type)));
+
+ if (sock->connected) {
+ isc_sockaddr_format(&sock->address, peerbuf,
+ sizeof(peerbuf));
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "peer-address",
+ ISC_XMLCHAR peerbuf));
+ }
+
+ len = sizeof(addr);
+ if (getsockname(sock->fd, &addr.type.sa, (void *)&len) == 0) {
+ isc_sockaddr_format(&addr, peerbuf, sizeof(peerbuf));
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "local-address",
+ ISC_XMLCHAR peerbuf));
+ }
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "states"));
+ if (sock->pending_recv) {
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "state",
+ ISC_XMLCHAR "pending-receive"));
+ }
+ if (sock->pending_send) {
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "state",
+ ISC_XMLCHAR "pending-send"));
+ }
+ if (sock->pending_accept) {
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "state",
+ ISC_XMLCHAR "pending_accept"));
+ }
+ if (sock->listener) {
+ TRY0(xmlTextWriterWriteElement(writer,
+ ISC_XMLCHAR "state",
+ ISC_XMLCHAR "listener"));
+ }
+ if (sock->connected) {
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "state",
+ ISC_XMLCHAR "connected"));
+ }
+ if (sock->pending_connect) {
+ TRY0(xmlTextWriterWriteElement(
+ writer, ISC_XMLCHAR "state",
+ ISC_XMLCHAR "connecting"));
+ }
+ if (sock->bound) {
+ TRY0(xmlTextWriterWriteElement(writer,
+ ISC_XMLCHAR "state",
+ ISC_XMLCHAR "bound"));
+ }
+
+ TRY0(xmlTextWriterEndElement(writer)); /* states */
+
+ TRY0(xmlTextWriterEndElement(writer)); /* socket */
+
+ UNLOCK(&sock->lock);
+ sock = ISC_LIST_NEXT(sock, link);
+ }
+ TRY0(xmlTextWriterEndElement(writer)); /* sockets */
+
+error:
+ if (sock != NULL) {
+ UNLOCK(&sock->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_socketmgr_renderjson(isc_socketmgr_t *mgr, void *stats0) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_socket_t *sock = NULL;
+ char peerbuf[ISC_SOCKADDR_FORMATSIZE];
+ isc_sockaddr_t addr;
+ socklen_t len;
+ json_object *obj, *array = json_object_new_array();
+ json_object *stats = (json_object *)stats;
+
+ CHECKMEM(array);
+
+ LOCK(&mgr->lock);
+
+#ifdef USE_SHARED_MANAGER
+ obj = json_object_new_int(mgr->refs);
+ CHECKMEM(obj);
+ json_object_object_add(stats, "references", obj);
+#endif /* USE_SHARED_MANAGER */
+
+ sock = ISC_LIST_HEAD(mgr->socklist);
+ while (sock != NULL) {
+ json_object *states, *entry = json_object_new_object();
+ char buf[255];
+
+ CHECKMEM(entry);
+ json_object_array_add(array, entry);
+
+ LOCK(&sock->lock);
+
+ snprintf(buf, sizeof(buf), "%p", sock);
+ obj = json_object_new_string(buf);
+ CHECKMEM(obj);
+ json_object_object_add(entry, "id", obj);
+
+ if (sock->name[0] != 0) {
+ obj = json_object_new_string(sock->name);
+ CHECKMEM(obj);
+ json_object_object_add(entry, "name", obj);
+ }
+
+ obj = json_object_new_int(
+ isc_refcount_current(&sock->references));
+ CHECKMEM(obj);
+ json_object_object_add(entry, "references", obj);
+
+ obj = json_object_new_string(_socktype(sock->type));
+ CHECKMEM(obj);
+ json_object_object_add(entry, "type", obj);
+
+ if (sock->connected) {
+ isc_sockaddr_format(&sock->address, peerbuf,
+ sizeof(peerbuf));
+ obj = json_object_new_string(peerbuf);
+ CHECKMEM(obj);
+ json_object_object_add(entry, "peer-address", obj);
+ }
+
+ len = sizeof(addr);
+ if (getsockname(sock->fd, &addr.type.sa, (void *)&len) == 0) {
+ isc_sockaddr_format(&addr, peerbuf, sizeof(peerbuf));
+ obj = json_object_new_string(peerbuf);
+ CHECKMEM(obj);
+ json_object_object_add(entry, "local-address", obj);
+ }
+
+ states = json_object_new_array();
+ CHECKMEM(states);
+ json_object_object_add(entry, "states", states);
+
+ if (sock->pending_recv) {
+ obj = json_object_new_string("pending-receive");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ if (sock->pending_send) {
+ obj = json_object_new_string("pending-send");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ if (sock->pending_accept) {
+ obj = json_object_new_string("pending-accept");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ if (sock->listener) {
+ obj = json_object_new_string("listener");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ if (sock->connected) {
+ obj = json_object_new_string("connected");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ if (sock->pending_connect) {
+ obj = json_object_new_string("connecting");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ if (sock->bound) {
+ obj = json_object_new_string("bound");
+ CHECKMEM(obj);
+ json_object_array_add(states, obj);
+ }
+
+ UNLOCK(&sock->lock);
+ sock = ISC_LIST_NEXT(sock, link);
+ }
+
+ json_object_object_add(stats, "sockets", array);
+ array = NULL;
+ result = ISC_R_SUCCESS;
+
+error:
+ if (array != NULL) {
+ json_object_put(array);
+ }
+
+ if (sock != NULL) {
+ UNLOCK(&sock->lock);
+ }
+
+ UNLOCK(&mgr->lock);
+
+ return (result);
+}
+#endif /* HAVE_JSON_C */
+
+void
+isc_socketmgr_maxudp(isc_socketmgr_t *manager, unsigned int maxudp) {
+ REQUIRE(VALID_MANAGER(manager));
+
+ manager->maxudp = maxudp;
+}
diff --git a/lib/isc/win32/stdio.c b/lib/isc/win32/stdio.c
new file mode 100644
index 0000000..1e55c30
--- /dev/null
+++ b/lib/isc/win32/stdio.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <errno.h>
+#include <io.h>
+#include <sys/stat.h>
+#include <sys/types.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;
+
+#ifndef _WIN64
+ r = fseek(f, offset, whence);
+#else /* ifndef _WIN64 */
+ r = _fseeki64(f, offset, whence);
+#endif /* ifndef _WIN64 */
+ if (r == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (isc__errno2result(errno));
+ }
+}
+
+isc_result_t
+isc_stdio_tell(FILE *f, off_t *offsetp) {
+#ifndef _WIN64
+ long r;
+#else /* ifndef _WIN64 */
+ __int64 r;
+#endif /* ifndef _WIN64 */
+
+ REQUIRE(offsetp != NULL);
+
+#ifndef _WIN64
+ r = ftell(f);
+#else /* ifndef _WIN64 */
+ r = _ftelli64(f);
+#endif /* ifndef _WIN64 */
+ 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));
+ }
+}
+
+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 _commit() on regular files.
+ */
+ if ((buf.st_mode & S_IFMT) != S_IFREG) {
+ return (ISC_R_SUCCESS);
+ }
+
+ r = _commit(_fileno(f));
+ if (r == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (isc__errno2result(errno));
+ }
+}
diff --git a/lib/isc/win32/stdtime.c b/lib/isc/win32/stdtime.c
new file mode 100644
index 0000000..93fcd66
--- /dev/null
+++ b/lib/isc/win32/stdtime.c
@@ -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.
+ */
+
+#include <time.h>
+
+#include <isc/assertions.h>
+#include <isc/stdtime.h>
+#include <isc/util.h>
+
+void
+isc_stdtime_get(isc_stdtime_t *t) {
+ /*
+ * Set 't' to the number of seconds past 00:00:00 UTC, January 1, 1970.
+ */
+
+ REQUIRE(t != NULL);
+
+ (void)_time32(t);
+}
+
+void
+isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen) {
+ time_t when;
+
+ REQUIRE(out != NULL);
+ /* Minimum buffer as per ctime_r() specification. */
+ REQUIRE(outlen >= 26);
+
+ /* time_t and isc_stdtime_t might be different sizes */
+ when = t;
+ INSIST((ctime_s(out, outlen, &when) == 0));
+ *(out + strlen(out) - 1) = '\0';
+}
diff --git a/lib/isc/win32/syslog.c b/lib/isc/win32/syslog.c
new file mode 100644
index 0000000..ad23a0f
--- /dev/null
+++ b/lib/isc/win32/syslog.c
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <windows.h>
+
+#include <isc/bindevt.h>
+#include <isc/result.h>
+#include <isc/syslog.h>
+#include <isc/util.h>
+
+static HANDLE hAppLog = NULL;
+static FILE *log_stream;
+static int debug_level = 0;
+
+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);
+}
+
+/*
+ * Log to the NT Event Log
+ */
+void
+syslog(int level, const char *fmt, ...) {
+ va_list ap;
+ char buf[1024];
+ char *str[1];
+
+ str[0] = buf;
+
+ va_start(ap, fmt);
+ vsprintf(buf, fmt, ap);
+ va_end(ap);
+
+ /* Make sure that the channel is open to write the event */
+ if (hAppLog != NULL) {
+ switch (level) {
+ case LOG_INFO:
+ case LOG_NOTICE:
+ case LOG_DEBUG:
+ ReportEvent(hAppLog, EVENTLOG_INFORMATION_TYPE, 0,
+ BIND_INFO_MSG, NULL, 1, 0, str, NULL);
+ break;
+ case LOG_WARNING:
+ ReportEvent(hAppLog, EVENTLOG_WARNING_TYPE, 0,
+ BIND_WARN_MSG, NULL, 1, 0, str, NULL);
+ break;
+ default:
+ ReportEvent(hAppLog, EVENTLOG_ERROR_TYPE, 0,
+ BIND_ERR_MSG, NULL, 1, 0, str, NULL);
+ break;
+ }
+ }
+}
+
+/*
+ * Initialize event logging
+ */
+void
+openlog(const char *name, int flags, ...) {
+ /* Get a handle to the Application event log */
+ hAppLog = RegisterEventSource(NULL, name);
+}
+
+/*
+ * Close the Handle to the application Event Log
+ * We don't care whether or not we succeeded so ignore return values
+ * In fact if we failed then we would have nowhere to put the message
+ */
+void
+closelog(void) {
+ DeregisterEventSource(hAppLog);
+}
+
+/*
+ * Keep event logging synced with the current debug level
+ */
+void
+ModifyLogLevel(int level) {
+ debug_level = level;
+}
+
+/*
+ * Initialize logging for the port section of libbind.
+ * Piggyback onto stream given.
+ */
+void
+InitNTLogging(FILE *stream, int debug) {
+ log_stream = stream;
+ ModifyLogLevel(debug);
+}
+/*
+ * This function is for reporting errors to the application
+ * event log in case the regular syslog is not available
+ * mainly during startup. It should not be used under normal
+ * circumstances.
+ */
+void
+NTReportError(const char *name, const char *str) {
+ HANDLE hNTAppLog = NULL;
+ const char *buf[1];
+
+ buf[0] = str;
+
+ hNTAppLog = RegisterEventSource(NULL, name);
+
+ ReportEvent(hNTAppLog, EVENTLOG_ERROR_TYPE, 0, BIND_ERR_MSG, NULL, 1, 0,
+ buf, NULL);
+
+ DeregisterEventSource(hNTAppLog);
+}
diff --git a/lib/isc/win32/syslog.h b/lib/isc/win32/syslog.h
new file mode 100644
index 0000000..e785144
--- /dev/null
+++ b/lib/isc/win32/syslog.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef _SYSLOG_H
+#define _SYSLOG_H
+
+#include <stdio.h>
+
+/* Constant definitions for openlog() */
+#define LOG_PID 1
+#define LOG_CONS 2
+/* NT event log does not support facility level */
+#define LOG_KERN 0
+#define LOG_USER 0
+#define LOG_MAIL 0
+#define LOG_DAEMON 0
+#define LOG_AUTH 0
+#define LOG_SYSLOG 0
+#define LOG_LPR 0
+#define LOG_LOCAL0 0
+#define LOG_LOCAL1 0
+#define LOG_LOCAL2 0
+#define LOG_LOCAL3 0
+#define LOG_LOCAL4 0
+#define LOG_LOCAL5 0
+#define LOG_LOCAL6 0
+#define LOG_LOCAL7 0
+
+#define LOG_EMERG 0 /* system is unusable */
+#define LOG_ALERT 1 /* action must be taken immediately */
+#define LOG_CRIT 2 /* critical conditions */
+#define LOG_ERR 3 /* error conditions */
+#define LOG_WARNING 4 /* warning conditions */
+#define LOG_NOTICE 5 /* normal but signification condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+
+void
+syslog(int level, const char *fmt, ...);
+
+void
+openlog(const char *, int, ...);
+
+void
+closelog(void);
+
+void
+ModifyLogLevel(int level);
+
+void
+InitNTLogging(FILE *, int);
+
+void
+NTReportError(const char *, const char *);
+/*
+ * Include the event codes required for logging.
+ */
+#include <isc/bindevt.h>
+
+#endif /* ifndef _SYSLOG_H */
diff --git a/lib/isc/win32/thread.c b/lib/isc/win32/thread.c
new file mode 100644
index 0000000..e373e75
--- /dev/null
+++ b/lib/isc/win32/thread.c
@@ -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.
+ */
+
+#include <process.h>
+
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/strerr.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "trampoline_p.h"
+
+void
+isc_thread_create(isc_threadfunc_t start, isc_threadarg_t arg,
+ isc_thread_t *threadp) {
+ isc_thread_t thread;
+ unsigned int id;
+ isc__trampoline_t *trampoline_arg;
+
+ trampoline_arg = isc__trampoline_get(start, arg);
+
+ thread = (isc_thread_t)_beginthreadex(NULL, 0, isc__trampoline_run,
+ trampoline_arg, 0, &id);
+ if (thread == NULL) {
+ char strbuf[ISC_STRERRORSIZE];
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ isc_error_fatal(__FILE__, __LINE__, "_beginthreadex failed: %s",
+ strbuf);
+ }
+
+ *threadp = thread;
+
+ return;
+}
+
+void
+isc_thread_join(isc_thread_t thread, isc_threadresult_t *rp) {
+ DWORD result;
+
+ result = WaitForSingleObject(thread, INFINITE);
+ if (result != WAIT_OBJECT_0) {
+ isc_error_fatal(__FILE__, __LINE__,
+ "WaitForSingleObject() != WAIT_OBJECT_0");
+ }
+ if (rp != NULL && !GetExitCodeThread(thread, rp)) {
+ isc_error_fatal(__FILE__, __LINE__,
+ "GetExitCodeThread() failed: %d",
+ GetLastError());
+ }
+ (void)CloseHandle(thread);
+}
+
+void
+isc_thread_setconcurrency(unsigned int level) {
+ /*
+ * This is unnecessary on Win32 systems, but is here so that the
+ * call exists
+ */
+}
+
+void
+isc_thread_setname(isc_thread_t thread, const char *name) {
+ UNUSED(thread);
+ UNUSED(name);
+}
+
+isc_result_t
+isc_thread_setaffinity(int cpu) {
+ /* no-op on Windows for now */
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/win32/time.c b/lib/isc/win32/time.c
new file mode 100644
index 0000000..c29edd3
--- /dev/null
+++ b/lib/isc/win32/time.c
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <windows.h>
+
+#include <isc/assertions.h>
+#include <isc/once.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/tm.h>
+#include <isc/util.h>
+
+/*
+ * struct FILETIME uses "100-nanoseconds intervals".
+ * NS / S = 1000000000 (10^9).
+ * While it is reasonably obvious that this makes the needed
+ * conversion factor 10^7, it is coded this way for additional clarity.
+ */
+#define NS_PER_S 1000000000
+#define NS_INTERVAL 100
+#define INTERVALS_PER_S (NS_PER_S / NS_INTERVAL)
+
+/***
+ *** Absolute Times
+ ***/
+
+static const isc_time_t epoch = { { 0, 0 } };
+LIBISC_EXTERNAL_DATA const isc_time_t *const isc_time_epoch = &epoch;
+
+/***
+ *** Intervals
+ ***/
+
+static const isc_interval_t zero_interval = { 0 };
+LIBISC_EXTERNAL_DATA const isc_interval_t *const isc_interval_zero =
+ &zero_interval;
+
+void
+isc_interval_set(isc_interval_t *i, unsigned int seconds,
+ unsigned int nanoseconds) {
+ REQUIRE(i != NULL);
+ REQUIRE(nanoseconds < NS_PER_S);
+
+ /*
+ * This rounds nanoseconds up not down.
+ */
+ i->interval = (LONGLONG)seconds * INTERVALS_PER_S +
+ (nanoseconds + NS_INTERVAL - 1) / NS_INTERVAL;
+}
+
+bool
+isc_interval_iszero(const isc_interval_t *i) {
+ REQUIRE(i != NULL);
+ if (i->interval == 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+void
+isc_time_set(isc_time_t *t, unsigned int seconds, unsigned int nanoseconds) {
+ SYSTEMTIME epoch1970 = { 1970, 1, 4, 1, 0, 0, 0, 0 };
+ FILETIME temp;
+ ULARGE_INTEGER i1;
+
+ REQUIRE(t != NULL);
+ REQUIRE(nanoseconds < NS_PER_S);
+
+ SystemTimeToFileTime(&epoch1970, &temp);
+
+ i1.LowPart = temp.dwLowDateTime;
+ i1.HighPart = temp.dwHighDateTime;
+
+ /* cppcheck-suppress unreadVariable */
+ i1.QuadPart += (unsigned __int64)nanoseconds / 100;
+ /* cppcheck-suppress unreadVariable */
+ i1.QuadPart += (unsigned __int64)seconds * 10000000;
+
+ t->absolute.dwLowDateTime = i1.LowPart;
+ t->absolute.dwHighDateTime = i1.HighPart;
+}
+
+void
+isc_time_settoepoch(isc_time_t *t) {
+ REQUIRE(t != NULL);
+
+ t->absolute.dwLowDateTime = 0;
+ t->absolute.dwHighDateTime = 0;
+}
+
+bool
+isc_time_isepoch(const isc_time_t *t) {
+ REQUIRE(t != NULL);
+
+ if (t->absolute.dwLowDateTime == 0 && t->absolute.dwHighDateTime == 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+isc_result_t
+isc_time_now(isc_time_t *t) {
+ REQUIRE(t != NULL);
+
+ GetSystemTimeAsFileTime(&t->absolute);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_time_now_hires(isc_time_t *t) {
+ REQUIRE(t != NULL);
+
+ GetSystemTimePreciseAsFileTime(&t->absolute);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i) {
+ ULARGE_INTEGER i1;
+
+ REQUIRE(t != NULL);
+ REQUIRE(i != NULL);
+
+ GetSystemTimeAsFileTime(&t->absolute);
+
+ i1.LowPart = t->absolute.dwLowDateTime;
+ i1.HighPart = t->absolute.dwHighDateTime;
+
+ if (UINT64_MAX - i1.QuadPart < (unsigned __int64)i->interval) {
+ return (ISC_R_RANGE);
+ }
+
+ /* cppcheck-suppress unreadVariable */
+ i1.QuadPart += i->interval;
+
+ t->absolute.dwLowDateTime = i1.LowPart;
+ t->absolute.dwHighDateTime = i1.HighPart;
+
+ return (ISC_R_SUCCESS);
+}
+
+int
+isc_time_compare(const isc_time_t *t1, const isc_time_t *t2) {
+ REQUIRE(t1 != NULL && t2 != NULL);
+
+ return ((int)CompareFileTime(&t1->absolute, &t2->absolute));
+}
+
+isc_result_t
+isc_time_add(const isc_time_t *t, const isc_interval_t *i, isc_time_t *result) {
+ ULARGE_INTEGER i1;
+
+ REQUIRE(t != NULL && i != NULL && result != NULL);
+
+ i1.LowPart = t->absolute.dwLowDateTime;
+ i1.HighPart = t->absolute.dwHighDateTime;
+
+ if (UINT64_MAX - i1.QuadPart < (unsigned __int64)i->interval) {
+ return (ISC_R_RANGE);
+ }
+
+ /* cppcheck-suppress unreadVariable */
+ i1.QuadPart += i->interval;
+
+ result->absolute.dwLowDateTime = i1.LowPart;
+ result->absolute.dwHighDateTime = i1.HighPart;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_time_subtract(const isc_time_t *t, const isc_interval_t *i,
+ isc_time_t *result) {
+ ULARGE_INTEGER i1;
+
+ REQUIRE(t != NULL && i != NULL && result != NULL);
+
+ i1.LowPart = t->absolute.dwLowDateTime;
+ i1.HighPart = t->absolute.dwHighDateTime;
+
+ if (i1.QuadPart < (unsigned __int64)i->interval) {
+ return (ISC_R_RANGE);
+ }
+
+ /* cppcheck-suppress unreadVariable */
+ i1.QuadPart -= i->interval;
+
+ result->absolute.dwLowDateTime = i1.LowPart;
+ result->absolute.dwHighDateTime = i1.HighPart;
+
+ return (ISC_R_SUCCESS);
+}
+
+uint64_t
+isc_time_microdiff(const isc_time_t *t1, const isc_time_t *t2) {
+ ULARGE_INTEGER i1, i2;
+ LONGLONG i3;
+
+ REQUIRE(t1 != NULL && t2 != NULL);
+
+ /* cppcheck-suppress unreadVariable */
+ i1.LowPart = t1->absolute.dwLowDateTime;
+ /* cppcheck-suppress unreadVariable */
+ i1.HighPart = t1->absolute.dwHighDateTime;
+ /* cppcheck-suppress unreadVariable */
+ i2.LowPart = t2->absolute.dwLowDateTime;
+ /* cppcheck-suppress unreadVariable */
+ i2.HighPart = t2->absolute.dwHighDateTime;
+
+ if (i1.QuadPart <= i2.QuadPart) {
+ return (0);
+ }
+
+ /*
+ * Convert to microseconds.
+ */
+ i3 = (i1.QuadPart - i2.QuadPart) / 10;
+
+ return (i3);
+}
+
+uint32_t
+isc_time_seconds(const isc_time_t *t) {
+ SYSTEMTIME epoch1970 = { 1970, 1, 4, 1, 0, 0, 0, 0 };
+ FILETIME temp;
+ ULARGE_INTEGER i1, i2;
+ LONGLONG i3;
+
+ SystemTimeToFileTime(&epoch1970, &temp);
+
+ /* cppcheck-suppress unreadVariable */
+ i1.LowPart = t->absolute.dwLowDateTime;
+ /* cppcheck-suppress unreadVariable */
+ i1.HighPart = t->absolute.dwHighDateTime;
+ /* cppcheck-suppress unreadVariable */
+ i2.LowPart = temp.dwLowDateTime;
+ /* cppcheck-suppress unreadVariable */
+ i2.HighPart = temp.dwHighDateTime;
+
+ i3 = (i1.QuadPart - i2.QuadPart) / 10000000;
+
+ return ((uint32_t)i3);
+}
+
+isc_result_t
+isc_time_secondsastimet(const isc_time_t *t, time_t *secondsp) {
+ time_t seconds;
+
+ REQUIRE(t != NULL);
+
+ seconds = (time_t)isc_time_seconds(t);
+
+ INSIST(sizeof(unsigned int) == sizeof(uint32_t));
+ INSIST(sizeof(time_t) >= sizeof(uint32_t));
+
+ if (isc_time_seconds(t) > (~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) {
+ ULARGE_INTEGER i;
+
+ i.LowPart = t->absolute.dwLowDateTime;
+ i.HighPart = t->absolute.dwHighDateTime;
+ return ((uint32_t)(i.QuadPart % 10000000) * 100);
+}
+
+void
+isc_time_formattimestamp(const isc_time_t *t, char *buf, unsigned int len) {
+ FILETIME localft;
+ SYSTEMTIME st;
+ char DateBuf[50];
+ char TimeBuf[50];
+
+ REQUIRE(t != NULL);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ if (FileTimeToLocalFileTime(&t->absolute, &localft) &&
+ FileTimeToSystemTime(&localft, &st))
+ {
+ GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "dd-MMM-yyyy",
+ DateBuf, 50);
+ GetTimeFormat(LOCALE_USER_DEFAULT,
+ TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st,
+ NULL, TimeBuf, 50);
+
+ snprintf(buf, len, "%s %s.%03u", DateBuf, TimeBuf,
+ st.wMilliseconds);
+ } 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) {
+ SYSTEMTIME st;
+ char DateBuf[50];
+ char TimeBuf[50];
+
+ /* strftime() format: "%a, %d %b %Y %H:%M:%S GMT" */
+
+ REQUIRE(t != NULL);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ if (FileTimeToSystemTime(&t->absolute, &st)) {
+ GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "ddd',' dd MMM yyyy",
+ DateBuf, 50);
+ GetTimeFormat(LOCALE_USER_DEFAULT,
+ TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st,
+ "hh':'mm':'ss", TimeBuf, 50);
+
+ snprintf(buf, len, "%s %s GMT", DateBuf, TimeBuf);
+ } else {
+ buf[0] = 0;
+ }
+}
+
+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, (unsigned int)when, 0);
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_time_formatISO8601L(const isc_time_t *t, char *buf, unsigned int len) {
+ SYSTEMTIME st;
+ char DateBuf[50];
+ char TimeBuf[50];
+
+ /* strtime() format: "%Y-%m-%dT%H:%M:%S" */
+
+ REQUIRE(t != NULL);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ if (FileTimeToSystemTime(&t->absolute, &st)) {
+ GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "yyyy-MM-dd",
+ DateBuf, 50);
+ GetTimeFormat(LOCALE_USER_DEFAULT,
+ TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st,
+ "hh':'mm':'ss", TimeBuf, 50);
+ snprintf(buf, len, "%sT%s", DateBuf, TimeBuf);
+ } else {
+ buf[0] = 0;
+ }
+}
+
+void
+isc_time_formatISO8601Lms(const isc_time_t *t, char *buf, unsigned int len) {
+ SYSTEMTIME st;
+ char DateBuf[50];
+ char TimeBuf[50];
+
+ /* strtime() format: "%Y-%m-%dT%H:%M:%S.SSS" */
+
+ REQUIRE(t != NULL);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ if (FileTimeToSystemTime(&t->absolute, &st)) {
+ GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "yyyy-MM-dd",
+ DateBuf, 50);
+ GetTimeFormat(LOCALE_USER_DEFAULT,
+ TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st,
+ "hh':'mm':'ss", TimeBuf, 50);
+ snprintf(buf, len, "%sT%s.%03u", DateBuf, TimeBuf,
+ st.wMilliseconds);
+ } else {
+ buf[0] = 0;
+ }
+}
+
+void
+isc_time_formatISO8601Lus(const isc_time_t *t, char *buf, unsigned int len) {
+ SYSTEMTIME st;
+ char DateBuf[50];
+ char TimeBuf[50];
+
+ /* strtime() format: "%Y-%m-%dT%H:%M:%S.SSSSSS" */
+
+ REQUIRE(t != NULL);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ if (FileTimeToSystemTime(&t->absolute, &st)) {
+ ULARGE_INTEGER i;
+
+ GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "yyyy-MM-dd",
+ DateBuf, 50);
+ GetTimeFormat(LOCALE_USER_DEFAULT,
+ TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st,
+ "hh':'mm':'ss", TimeBuf, 50);
+ i.LowPart = t->absolute.dwLowDateTime;
+ i.HighPart = t->absolute.dwHighDateTime;
+ snprintf(buf, len, "%sT%s.%06u", DateBuf, TimeBuf,
+ (uint32_t)(i.QuadPart % 10000000) / 10);
+ } else {
+ buf[0] = 0;
+ }
+}
+
+void
+isc_time_formatISO8601(const isc_time_t *t, char *buf, unsigned int len) {
+ SYSTEMTIME st;
+ char DateBuf[50];
+ char TimeBuf[50];
+
+ /* strtime() format: "%Y-%m-%dT%H:%M:%SZ" */
+
+ REQUIRE(t != NULL);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ if (FileTimeToSystemTime(&t->absolute, &st)) {
+ GetDateFormat(LOCALE_NEUTRAL, 0, &st, "yyyy-MM-dd", DateBuf,
+ 50);
+ GetTimeFormat(LOCALE_NEUTRAL,
+ TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st,
+ "hh':'mm':'ss", TimeBuf, 50);
+ snprintf(buf, len, "%sT%sZ", DateBuf, TimeBuf);
+ } else {
+ buf[0] = 0;
+ }
+}
+
+void
+isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len) {
+ SYSTEMTIME st;
+ char DateBuf[50];
+ char TimeBuf[50];
+
+ /* strtime() format: "%Y-%m-%dT%H:%M:%S.SSSZ" */
+
+ REQUIRE(t != NULL);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ if (FileTimeToSystemTime(&t->absolute, &st)) {
+ GetDateFormat(LOCALE_NEUTRAL, 0, &st, "yyyy-MM-dd", DateBuf,
+ 50);
+ GetTimeFormat(LOCALE_NEUTRAL,
+ TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st,
+ "hh':'mm':'ss", TimeBuf, 50);
+ snprintf(buf, len, "%sT%s.%03uZ", DateBuf, TimeBuf,
+ st.wMilliseconds);
+ } else {
+ buf[0] = 0;
+ }
+}
+
+void
+isc_time_formatISO8601us(const isc_time_t *t, char *buf, unsigned int len) {
+ SYSTEMTIME st;
+ char DateBuf[50];
+ char TimeBuf[50];
+
+ /* strtime() format: "%Y-%m-%dT%H:%M:%S.SSSSSSZ" */
+
+ REQUIRE(t != NULL);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ if (FileTimeToSystemTime(&t->absolute, &st)) {
+ ULARGE_INTEGER i;
+
+ GetDateFormat(LOCALE_NEUTRAL, 0, &st, "yyyy-MM-dd", DateBuf,
+ 50);
+ GetTimeFormat(LOCALE_NEUTRAL,
+ TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st,
+ "hh':'mm':'ss", TimeBuf, 50);
+ i.LowPart = t->absolute.dwLowDateTime;
+ i.HighPart = t->absolute.dwHighDateTime;
+ snprintf(buf, len, "%sT%s.%06uZ", DateBuf, TimeBuf,
+ (uint32_t)(i.QuadPart % 10000000) / 10);
+ } else {
+ buf[0] = 0;
+ }
+}
+
+void
+isc_time_formatshorttimestamp(const isc_time_t *t, char *buf,
+ unsigned int len) {
+ SYSTEMTIME st;
+ char DateBuf[50];
+ char TimeBuf[50];
+
+ /* strtime() format: "%Y%m%d%H%M%SSSS" */
+
+ REQUIRE(t != NULL);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ if (FileTimeToSystemTime(&t->absolute, &st)) {
+ GetDateFormat(LOCALE_NEUTRAL, 0, &st, "yyyyMMdd", DateBuf, 50);
+ GetTimeFormat(LOCALE_NEUTRAL,
+ TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st,
+ "hhmmss", TimeBuf, 50);
+ snprintf(buf, len, "%s%s%03u", DateBuf, TimeBuf,
+ st.wMilliseconds);
+ } else {
+ buf[0] = 0;
+ }
+}
+
+/*
+ * POSIX Shims
+ */
+
+struct tm *
+gmtime_r(const time_t *clock, struct tm *result) {
+ errno_t ret = gmtime_s(result, clock);
+ if (ret != 0) {
+ errno = ret;
+ return (NULL);
+ }
+ return (result);
+}
+
+struct tm *
+localtime_r(const time_t *clock, struct tm *result) {
+ errno_t ret = localtime_s(result, clock);
+ if (ret != 0) {
+ errno = ret;
+ return (NULL);
+ }
+ return (result);
+}
+
+#define BILLION 1000000000
+
+static isc_once_t nsec_ticks_once = ISC_ONCE_INIT;
+static double nsec_ticks = 0;
+
+static void
+nsec_ticks_init(void) {
+ LARGE_INTEGER ticks;
+ RUNTIME_CHECK(QueryPerformanceFrequency(&ticks) != 0);
+ nsec_ticks = (double)ticks.QuadPart / 1000000000.0;
+ RUNTIME_CHECK(nsec_ticks != 0.0);
+}
+
+int
+nanosleep(const struct timespec *req, struct timespec *rem) {
+ int_fast64_t sleep_msec;
+ uint_fast64_t ticks, until;
+ LARGE_INTEGER before, after;
+
+ RUNTIME_CHECK(isc_once_do(&nsec_ticks_once, nsec_ticks_init) ==
+ ISC_R_SUCCESS);
+
+ if (req->tv_nsec < 0 || BILLION <= req->tv_nsec) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /* Sleep() is not interruptible; there is no remaining delay ever */
+ if (rem != NULL) {
+ rem->tv_sec = 0;
+ rem->tv_nsec = 0;
+ }
+
+ if (req->tv_sec >= 0) {
+ /*
+ * For requested delays of one second or more, 15ms resolution
+ * granularity is sufficient.
+ */
+ Sleep(req->tv_sec * 1000 + req->tv_nsec / 1000000);
+
+ return (0);
+ }
+
+ /* Sleep has <-8,8> ms precision, so substract 10 milliseconds */
+ sleep_msec = (int64_t)req->tv_nsec / 1000000 - 10;
+ ticks = req->tv_nsec * nsec_ticks;
+
+ RUNTIME_CHECK(QueryPerformanceCounter(&before) != 0);
+
+ until = before.QuadPart + ticks;
+
+ if (sleep_msec > 0) {
+ Sleep(sleep_msec);
+ }
+
+ while (true) {
+ LARGE_INTEGER after;
+ RUNTIME_CHECK(QueryPerformanceCounter(&after) != 0);
+ if (after.QuadPart >= until) {
+ break;
+ }
+ }
+
+done:
+ return (0);
+}
+
+int
+usleep(useconds_t useconds) {
+ struct timespec req;
+
+ req.tv_sec = useconds / 1000000;
+ req.tv_nsec = (useconds * 1000) % 1000000000;
+
+ nanosleep(&req, NULL);
+
+ return (0);
+}
diff --git a/lib/isc/win32/unistd.h b/lib/isc/win32/unistd.h
new file mode 100644
index 0000000..e1aff40
--- /dev/null
+++ b/lib/isc/win32/unistd.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.
+ */
+
+/* None of these are defined in NT, so define them for our use */
+#define O_NONBLOCK 1
+#define PORT_NONBLOCK O_NONBLOCK
+
+/*
+ * fcntl() commands
+ */
+#define F_SETFL 0
+#define F_GETFL 1
+#define F_SETFD 2
+#define F_GETFD 3
+/*
+ * Enough problems not having full fcntl() without worrying about this!
+ */
+#undef F_DUPFD
+
+int
+fcntl(int, int, ...);
+
+/*
+ * access() related definitions for winXP
+ */
+#include <io.h>
+#ifndef F_OK
+#define F_OK 0
+#endif /* ifndef F_OK */
+
+#ifndef X_OK
+#define X_OK 1
+#endif /* ifndef X_OK */
+
+#ifndef W_OK
+#define W_OK 2
+#endif /* ifndef W_OK */
+
+#ifndef R_OK
+#define R_OK 4
+#endif /* ifndef R_OK */
+
+#define access _access
+
+#include <process.h>
diff --git a/lib/isc/win32/version.c b/lib/isc/win32/version.c
new file mode 100644
index 0000000..b49347f
--- /dev/null
+++ b/lib/isc/win32/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <versions.h>
+
+#include <isc/version.h>
+
+LIBISC_EXTERNAL_DATA const char isc_version[] = VERSION;
diff --git a/lib/isc/win32/win32os.c b/lib/isc/win32/win32os.c
new file mode 100644
index 0000000..4f86d38
--- /dev/null
+++ b/lib/isc/win32/win32os.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 <windows.h>
+
+#ifndef TESTVERSION
+#include <isc/win32os.h>
+#else /* ifndef TESTVERSION */
+#include <stdio.h>
+
+#include <isc/util.h>
+#endif /* ifndef TESTVERSION */
+#include <isc/print.h>
+
+int
+isc_win32os_versioncheck(unsigned int major, unsigned int minor,
+ unsigned int spmajor, unsigned int spminor) {
+ OSVERSIONINFOEX osVer;
+ DWORD typeMask;
+ ULONGLONG conditionMask;
+
+ memset(&osVer, 0, sizeof(OSVERSIONINFOEX));
+ osVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ typeMask = 0;
+ conditionMask = 0;
+
+ /* Optimistic: likely greater */
+ osVer.dwMajorVersion = major;
+ typeMask |= VER_MAJORVERSION;
+ conditionMask = VerSetConditionMask(conditionMask, VER_MAJORVERSION,
+ VER_GREATER);
+ osVer.dwMinorVersion = minor;
+ typeMask |= VER_MINORVERSION;
+ conditionMask = VerSetConditionMask(conditionMask, VER_MINORVERSION,
+ VER_GREATER);
+ osVer.wServicePackMajor = spmajor;
+ typeMask |= VER_SERVICEPACKMAJOR;
+ conditionMask = VerSetConditionMask(conditionMask, VER_SERVICEPACKMAJOR,
+ VER_GREATER);
+ osVer.wServicePackMinor = spminor;
+ typeMask |= VER_SERVICEPACKMINOR;
+ conditionMask = VerSetConditionMask(conditionMask, VER_SERVICEPACKMINOR,
+ VER_GREATER);
+ if (VerifyVersionInfo(&osVer, typeMask, conditionMask)) {
+ return (1);
+ }
+
+ /* Failed: retry with equal */
+ conditionMask = 0;
+ conditionMask = VerSetConditionMask(conditionMask, VER_MAJORVERSION,
+ VER_EQUAL);
+ conditionMask = VerSetConditionMask(conditionMask, VER_MINORVERSION,
+ VER_EQUAL);
+ conditionMask = VerSetConditionMask(conditionMask, VER_SERVICEPACKMAJOR,
+ VER_EQUAL);
+ conditionMask = VerSetConditionMask(conditionMask, VER_SERVICEPACKMINOR,
+ VER_EQUAL);
+ if (VerifyVersionInfo(&osVer, typeMask, conditionMask)) {
+ return (0);
+ } else {
+ return (-1);
+ }
+}
+
+#ifdef TESTVERSION
+int
+main(int argc, char **argv) {
+ unsigned int major = 0;
+ unsigned int minor = 0;
+ unsigned int spmajor = 0;
+ unsigned int spminor = 0;
+ int ret;
+
+ if (argc > 1) {
+ --argc;
+ ++argv;
+ major = (unsigned int)atoi(argv[0]);
+ }
+ if (argc > 1) {
+ --argc;
+ ++argv;
+ minor = (unsigned int)atoi(argv[0]);
+ }
+ if (argc > 1) {
+ --argc;
+ ++argv;
+ spmajor = (unsigned int)atoi(argv[0]);
+ }
+ if (argc > 1) {
+ --argc;
+ POST(argc);
+ ++argv;
+ spminor = (unsigned int)atoi(argv[0]);
+ }
+
+ ret = isc_win32os_versioncheck(major, minor, spmajor, spminor);
+
+ printf("%s major %u minor %u SP major %u SP minor %u\n",
+ ret > 0 ? "greater" : (ret == 0 ? "equal" : "less"), major,
+ minor, spmajor, spminor);
+ return (ret);
+}
+#endif /* ifdef TESTVERSION */
diff --git a/lib/isc/xoshiro128starstar.c b/lib/isc/xoshiro128starstar.c
new file mode 100644
index 0000000..7698ee8
--- /dev/null
+++ b/lib/isc/xoshiro128starstar.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.
+ */
+
+/*
+ * 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/>.
+ */
+
+#include <inttypes.h>
+
+#include <isc/thread.h>
+
+/*
+ * 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.
+ */
+ISC_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);
+}
diff --git a/lib/isccc/Kyuafile b/lib/isccc/Kyuafile
new file mode 100644
index 0000000..c796010
--- /dev/null
+++ b/lib/isccc/Kyuafile
@@ -0,0 +1,15 @@
+-- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+--
+-- SPDX-License-Identifier: MPL-2.0
+--
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, you can obtain one at https://mozilla.org/MPL/2.0/.
+--
+-- See the COPYRIGHT file distributed with this work for additional
+-- information regarding copyright ownership.
+
+syntax(2)
+test_suite('bind9')
+
+include('tests/Kyuafile')
diff --git a/lib/isccc/Makefile.in b/lib/isccc/Makefile.in
new file mode 100644
index 0000000..b542441
--- /dev/null
+++ b/lib/isccc/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. ${DNS_INCLUDES} ${ISC_INCLUDES} \
+ ${ISCCC_INCLUDES} \
+ ${OPENSSL_CFLAGS}
+
+CDEFINES =
+CWARNINGS =
+
+ISCLIBS = ../../lib/isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@
+ISCCCLIBS = ../../lib/isccc/libisccc.@A@
+
+ISCDEPLIBS = ../../lib/isc/libisc.@A@
+ISCCCDEPLIBS = libisccc.@A@
+
+LIBS = @LIBS@
+
+SUBDIRS = include
+
+# Alphabetically
+OBJS = alist.@O@ base64.@O@ cc.@O@ ccmsg.@O@ \
+ result.@O@ sexpr.@O@ symtab.@O@ version.@O@
+
+# Alphabetically
+SRCS = alist.c base64.c cc.c ccmsg.c \
+ result.c sexpr.c symtab.c version.c
+
+
+TARGETS = timestamp
+TESTDIRS = @UNITTESTS@
+
+@BIND9_MAKE_RULES@
+
+version.@O@: version.c
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} \
+ -DVERSION=\"${VERSION}\" \
+ -c ${srcdir}/version.c
+
+libisccc.@SA@: ${OBJS}
+ ${AR} ${ARFLAGS} $@ ${OBJS}
+ ${RANLIB} $@
+
+libisccc.la: ${OBJS}
+ ${LIBTOOL_MODE_LINK} \
+ ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libisccc.la -rpath ${libdir} \
+ -release "${VERSION}" \
+ ${OBJS} ${ISCLIBS} ${LIBS}
+
+timestamp: libisccc.@A@
+ touch timestamp
+
+testdirs: libisccc.@A@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}
+
+install:: timestamp installdirs
+ ${LIBTOOL_MODE_INSTALL} ${INSTALL_LIBRARY} libisccc.@A@ ${DESTDIR}${libdir}
+
+uninstall::
+ ${LIBTOOL_MODE_UNINSTALL} rm -f ${DESTDIR}${libdir}/libisccc.@A@
+
+clean distclean::
+ rm -f libisccc.@A@ timestamp
diff --git a/lib/isccc/alist.c b/lib/isccc/alist.c
new file mode 100644
index 0000000..a39d4f9
--- /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 <isccc/alist.h>
+#include <isccc/result.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..344d96a
--- /dev/null
+++ b/lib/isccc/base64.c
@@ -0,0 +1,75 @@
+/*
+ * 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/result.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..5a716bd
--- /dev/null
+++ b/lib/isccc/cc.c
@@ -0,0 +1,1059 @@
+/*
+ * 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/safe.h>
+
+#include <pk11/site.h>
+
+#include <isccc/alist.h>
+#include <isccc/base64.h>
+#include <isccc/cc.h>
+#include <isccc/result.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;
+ 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;
+ 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..bb60a53
--- /dev/null
+++ b/lib/isccc/ccmsg.c
@@ -0,0 +1,231 @@
+/*
+ * 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/result.h>
+#include <isc/task.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_length(isc_task_t *, isc_event_t *);
+static void
+recv_message(isc_task_t *, isc_event_t *);
+
+static void
+recv_length(isc_task_t *task, isc_event_t *ev_in) {
+ isc_socketevent_t *ev = (isc_socketevent_t *)ev_in;
+ isc_event_t *dev;
+ isccc_ccmsg_t *ccmsg = ev_in->ev_arg;
+ isc_region_t region;
+ isc_result_t result;
+
+ INSIST(VALID_CCMSG(ccmsg));
+
+ dev = &ccmsg->event;
+
+ if (ev->result != ISC_R_SUCCESS) {
+ ccmsg->result = ev->result;
+ goto send_and_free;
+ }
+
+ /*
+ * Success.
+ */
+ ccmsg->size = ntohl(ccmsg->size);
+ if (ccmsg->size == 0) {
+ ccmsg->result = ISC_R_UNEXPECTEDEND;
+ goto send_and_free;
+ }
+ if (ccmsg->size > ccmsg->maxsize) {
+ ccmsg->result = ISC_R_RANGE;
+ goto send_and_free;
+ }
+
+ region.base = isc_mem_get(ccmsg->mctx, ccmsg->size);
+ region.length = ccmsg->size;
+ if (region.base == NULL) {
+ ccmsg->result = ISC_R_NOMEMORY;
+ goto send_and_free;
+ }
+
+ isc_buffer_init(&ccmsg->buffer, region.base, region.length);
+ result = isc_socket_recv(ccmsg->sock, &region, 0, task, recv_message,
+ ccmsg);
+ if (result != ISC_R_SUCCESS) {
+ ccmsg->result = result;
+ goto send_and_free;
+ }
+
+ isc_event_free(&ev_in);
+ return;
+
+send_and_free:
+ isc_task_send(ccmsg->task, &dev);
+ ccmsg->task = NULL;
+ isc_event_free(&ev_in);
+ return;
+}
+
+static void
+recv_message(isc_task_t *task, isc_event_t *ev_in) {
+ isc_socketevent_t *ev = (isc_socketevent_t *)ev_in;
+ isc_event_t *dev;
+ isccc_ccmsg_t *ccmsg = ev_in->ev_arg;
+
+ (void)task;
+
+ INSIST(VALID_CCMSG(ccmsg));
+
+ dev = &ccmsg->event;
+
+ if (ev->result != ISC_R_SUCCESS) {
+ ccmsg->result = ev->result;
+ goto send_and_free;
+ }
+
+ ccmsg->result = ISC_R_SUCCESS;
+ isc_buffer_add(&ccmsg->buffer, ev->n);
+ ccmsg->address = ev->address;
+
+send_and_free:
+ isc_task_send(ccmsg->task, &dev);
+ ccmsg->task = NULL;
+ isc_event_free(&ev_in);
+}
+
+void
+isccc_ccmsg_init(isc_mem_t *mctx, isc_socket_t *sock, isccc_ccmsg_t *ccmsg) {
+ REQUIRE(mctx != NULL);
+ REQUIRE(sock != NULL);
+ REQUIRE(ccmsg != NULL);
+
+ ccmsg->magic = CCMSG_MAGIC;
+ ccmsg->size = 0;
+ ccmsg->buffer.base = NULL;
+ ccmsg->buffer.length = 0;
+ ccmsg->maxsize = 4294967295U; /* Largest message possible. */
+ ccmsg->mctx = mctx;
+ ccmsg->sock = sock;
+ ccmsg->task = NULL; /* None yet. */
+ ccmsg->result = ISC_R_UNEXPECTED; /* None yet. */
+ /*
+ * Should probably initialize the
+ *event here, but it can wait.
+ */
+}
+
+void
+isccc_ccmsg_setmaxsize(isccc_ccmsg_t *ccmsg, unsigned int maxsize) {
+ REQUIRE(VALID_CCMSG(ccmsg));
+
+ ccmsg->maxsize = maxsize;
+}
+
+isc_result_t
+isccc_ccmsg_readmessage(isccc_ccmsg_t *ccmsg, isc_task_t *task,
+ isc_taskaction_t action, void *arg) {
+ isc_result_t result;
+ isc_region_t region;
+
+ REQUIRE(VALID_CCMSG(ccmsg));
+ REQUIRE(task != NULL);
+ REQUIRE(ccmsg->task == NULL); /* not currently in use */
+
+ if (ccmsg->buffer.base != NULL) {
+ isc_mem_put(ccmsg->mctx, ccmsg->buffer.base,
+ ccmsg->buffer.length);
+ ccmsg->buffer.base = NULL;
+ ccmsg->buffer.length = 0;
+ }
+
+ ccmsg->task = task;
+ ccmsg->action = action;
+ ccmsg->arg = arg;
+ ccmsg->result = ISC_R_UNEXPECTED; /* unknown right now */
+
+ ISC_EVENT_INIT(&ccmsg->event, sizeof(isc_event_t), 0, 0,
+ ISCCC_EVENT_CCMSG, action, arg, ccmsg, NULL, NULL);
+
+ region.base = (unsigned char *)&ccmsg->size;
+ region.length = 4; /* uint32_t */
+ result = isc_socket_recv(ccmsg->sock, &region, 0, ccmsg->task,
+ recv_length, ccmsg);
+
+ if (result != ISC_R_SUCCESS) {
+ ccmsg->task = NULL;
+ }
+
+ return (result);
+}
+
+void
+isccc_ccmsg_cancelread(isccc_ccmsg_t *ccmsg) {
+ REQUIRE(VALID_CCMSG(ccmsg));
+
+ isc_socket_cancel(ccmsg->sock, NULL, ISC_SOCKCANCEL_RECV);
+}
+
+#if 0
+void
+isccc_ccmsg_freebuffer(isccc_ccmsg_t*ccmsg) {
+ REQUIRE(VALID_CCMSG(ccmsg));
+
+ if (ccmsg->buffer.base == NULL) {
+ return;
+ }
+
+ isc_mem_put(ccmsg->mctx,ccmsg->buffer.base,ccmsg->buffer.length);
+ ccmsg->buffer.base = NULL;
+ ccmsg->buffer.length = 0;
+}
+#endif /* if 0 */
+
+void
+isccc_ccmsg_invalidate(isccc_ccmsg_t *ccmsg) {
+ REQUIRE(VALID_CCMSG(ccmsg));
+
+ ccmsg->magic = 0;
+
+ if (ccmsg->buffer.base != NULL) {
+ isc_mem_put(ccmsg->mctx, ccmsg->buffer.base,
+ ccmsg->buffer.length);
+ ccmsg->buffer.base = NULL;
+ ccmsg->buffer.length = 0;
+ }
+}
diff --git a/lib/isccc/include/.clang-format b/lib/isccc/include/.clang-format
new file mode 120000
index 0000000..0e62f72
--- /dev/null
+++ b/lib/isccc/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/isccc/include/Makefile.in b/lib/isccc/include/Makefile.in
new file mode 100644
index 0000000..09c4dd7
--- /dev/null
+++ b/lib/isccc/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = isccc
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/isccc/include/isccc/Makefile.in b/lib/isccc/include/isccc/Makefile.in
new file mode 100644
index 0000000..af4ddf3
--- /dev/null
+++ b/lib/isccc/include/isccc/Makefile.in
@@ -0,0 +1,41 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+#
+# Only list headers that are to be installed and are not
+# machine generated. The latter are handled specially in the
+# install target below.
+#
+HEADERS = alist.h base64.h cc.h ccmsg.h events.h result.h \
+ sexpr.h symtab.h symtype.h types.h util.h version.h
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/isccc
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/isccc || exit 1; \
+ done
+
+uninstall::
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/isccc/$$i || exit 1; \
+ done
diff --git a/lib/isccc/include/isccc/alist.h b/lib/isccc/include/isccc/alist.h
new file mode 100644
index 0000000..558378b
--- /dev/null
+++ b/lib/isccc/include/isccc/alist.h
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCC_ALIST_H
+#define ISCCC_ALIST_H 1
+
+/*! \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
+
+#endif /* ISCCC_ALIST_H */
diff --git a/lib/isccc/include/isccc/base64.h b/lib/isccc/include/isccc/base64.h
new file mode 100644
index 0000000..2eba95c
--- /dev/null
+++ b/lib/isccc/include/isccc/base64.h
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCC_BASE64_H
+#define ISCCC_BASE64_H 1
+
+/*! \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
+
+#endif /* ISCCC_BASE64_H */
diff --git a/lib/isccc/include/isccc/cc.h b/lib/isccc/include/isccc/cc.h
new file mode 100644
index 0000000..9b19904
--- /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.
+ */
+
+#ifndef ISCCC_CC_H
+#define ISCCC_CC_H 1
+
+/*! \file isccc/cc.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+
+#include <isccc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*% from lib/dns/include/dst/dst.h */
+
+#define ISCCC_ALG_UNKNOWN 0
+#define ISCCC_ALG_HMACMD5 157
+#define ISCCC_ALG_HMACSHA1 161
+#define ISCCC_ALG_HMACSHA224 162
+#define ISCCC_ALG_HMACSHA256 163
+#define ISCCC_ALG_HMACSHA384 164
+#define ISCCC_ALG_HMACSHA512 165
+
+/*% 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
+
+#endif /* ISCCC_CC_H */
diff --git a/lib/isccc/include/isccc/ccmsg.h b/lib/isccc/include/isccc/ccmsg.h
new file mode 100644
index 0000000..db64fcf
--- /dev/null
+++ b/lib/isccc/include/isccc/ccmsg.h
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCC_CCMSG_H
+#define ISCCC_CCMSG_H 1
+
+/*! \file isccc/ccmsg.h */
+
+#include <inttypes.h>
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+#include <isc/socket.h>
+
+/*% ISCCC Message Structure */
+typedef struct isccc_ccmsg {
+ /* private (don't touch!) */
+ unsigned int magic;
+ uint32_t size;
+ isc_buffer_t buffer;
+ unsigned int maxsize;
+ isc_mem_t *mctx;
+ isc_socket_t *sock;
+ isc_task_t *task;
+ isc_taskaction_t action;
+ void *arg;
+ isc_event_t event;
+ /* public (read-only) */
+ isc_result_t result;
+ isc_sockaddr_t address;
+} isccc_ccmsg_t;
+
+ISC_LANG_BEGINDECLS
+
+void
+isccc_ccmsg_init(isc_mem_t *mctx, isc_socket_t *sock, isccc_ccmsg_t *ccmsg);
+/*%
+ * Associate a cc message state with a given memory context and
+ * TCP socket.
+ *
+ * Requires:
+ *
+ *\li "mctx" and "sock" be non-NULL and valid types.
+ *
+ *\li "sock" be a read/write TCP socket.
+ *
+ *\li "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
+ */
+
+isc_result_t
+isccc_ccmsg_readmessage(isccc_ccmsg_t *ccmsg, isc_task_t *task,
+ isc_taskaction_t action, void *arg);
+/*%
+ * 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.
+ *
+ *\li "task", "taskaction", and "arg" be valid.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS -- no error
+ *\li Anything that the isc_socket_recv() call can return. XXXMLG
+ *
+ * Notes:
+ *
+ *\li The event delivered is a fully generic event. It will contain no
+ * actual data. The sender will be a pointer to the 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
+
+#endif /* ISCCC_CCMSG_H */
diff --git a/lib/isccc/include/isccc/events.h b/lib/isccc/include/isccc/events.h
new file mode 100644
index 0000000..78ee02d
--- /dev/null
+++ b/lib/isccc/include/isccc/events.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCC_EVENTS_H
+#define ISCCC_EVENTS_H 1
+
+/*! \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)
+
+#endif /* ISCCC_EVENTS_H */
diff --git a/lib/isccc/include/isccc/result.h b/lib/isccc/include/isccc/result.h
new file mode 100644
index 0000000..56f9c38
--- /dev/null
+++ b/lib/isccc/include/isccc/result.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2001 Nominum, Inc.
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISCCC_RESULT_H
+#define ISCCC_RESULT_H 1
+
+/*! \file isccc/result.h */
+
+#include <isc/lang.h>
+#include <isc/result.h>
+#include <isc/resultclass.h>
+
+#include <isccc/types.h>
+
+/*% Unknown Version */
+#define ISCCC_R_UNKNOWNVERSION (ISC_RESULTCLASS_ISCCC + 0)
+/*% Syntax Error */
+#define ISCCC_R_SYNTAX (ISC_RESULTCLASS_ISCCC + 1)
+/*% Bad Authorization */
+#define ISCCC_R_BADAUTH (ISC_RESULTCLASS_ISCCC + 2)
+/*% Expired */
+#define ISCCC_R_EXPIRED (ISC_RESULTCLASS_ISCCC + 3)
+/*% Clock Skew */
+#define ISCCC_R_CLOCKSKEW (ISC_RESULTCLASS_ISCCC + 4)
+/*% Duplicate */
+#define ISCCC_R_DUPLICATE (ISC_RESULTCLASS_ISCCC + 5)
+/*% Maximum recursion depth */
+#define ISCCC_R_MAXDEPTH (ISC_RESULTCLASS_ISCCC + 6)
+
+#define ISCCC_R_NRESULTS 7 /*%< Number of results */
+
+ISC_LANG_BEGINDECLS
+
+const char *
+isccc_result_totext(isc_result_t result);
+/*%
+ * Convert a isccc_result_t into a string message describing the result.
+ */
+
+void
+isccc_result_register(void);
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISCCC_RESULT_H */
diff --git a/lib/isccc/include/isccc/sexpr.h b/lib/isccc/include/isccc/sexpr.h
new file mode 100644
index 0000000..dae9906
--- /dev/null
+++ b/lib/isccc/include/isccc/sexpr.h
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCC_SEXPR_H
+#define ISCCC_SEXPR_H 1
+
+/*! \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
+
+#endif /* ISCCC_SEXPR_H */
diff --git a/lib/isccc/include/isccc/symtab.h b/lib/isccc/include/isccc/symtab.h
new file mode 100644
index 0000000..ff978b2
--- /dev/null
+++ b/lib/isccc/include/isccc/symtab.h
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCC_SYMTAB_H
+#define ISCCC_SYMTAB_H 1
+
+/*****
+***** 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
+
+#endif /* ISCCC_SYMTAB_H */
diff --git a/lib/isccc/include/isccc/symtype.h b/lib/isccc/include/isccc/symtype.h
new file mode 100644
index 0000000..9003683
--- /dev/null
+++ b/lib/isccc/include/isccc/symtype.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCC_SYMTYPE_H
+#define ISCCC_SYMTYPE_H 1
+
+/*! \file isccc/symtype.h */
+
+#define ISCCC_SYMTYPE_ZONESTATS 0x0001
+#define ISCCC_SYMTYPE_CCDUP 0x0002
+#define ISCCC_SYMTYPE_TELLSERVICE 0x0003
+#define ISCCC_SYMTYPE_TELLRESPONSE 0x0004
+
+#endif /* ISCCC_SYMTYPE_H */
diff --git a/lib/isccc/include/isccc/types.h b/lib/isccc/include/isccc/types.h
new file mode 100644
index 0000000..477e2df
--- /dev/null
+++ b/lib/isccc/include/isccc/types.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCC_TYPES_H
+#define ISCCC_TYPES_H 1
+
+/*! \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;
+
+#endif /* ISCCC_TYPES_H */
diff --git a/lib/isccc/include/isccc/util.h b/lib/isccc/include/isccc/util.h
new file mode 100644
index 0000000..546423d
--- /dev/null
+++ b/lib/isccc/include/isccc/util.h
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCC_UTIL_H
+#define ISCCC_UTIL_H 1
+
+#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)
+
+/*%
+ * 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)
+
+#endif /* ISCCC_UTIL_H */
diff --git a/lib/isccc/include/isccc/version.h b/lib/isccc/include/isccc/version.h
new file mode 100644
index 0000000..9dc1a6d
--- /dev/null
+++ b/lib/isccc/include/isccc/version.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file isccc/version.h */
+
+#include <isc/platform.h>
+
+LIBISCCC_EXTERNAL_DATA extern const char isccc_version[];
diff --git a/lib/isccc/result.c b/lib/isccc/result.c
new file mode 100644
index 0000000..127dd77
--- /dev/null
+++ b/lib/isccc/result.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2001 Nominum, Inc.
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/util.h>
+
+#include <isccc/result.h>
+
+static const char *text[ISCCC_R_NRESULTS] = {
+ "unknown version", /* 1 */
+ "syntax error", /* 2 */
+ "bad auth", /* 3 */
+ "expired", /* 4 */
+ "clock skew", /* 5 */
+ "duplicate", /* 6 */
+ "max depth" /* 7 */
+};
+
+static const char *ids[ISCCC_R_NRESULTS] = {
+ "ISCCC_R_UNKNOWNVERSION", "ISCCC_R_SYNTAX", "ISCCC_R_BADAUTH",
+ "ISCCC_R_EXPIRED", "ISCCC_R_CLOCKSKEW", "ISCCC_R_DUPLICATE",
+ "ISCCC_R_MAXDEPTH"
+};
+
+#define ISCCC_RESULT_RESULTSET 2
+
+static isc_once_t once = ISC_ONCE_INIT;
+
+static void
+initialize_action(void) {
+ isc_result_t result;
+
+ result = isc_result_register(ISC_RESULTCLASS_ISCCC, ISCCC_R_NRESULTS,
+ text, ISCCC_RESULT_RESULTSET);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_result_register() failed: %u", result);
+ }
+
+ result = isc_result_registerids(ISC_RESULTCLASS_ISCCC, ISCCC_R_NRESULTS,
+ ids, ISCCC_RESULT_RESULTSET);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_result_registerids() failed: %u", result);
+ }
+}
+
+static void
+initialize(void) {
+ RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS);
+}
+
+const char *
+isccc_result_totext(isc_result_t result) {
+ initialize();
+
+ return (isc_result_totext(result));
+}
+
+void
+isccc_result_register(void) {
+ initialize();
+}
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..2ab969a
--- /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/string.h>
+
+#include <isccc/result.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/isccc/tests/Kyuafile b/lib/isccc/tests/Kyuafile
new file mode 100644
index 0000000..79c8b3c
--- /dev/null
+++ b/lib/isccc/tests/Kyuafile
@@ -0,0 +1,15 @@
+-- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+--
+-- SPDX-License-Identifier: MPL-2.0
+--
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, you can obtain one at https://mozilla.org/MPL/2.0/.
+--
+-- See the COPYRIGHT file distributed with this work for additional
+-- information regarding copyright ownership.
+
+syntax(2)
+test_suite('bind9')
+
+tap_test_program{name='result_test'}
diff --git a/lib/isccc/tests/Makefile.in b/lib/isccc/tests/Makefile.in
new file mode 100644
index 0000000..f9f1968
--- /dev/null
+++ b/lib/isccc/tests/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+# Attempt to disable parallel processing.
+.NOTPARALLEL:
+.NO_PARALLEL:
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. -Iinclude ${ISCCC_INCLUDES} ${ISC_INCLUDES} @CMOCKA_CFLAGS@
+CDEFINES =
+
+ISCLIBS = ../../isc/libisc.@A@
+ISCDEPLIBS = ../../isc/libisc.@A@
+ISCCCLIBS = ../libisccc.@A@
+ISCCCDEPLIBS = ../libisccc.@A@
+
+LIBS = @LIBS@ @CMOCKA_LIBS@
+
+OBJS =
+SRCS = result_test.c
+SUBDIRS =
+TARGETS = result_test@EXEEXT@
+
+@BIND9_MAKE_RULES@
+
+result_test@EXEEXT@: result_test.@O@ ${ISCDEPLIBS} ${ISCCCDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ result_test.@O@ \
+ ${ISCCCLIBS} ${ISCLIBS} ${LIBS}
+
+unit::
+ sh ${top_builddir}/unit/unittest.sh
+
+clean distclean::
+ rm -f ${TARGETS}
+ rm -f atf.out
diff --git a/lib/isccc/tests/result_test.c b/lib/isccc/tests/result_test.c
new file mode 100644
index 0000000..b27ee22
--- /dev/null
+++ b/lib/isccc/tests/result_test.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <isccc/result.h>
+
+/*
+ * Check tables are populated.
+ */
+static void
+tables(void **state) {
+ const char *str;
+ isc_result_t result;
+
+ UNUSED(state);
+
+ isccc_result_register();
+
+ for (result = ISC_RESULTCLASS_ISCCC;
+ result < (ISC_RESULTCLASS_ISCCC + ISCCC_R_NRESULTS); result++)
+ {
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_not_equal(str, "(result code text not "
+ "available)");
+ }
+
+ str = isc_result_toid(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+
+ str = isc_result_totext(result);
+ assert_non_null(str);
+ assert_string_equal(str, "(result code text not available)");
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(tables),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isccc/version.c b/lib/isccc/version.c
new file mode 100644
index 0000000..95f9ee2
--- /dev/null
+++ b/lib/isccc/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <isccc/version.h>
+
+const char isccc_version[] = VERSION;
diff --git a/lib/isccc/win32/DLLMain.c b/lib/isccc/win32/DLLMain.c
new file mode 100644
index 0000000..62c6e53
--- /dev/null
+++ b/lib/isccc/win32/DLLMain.c
@@ -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.
+ */
+
+#include <signal.h>
+#include <windows.h>
+
+/*
+ * Called when we enter the DLL
+ */
+__declspec(dllexport) BOOL WINAPI
+ DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
+ switch (fdwReason) {
+ /*
+ * The DLL is loading due to process
+ * initialization or a call to LoadLibrary.
+ */
+ case DLL_PROCESS_ATTACH:
+ break;
+
+ /* The attached process creates a new thread. */
+ case DLL_THREAD_ATTACH:
+ break;
+
+ /* The thread of the attached process terminates. */
+ case DLL_THREAD_DETACH:
+ break;
+
+ /*
+ * The DLL is unloading from a process due to
+ * process termination or a call to FreeLibrary.
+ */
+ case DLL_PROCESS_DETACH:
+ break;
+
+ default:
+ break;
+ }
+ return (TRUE);
+}
diff --git a/lib/isccc/win32/libisccc.def b/lib/isccc/win32/libisccc.def
new file mode 100644
index 0000000..5cb5662
--- /dev/null
+++ b/lib/isccc/win32/libisccc.def
@@ -0,0 +1,65 @@
+LIBRARY libisccc
+
+; Exported Functions
+EXPORTS
+
+isccc_alist_create
+isccc_alist_alistp
+isccc_alist_emptyp
+isccc_alist_first
+isccc_alist_assq
+isccc_alist_delete
+isccc_alist_define
+isccc_alist_definestring
+isccc_alist_definebinary
+isccc_alist_lookup
+isccc_alist_lookupstring
+isccc_alist_lookupbinary
+isccc_alist_prettyprint
+isccc_base64_encode
+isccc_base64_decode
+isccc_cc_towire
+isccc_cc_fromwire
+isccc_cc_createmessage
+isccc_cc_createack
+isccc_cc_isack
+isccc_cc_isreply
+isccc_cc_createresponse
+isccc_cc_definestring
+isccc_cc_defineuint32
+isccc_cc_lookupstring
+isccc_cc_lookupuint32
+isccc_cc_createsymtab
+isccc_cc_cleansymtab
+isccc_cc_checkdup
+isccc_ccmsg_init
+isccc_ccmsg_setmaxsize
+isccc_ccmsg_readmessage
+isccc_ccmsg_cancelread
+isccc_ccmsg_invalidate
+isccc_result_totext
+isccc_result_register
+isccc_sexpr_cons
+isccc_sexpr_tconst
+isccc_sexpr_fromstring
+isccc_sexpr_frombinary
+isccc_sexpr_free
+isccc_sexpr_print
+isccc_sexpr_car
+isccc_sexpr_cdr
+isccc_sexpr_setcar
+isccc_sexpr_setcdr
+isccc_sexpr_addtolist
+isccc_sexpr_listp
+isccc_sexpr_emptyp
+isccc_sexpr_stringp
+isccc_sexpr_binaryp
+isccc_sexpr_tostring
+isccc_sexpr_tobinary
+isccc_symtab_destroy
+isccc_symtab_create
+isccc_symtab_destroy
+isccc_symtab_lookup
+isccc_symtab_define
+isccc_symtab_undefine
+isccc_symtab_foreach
diff --git a/lib/isccc/win32/libisccc.vcxproj.filters.in b/lib/isccc/win32/libisccc.vcxproj.filters.in
new file mode 100644
index 0000000..1d1ff10
--- /dev/null
+++ b/lib/isccc/win32/libisccc.vcxproj.filters.in
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="libisccc.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="DLLMain.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="version.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\alist.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\base64.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\cc.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ccmsg.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\result.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\sexpr.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\symtab.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\isccc\alist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\base64.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\cc.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\ccmsg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\events.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\result.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\sexpr.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\symtab.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\symtype.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\types.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\util.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccc\version.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project>
diff --git a/lib/isccc/win32/libisccc.vcxproj.in b/lib/isccc/win32/libisccc.vcxproj.in
new file mode 100644
index 0000000..86de718
--- /dev/null
+++ b/lib/isccc/win32/libisccc.vcxproj.in
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="@TOOLS_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|@PLATFORM@">
+ <Configuration>Debug</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|@PLATFORM@">
+ <Configuration>Release</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{B556705F-1920-4400-878A-B259D3556047}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>libisccc</RootNamespace>
+ @WINDOWS_TARGET_PLATFORM_VERSION@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISCCC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@include;..\include;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;..\..\dns\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BrowseInformation>true</BrowseInformation>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalLibraryDirectories>..\..\isc\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libisc.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>@INTRINSIC@</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBISCCC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@include;..\include;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;..\..\dns\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <StringPooling>true</StringPooling>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WholeProgramOptimization>false</WholeProgramOptimization>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalLibraryDirectories>..\..\isc\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libisc.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <None Include="libisccc.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\alist.c" />
+ <ClCompile Include="..\base64.c" />
+ <ClCompile Include="..\cc.c" />
+ <ClCompile Include="..\ccmsg.c" />
+ <ClCompile Include="..\result.c" />
+ <ClCompile Include="..\sexpr.c" />
+ <ClCompile Include="..\symtab.c" />
+ <ClCompile Include="DLLMain.c" />
+ <ClCompile Include="version.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\isccc\alist.h" />
+ <ClInclude Include="..\include\isccc\base64.h" />
+ <ClInclude Include="..\include\isccc\cc.h" />
+ <ClInclude Include="..\include\isccc\ccmsg.h" />
+ <ClInclude Include="..\include\isccc\events.h" />
+ <ClInclude Include="..\include\isccc\result.h" />
+ <ClInclude Include="..\include\isccc\sexpr.h" />
+ <ClInclude Include="..\include\isccc\symtab.h" />
+ <ClInclude Include="..\include\isccc\symtype.h" />
+ <ClInclude Include="..\include\isccc\types.h" />
+ <ClInclude Include="..\include\isccc\util.h" />
+ <ClInclude Include="..\include\isccc\version.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/lib/isccc/win32/libisccc.vcxproj.user b/lib/isccc/win32/libisccc.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/lib/isccc/win32/libisccc.vcxproj.user
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+</Project> \ No newline at end of file
diff --git a/lib/isccc/win32/version.c b/lib/isccc/win32/version.c
new file mode 100644
index 0000000..6576b52
--- /dev/null
+++ b/lib/isccc/win32/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <versions.h>
+
+#include <isccc/version.h>
+
+LIBISCCC_EXTERNAL_DATA const char isccc_version[] = VERSION;
diff --git a/lib/isccfg/Kyuafile b/lib/isccfg/Kyuafile
new file mode 100644
index 0000000..c796010
--- /dev/null
+++ b/lib/isccfg/Kyuafile
@@ -0,0 +1,15 @@
+-- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+--
+-- SPDX-License-Identifier: MPL-2.0
+--
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, you can obtain one at https://mozilla.org/MPL/2.0/.
+--
+-- See the COPYRIGHT file distributed with this work for additional
+-- information regarding copyright ownership.
+
+syntax(2)
+test_suite('bind9')
+
+include('tests/Kyuafile')
diff --git a/lib/isccfg/Makefile.in b/lib/isccfg/Makefile.in
new file mode 100644
index 0000000..efe04b4
--- /dev/null
+++ b/lib/isccfg/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. ${DNS_INCLUDES} ${ISC_INCLUDES} ${ISCCFG_INCLUDES}
+
+CDEFINES =
+CWARNINGS =
+
+ISCLIBS = ../../lib/isc/libisc.@A@
+DNSLIBS = ../../lib/dns/libdns.@A@ @NO_LIBTOOL_DNSLIBS@
+ISCCFGLIBS = ../../lib/cfg/libisccfg.@A@
+
+ISCDEPLIBS = ../../lib/isc/libisc.@A@
+ISCCFGDEPLIBS = libisccfg.@A@
+
+LIBS = @LIBS@
+
+SUBDIRS = include
+TESTDIRS = @UNITTESTS@
+
+# Alphabetically
+OBJS = aclconf.@O@ dnsconf.@O@ kaspconf.@O@ log.@O@ namedconf.@O@ \
+ parser.@O@ version.@O@
+
+# Alphabetically
+SRCS = aclconf.c dnsconf.c kaspconf.c log.c namedconf.c \
+ parser.c version.c
+
+TARGETS = timestamp
+
+@BIND9_MAKE_RULES@
+
+version.@O@: version.c
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} \
+ -DVERSION=\"${VERSION}\" \
+ -c ${srcdir}/version.c
+
+libisccfg.@SA@: ${OBJS}
+ ${AR} ${ARFLAGS} $@ ${OBJS}
+ ${RANLIB} $@
+
+libisccfg.la: ${OBJS}
+ ${LIBTOOL_MODE_LINK} \
+ ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libisccfg.la -rpath ${libdir} \
+ -release "${VERSION}" \
+ ${OBJS} ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+timestamp: libisccfg.@A@
+ touch timestamp
+
+testdirs: libisccfg.@A@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}
+
+install:: timestamp installdirs
+ ${LIBTOOL_MODE_INSTALL} ${INSTALL_LIBRARY} libisccfg.@A@ ${DESTDIR}${libdir}
+
+uninstall::
+ ${LIBTOOL_MODE_UNINSTALL} rm -f ${DESTDIR}${libdir}/libisccfg.@A@
+
+clean distclean::
+ rm -f libisccfg.@A@ timestamp
diff --git a/lib/isccfg/aclconf.c b/lib/isccfg/aclconf.c
new file mode 100644
index 0000000..f0d3b18
--- /dev/null
+++ b/lib/isccfg/aclconf.c
@@ -0,0 +1,934 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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))
+ {
+ /* cppcheck-suppress nullPointerRedundantCheck symbolName=dacl
+ */
+ 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 *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) {
+ 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;
+
+ if (nest_level != 0) {
+ new_nest_level = nest_level - 1;
+ }
+
+ REQUIRE(ctx != NULL);
+ REQUIRE(target != NULL);
+ REQUIRE(*target == NULL || DNS_ACL_VALID(*target));
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ 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..3c69256
--- /dev/null
+++ b/lib/isccfg/dnsconf.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 };
+
+LIBISCCFG_EXTERNAL_DATA 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/include/.clang-format b/lib/isccfg/include/.clang-format
new file mode 120000
index 0000000..0e62f72
--- /dev/null
+++ b/lib/isccfg/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/isccfg/include/Makefile.in b/lib/isccfg/include/Makefile.in
new file mode 100644
index 0000000..20b0c35
--- /dev/null
+++ b/lib/isccfg/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = isccfg
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/isccfg/include/isccfg/Makefile.in b/lib/isccfg/include/isccfg/Makefile.in
new file mode 100644
index 0000000..208db25
--- /dev/null
+++ b/lib/isccfg/include/isccfg/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+#
+# Only list headers that are to be installed and are not
+# machine generated. The latter are handled specially in the
+# install target below.
+#
+HEADERS = aclconf.h cfg.h dnsconf.h grammar.h kaspconf.h log.h \
+ namedconf.h version.h
+
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/isccfg
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/isccfg || exit 1; \
+ done
+
+uninstall::
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/isccfg/$$i || exit 1; \
+ done
diff --git a/lib/isccfg/include/isccfg/aclconf.h b/lib/isccfg/include/isccfg/aclconf.h
new file mode 100644
index 0000000..b0bceca
--- /dev/null
+++ b/lib/isccfg/include/isccfg/aclconf.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISCCFG_ACLCONF_H
+#define ISCCFG_ACLCONF_H 1
+
+#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
+
+#endif /* ISCCFG_ACLCONF_H */
diff --git a/lib/isccfg/include/isccfg/cfg.h b/lib/isccfg/include/isccfg/cfg.h
new file mode 100644
index 0000000..7c460d2
--- /dev/null
+++ b/lib/isccfg/include/isccfg/cfg.h
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISCCFG_CFG_H
+#define ISCCFG_CFG_H 1
+
+/*****
+***** 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.
+ */
+
+isc_dscp_t
+cfg_obj_getdscp(const cfg_obj_t *obj);
+/*%<
+ * Returns the DSCP 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 DSCP value associated with a sockaddr, or -1.
+ */
+
+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
+
+#endif /* ISCCFG_CFG_H */
diff --git a/lib/isccfg/include/isccfg/dnsconf.h b/lib/isccfg/include/isccfg/dnsconf.h
new file mode 100644
index 0000000..5050b70
--- /dev/null
+++ b/lib/isccfg/include/isccfg/dnsconf.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISCCFG_DNSCONF_H
+#define ISCCFG_DNSCONF_H 1
+
+/*! \file
+ * \brief
+ * This module defines the named.conf, rndc.conf, and rndc.key grammars.
+ */
+
+#include <isccfg/cfg.h>
+
+/*
+ * Configuration object types.
+ */
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_dnsconf;
+/*%< A complete dns.conf file. */
+
+#endif /* ISCCFG_DNSCONF_H */
diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h
new file mode 100644
index 0000000..0bc40e0
--- /dev/null
+++ b/lib/isccfg/include/isccfg/grammar.h
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISCCFG_GRAMMAR_H
+#define ISCCFG_GRAMMAR_H 1
+
+/*! \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>
+
+/*
+ * 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
+/*% Clause is not implemented, and may never be */
+#define CFG_CLAUSEFLAG_NOTIMP 0x00000004
+/*% Clause is not implemented yet */
+#define CFG_CLAUSEFLAG_NYI 0x00000008
+/*% Default value has changed since earlier release */
+#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
+/*% A 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
+/*% A option for a experimental feature. */
+#define CFG_CLAUSEFLAG_EXPERIMENTAL 0x00000100
+/*% A configuration option that is ineffective due to
+ * compile time options, but is harmless. */
+#define CFG_CLAUSEFLAG_NOOP 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;
+typedef struct cfg_duration cfg_duration_t;
+
+#define CFG_DURATION_MAXLEN 80
+
+/*
+ * 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 object to store ISO 8601 durations.
+ */
+struct cfg_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;
+};
+
+/*%
+ * 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;
+ isc_dscp_t dscp;
+ } sockaddrdscp;
+ cfg_netprefix_t netprefix;
+ cfg_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_MASK (CFG_ADDR_V6OK | CFG_ADDR_V4OK)
+/*@}*/
+
+/*@{*/
+/*%
+ * Predefined data representation types.
+ */
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_uint32;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_uint64;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_string;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_boolean;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_map;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_list;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_tuple;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_sockaddr;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_netprefix;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_void;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_fixedpoint;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_percentage;
+LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_duration;
+/*@}*/
+
+/*@{*/
+/*%
+ * Predefined configuration object types.
+ */
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_boolean;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_uint32;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_uint64;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_qstring;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_astring;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_ustring;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_sstring;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_bracketed_aml;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_bracketed_text;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_optional_bracketed_text;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_keyref;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_sockaddr;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_sockaddrdscp;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr4;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr4wild;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr6;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr6wild;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netprefix;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_void;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_token;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_unsupported;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_fixedpoint;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_percentage;
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_duration;
+LIBISCCFG_EXTERNAL_DATA 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_dscp(cfg_parser_t *pctx, isc_dscp_t *dscp);
+
+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'.
+ */
+
+#endif /* ISCCFG_GRAMMAR_H */
diff --git a/lib/isccfg/include/isccfg/kaspconf.h b/lib/isccfg/include/isccfg/kaspconf.h
new file mode 100644
index 0000000..66b316f
--- /dev/null
+++ b/lib/isccfg/include/isccfg/kaspconf.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.
+ */
+
+#ifndef ISCCFG_KASPCONF_H
+#define ISCCFG_KASPCONF_H 1
+
+#include <isc/lang.h>
+
+#include <dns/types.h>
+
+#include <isccfg/cfg.h>
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+cfg_kasp_fromconfig(const cfg_obj_t *config, const char *name, isc_mem_t *mctx,
+ isc_log_t *logctx, dns_kasplist_t *kasplist,
+ dns_kasp_t **kaspp);
+/*%<
+ * Create and configure a KASP. If 'config' is NULL, a built-in configuration
+ * is used, referred to by 'name'. 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
+
+#endif /* ISCCFG_KASPCONF_H */
diff --git a/lib/isccfg/include/isccfg/log.h b/lib/isccfg/include/isccfg/log.h
new file mode 100644
index 0000000..62fa68f
--- /dev/null
+++ b/lib/isccfg/include/isccfg/log.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef ISCCFG_LOG_H
+#define ISCCFG_LOG_H 1
+
+/*! \file isccfg/log.h */
+
+#include <isc/lang.h>
+#include <isc/log.h>
+
+LIBISCCFG_EXTERNAL_DATA extern isc_logcategory_t cfg_categories[];
+LIBISCCFG_EXTERNAL_DATA 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
+
+#endif /* ISCCFG_LOG_H */
diff --git a/lib/isccfg/include/isccfg/namedconf.h b/lib/isccfg/include/isccfg/namedconf.h
new file mode 100644
index 0000000..98878c0
--- /dev/null
+++ b/lib/isccfg/include/isccfg/namedconf.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.
+ */
+
+#ifndef ISCCFG_NAMEDCONF_H
+#define ISCCFG_NAMEDCONF_H 1
+
+/*! \file isccfg/namedconf.h
+ * \brief
+ * This module defines the named.conf, rndc.conf, and rndc.key grammars.
+ */
+
+#include <isccfg/cfg.h>
+
+/*
+ * Configuration object types.
+ */
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_namedconf;
+/*%< A complete named.conf file. */
+
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_bindkeys;
+/*%< A bind.keys file. */
+
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_newzones;
+/*%< A new-zones file (for zones added by 'rndc addzone'). */
+
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_addzoneconf;
+/*%< A single zone passed via the addzone rndc command. */
+
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_rndcconf;
+/*%< A complete rndc.conf file. */
+
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_rndckey;
+/*%< A complete rndc.key file. */
+
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_sessionkey;
+/*%< A complete session.key file. */
+
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_keyref;
+/*%< A key reference, used as an ACL element */
+
+/*%< Zone options */
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_zoneopts;
+
+/*%< DNSSEC Key and Signing Policy options */
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_dnssecpolicyopts;
+
+#endif /* ISCCFG_NAMEDCONF_H */
diff --git a/lib/isccfg/include/isccfg/version.h b/lib/isccfg/include/isccfg/version.h
new file mode 100644
index 0000000..846adbf
--- /dev/null
+++ b/lib/isccfg/include/isccfg/version.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file isccfg/version.h */
+
+#include <isc/platform.h>
+
+LIBISCCFG_EXTERNAL_DATA extern const char cfg_version[];
diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c
new file mode 100644
index 0000000..32f7684
--- /dev/null
+++ b/lib/isccfg/kaspconf.c
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/kasp.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/nsec3.h>
+#include <dns/result.h>
+#include <dns/secalg.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/kaspconf.h>
+#include <isccfg/namedconf.h>
+
+#define DEFAULT_NSEC3PARAM_ITER 5
+#define DEFAULT_NSEC3PARAM_SALTLEN 8
+
+/*
+ * 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 configuring durations.
+ */
+static uint32_t
+get_duration(const cfg_obj_t **maps, const char *option, uint32_t dfl) {
+ const cfg_obj_t *obj;
+ isc_result_t result;
+ obj = NULL;
+
+ result = confget(maps, option, &obj);
+ if (result == ISC_R_NOTFOUND) {
+ return (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) {
+ 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;
+
+ 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);
+ }
+
+ 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, const char *name, 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;
+
+ REQUIRE(kaspp != NULL && *kaspp == NULL);
+
+ kaspname = (name == NULL)
+ ? cfg_obj_asstring(cfg_tuple_get(config, "name"))
+ : name;
+ INSIST(kaspname != NULL);
+
+ 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 */
+ dns_kasp_setsigrefresh(kasp, get_duration(maps, "signatures-refresh",
+ DNS_KASP_SIG_REFRESH));
+ dns_kasp_setsigvalidity(kasp, get_duration(maps, "signatures-validity",
+ DNS_KASP_SIG_VALIDITY));
+ dns_kasp_setsigvalidity_dnskey(
+ kasp, get_duration(maps, "signatures-validity-dnskey",
+ DNS_KASP_SIG_VALIDITY_DNSKEY));
+
+ /* Configuration: Keys */
+ dns_kasp_setdnskeyttl(
+ kasp, get_duration(maps, "dnskey-ttl", DNS_KASP_KEY_TTL));
+ dns_kasp_setpublishsafety(kasp, get_duration(maps, "publish-safety",
+ DNS_KASP_PUBLISH_SAFETY));
+ dns_kasp_setretiresafety(kasp, get_duration(maps, "retire-safety",
+ DNS_KASP_RETIRE_SAFETY));
+ dns_kasp_setpurgekeys(
+ kasp, get_duration(maps, "purge-keys", DNS_KASP_PURGE_KEYS));
+
+ (void)confget(maps, "keys", &keys);
+ if (keys != NULL) {
+ char role[256] = { 0 };
+ 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);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ INSIST(!(dns_kasp_keylist_empty(kasp)));
+ 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)) {
+ role[keyalg] |= DNS_KASP_KEY_ROLE_ZSK;
+ }
+
+ if (dns_kasp_key_ksk(kkey)) {
+ role[keyalg] |= DNS_KASP_KEY_ROLE_KSK;
+ }
+ }
+ dns_kasp_thaw(kasp);
+ for (i = 0; i < ARRAY_SIZE(role); i++) {
+ if (role[i] != 0 && 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 (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ } else if (strcmp(kaspname, "insecure") == 0) {
+ /* "dnssec-policy insecure": key list must be empty */
+ INSIST(strcmp(kaspname, "insecure") == 0);
+ INSIST(dns_kasp_keylist_empty(kasp));
+ } else {
+ /* No keys clause configured, use the "default". */
+ result = cfg_kaspkey_fromconfig(NULL, kasp, logctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ INSIST(!(dns_kasp_keylist_empty(kasp)));
+ }
+
+ /* Configuration: NSEC3 */
+ (void)confget(maps, "nsec3param", &nsec3);
+ if (nsec3 == NULL) {
+ 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;
+ }
+ }
+
+ /* Configuration: Zone settings */
+ dns_kasp_setzonemaxttl(
+ kasp, get_duration(maps, "max-zone-ttl", DNS_KASP_ZONE_MAXTTL));
+ dns_kasp_setzonepropagationdelay(
+ kasp, get_duration(maps, "zone-propagation-delay",
+ DNS_KASP_ZONE_PROPDELAY));
+
+ /* Configuration: Parent settings */
+ dns_kasp_setdsttl(kasp,
+ get_duration(maps, "parent-ds-ttl", DNS_KASP_DS_TTL));
+ dns_kasp_setparentpropagationdelay(
+ kasp, get_duration(maps, "parent-propagation-delay",
+ DNS_KASP_PARENT_PROPDELAY));
+
+ /* 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..7a6b7fd
--- /dev/null
+++ b/lib/isccfg/log.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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>.
+ */
+LIBISCCFG_EXTERNAL_DATA 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>.
+ */
+LIBISCCFG_EXTERNAL_DATA 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..6e63d86
--- /dev/null
+++ b/lib/isccfg/namedconf.c
@@ -0,0 +1,3891 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/result.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 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_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_plugin;
+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_options;
+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_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_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_optional_dscp, CFG_CLAUSEFLAG_DEPRECATED },
+ { "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 */
+
+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_DEPRECATED },
+ { "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 },
+ { 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_DEPRECATED },
+ { "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_DEPRECATED },
+ { "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 };
+
+/*
+ * Obsolete format for the "pubkey" statement.
+ */
+static cfg_tuplefielddef_t pubkey_fields[] = {
+ { "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_pubkey = { "pubkey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, pubkey_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",
+ "ms-self",
+ "ms-selfsub",
+ "ms-subdomain",
+ "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,
+ doc_optional_keyvalue, &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 },
+ { "logging", &cfg_type_logging, 0 },
+ { "lwres", &cfg_type_bracketed_text,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_OBSOLETE },
+ { "masters", &cfg_type_remoteservers, CFG_CLAUSEFLAG_MULTI },
+ { "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 },
+ { "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 },
+ { 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, 0 },
+ { "avoid-v6-udp-ports", &cfg_type_bracketed_portlist, 0 },
+ { "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, 0 },
+ { "datasize", &cfg_type_size, 0 },
+ { "deallocate-on-exit", &cfg_type_boolean, 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_DEPRECATED },
+ { "dump-file", &cfg_type_qstring, 0 },
+ { "fake-iquery", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "files", &cfg_type_size, 0 },
+ { "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", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "has-old-clients", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "heartbeat-interval", &cfg_type_uint32, 0 },
+ { "host-statistics", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "host-statistics-max", &cfg_type_uint32, 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", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "named-xfer", &cfg_type_qstring, CFG_CLAUSEFLAG_ANCIENT },
+ { "notify-rate", &cfg_type_uint32, 0 },
+ { "pid-file", &cfg_type_qstringornone, 0 },
+ { "port", &cfg_type_uint32, 0 },
+ { "querylog", &cfg_type_boolean, 0 },
+ { "random-device", &cfg_type_qstringornone, 0 },
+ { "recursing-file", &cfg_type_qstring, 0 },
+ { "recursive-clients", &cfg_type_uint32, 0 },
+ { "reuseport", &cfg_type_boolean, 0 },
+ { "reserved-sockets", &cfg_type_uint32, 0 },
+ { "secroots-file", &cfg_type_qstring, 0 },
+ { "serial-queries", &cfg_type_uint32, 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", &cfg_type_sstring, CFG_CLAUSEFLAG_OBSOLETE },
+ { "stacksize", &cfg_type_size, 0 },
+ { "startup-notify-rate", &cfg_type_uint32, 0 },
+ { "statistics-file", &cfg_type_qstring, 0 },
+ { "statistics-interval", &cfg_type_uint32, 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 },
+ { "tkey-dhkey", &cfg_type_tkey_dhkey, 0 },
+ { "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", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "update-quota", &cfg_type_uint32, 0 },
+ { "use-id-pool", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "use-ixfr", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "use-v4-udp-ports", &cfg_type_bracketed_portlist, 0 },
+ { "use-v6-udp-ports", &cfg_type_bracketed_portlist, 0 },
+ { "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[] = { "map", "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 != 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 },
+ { "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, 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 };
+
+/*%
+ * dnssec-lookaside
+ */
+
+static void
+print_lookaside(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const cfg_obj_t *domain = obj->value.tuple[0];
+
+ if (domain->value.string.length == 4 &&
+ strncmp(domain->value.string.base, "auto", 4) == 0)
+ {
+ cfg_print_cstr(pctx, "auto");
+ } else {
+ cfg_print_tuple(pctx, obj);
+ }
+}
+
+static void
+doc_lookaside(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( <string> trust-anchor <string> | auto | no )");
+}
+
+static keyword_type_t trustanchor_kw = { "trust-anchor", &cfg_type_astring };
+
+static cfg_type_t cfg_type_optional_trustanchor = {
+ "optional_trustanchor", parse_optional_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_string, &trustanchor_kw
+};
+
+static cfg_tuplefielddef_t lookaside_fields[] = {
+ { "domain", &cfg_type_astring, 0 },
+ { "trust-anchor", &cfg_type_optional_trustanchor, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_lookaside = { "lookaside", cfg_parse_tuple,
+ print_lookaside, doc_lookaside,
+ &cfg_rep_tuple, lookaside_fields };
+
+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", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_OBSOLETE },
+ { "acache-enable", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "additional-from-auth", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "additional-from-cache", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "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", &cfg_type_bracketed_aml,
+ CFG_CLAUSEFLAG_OBSOLETE },
+ { "attach-cache", &cfg_type_astring, 0 },
+ { "auth-nxdomain", &cfg_type_boolean, CFG_CLAUSEFLAG_NEWDEFAULT },
+ { "cache-file", &cfg_type_qstring, CFG_CLAUSEFLAG_DEPRECATED },
+ { "catalog-zones", &cfg_type_catz, 0 },
+ { "check-names", &cfg_type_checknames, CFG_CLAUSEFLAG_MULTI },
+ { "cleaning-interval", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE },
+ { "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", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "dnssec-lookaside", &cfg_type_lookaside,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_OBSOLETE },
+ { "dnssec-must-be-secure", &cfg_type_mustbesecure,
+ CFG_CLAUSEFLAG_MULTI },
+ { "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", &cfg_type_boolean, 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_OBSOLETE },
+ { "filter-aaaa-on-v4", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "filter-aaaa-on-v6", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "glue-cache", &cfg_type_boolean, 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_NOOP },
+#endif /* ifdef HAVE_LMDB */
+ { "max-acache-size", &cfg_type_sizenodefault, CFG_CLAUSEFLAG_OBSOLETE },
+ { "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", &cfg_type_uint32, 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", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE },
+ { "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", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE },
+ { "queryport-pool-updateinterval", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_OBSOLETE },
+ { "rate-limit", &cfg_type_rrl, 0 },
+ { "recursion", &cfg_type_boolean, 0 },
+ { "request-nsid", &cfg_type_boolean, 0 },
+ { "request-sit", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "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", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "root-delegation-only", &cfg_type_optional_exclude, 0 },
+ { "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_NYI },
+ { "synth-from-dnssec", &cfg_type_boolean, 0 },
+ { "topology", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_ANCIENT },
+ { "transfer-format", &cfg_type_transferformat, 0 },
+ { "trust-anchor-telemetry", &cfg_type_boolean,
+ CFG_CLAUSEFLAG_EXPERIMENTAL },
+ { "use-queryport-pool", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "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.
+ */
+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_bracketed_aml,
+ 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 },
+ { "alt-transfer-source-v6", &cfg_type_sockaddr6wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "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 },
+ { "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", &cfg_type_boolean, 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", &cfg_type_size, 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 },
+ { "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 },
+ { "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", &cfg_type_qstring, CFG_CLAUSEFLAG_ANCIENT },
+ { "ixfr-from-differences", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "ixfr-tmp-file", &cfg_type_qstring, 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 },
+ { "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", &cfg_type_pubkey, 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 };
+LIBISCCFG_EXTERNAL_DATA 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 };
+LIBISCCFG_EXTERNAL_DATA 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 };
+LIBISCCFG_EXTERNAL_DATA 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 };
+LIBISCCFG_EXTERNAL_DATA 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.
+ */
+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", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "send-cookie", &cfg_type_boolean, 0 },
+ { "support-ixfr", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
+ { "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 };
+
+LIBISCCFG_EXTERNAL_DATA 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 };
+
+LIBISCCFG_EXTERNAL_DATA 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;
+ isc_dscp_t dscp = -1;
+ unsigned int have_address = 0;
+ unsigned int have_port = 0;
+ unsigned int have_dscp = 0;
+ const unsigned int *flagp = type->of;
+
+ 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" */
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_rawport(pctx, CFG_ADDR_WILDOK,
+ &port));
+ have_port++;
+ } else if (strcasecmp(TOKEN_STRING(pctx), "dscp") == 0)
+ {
+ if ((pctx->flags & CFG_PCTX_NODEPRECATED) == 0)
+ {
+ cfg_parser_warning(
+ pctx, 0,
+ "token 'dscp' is deprecated");
+ }
+ /* read "dscp" */
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_dscp(pctx, &dscp));
+ 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', 'port', "
+ "or 'dscp'");
+ 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, " | * ) [ port ( <integer> | * ) ] ) | "
+ "( [ [ 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, " | * ) ] port ( <integer> | * ) ) )"
+ " [ dscp <integer> ]");
+}
+
+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;
+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 with optional dscp and port, "*" 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 with optional port, "*" 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 };
+
+LIBISCCFG_EXTERNAL_DATA 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 };
+
+LIBISCCFG_EXTERNAL_DATA 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).
+ */
+LIBISCCFG_EXTERNAL_DATA 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_DEPRECATED },
+ { 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, "[ dscp <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, "[ dscp <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, " ");
+ cfg_print_cstr(pctx, "[ dscp <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
+};
+
+/*%
+ * primaries 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 ( master | primary );\n");
+ break;
+ case CFG_ZONE_SECONDARY:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type ( slave | 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_ANCIENT) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_NYI) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_TESTONLY) != 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");
+}
diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c
new file mode 100644
index 0000000..4ba93ee
--- /dev/null
+++ b/lib/isccfg/parser.c
@@ -0,0 +1,4145 @@
+/*
+ * 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.
+ */
+
+/*
+ * duration_fromtext initially taken from OpenDNSSEC code base.
+ * Modified to fit the BIND 9 code.
+ *
+ * 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 <stdint.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/dir.h>
+#include <isc/formatcheck.h>
+#include <isc/lex.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/net.h>
+#include <isc/netaddr.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).
+ */
+
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_uint32 = { "uint32", free_noop };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_uint64 = { "uint64", free_noop };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_string = { "string", free_string };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_boolean = { "boolean", free_noop };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_map = { "map", free_map };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_list = { "list", free_list };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_tuple = { "tuple", free_tuple };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_sockaddr = { "sockaddr", free_noop };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_netprefix = { "netprefix",
+ free_noop };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_void = { "void", free_noop };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_fixedpoint = { "fixedpoint",
+ free_noop };
+LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_percentage = { "percentage",
+ free_noop };
+LIBISCCFG_EXTERNAL_DATA 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);
+}
+
+LIBISCCFG_EXTERNAL_DATA 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);
+}
+
+LIBISCCFG_EXTERNAL_DATA 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);
+}
+
+LIBISCCFG_EXTERNAL_DATA 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);
+}
+
+LIBISCCFG_EXTERNAL_DATA 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);
+}
+
+LIBISCCFG_EXTERNAL_DATA 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 };
+ cfg_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) {
+ cfg_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) {
+ uint64_t seconds = 0;
+ cfg_duration_t duration;
+
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_duration);
+
+ duration = obj->value.duration;
+
+ 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);
+}
+
+static isc_result_t
+duration_fromtext(isc_textregion_t *source, cfg_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;
+ }
+
+ /* Every duration starts with 'P' */
+ P = strpbrk(str, "Pp");
+ if (P == NULL) {
+ return (ISC_R_BADNUMBER);
+ }
+
+ /* 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);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+parse_duration(cfg_parser_t *pctx, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ cfg_duration_t duration;
+
+ duration.unlimited = false;
+
+ if (toupper((unsigned char)TOKEN_STRING(pctx)[0]) == 'P') {
+ result = duration_fromtext(&pctx->token.value.as_textregion,
+ &duration);
+ duration.iso8601 = true;
+ } else {
+ uint32_t ttl;
+ result = dns_ttl_fromtext(&pctx->token.value.as_textregion,
+ &ttl);
+ /*
+ * With dns_ttl_fromtext() the information on optional units.
+ * is lost, and is treated as seconds from now on.
+ */
+ for (int i = 0; i < 6; i++) {
+ duration.parts[i] = 0;
+ }
+ duration.parts[6] = ttl;
+ duration.iso8601 = false;
+ }
+
+ 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;
+ cfg_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).
+ */
+LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_duration = {
+ "duration", cfg_parse_duration, cfg_print_duration,
+ cfg_doc_terminal, &cfg_rep_duration, NULL
+};
+LIBISCCFG_EXTERNAL_DATA 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 */
+LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_qstring = {
+ "quoted_string", cfg_parse_qstring, print_qstring,
+ cfg_doc_terminal, &cfg_rep_string, NULL
+};
+
+/* Unquoted string only */
+LIBISCCFG_EXTERNAL_DATA 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 */
+LIBISCCFG_EXTERNAL_DATA 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.
+ */
+LIBISCCFG_EXTERNAL_DATA 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.
+ */
+LIBISCCFG_EXTERNAL_DATA 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
+ */
+LIBISCCFG_EXTERNAL_DATA 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> } ]");
+}
+
+LIBISCCFG_EXTERNAL_DATA 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");
+ }
+}
+
+LIBISCCFG_EXTERNAL_DATA 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));
+ CHECK(parser_openfile(pctx,
+ includename->value.string.base));
+ cfg_obj_destroy(pctx, &includename);
+ 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);
+ }
+
+ /* 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_NOTIMP) != 0) {
+ cfg_parser_warning(pctx, 0,
+ "option '%s' is not implemented",
+ clause->name);
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_NYI) != 0) {
+ cfg_parser_warning(pctx, 0,
+ "option '%s' is not implemented",
+ clause->name);
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_NOOP) != 0) {
+ cfg_parser_warning(pctx, 0,
+ "option '%s' was not "
+ "enabled at compile time "
+ "(ignored)",
+ clause->name);
+ }
+
+ 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);
+ }
+
+ /*
+ * Don't log options with CFG_CLAUSEFLAG_NEWDEFAULT
+ * set here - we need to log the *lack* of such an option,
+ * not its presence.
+ */
+
+ /* 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_NOTIMP, "not implemented" },
+ { CFG_CLAUSEFLAG_NYI, "not yet implemented" },
+ { CFG_CLAUSEFLAG_OBSOLETE, "obsolete" },
+ { CFG_CLAUSEFLAG_NEWDEFAULT, "default changed" },
+ { CFG_CLAUSEFLAG_TESTONLY, "test only" },
+ { CFG_CLAUSEFLAG_NOTCONFIGURED, "not configured" },
+ { CFG_CLAUSEFLAG_MULTI, "may occur multiple times" },
+ { CFG_CLAUSEFLAG_EXPERIMENTAL, "experimental" },
+ { CFG_CLAUSEFLAG_NOOP, "non-operational" },
+ { 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_ANCIENT) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_NYI) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_TESTONLY) != 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_ANCIENT) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_NYI) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_TESTONLY) != 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);
+}
+
+LIBISCCFG_EXTERNAL_DATA 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);
+}
+
+LIBISCCFG_EXTERNAL_DATA 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));
+}
+
+isc_result_t
+cfg_parse_dscp(cfg_parser_t *pctx, isc_dscp_t *dscp) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(dscp != NULL);
+
+ 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);
+ }
+ if (pctx->token.value.as_ulong > 63U) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "dscp out of range");
+ return (ISC_R_RANGE);
+ }
+ *dscp = (isc_dscp_t)(pctx->token.value.as_ulong);
+ return (ISC_R_SUCCESS);
+cleanup:
+ return (result);
+}
+
+/* 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, " )");
+ }
+}
+
+LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_netaddr = {
+ "netaddr", parse_netaddr, cfg_print_sockaddr,
+ cfg_doc_netaddr, &cfg_rep_sockaddr, &netaddr_flags
+};
+
+LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_netaddr4 = {
+ "netaddr4", parse_netaddr, cfg_print_sockaddr,
+ cfg_doc_netaddr, &cfg_rep_sockaddr, &netaddr4_flags
+};
+
+LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_netaddr4wild = {
+ "netaddr4wild", parse_netaddr, cfg_print_sockaddr,
+ cfg_doc_netaddr, &cfg_rep_sockaddr, &netaddr4wild_flags
+};
+
+LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_netaddr6 = {
+ "netaddr6", parse_netaddr, cfg_print_sockaddr,
+ cfg_doc_netaddr, &cfg_rep_sockaddr, &netaddr6_flags
+};
+
+LIBISCCFG_EXTERNAL_DATA 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;
+}
+
+LIBISCCFG_EXTERNAL_DATA 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;
+ isc_dscp_t dscp = -1;
+ cfg_obj_t *obj = NULL;
+ int have_port = 0, have_dscp = 0;
+
+ CHECK(cfg_create_obj(pctx, type, &obj));
+ CHECK(cfg_parse_rawaddr(pctx, flags, &netaddr));
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ if (strcasecmp(TOKEN_STRING(pctx), "port") == 0) {
+ 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)
+ {
+ if ((pctx->flags & CFG_PCTX_NODEPRECATED) == 0)
+ {
+ cfg_parser_warning(
+ pctx, 0,
+ "token 'dscp' is deprecated");
+ }
+ CHECK(cfg_gettoken(pctx, 0)); /* read "dscp" */
+ CHECK(cfg_parse_dscp(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);
+ obj->value.sockaddrdscp.dscp = dscp;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static unsigned int sockaddr_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK;
+LIBISCCFG_EXTERNAL_DATA 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;
+LIBISCCFG_EXTERNAL_DATA 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_WILDOK) != 0) {
+ cfg_print_cstr(pctx, "[ port ( <integer> | * ) ]");
+ } else {
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ }
+ if ((*flagp & CFG_ADDR_DSCPOK) != 0) {
+ cfg_print_cstr(pctx, " [ dscp <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_dscp_t
+cfg_obj_getdscp(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_sockaddr);
+ return (obj->value.sockaddrdscp.dscp);
+}
+
+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/isccfg/tests/Kyuafile b/lib/isccfg/tests/Kyuafile
new file mode 100644
index 0000000..ad599af
--- /dev/null
+++ b/lib/isccfg/tests/Kyuafile
@@ -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.
+
+syntax(2)
+test_suite('bind9')
+
+tap_test_program{name='duration_test'}
+tap_test_program{name='parser_test'}
diff --git a/lib/isccfg/tests/Makefile.in b/lib/isccfg/tests/Makefile.in
new file mode 100644
index 0000000..33b74c8
--- /dev/null
+++ b/lib/isccfg/tests/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. -Iinclude \
+ ${DNS_INCLUDES} ${ISC_INCLUDES} ${ISCCFG_INCLUDES} \
+ ${OPENSSL_CFLAGS} @CMOCKA_CFLAGS@
+CDEFINES = -DTESTS="\"${top_builddir}/lib/dns/tests/\""
+
+ISCLIBS = ../../isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@
+ISCDEPLIBS = ../../isc/libisc.@A@
+DNSLIBS = ../../dns/libdns.@A@ @NO_LIBTOOL_DNSLIBS@
+DNSDEPLIBS = ../../dns/libdns.@A@
+ISCCFGLIBS = ../libisccfg.@A@
+ISCCFGDEPLIBS = ../libisccfg.@A@
+
+LIBS = @LIBS@ @CMOCKA_LIBS@
+
+OBJS =
+SRCS = duration_test.c parser_test.c
+
+SUBDIRS =
+TARGETS = duration_test@EXEEXT@ parser_test@EXEEXT@
+
+@BIND9_MAKE_RULES@
+
+duration_test@EXEEXT@: duration_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ${ISCCFGDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ duration_test.@O@ \
+ ${ISCCFGLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+parser_test@EXEEXT@: parser_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ${ISCCFGDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} -o $@ parser_test.@O@ \
+ ${ISCCFGLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+unit::
+ sh ${top_builddir}/unit/unittest.sh
+
+clean distclean::
+ rm -f ${TARGETS}
+ rm -f atf.out
diff --git a/lib/isccfg/tests/duration_test.c b/lib/isccfg/tests/duration_test.c
new file mode 100644
index 0000000..da52903
--- /dev/null
+++ b/lib/isccfg/tests/duration_test.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.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/lex.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/grammar.h>
+#include <isccfg/namedconf.h>
+
+#define CHECK(r) \
+ do { \
+ result = (r); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+isc_mem_t *mctx = NULL;
+isc_log_t *lctx = NULL;
+static isc_logcategory_t categories[] = { { "", 0 },
+ { "client", 0 },
+ { "network", 0 },
+ { "update", 0 },
+ { "queries", 0 },
+ { "unmatched", 0 },
+ { "update-security", 0 },
+ { "query-errors", 0 },
+ { NULL, 0 } };
+
+static void
+cleanup() {
+ if (lctx != NULL) {
+ isc_log_destroy(&lctx);
+ }
+ if (mctx != NULL) {
+ isc_mem_destroy(&mctx);
+ }
+}
+
+static isc_result_t
+setup() {
+ isc_result_t result;
+
+ isc_mem_debugging |= ISC_MEM_DEBUGRECORD;
+ isc_mem_create(&mctx);
+
+ isc_logdestination_t destination;
+ isc_logconfig_t *logconfig = NULL;
+
+ isc_log_create(mctx, &lctx, &logconfig);
+ isc_log_registercategories(lctx, categories);
+ isc_log_setcontext(lctx);
+
+ destination.file.stream = stderr;
+ destination.file.name = NULL;
+ destination.file.versions = ISC_LOG_ROLLNEVER;
+ destination.file.maximum_size = 0;
+ isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC,
+ ISC_LOG_DYNAMIC, &destination, 0);
+ CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL));
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cleanup();
+ return (result);
+}
+
+struct duration_conf {
+ const char *string;
+ uint32_t time;
+ const char *out;
+};
+typedef struct duration_conf duration_conf_t;
+
+static void
+output(void *closure, const char *text, int textlen) {
+ int r;
+
+ REQUIRE(textlen >= 0 && textlen < CFG_DURATION_MAXLEN);
+
+ r = snprintf(closure, CFG_DURATION_MAXLEN, "%s", text);
+ INSIST(r == textlen);
+}
+
+/* test cfg_obj_asduration() and cfg_print_duration() */
+static void
+duration_test(void **state) {
+ isc_result_t result;
+ /*
+ * When 'out' is NULL, the printed result is expected to be the same as
+ * the input 'string', compared by ignoring the case of the characters.
+ */
+ duration_conf_t durations[] = {
+ { .string = "unlimited", .time = 0 },
+ { .string = "PT0S", .time = 0 },
+ { .string = "PT42S", .time = 42 },
+ { .string = "PT10m", .time = 600 },
+ { .string = "PT10m4S", .time = 604 },
+ { .string = "PT3600S", .time = 3600 },
+ { .string = "pT2H", .time = 7200 },
+ { .string = "Pt2H3S", .time = 7203 },
+ { .string = "PT2h1m3s", .time = 7263 },
+ { .string = "p7d", .time = 604800 },
+ { .string = "P7DT2h", .time = 612000 },
+ { .string = "P2W", .time = 1209600 },
+ { .string = "P3M", .time = 8035200 },
+ { .string = "P3MT10M", .time = 8035800 },
+ { .string = "p5y", .time = 157680000 },
+ { .string = "P5YT2H", .time = 157687200 },
+ { .string = "P1Y1M1DT1H1M1S", .time = 34304461 },
+ { .string = "P99Y399M999DT3999H9999M2911754S",
+ .time = UINT32_MAX - 1 },
+ { .string = "P99Y399M999DT3999H9999M2911755S",
+ .time = UINT32_MAX },
+ { .string = "P4294967295Y4294967295M4294967295D"
+ "T4294967295H4294967295M4294967295S",
+ .time = UINT32_MAX },
+ { .string = "PT4294967294S", .time = UINT32_MAX - 1 },
+ { .string = "PT4294967295S", .time = UINT32_MAX },
+ { .string = "0", .time = 0 },
+ { .string = "30", .time = 30 },
+ { .string = "42s", .time = 42, .out = "42" },
+ { .string = "10m", .time = 600, .out = "600" },
+ { .string = "2H", .time = 7200, .out = "7200" },
+ { .string = "7d", .time = 604800, .out = "604800" },
+ { .string = "2w", .time = 1209600, .out = "1209600" },
+ { 0 }, /* Indicates that the remaining durations are invalid. */
+ { .string = "PT4Y" },
+ { .string = "P-4Y2M" },
+ { .string = "P5H1M30S" },
+ { .string = "P7Y4W" },
+ { .string = "X7Y4M" },
+ { .string = "T7H4M" },
+ { .string = "1Y6M" },
+ { .string = "PT4294967296S" },
+ { .string = "PT99999999999S" },
+ { .string = "P99999999999Y99999999999M99999999999D"
+ "T99999999999H99999999999M99999999999S" },
+ };
+ isc_buffer_t buf1;
+ cfg_parser_t *p1 = NULL;
+ cfg_obj_t *c1 = NULL;
+ bool must_fail = false;
+
+ UNUSED(state);
+
+ setup();
+
+ for (size_t i = 0; i < ARRAY_SIZE(durations); i++) {
+ const cfg_listelt_t *element;
+ const cfg_obj_t *kasps = NULL;
+ const char cfg_tpl[] =
+ "dnssec-policy \"dp\"\n"
+ "{\nkeys {csk lifetime %s algorithm rsasha256;};\n};\n";
+ char conf[sizeof(cfg_tpl) + CFG_DURATION_MAXLEN] = { 0 };
+ char out[CFG_DURATION_MAXLEN] = { 0 };
+ cfg_printer_t pctx = { .f = output, .closure = out };
+
+ if (durations[i].string == NULL) {
+ must_fail = true;
+ continue;
+ }
+
+ snprintf(&conf[0], sizeof(conf), cfg_tpl, durations[i].string);
+
+ isc_buffer_init(&buf1, conf, strlen(conf) - 1);
+ isc_buffer_add(&buf1, strlen(conf) - 1);
+
+ /* Parse with default line numbering */
+ result = cfg_parser_create(mctx, lctx, &p1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = cfg_parse_buffer(p1, &buf1, "text1", 0,
+ &cfg_type_namedconf, 0, &c1);
+ if (must_fail) {
+ assert_int_not_equal(result, ISC_R_SUCCESS);
+ cfg_parser_destroy(&p1);
+ continue;
+ }
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ (void)cfg_map_get(c1, "dnssec-policy", &kasps);
+ assert_non_null(kasps);
+ for (element = cfg_list_first(kasps); element != NULL;
+ element = cfg_list_next(element))
+ {
+ const cfg_listelt_t *key_element;
+ const cfg_obj_t *lifetime = NULL;
+ const cfg_obj_t *keys = NULL;
+ const cfg_obj_t *key = NULL;
+ const cfg_obj_t *kopts = NULL;
+ cfg_obj_t *kconf = cfg_listelt_value(element);
+ int cmp;
+
+ assert_non_null(kconf);
+
+ kopts = cfg_tuple_get(kconf, "options");
+ result = cfg_map_get(kopts, "keys", &keys);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ key_element = cfg_list_first(keys);
+ assert_non_null(key_element);
+
+ key = cfg_listelt_value(key_element);
+ assert_non_null(key);
+
+ lifetime = cfg_tuple_get(key, "lifetime");
+ assert_non_null(lifetime);
+
+ assert_int_equal(durations[i].time,
+ cfg_obj_asduration(lifetime));
+
+ cfg_print_duration_or_unlimited(&pctx, lifetime);
+ cmp = strncasecmp(durations[i].out != NULL
+ ? durations[i].out
+ : durations[i].string,
+ out, strlen(durations[i].string));
+ assert_int_equal(cmp, 0);
+ }
+
+ cfg_obj_destroy(p1, &c1);
+ cfg_parser_destroy(&p1);
+ }
+
+ cleanup();
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(duration_test),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isccfg/tests/parser_test.c b/lib/isccfg/tests/parser_test.c
new file mode 100644
index 0000000..960f592
--- /dev/null
+++ b/lib/isccfg/tests/parser_test.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/lex.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/grammar.h>
+#include <isccfg/namedconf.h>
+
+#define CHECK(r) \
+ do { \
+ result = (r); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+isc_mem_t *mctx = NULL;
+isc_log_t *lctx = NULL;
+static isc_logcategory_t categories[] = { { "", 0 },
+ { "client", 0 },
+ { "network", 0 },
+ { "update", 0 },
+ { "queries", 0 },
+ { "unmatched", 0 },
+ { "update-security", 0 },
+ { "query-errors", 0 },
+ { NULL, 0 } };
+
+static void
+cleanup() {
+ if (lctx != NULL) {
+ isc_log_setcontext(NULL);
+ isc_log_destroy(&lctx);
+ }
+ if (mctx != NULL) {
+ isc_mem_destroy(&mctx);
+ }
+}
+
+static isc_result_t
+setup() {
+ isc_result_t result;
+
+ isc_mem_debugging |= ISC_MEM_DEBUGRECORD;
+ isc_mem_create(&mctx);
+
+ isc_logdestination_t destination;
+ isc_logconfig_t *logconfig = NULL;
+
+ isc_log_create(mctx, &lctx, &logconfig);
+ isc_log_registercategories(lctx, categories);
+ isc_log_setcontext(lctx);
+
+ destination.file.stream = stderr;
+ destination.file.name = NULL;
+ destination.file.versions = ISC_LOG_ROLLNEVER;
+ destination.file.maximum_size = 0;
+ isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC,
+ ISC_LOG_DYNAMIC, &destination, 0);
+ CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL));
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cleanup();
+ return (result);
+}
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = setup();
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ cleanup();
+
+ return (0);
+}
+
+/* mimic calling nzf_append() */
+static void
+append(void *arg, const char *str, int len) {
+ char *buf = arg;
+ size_t l = strlen(buf);
+ snprintf(buf + l, 1024 - l, "%.*s", len, str);
+}
+
+static void
+addzoneconf(void **state) {
+ isc_result_t result;
+ isc_buffer_t b;
+ cfg_parser_t *p = NULL;
+ const char *tests[] = {
+ "zone \"test4.baz\" { type master; file \"e.db\"; };",
+ "zone \"test/.baz\" { type master; file \"e.db\"; };",
+ "zone \"test\\\".baz\" { type master; file \"e.db\"; };",
+ "zone \"test\\.baz\" { type master; file \"e.db\"; };",
+ "zone \"test\\\\.baz\" { type master; file \"e.db\"; };",
+ "zone \"test\\032.baz\" { type master; file \"e.db\"; };",
+ "zone \"test\\010.baz\" { type master; file \"e.db\"; };"
+ };
+ char buf[1024];
+
+ UNUSED(state);
+
+ /* Parse with default line numbering */
+ result = cfg_parser_create(mctx, lctx, &p);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
+
+ for (size_t i = 0; i < ARRAYSIZE(tests); i++) {
+ cfg_obj_t *conf = NULL;
+ const cfg_obj_t *obj = NULL, *zlist = NULL;
+
+ isc_buffer_constinit(&b, tests[i], strlen(tests[i]));
+ isc_buffer_add(&b, strlen(tests[i]));
+
+ result = cfg_parse_buffer(p, &b, "text1", 0,
+ &cfg_type_namedconf, 0, &conf);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Mimic calling nzf_append() from bin/named/server.c
+ * and check that the output matches the input.
+ */
+ result = cfg_map_get(conf, "zone", &zlist);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ obj = cfg_listelt_value(cfg_list_first(zlist));
+ assert_ptr_not_equal(obj, NULL);
+
+ strlcpy(buf, "zone ", sizeof(buf));
+ cfg_printx(obj, CFG_PRINTER_ONELINE, append, buf);
+ strlcat(buf, ";", sizeof(buf));
+ assert_string_equal(tests[i], buf);
+
+ cfg_obj_destroy(p, &conf);
+ cfg_parser_reset(p);
+ }
+
+ cfg_parser_destroy(&p);
+}
+
+/* test cfg_parse_buffer() */
+static void
+parse_buffer_test(void **state) {
+ isc_result_t result;
+ unsigned char text[] = "options\n{\nrecursion yes;\n};\n";
+ isc_buffer_t buf1, buf2;
+ cfg_parser_t *p1 = NULL, *p2 = NULL;
+ cfg_obj_t *c1 = NULL, *c2 = NULL;
+
+ UNUSED(state);
+
+ isc_buffer_init(&buf1, &text[0], sizeof(text) - 1);
+ isc_buffer_add(&buf1, sizeof(text) - 1);
+
+ /* Parse with default line numbering */
+ result = cfg_parser_create(mctx, lctx, &p1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = cfg_parse_buffer(p1, &buf1, "text1", 0, &cfg_type_namedconf, 0,
+ &c1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(p1->line, 5);
+
+ isc_buffer_init(&buf2, &text[0], sizeof(text) - 1);
+ isc_buffer_add(&buf2, sizeof(text) - 1);
+
+ /* Parse with changed line number */
+ result = cfg_parser_create(mctx, lctx, &p2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = cfg_parse_buffer(p2, &buf2, "text2", 100, &cfg_type_namedconf,
+ 0, &c2);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(p2->line, 104);
+
+ cfg_obj_destroy(p1, &c1);
+ cfg_obj_destroy(p2, &c2);
+
+ cfg_parser_destroy(&p1);
+ cfg_parser_destroy(&p2);
+}
+
+/* test cfg_map_firstclause() */
+static void
+cfg_map_firstclause_test(void **state) {
+ const char *name = NULL;
+ const void *clauses = NULL;
+ unsigned int idx;
+
+ UNUSED(state);
+
+ name = cfg_map_firstclause(&cfg_type_zoneopts, &clauses, &idx);
+ assert_non_null(name);
+ assert_non_null(clauses);
+ assert_int_equal(idx, 0);
+}
+
+/* test cfg_map_nextclause() */
+static void
+cfg_map_nextclause_test(void **state) {
+ const char *name = NULL;
+ const void *clauses = NULL;
+ unsigned int idx;
+
+ UNUSED(state);
+
+ name = cfg_map_firstclause(&cfg_type_zoneopts, &clauses, &idx);
+ assert_non_null(name);
+ assert_non_null(clauses);
+ assert_int_equal(idx, ISC_R_SUCCESS);
+
+ do {
+ name = cfg_map_nextclause(&cfg_type_zoneopts, &clauses, &idx);
+ if (name != NULL) {
+ assert_non_null(clauses);
+ } else {
+ assert_null(clauses);
+ assert_int_equal(idx, 0);
+ }
+ } while (name != NULL);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(addzoneconf),
+ cmocka_unit_test(parse_buffer_test),
+ cmocka_unit_test(cfg_map_firstclause_test),
+ cmocka_unit_test(cfg_map_nextclause_test),
+ };
+
+ return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isccfg/version.c b/lib/isccfg/version.c
new file mode 100644
index 0000000..8d8f424
--- /dev/null
+++ b/lib/isccfg/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/version.h>
+
+const char cfg_version[] = VERSION;
diff --git a/lib/isccfg/win32/DLLMain.c b/lib/isccfg/win32/DLLMain.c
new file mode 100644
index 0000000..5c38e89
--- /dev/null
+++ b/lib/isccfg/win32/DLLMain.c
@@ -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.
+ */
+
+#include <signal.h>
+#include <windows.h>
+
+/*
+ * Called when we enter the DLL
+ */
+__declspec(dllexport) BOOL WINAPI
+ DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
+ switch (fdwReason) {
+ /*
+ * The DLL is loading due to process
+ * initialization or a call to LoadLibrary.
+ */
+ case DLL_PROCESS_ATTACH:
+ break;
+
+ /*
+ * The attached process creates a new thread.
+ */
+ case DLL_THREAD_ATTACH:
+ break;
+
+ /* The thread of the attached process terminates. */
+ case DLL_THREAD_DETACH:
+ break;
+
+ /*
+ * The DLL is unloading from a process due to
+ * process termination or a call to FreeLibrary.
+ */
+ case DLL_PROCESS_DETACH:
+ break;
+
+ default:
+ break;
+ }
+ return (TRUE);
+}
diff --git a/lib/isccfg/win32/libisccfg.def b/lib/isccfg/win32/libisccfg.def
new file mode 100644
index 0000000..eba897e
--- /dev/null
+++ b/lib/isccfg/win32/libisccfg.def
@@ -0,0 +1,175 @@
+LIBRARY libisccfg
+
+; Exported Functions
+EXPORTS
+
+cfg_acl_fromconfig
+cfg_acl_fromconfig2
+cfg_aclconfctx_attach
+cfg_aclconfctx_create
+cfg_aclconfctx_detach
+cfg_clause_validforzone
+cfg_create_list
+cfg_create_obj
+cfg_create_tuple
+cfg_doc_bracketed_list
+cfg_doc_enum
+cfg_doc_enum_or_other
+cfg_doc_map
+cfg_doc_mapbody
+cfg_doc_obj
+cfg_doc_sockaddr
+cfg_doc_terminal
+cfg_doc_tuple
+cfg_doc_void
+cfg_gettoken
+cfg_is_enum
+cfg_kasp_fromconfig
+cfg_list_first
+cfg_list_length
+cfg_list_next
+cfg_listelt_value
+cfg_log_init
+cfg_lookingat_netaddr
+cfg_map_count
+cfg_map_firstclause
+cfg_map_get
+cfg_map_getname
+cfg_map_nextclause
+cfg_obj_asboolean
+cfg_obj_asduration
+cfg_obj_asfixedpoint
+cfg_obj_asnetprefix
+cfg_obj_aspercentage
+cfg_obj_assockaddr
+cfg_obj_asstring
+cfg_obj_asuint32
+cfg_obj_asuint64
+cfg_obj_attach
+cfg_obj_destroy
+cfg_obj_file
+cfg_obj_getdscp
+cfg_obj_isboolean
+cfg_obj_isduration
+cfg_obj_isfixedpoint
+cfg_obj_islist
+cfg_obj_ismap
+cfg_obj_isnetprefix
+cfg_obj_ispercentage
+cfg_obj_issockaddr
+cfg_obj_isstring
+cfg_obj_istuple
+cfg_obj_istype
+cfg_obj_isuint32
+cfg_obj_isuint64
+cfg_obj_isvoid
+cfg_obj_line
+cfg_obj_log
+cfg_parse_addressed_map
+cfg_parse_astring
+cfg_parse_boolean
+cfg_parse_bracketed_list
+cfg_parse_buffer
+cfg_parse_dscp
+cfg_parse_duration
+cfg_parse_duration_or_unlimited
+cfg_parse_enum
+cfg_parse_enum_or_other
+cfg_parse_file
+cfg_parse_fixedpoint
+cfg_parse_listelt
+cfg_parse_map
+cfg_parse_mapbody
+cfg_parse_named_map
+cfg_parse_netprefix
+cfg_parse_netprefix_map
+cfg_parse_obj
+cfg_parse_percentage
+cfg_parse_qstring
+cfg_parse_rawaddr
+cfg_parse_rawport
+cfg_parse_sockaddr
+cfg_parse_spacelist
+cfg_parse_special
+cfg_parse_sstring
+cfg_parse_tuple
+cfg_parse_uint32
+cfg_parse_void
+cfg_parser_attach
+cfg_parser_create
+cfg_parser_destroy
+cfg_parser_error
+cfg_parser_mapadd
+cfg_parser_reset
+cfg_parser_setcallback
+cfg_parser_setflags
+cfg_parser_warning
+cfg_peektoken
+cfg_pluginlist_foreach
+cfg_print
+cfg_print_boolean
+cfg_print_bracketed_list
+cfg_print_chars
+cfg_print_clauseflags
+cfg_print_cstr
+cfg_print_duration
+cfg_print_duration_or_unlimited
+cfg_print_fixedpoint
+cfg_print_grammar
+cfg_print_indent
+cfg_print_map
+cfg_print_mapbody
+cfg_print_obj
+cfg_print_percentage
+cfg_print_rawaddr
+cfg_print_rawuint
+cfg_print_sockaddr
+cfg_print_spacelist
+cfg_print_tuple
+cfg_print_uint32
+cfg_print_uint64
+cfg_print_ustring
+cfg_print_void
+cfg_print_zonegrammar
+cfg_printx
+cfg_tuple_get
+cfg_ungettoken
+
+; Exported Data
+
+;cfg_rep_boolean
+;cfg_rep_duration
+;cfg_rep_fixedpoint
+;cfg_rep_list
+;cfg_rep_map
+;cfg_rep_netprefix
+;cfg_rep_percentage
+;cfg_rep_sockaddr
+;cfg_rep_string
+;cfg_rep_tuple
+;cfg_rep_uint32
+;cfg_rep_uint64
+;cfg_rep_void
+;cfg_type_astring
+;cfg_type_boolean
+;cfg_type_bracketed_text
+;cfg_type_fixedpoint
+;cfg_type_netaddr
+;cfg_type_netaddr4
+;cfg_type_netaddr4wild
+;cfg_type_netaddr6
+;cfg_type_netaddr6wild
+;cfg_type_netprefix
+;cfg_type_optional_bracketed_text
+;cfg_type_percentage
+;cfg_type_qstring
+;cfg_type_rndcconf
+;cfg_type_sockaddr
+;cfg_type_sockaddrdscp
+;cfg_type_sstring
+;cfg_type_token
+;cfg_type_uint32
+;cfg_type_uint64
+;cfg_type_unsupported
+;cfg_type_ustring
+;cfg_type_void
diff --git a/lib/isccfg/win32/libisccfg.vcxproj.filters.in b/lib/isccfg/win32/libisccfg.vcxproj.filters.in
new file mode 100644
index 0000000..91d4202
--- /dev/null
+++ b/lib/isccfg/win32/libisccfg.vcxproj.filters.in
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="libisccfg.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="DLLMain.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="version.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\aclconf.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\dnsconf.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\kaspconf.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\log.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\namedconf.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\parser.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\isccfg\aclconf.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccfg\dnsconf.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccfg\cfg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccfg\grammar.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccfg\kaspconf.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccfg\log.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccfg\namedconf.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\isccfg\version.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project>
diff --git a/lib/isccfg/win32/libisccfg.vcxproj.in b/lib/isccfg/win32/libisccfg.vcxproj.in
new file mode 100644
index 0000000..4b81300
--- /dev/null
+++ b/lib/isccfg/win32/libisccfg.vcxproj.in
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="@TOOLS_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|@PLATFORM@">
+ <Configuration>Debug</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|@PLATFORM@">
+ <Configuration>Release</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{B2DFA58C-6347-478E-81E8-01E06999D4F1}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>libisccfg</RootNamespace>
+ @WINDOWS_TARGET_PLATFORM_VERSION@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISCCFG_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;..\..\dns\include;@LIBXML2_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BrowseInformation>true</BrowseInformation>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>..\..\dns\win32\$(Configuration);..\..\isc\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libdns.lib;libisc.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>@INTRINSIC@</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBISCCFG_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;..\..\dns\include;@LIBXML2_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <StringPooling>true</StringPooling>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WholeProgramOptimization>false</WholeProgramOptimization>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>..\..\dns\win32\$(Configuration);..\..\isc\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libdns.lib;libisc.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <None Include="libisccfg.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\aclconf.c" />
+ <ClCompile Include="..\dnsconf.c" />
+ <ClCompile Include="..\kaspconf.c" />
+ <ClCompile Include="..\log.c" />
+ <ClCompile Include="..\namedconf.c" />
+ <ClCompile Include="..\parser.c" />
+ <ClCompile Include="DLLMain.c" />
+ <ClCompile Include="version.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\isccfg\aclconf.h" />
+ <ClInclude Include="..\include\isccfg\dnsconf.h" />
+ <ClInclude Include="..\include\isccfg\cfg.h" />
+ <ClInclude Include="..\include\isccfg\grammar.h" />
+ <ClInclude Include="..\include\isccfg\kaspconf.h" />
+ <ClInclude Include="..\include\isccfg\log.h" />
+ <ClInclude Include="..\include\isccfg\namedconf.h" />
+ <ClInclude Include="..\include\isccfg\version.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/lib/isccfg/win32/libisccfg.vcxproj.user b/lib/isccfg/win32/libisccfg.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/lib/isccfg/win32/libisccfg.vcxproj.user
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+</Project> \ No newline at end of file
diff --git a/lib/isccfg/win32/version.c b/lib/isccfg/win32/version.c
new file mode 100644
index 0000000..ad4271f
--- /dev/null
+++ b/lib/isccfg/win32/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <versions.h>
+
+#include <isccfg/version.h>
+
+LIBISCCFG_EXTERNAL_DATA const char cfg_version[] = VERSION;
diff --git a/lib/ns/Kyuafile b/lib/ns/Kyuafile
new file mode 100644
index 0000000..c796010
--- /dev/null
+++ b/lib/ns/Kyuafile
@@ -0,0 +1,15 @@
+-- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+--
+-- SPDX-License-Identifier: MPL-2.0
+--
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, you can obtain one at https://mozilla.org/MPL/2.0/.
+--
+-- See the COPYRIGHT file distributed with this work for additional
+-- information regarding copyright ownership.
+
+syntax(2)
+test_suite('bind9')
+
+include('tests/Kyuafile')
diff --git a/lib/ns/Makefile.in b/lib/ns/Makefile.in
new file mode 100644
index 0000000..efadf78
--- /dev/null
+++ b/lib/ns/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+@BIND9_MAJOR@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES = -I. -I${top_srcdir}/lib/ns -Iinclude \
+ ${NS_INCLUDES} ${DNS_INCLUDES} ${ISC_INCLUDES} \
+ ${OPENSSL_CFLAGS} @DST_GSSAPI_INC@ \
+ ${FSTRM_CFLAGS}
+
+CDEFINES = -DNAMED_PLUGINDIR=\"${plugindir}\"
+
+CWARNINGS =
+
+ISCLIBS = ../../lib/isc/libisc.@A@
+
+ISCDEPLIBS = ../../lib/isc/libisc.@A@
+
+DNSLIBS = ../../lib/dns/libdns.@A@ @NO_LIBTOOL_DNSLIBS@
+
+DNSDEPLIBS = ../../lib/dns/libdns.@A@
+
+LIBS = @LIBS@
+
+# Alphabetically
+OBJS = client.@O@ hooks.@O@ interfacemgr.@O@ lib.@O@ \
+ listenlist.@O@ log.@O@ notify.@O@ query.@O@ \
+ server.@O@ sortlist.@O@ stats.@O@ update.@O@ \
+ version.@O@ xfrout.@O@
+
+SRCS = client.c hooks.c interfacemgr.c lib.c listenlist.c \
+ log.c notify.c query.c server.c sortlist.c stats.c \
+ update.c version.c xfrout.c
+
+SUBDIRS = include
+TESTDIRS = @UNITTESTS@
+TARGETS = timestamp
+
+SO_CFLAGS = @CFLAGS@ @SO_CFLAGS@
+SO_LDFLAGS = @LDFLAGS@ @SO_LDFLAGS@
+
+@BIND9_MAKE_RULES@
+
+version.@O@: version.c
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} \
+ -DVERSION=\"${VERSION}\" \
+ -DMAJOR=\"${MAJOR}\" \
+ -c ${srcdir}/version.c
+
+libns.@SA@: ${OBJS}
+ ${AR} ${ARFLAGS} $@ ${OBJS}
+ ${RANLIB} $@
+
+libns.la: ${OBJS}
+ ${LIBTOOL_MODE_LINK} \
+ ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libns.la -rpath ${libdir} \
+ -release "${VERSION}" \
+ ${OBJS} ${ISCLIBS} ${DNSLIBS} @DNS_CRYPTO_LIBS@ ${LIBS}
+
+timestamp: libns.@A@
+ touch timestamp
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}
+
+install:: timestamp installdirs
+ ${LIBTOOL_MODE_INSTALL} ${INSTALL_LIBRARY} libns.@A@ \
+ ${DESTDIR}${libdir}
+
+uninstall::
+ ${LIBTOOL_MODE_UNINSTALL} rm -f ${DESTDIR}${libdir}/libns.@A@
+
+clean distclean::
+ rm -f libns.@A@ timestamp
diff --git a/lib/ns/client.c b/lib/ns/client.c
new file mode 100644
index 0000000..d4ce000
--- /dev/null
+++ b/lib/ns/client.c
@@ -0,0 +1,3053 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/mutex.h>
+#include <isc/nonce.h>
+#include <isc/once.h>
+#include <isc/platform.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/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/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>
+
+#ifndef _POSIX_HOST_NAME_MAX
+#define _POSIX_HOST_NAME_MAX 255
+#endif
+
+/***
+ *** 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 */
+
+#define CLIENT_NMCTXS_PERCPU 8
+/*%<
+ * Number of 'mctx pools' for clients. (Should this be configurable?)
+ * When enabling threads, we use a pool of memory contexts shared by
+ * client objects, since concurrent access to a shared context would cause
+ * heavy contentions. The above constant is expected to be enough for
+ * completely avoiding contentions among threads for an authoritative-only
+ * server.
+ */
+
+#define CLIENT_NTASKS_PERCPU 32
+/*%<
+ * Number of tasks to be used by clients - those are used only when recursing
+ */
+
+#if defined(_WIN32) && !defined(_WIN64)
+LIBNS_EXTERNAL_DATA atomic_uint_fast32_t ns_client_requests = 0;
+#else /* if defined(_WIN32) && !defined(_WIN64) */
+LIBNS_EXTERNAL_DATA atomic_uint_fast64_t ns_client_requests = 0;
+#endif /* if defined(_WIN32) && !defined(_WIN64) */
+
+static void
+clientmgr_attach(ns_clientmgr_t *source, ns_clientmgr_t **targetp);
+static void
+clientmgr_detach(ns_clientmgr_t **mp);
+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);
+static void
+get_clientmctx(ns_clientmgr_t *manager, isc_mem_t **mctxp);
+static void
+get_clienttask(ns_clientmgr_t *manager, isc_task_t **taskp);
+
+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
+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->signer = NULL;
+ client->udpsize = 512;
+ client->extflags = 0;
+ client->ednsversion = -1;
+ 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->mctx,
+ 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_region_t r;
+
+ REQUIRE(client->sendhandle == NULL);
+
+ isc_buffer_usedregion(buffer, &r);
+ isc_nmhandle_attach(client->handle, &client->sendhandle);
+ 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;
+ isc_buffer_t buffer;
+ isc_region_t r;
+ isc_region_t *mr;
+
+ 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->mctx, client->tcpbuf,
+ NS_CLIENT_TCP_BUFFER_SIZE);
+ client->tcpbuf = NULL;
+ }
+
+ ns_client_drop(client, result);
+}
+
+void
+ns_client_send(ns_client_t *client) {
+ isc_result_t result;
+ unsigned char *data;
+ 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;
+#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 = ns_interfacemgr_getaclenv(client->manager->interface->mgr);
+
+ 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->mctx, client->tcpbuf,
+ NS_CLIENT_TCP_BUFFER_SIZE);
+ client->tcpbuf = NULL;
+ }
+
+ 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;
+ dns_resolver_t *resolver;
+ 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;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(opt != NULL && *opt == NULL);
+ REQUIRE(message != NULL);
+
+ env = ns_interfacemgr_getaclenv(client->manager->interface->mgr);
+ 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->gethostname != NULL) {
+ result = client->sctx->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++;
+ }
+
+ /* 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->mctx, client->tcpbuf,
+ NS_CLIENT_TCP_BUFFER_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);
+}
+
+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;
+
+ if (client->manager != NULL) {
+ clientmgr_detach(&client->manager);
+ }
+
+ isc_mem_put(client->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);
+ }
+
+ dns_message_detach(&client->message);
+
+ /*
+ * 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);
+ }
+
+ if (client->mctx != NULL) {
+ 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 */
+
+ if (eresult != ISC_R_SUCCESS) {
+ return;
+ }
+
+ client = isc_nmhandle_getdata(handle);
+ if (client == NULL) {
+ ns_interface_t *ifp = (ns_interface_t *)arg;
+
+ INSIST(VALID_MANAGER(ifp->clientmgr));
+
+ client = isc_nmhandle_getextra(handle);
+
+ result = ns__client_setup(client, ifp->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;
+
+ isc_task_pause(client->task);
+ 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_task_unpause(client->task);
+ return;
+ }
+#endif /* if NS_CLIENT_DROPPORT */
+
+ env = ns_interfacemgr_getaclenv(client->manager->interface->mgr);
+ 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_task_unpause(client->task);
+ 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.
+ */
+ isc_task_unpause(client->task);
+ return;
+ }
+
+ /*
+ * 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) {
+ CTRACE("unexpected response");
+ isc_task_unpause(client->task);
+ 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);
+ isc_task_unpause(client->task);
+ 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_tcpdns_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;
+
+ /*
+ * 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);
+ isc_task_unpause(client->task);
+ return;
+ }
+
+ /*
+ * Are returning NOTIMP to all EDNS queries?
+ */
+ if ((client->sctx->options & NS_SERVER_EDNSNOTIMP) != 0) {
+ ns_client_error(client, DNS_R_NOTIMP);
+ isc_task_unpause(client->task);
+ return;
+ }
+
+ /*
+ * Are returning REFUSED to all EDNS queries?
+ */
+ if ((client->sctx->options & NS_SERVER_EDNSREFUSED) != 0) {
+ ns_client_error(client, DNS_R_REFUSED);
+ isc_task_unpause(client->task);
+ return;
+ }
+
+ /*
+ * Are we dropping all EDNS queries?
+ */
+ if ((client->sctx->options & NS_SERVER_DROPEDNS) != 0) {
+ ns_client_drop(client, ISC_R_SUCCESS);
+ isc_task_unpause(client->task);
+ return;
+ }
+
+ result = process_opt(client, opt);
+ if (result != ISC_R_SUCCESS) {
+ isc_task_unpause(client->task);
+ 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);
+ isc_task_unpause(client->task);
+ return;
+ }
+
+ if (notimp) {
+ client->message->rcode = dns_rcode_notimp;
+ }
+
+ ns_client_send(client);
+ isc_task_unpause(client->task);
+ 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);
+ isc_task_unpause(client->task);
+ return;
+ }
+
+ if ((client->manager->interface->flags & NS_INTERFACEFLAG_ANYADDR) == 0)
+ {
+ client->destsockaddr = client->manager->interface->addr;
+ } else {
+ 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_error(client, notimp ? DNS_R_NOTIMP : DNS_R_REFUSED);
+ isc_task_unpause(client->task);
+ 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 master.
+ */
+ if (!(client->message->tsigstatus == dns_tsigerror_badkey &&
+ client->message->opcode == dns_opcode_update))
+ {
+ ns_client_error(client, sigresult);
+ isc_task_unpause(client->task);
+ 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;
+ if (client->view->resolver != NULL && client->view->recursion &&
+ ns_client_checkaclsilent(client, NULL, client->view->recursionacl,
+ true) == ISC_R_SUCCESS &&
+ ns_client_checkaclsilent(client, NULL, client->view->cacheacl,
+ true) == ISC_R_SUCCESS &&
+ ns_client_checkaclsilent(client, &client->destaddr,
+ client->view->recursiononacl,
+ true) == ISC_R_SUCCESS &&
+ ns_client_checkaclsilent(client, &client->destaddr,
+ client->view->cacheonacl,
+ true) == ISC_R_SUCCESS)
+ {
+ ra = true;
+ }
+
+ if (ra) {
+ 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");
+
+ /*
+ * 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_task_unpause(client->task);
+}
+
+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);
+}
+
+static void
+get_clientmctx(ns_clientmgr_t *manager, isc_mem_t **mctxp) {
+ isc_mem_t *clientmctx;
+ MTRACE("clientmctx");
+
+ int tid = isc_nm_tid();
+ if (tid < 0) {
+ tid = isc_random_uniform(manager->ncpus);
+ }
+ int rand = isc_random_uniform(CLIENT_NMCTXS_PERCPU);
+ int nextmctx = (rand * manager->ncpus) + tid;
+ clientmctx = manager->mctxpool[nextmctx];
+
+ isc_mem_attach(clientmctx, mctxp);
+}
+
+static void
+get_clienttask(ns_clientmgr_t *manager, isc_task_t **taskp) {
+ MTRACE("clienttask");
+
+ int tid = isc_nm_tid();
+ if (tid < 0) {
+ tid = isc_random_uniform(manager->ncpus);
+ }
+
+ int rand = isc_random_uniform(CLIENT_NTASKS_PERCPU);
+ int nexttask = (rand * manager->ncpus) + tid;
+ isc_task_attach(manager->taskpool[nexttask], taskp);
+}
+
+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.
+ */
+
+ REQUIRE(NS_CLIENT_VALID(client) || (new &&client != NULL));
+ REQUIRE(VALID_MANAGER(mgr) || !new);
+
+ if (new) {
+ *client = (ns_client_t){ .magic = 0 };
+
+ get_clientmctx(mgr, &client->mctx);
+ clientmgr_attach(mgr, &client->manager);
+ ns_server_attach(mgr->sctx, &client->sctx);
+ get_clienttask(mgr, &client->task);
+
+ dns_message_create(client->mctx, DNS_MESSAGE_INTENTPARSE,
+ &client->message);
+
+ client->sendbuf = isc_mem_get(client->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 {
+ 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;
+
+ /*
+ * 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 };
+ }
+
+ 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->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) {
+ clientmgr_detach(&client->manager);
+ }
+ if (client->mctx != NULL) {
+ 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;
+}
+
+static void
+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) {
+ int i;
+
+ MTRACE("clientmgr_destroy");
+
+ isc_refcount_destroy(&manager->references);
+ manager->magic = 0;
+
+ for (i = 0; i < manager->ncpus * CLIENT_NMCTXS_PERCPU; i++) {
+ isc_mem_detach(&manager->mctxpool[i]);
+ }
+ isc_mem_put(manager->mctx, manager->mctxpool,
+ manager->ncpus * CLIENT_NMCTXS_PERCPU *
+ sizeof(isc_mem_t *));
+
+ if (manager->interface != NULL) {
+ ns_interface_detach(&manager->interface);
+ }
+
+ isc_mutex_destroy(&manager->lock);
+ isc_mutex_destroy(&manager->reclock);
+
+ if (manager->excl != NULL) {
+ isc_task_detach(&manager->excl);
+ }
+
+ for (i = 0; i < manager->ncpus * CLIENT_NTASKS_PERCPU; i++) {
+ if (manager->taskpool[i] != NULL) {
+ isc_task_detach(&manager->taskpool[i]);
+ }
+ }
+ isc_mem_put(manager->mctx, manager->taskpool,
+ manager->ncpus * CLIENT_NTASKS_PERCPU *
+ sizeof(isc_task_t *));
+ ns_server_detach(&manager->sctx);
+
+ isc_mem_put(manager->mctx, manager, sizeof(*manager));
+}
+
+isc_result_t
+ns_clientmgr_create(isc_mem_t *mctx, ns_server_t *sctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, ns_interface_t *interface,
+ int ncpus, ns_clientmgr_t **managerp) {
+ ns_clientmgr_t *manager;
+ isc_result_t result;
+ int i;
+ int npools;
+
+ manager = isc_mem_get(mctx, sizeof(*manager));
+ *manager = (ns_clientmgr_t){ .magic = 0 };
+
+ isc_mutex_init(&manager->lock);
+ isc_mutex_init(&manager->reclock);
+
+ manager->excl = NULL;
+ result = isc_taskmgr_excltask(taskmgr, &manager->excl);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_reclock;
+ }
+
+ manager->mctx = mctx;
+ manager->taskmgr = taskmgr;
+ manager->timermgr = timermgr;
+ manager->ncpus = ncpus;
+
+ ns_interface_attach(interface, &manager->interface);
+
+ manager->exiting = false;
+ int ntasks = CLIENT_NTASKS_PERCPU * manager->ncpus;
+ manager->taskpool = isc_mem_get(mctx, ntasks * sizeof(isc_task_t *));
+ for (i = 0; i < ntasks; i++) {
+ manager->taskpool[i] = NULL;
+ result = isc_task_create_bound(manager->taskmgr, 20,
+ &manager->taskpool[i],
+ i % CLIENT_NTASKS_PERCPU);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+ isc_refcount_init(&manager->references, 1);
+ manager->sctx = NULL;
+ ns_server_attach(sctx, &manager->sctx);
+
+ ISC_LIST_INIT(manager->recursing);
+
+ npools = CLIENT_NMCTXS_PERCPU * manager->ncpus;
+ manager->mctxpool = isc_mem_get(manager->mctx,
+ npools * sizeof(isc_mem_t *));
+ for (i = 0; i < npools; i++) {
+ manager->mctxpool[i] = NULL;
+ isc_mem_create(&manager->mctxpool[i]);
+ isc_mem_setname(manager->mctxpool[i], "client", NULL);
+ }
+
+ manager->magic = MANAGER_MAGIC;
+
+ MTRACE("create");
+
+ *managerp = manager;
+
+ return (ISC_R_SUCCESS);
+
+cleanup_reclock:
+ isc_mutex_destroy(&manager->reclock);
+ isc_mutex_destroy(&manager->lock);
+
+ isc_mem_put(mctx, manager, sizeof(*manager));
+
+ return (result);
+}
+
+void
+ns_clientmgr_shutdown(ns_clientmgr_t *manager) {
+ REQUIRE(VALID_MANAGER(manager));
+
+ LOCK(&manager->reclock);
+ for (ns_client_t *client = ISC_LIST_HEAD(manager->recursing);
+ client != NULL; client = ISC_LIST_NEXT(client, rlink))
+ {
+ ns_query_cancel(client);
+ }
+ UNLOCK(&manager->reclock);
+}
+
+void
+ns_clientmgr_destroy(ns_clientmgr_t **managerp) {
+ isc_result_t result;
+ ns_clientmgr_t *manager;
+ bool unlock = false;
+
+ REQUIRE(managerp != NULL);
+ manager = *managerp;
+ *managerp = NULL;
+ REQUIRE(VALID_MANAGER(manager));
+
+ MTRACE("destroy");
+
+ /*
+ * Check for success because we may already be task-exclusive
+ * at this point. Only if we succeed at obtaining an exclusive
+ * lock now will we need to relinquish it later.
+ */
+ result = isc_task_beginexclusive(manager->excl);
+ if (result == ISC_R_SUCCESS) {
+ unlock = true;
+ }
+
+ manager->exiting = true;
+
+ if (unlock) {
+ isc_task_endexclusive(manager->excl);
+ }
+
+ if (isc_refcount_decrement(&manager->references) == 1) {
+ clientmgr_destroy(manager);
+ }
+}
+
+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 =
+ ns_interfacemgr_getaclenv(client->manager->interface->mgr);
+ isc_netaddr_t tmpnetaddr;
+ int match;
+
+ if (acl == NULL) {
+ if (default_allow) {
+ goto allow;
+ } else {
+ goto deny;
+ }
+ }
+
+ if (netaddr == NULL) {
+ isc_netaddr_fromsockaddr(&tmpnetaddr, &client->peeraddr);
+ netaddr = &tmpnetaddr;
+ }
+
+ result = dns_acl_match(netaddr, 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_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..1b8b8d0
--- /dev/null
+++ b/lib/ns/hooks.c
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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>
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#elif _WIN32
+#include <windows.h>
+#endif /* if HAVE_DLFCN_H */
+
+#include <isc/errno.h>
+#include <isc/list.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/platform.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;
+ void *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];
+LIBNS_EXTERNAL_DATA ns_hooktable_t *ns__hook_table = &default_hooktable;
+
+isc_result_t
+ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) {
+ int result;
+
+#ifndef WIN32
+ /*
+ * 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);
+ }
+#else /* ifndef WIN32 */
+ /*
+ * On Windows, always copy 'src' do 'dst'.
+ */
+ result = snprintf(dst, dstsize, "%s", src);
+#endif /* ifndef WIN32 */
+
+ if (result < 0) {
+ return (isc_errno_toresult(errno));
+ } else if ((size_t)result >= dstsize) {
+ return (ISC_R_NOSPACE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+#if HAVE_DLFCN_H && HAVE_DLOPEN
+static isc_result_t
+load_symbol(void *handle, const char *modpath, const char *symbol_name,
+ void **symbolp) {
+ void *symbol = NULL;
+
+ REQUIRE(handle != NULL);
+ REQUIRE(symbolp != NULL && *symbolp == NULL);
+
+ /*
+ * Clear any pre-existing error conditions before running dlsym().
+ * (In this case, we expect dlsym() to return non-NULL values
+ * and will always return an error if it returns NULL, but
+ * this ensures that we'll report the correct error condition
+ * if there is one.)
+ */
+ dlerror();
+ symbol = dlsym(handle, symbol_name);
+ if (symbol == NULL) {
+ const char *errmsg = dlerror();
+ 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 isc_result_t
+load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) {
+ isc_result_t result;
+ void *handle = NULL;
+ ns_plugin_t *plugin = NULL;
+ ns_plugin_check_t *check_func = NULL;
+ ns_plugin_register_t *register_func = NULL;
+ ns_plugin_destroy_t *destroy_func = NULL;
+ ns_plugin_version_t *version_func = NULL;
+ int version, flags;
+
+ REQUIRE(pluginp != NULL && *pluginp == NULL);
+
+ flags = RTLD_LAZY | RTLD_LOCAL;
+#if defined(RTLD_DEEPBIND) && !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__
+ flags |= RTLD_DEEPBIND;
+#endif /* if defined(RTLD_DEEPBIND) && !__SANITIZE_ADDRESS__ && \
+ !__SANITIZE_THREAD__ */
+
+ handle = dlopen(modpath, flags);
+ if (handle == NULL) {
+ const char *errmsg = dlerror();
+ 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);
+ return (ISC_R_FAILURE);
+ }
+
+ CHECK(load_symbol(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(handle, modpath, "plugin_check",
+ (void **)&check_func));
+ CHECK(load_symbol(handle, modpath, "plugin_register",
+ (void **)&register_func));
+ CHECK(load_symbol(handle, modpath, "plugin_destroy",
+ (void **)&destroy_func));
+
+ plugin = isc_mem_get(mctx, sizeof(*plugin));
+ memset(plugin, 0, sizeof(*plugin));
+ isc_mem_attach(mctx, &plugin->mctx);
+ plugin->handle = handle;
+ plugin->modpath = isc_mem_strdup(plugin->mctx, modpath);
+ plugin->check_func = check_func;
+ plugin->register_func = register_func;
+ plugin->destroy_func = destroy_func;
+
+ ISC_LINK_INIT(plugin, link);
+
+ *pluginp = plugin;
+ plugin = NULL;
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ 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));
+
+ if (plugin != NULL) {
+ isc_mem_putanddetach(&plugin->mctx, plugin,
+ sizeof(*plugin));
+ }
+
+ (void)dlclose(handle);
+ }
+
+ 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);
+ }
+ if (plugin->handle != NULL) {
+ (void)dlclose(plugin->handle);
+ }
+ if (plugin->modpath != NULL) {
+ isc_mem_free(plugin->mctx, plugin->modpath);
+ }
+
+ isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin));
+}
+#elif _WIN32
+static isc_result_t
+load_symbol(HMODULE handle, const char *modpath, const char *symbol_name,
+ void **symbolp) {
+ void *symbol = NULL;
+
+ REQUIRE(handle != NULL);
+ REQUIRE(symbolp != NULL && *symbolp == NULL);
+
+ symbol = GetProcAddress(handle, symbol_name);
+ if (symbol == NULL) {
+ int errstatus = GetLastError();
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
+ NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
+ "failed to look up symbol %s in "
+ "plugin '%s': %d",
+ symbol_name, modpath, errstatus);
+ return (ISC_R_FAILURE);
+ }
+
+ *symbolp = symbol;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) {
+ isc_result_t result;
+ HMODULE handle;
+ ns_plugin_t *plugin = NULL;
+ ns_plugin_register_t *register_func = NULL;
+ ns_plugin_destroy_t *destroy_func = NULL;
+ ns_plugin_version_t *version_func = NULL;
+ int version;
+
+ REQUIRE(pluginp != NULL && *pluginp == NULL);
+
+ handle = LoadLibraryA(modpath);
+ if (handle == NULL) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ CHECK(load_symbol(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(handle, modpath, "plugin_register",
+ (void **)&register_func));
+ CHECK(load_symbol(handle, modpath, "plugin_destroy",
+ (void **)&destroy_func));
+
+ plugin = isc_mem_get(mctx, sizeof(*plugin));
+ memset(plugin, 0, sizeof(*plugin));
+ isc_mem_attach(mctx, &plugin->mctx);
+ plugin->handle = handle;
+ plugin->modpath = isc_mem_strdup(plugin->mctx, modpath);
+ plugin->register_func = register_func;
+ plugin->destroy_func = destroy_func;
+
+ ISC_LINK_INIT(plugin, link);
+
+ *pluginp = plugin;
+ plugin = NULL;
+
+cleanup:
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
+ NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
+ "failed to dynamically load "
+ "plugin '%s': %d (%s)",
+ modpath, GetLastError(),
+ isc_result_totext(result));
+
+ if (plugin != NULL) {
+ isc_mem_putanddetach(&plugin->mctx, plugin,
+ sizeof(*plugin));
+ }
+
+ if (handle != NULL) {
+ FreeLibrary(handle);
+ }
+ }
+
+ 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);
+ }
+ if (plugin->handle != NULL) {
+ FreeLibrary(plugin->handle);
+ }
+
+ if (plugin->modpath != NULL) {
+ isc_mem_free(plugin->mctx, plugin->modpath);
+ }
+
+ isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin));
+}
+#else /* HAVE_DLFCN_H || _WIN32 */
+static isc_result_t
+load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) {
+ UNUSED(mctx);
+ UNUSED(modpath);
+ UNUSED(pluginp);
+
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
+ ISC_LOG_ERROR, "plugin support is not implemented");
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+static void
+unload_plugin(ns_plugin_t **pluginp) {
+ UNUSED(pluginp);
+}
+#endif /* HAVE_DLFCN_H */
+
+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/.clang-format b/lib/ns/include/.clang-format
new file mode 120000
index 0000000..0e62f72
--- /dev/null
+++ b/lib/ns/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format.headers \ No newline at end of file
diff --git a/lib/ns/include/Makefile.in b/lib/ns/include/Makefile.in
new file mode 100644
index 0000000..5863acb
--- /dev/null
+++ b/lib/ns/include/Makefile.in
@@ -0,0 +1,19 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+SUBDIRS = ns
+TARGETS =
+
+@BIND9_MAKE_RULES@
diff --git a/lib/ns/include/ns/Makefile.in b/lib/ns/include/ns/Makefile.in
new file mode 100644
index 0000000..6976e1e
--- /dev/null
+++ b/lib/ns/include/ns/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+HEADERS = client.h hooks.h interfacemgr.h lib.h listenlist.h log.h \
+ notify.h query.h server.h sortlist.h stats.h \
+ types.h update.h version.h xfrout.h
+SUBDIRS =
+TARGETS =
+
+@BIND9_MAKE_RULES@
+
+installdirs:
+ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/ns
+
+install:: installdirs
+ for i in ${HEADERS}; do \
+ ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/ns || exit 1 ; \
+ done
+
+uninstall::
+ for i in ${HEADERS}; do \
+ rm -f ${DESTDIR}${includedir}/ns/$$i || exit 1 ; \
+ done
diff --git a/lib/ns/include/ns/client.h b/lib/ns/include/ns/client.h
new file mode 100644
index 0000000..d1e2fde
--- /dev/null
+++ b/lib/ns/include/ns/client.h
@@ -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.
+ */
+
+#ifndef NS_CLIENT_H
+#define NS_CLIENT_H 1
+
+/*****
+***** 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/platform.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/tcpmsg.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;
+ ns_server_t *sctx;
+ isc_taskmgr_t *taskmgr;
+ isc_timermgr_t *timermgr;
+ isc_task_t *excl;
+ isc_refcount_t references;
+ int ncpus;
+
+ /* Attached by clients, needed for e.g. recursion */
+ isc_task_t **taskpool;
+
+ ns_interface_t *interface;
+
+ /* Lock covers manager state. */
+ isc_mutex_t lock;
+ bool exiting;
+
+ /* Lock covers the recursing list */
+ isc_mutex_t reclock;
+ client_list_t recursing; /*%< Recursing clients */
+
+ /*%< mctx pool for clients. */
+ isc_mem_t **mctxpool;
+};
+
+/*% nameserver client structure */
+struct ns_client {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ 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;
+ dns_message_t *message;
+ unsigned char *sendbuf;
+ dns_rdataset_t *opt;
+ uint16_t udpsize;
+ uint16_t extflags;
+ int16_t ednsversion; /* -1 noedns */
+ 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;
+ isc_dscp_t dscp;
+ /*%
+ * 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
+
+#if defined(_WIN32) && !defined(_WIN64)
+LIBNS_EXTERNAL_DATA extern atomic_uint_fast32_t ns_client_requests;
+#else /* if defined(_WIN32) && !defined(_WIN64) */
+LIBNS_EXTERNAL_DATA extern atomic_uint_fast64_t ns_client_requests;
+#endif /* if defined(_WIN32) && !defined(_WIN64) */
+
+/***
+ *** 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_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(isc_mem_t *mctx, ns_server_t *sctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, ns_interface_t *ifp, int ncpus,
+ 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_destroy(ns_clientmgr_t **managerp);
+/*%<
+ * Destroy 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.
+ */
+
+#endif /* NS_CLIENT_H */
diff --git a/lib/ns/include/ns/hooks.h b/lib/ns/include/ns/hooks.h
new file mode 100644
index 0000000..db3846d
--- /dev/null
+++ b/lib/ns/include/ns/hooks.h
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef NS_HOOKS_H
+#define NS_HOOKS_H 1
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/list.h>
+#include <isc/magic.h>
+#include <isc/result.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.
+ */
+
+/*!
+ * Currently-defined hook points. So long as these are unique,
+ * the order in which they are declared is unimportant, but
+ * 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.
+ */
+LIBNS_EXTERNAL_DATA extern ns_hooktable_t *ns__hook_table;
+
+/*
+ * 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'.
+ *
+ * On Windows, 'src' is always copied to 'dst' verbatim.
+ *
+ * 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.
+ */
+#endif /* NS_HOOKS_H */
diff --git a/lib/ns/include/ns/interfacemgr.h b/lib/ns/include/ns/interfacemgr.h
new file mode 100644
index 0000000..9f696e2
--- /dev/null
+++ b/lib/ns/include/ns/interfacemgr.h
@@ -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.
+ */
+
+#ifndef NS_INTERFACEMGR_H
+#define NS_INTERFACEMGR_H 1
+
+/*****
+***** 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/socket.h>
+
+#include <dns/geoip.h>
+#include <dns/result.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 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;
+ isc_refcount_t references;
+ unsigned int generation; /*%< Generation number. */
+ isc_sockaddr_t addr; /*%< Address and port. */
+ unsigned int flags; /*%< Interface flags */
+ char name[32]; /*%< Null terminated. */
+ dns_dispatch_t *udpdispatch[MAX_UDP_DISPATCH];
+ /*%< UDP dispatchers. */
+ isc_socket_t *tcpsocket; /*%< TCP socket. */
+ isc_nmsocket_t *udplistensocket;
+ isc_nmsocket_t *tcplistensocket;
+ isc_dscp_t dscp; /*%< "listen-on" DSCP value */
+ 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) */
+ int nudpdispatch; /*%< Number of UDP dispatches */
+ 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_socketmgr_t *socketmgr, isc_nm_t *nm,
+ dns_dispatchmgr_t *dispatchmgr, isc_task_t *task,
+ unsigned int udpdisp, dns_geoip_databases_t *geoip,
+ int ncpus, 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);
+/*%<
+ * 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.
+ *
+ * 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_attach(ns_interface_t *source, ns_interface_t **target);
+
+void
+ns_interface_detach(ns_interface_t **targetp);
+
+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_interface_t *
+ns__interfacemgr_getif(ns_interfacemgr_t *mgr);
+ns_interface_t *
+ns__interfacemgr_nextif(ns_interface_t *ifp);
+/*
+ * Functions to allow external callers to walk the interfaces list.
+ * (Not intended for use outside this module and associated tests.)
+ */
+#endif /* NS_INTERFACEMGR_H */
diff --git a/lib/ns/include/ns/lib.h b/lib/ns/include/ns/lib.h
new file mode 100644
index 0000000..8c1d113
--- /dev/null
+++ b/lib/ns/include/ns/lib.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
+
+/*! \file include/ns/lib.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+LIBNS_EXTERNAL_DATA extern unsigned int ns_pps;
+
+isc_result_t
+ns_lib_init(void);
+/*%<
+ * A set of initialization procedures used in the NS library.
+ */
+
+void
+ns_lib_shutdown(void);
+/*%<
+ * Free temporary resources allocated in ns_lib_init().
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/ns/include/ns/listenlist.h b/lib/ns/include/ns/listenlist.h
new file mode 100644
index 0000000..74ff87b
--- /dev/null
+++ b/lib/ns/include/ns/listenlist.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.
+ */
+
+#ifndef NS_LISTENLIST_H
+#define NS_LISTENLIST_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * "Listen lists", as in the "listen-on" configuration statement.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/net.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;
+ isc_dscp_t dscp; /* -1 = not set, 0..63 */
+ dns_acl_t *acl;
+ ISC_LINK(ns_listenelt_t) link;
+};
+
+struct ns_listenlist {
+ isc_mem_t *mctx;
+ int refcount;
+ ISC_LIST(ns_listenelt_t) elts;
+};
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp,
+ dns_acl_t *acl, ns_listenelt_t **target);
+/*%<
+ * Create a listen-on list element.
+ */
+
+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, isc_dscp_t dscp,
+ bool enabled, 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).
+ */
+
+#endif /* NS_LISTENLIST_H */
diff --git a/lib/ns/include/ns/log.h b/lib/ns/include/ns/log.h
new file mode 100644
index 0000000..eb12ff6
--- /dev/null
+++ b/lib/ns/include/ns/log.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef NS_LOG_H
+#define NS_LOG_H 1
+
+/*! \file */
+
+#include <isc/log.h>
+#include <isc/types.h>
+
+LIBNS_EXTERNAL_DATA extern isc_log_t *ns_lctx;
+LIBNS_EXTERNAL_DATA extern isc_logcategory_t ns_categories[];
+LIBNS_EXTERNAL_DATA 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.
+ */
+#endif /* NS_LOG_H */
diff --git a/lib/ns/include/ns/notify.h b/lib/ns/include/ns/notify.h
new file mode 100644
index 0000000..2c5fcf3
--- /dev/null
+++ b/lib/ns/include/ns/notify.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef NS_NOTIFY_H
+#define NS_NOTIFY_H 1
+
+#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.
+ */
+
+#endif /* NS_NOTIFY_H */
diff --git a/lib/ns/include/ns/query.h b/lib/ns/include/ns/query.h
new file mode 100644
index 0000000..be0dadd
--- /dev/null
+++ b/lib/ns/include/ns/query.h
@@ -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 NS_QUERY_H
+#define NS_QUERY_H 1
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/netaddr.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;
+ 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 */
+};
+
+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_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.)
+ */
+
+#endif /* NS_QUERY_H */
diff --git a/lib/ns/include/ns/server.h b/lib/ns/include/ns/server.h
new file mode 100644
index 0000000..6ace9f3
--- /dev/null
+++ b/lib/ns/include/ns/server.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef NS_SERVER_H
+#define NS_SERVER_H 1
+
+/*! \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/types.h>
+
+#define NS_EVENT_CLIENTCONTROL (ISC_EVENTCLASS_NS + 0)
+
+#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 */
+
+/*%
+ * 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;
+
+ /*% 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;
+ ns_hostnamecb_t gethostname;
+
+ /*% 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.
+ */
+#endif /* NS_SERVER_H */
diff --git a/lib/ns/include/ns/sortlist.h b/lib/ns/include/ns/sortlist.h
new file mode 100644
index 0000000..a2eedd9
--- /dev/null
+++ b/lib/ns/include/ns/sortlist.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef NS_SORTLIST_H
+#define NS_SORTLIST_H 1
+
+/*! \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,
+ const 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, const 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.
+ */
+
+#endif /* NS_SORTLIST_H */
diff --git a/lib/ns/include/ns/stats.h b/lib/ns/include/ns/stats.h
new file mode 100644
index 0000000..b9564aa
--- /dev/null
+++ b/lib/ns/include/ns/stats.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef NS_STATS_H
+#define NS_STATS_H 1
+
+/*! \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);
+
+#endif /* NS_STATS_H */
diff --git a/lib/ns/include/ns/types.h b/lib/ns/include/ns/types.h
new file mode 100644
index 0000000..f16f9a0
--- /dev/null
+++ b/lib/ns/include/ns/types.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef NS_TYPES_H
+#define NS_TYPES_H 1
+
+/*! \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 enum { ns_cookiealg_aes, ns_cookiealg_siphash24 } ns_cookiealg_t;
+
+#define NS_COOKIE_VERSION_1 1
+
+#endif /* NS_TYPES_H */
diff --git a/lib/ns/include/ns/update.h b/lib/ns/include/ns/update.h
new file mode 100644
index 0000000..72ac604
--- /dev/null
+++ b/lib/ns/include/ns/update.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#ifndef NS_UPDATE_H
+#define NS_UPDATE_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * RFC2136 Dynamic Update
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <dns/result.h>
+#include <dns/types.h>
+
+/***
+ *** Types.
+ ***/
+
+/***
+ *** Functions
+ ***/
+
+void
+ns_update_start(ns_client_t *client, isc_nmhandle_t *handle,
+ isc_result_t sigresult);
+
+#endif /* NS_UPDATE_H */
diff --git a/lib/ns/include/ns/version.h b/lib/ns/include/ns/version.h
new file mode 100644
index 0000000..e342717
--- /dev/null
+++ b/lib/ns/include/ns/version.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file include/ns/version.h */
+
+#include <isc/platform.h>
+
+LIBNS_EXTERNAL_DATA extern const char ns_version[];
diff --git a/lib/ns/include/ns/xfrout.h b/lib/ns/include/ns/xfrout.h
new file mode 100644
index 0000000..8f0809e
--- /dev/null
+++ b/lib/ns/include/ns/xfrout.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.
+ */
+
+#ifndef NS_XFROUT_H
+#define NS_XFROUT_H 1
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * Outgoing zone transfers (AXFR + IXFR).
+ */
+
+/***
+ *** Functions
+ ***/
+
+void
+ns_xfr_start(ns_client_t *client, dns_rdatatype_t xfrtype);
+
+#endif /* NS_XFROUT_H */
diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c
new file mode 100644
index 0000000..216e274
--- /dev/null
+++ b/lib/ns/interfacemgr.c
@@ -0,0 +1,1263 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 USE_ROUTE_SOCKET 1
+#define ROUTE_SOCKET_PROTOCOL PF_ROUTE
+#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 ROUTE_SOCKET_PROTOCOL PF_NETLINK
+#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) \
+ */
+
+#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 *excl; /*%< Exclusive task. */
+ isc_timermgr_t *timermgr; /*%< Timer manager. */
+ isc_socketmgr_t *socketmgr; /*%< Socket 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 */
+ unsigned int udpdisp; /*%< UDP dispatch count */
+ atomic_bool shuttingdown; /*%< Interfacemgr is shutting
+ * down */
+#ifdef USE_ROUTE_SOCKET
+ isc_task_t *task;
+ isc_socket_t *route;
+ unsigned char buf[2048];
+#endif /* ifdef USE_ROUTE_SOCKET */
+};
+
+static void
+purge_old_interfaces(ns_interfacemgr_t *mgr);
+
+static void
+clearlistenon(ns_interfacemgr_t *mgr);
+
+#ifdef USE_ROUTE_SOCKET
+static void
+route_event(isc_task_t *task, isc_event_t *event) {
+ isc_socketevent_t *sevent = NULL;
+ ns_interfacemgr_t *mgr = NULL;
+ isc_region_t r;
+ isc_result_t result;
+ struct MSGHDR *rtm;
+ bool done = true;
+
+ UNUSED(task);
+
+ REQUIRE(event->ev_type == ISC_SOCKEVENT_RECVDONE);
+ mgr = event->ev_arg;
+ sevent = (isc_socketevent_t *)event;
+
+ if (sevent->result != ISC_R_SUCCESS) {
+ if (sevent->result != ISC_R_CANCELED) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "automatic interface scanning "
+ "terminated: %s",
+ isc_result_totext(sevent->result));
+ }
+ ns_interfacemgr_detach(&mgr);
+ isc_event_free(&event);
+ return;
+ }
+
+ rtm = (struct MSGHDR *)mgr->buf;
+#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);
+ ns_interfacemgr_detach(&mgr);
+ isc_event_free(&event);
+ return;
+ }
+#endif /* ifdef RTM_VERSION */
+
+ switch (rtm->MSGTYPE) {
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ if (mgr->route != NULL && mgr->sctx->interface_auto) {
+ ns_interfacemgr_scan(mgr, false);
+ }
+ break;
+ default:
+ break;
+ }
+
+ LOCK(&mgr->lock);
+ if (mgr->route != NULL) {
+ /*
+ * Look for next route event.
+ */
+ r.base = mgr->buf;
+ r.length = sizeof(mgr->buf);
+ result = isc_socket_recv(mgr->route, &r, 1, mgr->task,
+ route_event, mgr);
+ if (result == ISC_R_SUCCESS) {
+ done = false;
+ }
+ }
+ UNLOCK(&mgr->lock);
+
+ if (done) {
+ ns_interfacemgr_detach(&mgr);
+ }
+ isc_event_free(&event);
+ return;
+}
+#endif /* ifdef USE_ROUTE_SOCKET */
+
+isc_result_t
+ns_interfacemgr_create(isc_mem_t *mctx, ns_server_t *sctx,
+ isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr,
+ isc_socketmgr_t *socketmgr, isc_nm_t *nm,
+ dns_dispatchmgr_t *dispatchmgr, isc_task_t *task,
+ unsigned int udpdisp, dns_geoip_databases_t *geoip,
+ int ncpus, ns_interfacemgr_t **mgrp) {
+ isc_result_t result;
+ ns_interfacemgr_t *mgr;
+
+#ifndef USE_ROUTE_SOCKET
+ UNUSED(task);
+#endif /* ifndef USE_ROUTE_SOCKET */
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(mgrp != NULL);
+ REQUIRE(*mgrp == NULL);
+
+ mgr = isc_mem_get(mctx, sizeof(*mgr));
+
+ mgr->mctx = NULL;
+ isc_mem_attach(mctx, &mgr->mctx);
+
+ mgr->sctx = NULL;
+ ns_server_attach(sctx, &mgr->sctx);
+
+ isc_mutex_init(&mgr->lock);
+
+ mgr->excl = NULL;
+ result = isc_taskmgr_excltask(taskmgr, &mgr->excl);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_lock;
+ }
+
+ mgr->taskmgr = taskmgr;
+ mgr->timermgr = timermgr;
+ mgr->socketmgr = socketmgr;
+ mgr->nm = nm;
+ mgr->dispatchmgr = dispatchmgr;
+ mgr->generation = 1;
+ mgr->listenon4 = NULL;
+ mgr->listenon6 = NULL;
+ mgr->udpdisp = udpdisp;
+ mgr->ncpus = ncpus;
+ 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_ctx;
+ }
+ ns_listenlist_attach(mgr->listenon4, &mgr->listenon6);
+
+ result = dns_aclenv_init(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) */
+
+#ifdef USE_ROUTE_SOCKET
+ mgr->route = NULL;
+ result = isc_socket_create(mgr->socketmgr, ROUTE_SOCKET_PROTOCOL,
+ isc_sockettype_raw, &mgr->route);
+ switch (result) {
+ case ISC_R_NOPERM:
+ case ISC_R_SUCCESS:
+ case ISC_R_NOTIMPLEMENTED:
+ case ISC_R_FAMILYNOSUPPORT:
+ break;
+ default:
+ goto cleanup_aclenv;
+ }
+
+ mgr->task = NULL;
+ if (mgr->route != NULL) {
+ isc_task_attach(task, &mgr->task);
+ }
+ isc_refcount_init(&mgr->references, (mgr->route != NULL) ? 2 : 1);
+#else /* ifdef USE_ROUTE_SOCKET */
+ isc_refcount_init(&mgr->references, 1);
+#endif /* ifdef USE_ROUTE_SOCKET */
+ mgr->magic = IFMGR_MAGIC;
+ *mgrp = mgr;
+
+#ifdef USE_ROUTE_SOCKET
+ if (mgr->route != NULL) {
+ isc_region_t r = { mgr->buf, sizeof(mgr->buf) };
+
+ result = isc_socket_recv(mgr->route, &r, 1, mgr->task,
+ route_event, mgr);
+ if (result != ISC_R_SUCCESS) {
+ isc_task_detach(&mgr->task);
+ isc_socket_detach(&mgr->route);
+ ns_interfacemgr_detach(&mgr);
+ }
+ }
+#endif /* ifdef USE_ROUTE_SOCKET */
+ return (ISC_R_SUCCESS);
+
+#ifdef USE_ROUTE_SOCKET
+cleanup_aclenv:
+ dns_aclenv_destroy(&mgr->aclenv);
+#endif /* ifdef USE_ROUTE_SOCKET */
+cleanup_listenon:
+ ns_listenlist_detach(&mgr->listenon4);
+ ns_listenlist_detach(&mgr->listenon6);
+cleanup_lock:
+ isc_mutex_destroy(&mgr->lock);
+cleanup_ctx:
+ 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);
+
+#ifdef USE_ROUTE_SOCKET
+ if (mgr->route != NULL) {
+ isc_socket_detach(&mgr->route);
+ }
+ if (mgr->task != NULL) {
+ isc_task_detach(&mgr->task);
+ }
+#endif /* ifdef USE_ROUTE_SOCKET */
+ dns_aclenv_destroy(&mgr->aclenv);
+ ns_listenlist_detach(&mgr->listenon4);
+ ns_listenlist_detach(&mgr->listenon6);
+ clearlistenon(mgr);
+ isc_mutex_destroy(&mgr->lock);
+ if (mgr->sctx != NULL) {
+ ns_server_detach(&mgr->sctx);
+ }
+ if (mgr->excl != NULL) {
+ isc_task_detach(&mgr->excl);
+ }
+ 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) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ return (&mgr->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);
+#ifdef USE_ROUTE_SOCKET
+ LOCK(&mgr->lock);
+ if (mgr->route != NULL) {
+ isc_socket_cancel(mgr->route, mgr->task, ISC_SOCKCANCEL_RECV);
+ isc_socket_detach(&mgr->route);
+ isc_task_detach(&mgr->task);
+ }
+ UNLOCK(&mgr->lock);
+#endif /* ifdef USE_ROUTE_SOCKET */
+ purge_old_interfaces(mgr);
+}
+
+static isc_result_t
+ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
+ const char *name, ns_interface_t **ifpret) {
+ ns_interface_t *ifp = NULL;
+ isc_result_t result;
+ int disp;
+
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ ifp = isc_mem_get(mgr->mctx, sizeof(*ifp));
+ *ifp = (ns_interface_t){ .generation = mgr->generation,
+ .addr = *addr,
+ .dscp = -1 };
+
+ strlcpy(ifp->name, name, sizeof(ifp->name));
+
+ isc_mutex_init(&ifp->lock);
+
+ for (disp = 0; disp < MAX_UDP_DISPATCH; disp++) {
+ ifp->udpdispatch[disp] = NULL;
+ }
+
+ /*
+ * Create a single TCP client object. It will replace itself
+ * with a new one as soon as it gets a connection, so the actual
+ * connections will be handled in parallel even though there is
+ * only one client initially.
+ */
+ isc_refcount_init(&ifp->ntcpaccepting, 0);
+ isc_refcount_init(&ifp->ntcpactive, 0);
+
+ ISC_LINK_INIT(ifp, link);
+
+ ns_interfacemgr_attach(mgr, &ifp->mgr);
+ isc_refcount_init(&ifp->references, 1);
+ ifp->magic = IFACE_MAGIC;
+
+ LOCK(&mgr->lock);
+ ISC_LIST_APPEND(mgr->interfaces, ifp, link);
+ UNLOCK(&mgr->lock);
+
+ result = ns_clientmgr_create(mgr->mctx, mgr->sctx, mgr->taskmgr,
+ mgr->timermgr, ifp, mgr->ncpus,
+ &ifp->clientmgr);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "ns_clientmgr_create() failed: %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ *ifpret = ifp;
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ LOCK(&ifp->mgr->lock);
+ ISC_LIST_UNLINK(ifp->mgr->interfaces, ifp, link);
+ UNLOCK(&ifp->mgr->lock);
+
+ ifp->magic = 0;
+ ns_interfacemgr_detach(&ifp->mgr);
+ isc_refcount_decrement(&ifp->references);
+ isc_refcount_destroy(&ifp->references);
+ isc_mutex_destroy(&ifp->lock);
+
+ isc_mem_put(mgr->mctx, ifp, sizeof(*ifp));
+ return (ISC_R_UNEXPECTED);
+}
+
+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));
+ }
+
+#if 0
+#ifndef ISC_ALLOW_MAPPED
+ isc_socket_ipv6only(ifp->tcpsocket,true);
+#endif /* ifndef ISC_ALLOW_MAPPED */
+
+ if (ifp->dscp != -1) {
+ isc_socket_dscp(ifp->tcpsocket,ifp->dscp);
+ }
+
+ (void)isc_socket_filter(ifp->tcpsocket,"dataready");
+#endif /* if 0 */
+ return (result);
+}
+
+static isc_result_t
+ns_interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
+ const char *name, ns_interface_t **ifpret, isc_dscp_t dscp,
+ bool *addr_in_use) {
+ isc_result_t result;
+ ns_interface_t *ifp = NULL;
+ REQUIRE(ifpret != NULL && *ifpret == NULL);
+ REQUIRE(addr_in_use == NULL || !*addr_in_use);
+
+ result = ns_interface_create(mgr, addr, name, &ifp);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ ifp->dscp = dscp;
+
+ 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:
+ LOCK(&ifp->mgr->lock);
+ ISC_LIST_UNLINK(ifp->mgr->interfaces, ifp, link);
+ UNLOCK(&ifp->mgr->lock);
+ ns_interface_shutdown(ifp);
+ ns_interface_detach(&ifp);
+ return (result);
+}
+
+void
+ns_interface_shutdown(ns_interface_t *ifp) {
+ 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->clientmgr != NULL) {
+ ns_clientmgr_shutdown(ifp->clientmgr);
+ ns_clientmgr_destroy(&ifp->clientmgr);
+ }
+}
+
+static void
+ns_interface_destroy(ns_interface_t *ifp) {
+ REQUIRE(NS_INTERFACE_VALID(ifp));
+
+ isc_mem_t *mctx = ifp->mgr->mctx;
+
+ ns_interface_shutdown(ifp);
+
+ for (int disp = 0; disp < ifp->nudpdispatch; disp++) {
+ if (ifp->udpdispatch[disp] != NULL) {
+ dns_dispatch_changeattributes(
+ ifp->udpdispatch[disp], 0,
+ DNS_DISPATCHATTR_NOLISTEN);
+ dns_dispatch_detach(&(ifp->udpdispatch[disp]));
+ }
+ }
+
+ if (ifp->tcpsocket != NULL) {
+ isc_socket_detach(&ifp->tcpsocket);
+ }
+
+ isc_mutex_destroy(&ifp->lock);
+
+ ns_interfacemgr_detach(&ifp->mgr);
+
+ isc_refcount_destroy(&ifp->ntcpactive);
+ isc_refcount_destroy(&ifp->ntcpaccepting);
+
+ ifp->magic = 0;
+
+ isc_mem_put(mctx, ifp, sizeof(*ifp));
+}
+
+void
+ns_interface_attach(ns_interface_t *source, ns_interface_t **target) {
+ REQUIRE(NS_INTERFACE_VALID(source));
+ isc_refcount_increment(&source->references);
+ *target = source;
+}
+
+void
+ns_interface_detach(ns_interface_t **targetp) {
+ ns_interface_t *target = *targetp;
+ *targetp = NULL;
+ REQUIRE(target != NULL);
+ REQUIRE(NS_INTERFACE_VALID(target));
+ if (isc_refcount_decrement(&target->references) == 1) {
+ ns_interface_destroy(target);
+ }
+}
+
+/*%
+ * 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, *next;
+ 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) {
+ char sabuf[256];
+ ISC_LIST_UNLINK(ifp->mgr->interfaces, ifp, link);
+ 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);
+ ns_interface_detach(&ifp);
+ }
+ }
+ UNLOCK(&mgr->lock);
+}
+
+static isc_result_t
+clearacl(isc_mem_t *mctx, dns_acl_t **aclp) {
+ dns_acl_t *newacl = NULL;
+ isc_result_t result;
+ result = dns_acl_create(mctx, 0, &newacl);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_acl_detach(aclp);
+ dns_acl_attach(newacl, aclp);
+ dns_acl_detach(&newacl);
+ return (ISC_R_SUCCESS);
+}
+
+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(ns_interfacemgr_t *mgr, isc_interface_t *interface) {
+ 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(mgr->aclenv.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(mgr->aclenv.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)) {
+ break;
+ }
+ }
+
+ if (old != NULL) {
+ isc_mem_put(mgr->mctx, addr, sizeof(*addr));
+ } else {
+ ISC_LIST_APPEND(mgr->listenon, addr, link);
+ }
+ UNLOCK(&mgr->lock);
+}
+
+static void
+clearlistenon(ns_interfacemgr_t *mgr) {
+ isc_sockaddr_t *old;
+
+ LOCK(&mgr->lock);
+ old = ISC_LIST_HEAD(mgr->listenon);
+ while (old != NULL) {
+ ISC_LIST_UNLINK(mgr->listenon, old, link);
+ isc_mem_put(mgr->mctx, old, sizeof(*old));
+ old = ISC_LIST_HEAD(mgr->listenon);
+ }
+ UNLOCK(&mgr->lock);
+}
+
+static isc_result_t
+do_scan(ns_interfacemgr_t *mgr, bool verbose) {
+ 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;
+ isc_sockaddr_t listen_addr;
+ ns_interface_t *ifp;
+ bool log_explicit = false;
+ bool dolistenon;
+ char sabuf[ISC_SOCKADDR_FORMATSIZE];
+ bool tried_listening;
+ bool all_addresses_in_use;
+
+ 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.
+ */
+#ifndef ISC_ALLOW_MAPPED
+ if (scan_ipv6 && isc_net_probe_ipv6only() != ISC_R_SUCCESS) {
+ ipv6only = false;
+ log_explicit = true;
+ }
+#endif /* ifndef ISC_ALLOW_MAPPED */
+ 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 (le->dscp != -1 && ifp->dscp == -1) {
+ ifp->dscp = le->dscp;
+ } else if (le->dscp != ifp->dscp) {
+ isc_sockaddr_format(&listen_addr, sabuf,
+ sizeof(sabuf));
+ isc_log_write(IFMGR_COMMON_LOGARGS,
+ ISC_LOG_WARNING,
+ "%s: conflicting DSCP "
+ "values, using %d",
+ sabuf, ifp->dscp);
+ }
+ } else {
+ isc_log_write(IFMGR_COMMON_LOGARGS,
+ ISC_LOG_INFO,
+ "listening on IPv6 "
+ "interfaces, port %u",
+ le->port);
+ result = ns_interface_setup(mgr, &listen_addr,
+ "<any>", &ifp,
+ le->dscp, 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 = clearacl(mgr->mctx, &mgr->aclenv.localhost);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_iter;
+ }
+ result = clearacl(mgr->mctx, &mgr->aclenv.localnets);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_iter;
+ }
+ 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;
+ 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(mgr, &interface);
+ 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 ipv6_wildcard = false;
+ isc_netaddr_t listen_netaddr;
+ isc_sockaddr_t listen_sockaddr;
+
+ /*
+ * Construct a socket address for this IP/port
+ * combination.
+ */
+ if (family == AF_INET) {
+ isc_netaddr_fromin(&listen_netaddr,
+ &interface.address.type.in);
+ } else {
+ isc_netaddr_fromin6(
+ &listen_netaddr,
+ &interface.address.type.in6);
+ isc_netaddr_setzone(&listen_netaddr,
+ interface.address.zone);
+ }
+ isc_sockaddr_fromnetaddr(&listen_sockaddr,
+ &listen_netaddr, le->port);
+
+ /*
+ * See if the address matches the listen-on statement;
+ * if not, ignore the interface.
+ */
+ (void)dns_acl_match(&listen_netaddr, NULL, le->acl,
+ &mgr->aclenv, &match, NULL);
+ if (match <= 0) {
+ 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 (le->dscp != -1 && ifp->dscp == -1) {
+ ifp->dscp = le->dscp;
+ } else if (le->dscp != ifp->dscp) {
+ isc_sockaddr_format(&listen_sockaddr,
+ sabuf,
+ sizeof(sabuf));
+ isc_log_write(IFMGR_COMMON_LOGARGS,
+ ISC_LOG_WARNING,
+ "%s: conflicting DSCP "
+ "values, using %d",
+ sabuf, ifp->dscp);
+ }
+ } else {
+ bool addr_in_use = false;
+
+ 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 = ns_interface_setup(
+ mgr, &listen_sockaddr, interface.name,
+ &ifp, le->dscp, &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) ? "IP"
+ "v4"
+ : "IP"
+ "v"
+ "6",
+ 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(__FILE__, __LINE__,
+ "interface iteration failed: %s",
+ isc_result_totext(result));
+ } else {
+ result = ((tried_listening && all_addresses_in_use)
+ ? ISC_R_ADDRINUSE
+ : ISC_R_SUCCESS);
+ }
+cleanup_iter:
+ isc_interfaceiter_destroy(&iter);
+ return (result);
+}
+
+static isc_result_t
+ns_interfacemgr_scan0(ns_interfacemgr_t *mgr, bool verbose) {
+ isc_result_t result;
+ bool purge = true;
+
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ mgr->generation++; /* Increment the generation count. */
+
+ result = do_scan(mgr, verbose);
+ 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);
+}
+
+isc_result_t
+ns_interfacemgr_scan(ns_interfacemgr_t *mgr, bool verbose) {
+ isc_result_t result;
+ bool unlock = false;
+
+ /*
+ * Check for success because we may already be task-exclusive
+ * at this point. Only if we succeed at obtaining an exclusive
+ * lock now will we need to relinquish it later.
+ */
+ result = isc_task_beginexclusive(mgr->excl);
+ if (result == ISC_R_SUCCESS) {
+ unlock = true;
+ }
+
+ result = ns_interfacemgr_scan0(mgr, verbose);
+
+ if (unlock) {
+ isc_task_endexclusive(mgr->excl);
+ }
+
+ return (result);
+}
+
+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) {
+ ns_interface_t *interface;
+
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ LOCK(&mgr->lock);
+ interface = ISC_LIST_HEAD(mgr->interfaces);
+ while (interface != NULL) {
+ if (interface->clientmgr != NULL) {
+ ns_client_dumprecursing(f, interface->clientmgr);
+ }
+ interface = ISC_LIST_NEXT(interface, link);
+ }
+ 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_interface_t *
+ns__interfacemgr_getif(ns_interfacemgr_t *mgr) {
+ ns_interface_t *head;
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+ LOCK(&mgr->lock);
+ head = ISC_LIST_HEAD(mgr->interfaces);
+ UNLOCK(&mgr->lock);
+ return (head);
+}
+
+ns_interface_t *
+ns__interfacemgr_nextif(ns_interface_t *ifp) {
+ ns_interface_t *next;
+ LOCK(&ifp->lock);
+ next = ISC_LIST_NEXT(ifp, link);
+ UNLOCK(&ifp->lock);
+ return (next);
+}
diff --git a/lib/ns/lib.c b/lib/ns/lib.c
new file mode 100644
index 0000000..0c4a18f
--- /dev/null
+++ b/lib/ns/lib.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/mem.h>
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/refcount.h>
+#include <isc/util.h>
+
+#include <dns/name.h>
+
+#include <ns/lib.h>
+
+/***
+ *** Globals
+ ***/
+
+LIBNS_EXTERNAL_DATA unsigned int ns_pps = 0U;
+
+/***
+ *** Private
+ ***/
+
+static isc_once_t init_once = ISC_ONCE_INIT;
+static isc_mem_t *ns_g_mctx = NULL;
+static bool initialize_done = false;
+static isc_refcount_t references;
+
+static void
+initialize(void) {
+ REQUIRE(!initialize_done);
+
+ isc_mem_create(&ns_g_mctx);
+
+ isc_refcount_init(&references, 0);
+ initialize_done = true;
+ return;
+}
+
+isc_result_t
+ns_lib_init(void) {
+ isc_result_t result;
+
+ /*
+ * Since this routine is expected to be used by a normal application,
+ * it should be better to return an error, instead of an emergency
+ * abort, on any failure.
+ */
+ result = isc_once_do(&init_once, initialize);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (!initialize_done) {
+ return (ISC_R_FAILURE);
+ }
+
+ isc_refcount_increment0(&references);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+ns_lib_shutdown(void) {
+ if (isc_refcount_decrement(&references) == 1) {
+ isc_refcount_destroy(&references);
+ if (ns_g_mctx != NULL) {
+ isc_mem_detach(&ns_g_mctx);
+ }
+ }
+}
diff --git a/lib/ns/listenlist.c b/lib/ns/listenlist.c
new file mode 100644
index 0000000..9fb3674
--- /dev/null
+++ b/lib/ns/listenlist.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/util.h>
+
+#include <dns/acl.h>
+
+#include <ns/listenlist.h>
+
+static void
+destroy(ns_listenlist_t *list);
+
+isc_result_t
+ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp,
+ dns_acl_t *acl, ns_listenelt_t **target) {
+ ns_listenelt_t *elt = NULL;
+ REQUIRE(target != NULL && *target == NULL);
+ elt = isc_mem_get(mctx, sizeof(*elt));
+ elt->mctx = mctx;
+ ISC_LINK_INIT(elt, link);
+ elt->port = port;
+ elt->dscp = dscp;
+ elt->acl = acl;
+ *target = elt;
+ return (ISC_R_SUCCESS);
+}
+
+void
+ns_listenelt_destroy(ns_listenelt_t *elt) {
+ if (elt->acl != NULL) {
+ dns_acl_detach(&elt->acl);
+ }
+ 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, isc_dscp_t dscp,
+ bool enabled, 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, dscp, acl, &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..01ea0b2
--- /dev/null
+++ b/lib/ns/log.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 <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>
+ */
+LIBNS_EXTERNAL_DATA 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>.
+ */
+LIBNS_EXTERNAL_DATA 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 }
+};
+
+LIBNS_EXTERNAL_DATA 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..86f8647
--- /dev/null
+++ b/lib/ns/notify.c
@@ -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.
+ */
+
+#include <isc/log.h>
+#include <isc/print.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..4503b8d
--- /dev/null
+++ b/lib/ns/query.c
@@ -0,0 +1,12013 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/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/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/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
+
+/*
+ * These have the same semantics as:
+ *
+ * foo_attach(b, a);
+ * foo_detach(&a);
+ *
+ * without the locking and magic testing.
+ *
+ * We use SAVE and RESTORE as that shows the operation being performed.
+ */
+#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);
+
+/*
+ * 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; \
+ 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, bool empty_wild);
+
+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;
+ }
+ 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) {
+ /*
+ * The view's cache ACLs have not yet been evaluated.
+ * Do it now. Both allow-query-cache and
+ * allow-query-cache-on must be satsified.
+ */
+ bool log = ((options & DNS_GETDB_NOLOG) == 0);
+ char msg[NS_CLIENT_ACLMSGSIZE("query (cache)")];
+
+ result = ns_client_checkaclsilent(client, NULL,
+ client->view->cacheacl, true);
+ if (result == ISC_R_SUCCESS) {
+ 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 if (log) {
+ /*
+ * 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_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", msg);
+ }
+
+ /*
+ * 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);
+ }
+ }
+
+ 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 ((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));
+
+ ns_client_log(client, DNS_LOGCATEGORY_RPZ, 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 (ISC_UNLIKELY(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, &client->ecs, NULL);
+
+ 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, 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) {
+ 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, 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);
+
+ /*
+ * 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);
+ }
+ fname = NULL;
+
+ /*
+ * 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)) {
+ eresult = dns_rdataset_additionaldata(
+ trdataset, query_additional_cb, qctx);
+ }
+
+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_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, 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, 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, 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, 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, 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) {
+ query_rpzfetch(client, name, type);
+ result = DNS_R_NXRRSET;
+ } else {
+ dns_name_copynf(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, 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_copynf(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, 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_copynf(&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 =
+ ns_interfacemgr_getaclenv(client->manager->interface->mgr);
+ 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, &client->ecs, NULL);
+
+ 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_copynf(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, &client->ecs, NULL);
+
+ 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_copynf(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_copynf(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);
+}
+
+/*%
+ * 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;
+ 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;
+ 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 (ISC_UNLIKELY((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 (ISC_UNLIKELY(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 (ISC_UNLIKELY(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 (ISC_UNLIKELY(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;
+
+ 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,
+ HAVEECS(qctx->client) ? &qctx->client->ecs : NULL,
+ NULL);
+
+ /*
+ * 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_copynf(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))
+ {
+ qctx->rdataset->ttl = qctx->view->staleanswerttl;
+ stale_found = true;
+ 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 && !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 && !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;
+ }
+ } 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 && !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_copynf(qname, param->qname);
+ }
+
+ if (qdomain == NULL) {
+ param->qdomain = NULL;
+ } else {
+ param->qdomain = dns_fixedname_initname(&param->fqdomain);
+ dns_name_copynf(qdomain, param->qdomain);
+ }
+}
+static atomic_uint_fast32_t last_soft, last_hard;
+
+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);
+ }
+
+ /*
+ * 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);
+ }
+
+ /*
+ * 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;
+ 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 = dns_fixedname_name(&qctx->event->foundname);
+ }
+
+ dns_name_copynf(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);
+}
+
+/*%
+ * 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 or we have "additional-from-cache no".
+ */
+ 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_copynf(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_copynf(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_copynf(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:
+ return (query_nodata(qctx, DNS_R_EMPTYNAME));
+ case DNS_R_NXRRSET:
+ return (query_nodata(qctx, DNS_R_NXRRSET));
+
+ case DNS_R_EMPTYWILD:
+ return (query_nxdomain(qctx, true));
+
+ case DNS_R_NXDOMAIN:
+ return (query_nxdomain(qctx, false));
+
+ 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;
+ 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 slave, mirror, or
+ * master 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;
+
+ 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;
+
+ 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 =
+ ns_interfacemgr_getaclenv(client->manager->interface->mgr);
+ 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;
+
+ 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, 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;
+ 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_copynf(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;
+
+ 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;
+
+ 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;
+ 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_copynf(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, bool empty_wild) {
+ dns_section_t section;
+ uint32_t ttl;
+ isc_result_t result;
+
+ 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_copynf(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_copynf(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_copynf(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_copynf(&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 response from qctx (which contains the
+ * NODATA proof), nowild + nowildrdataset + signowildrdataset (which
+ * contains the NOWILDCARD proof) and signer + soardatasetp + sigsoardatasetp
+ * which contain the SOA record + RRSIG for the negative answer.
+ */
+static isc_result_t
+query_synthnxdomain(query_ctx_t *qctx, 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_copynf(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_copynf(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);
+ }
+
+ qctx->client->message->rcode = dns_rcode_nxdomain;
+ result = ISC_R_SUCCESS;
+ inc_stats(qctx->client, ns_statscounter_nxdomainsynth);
+
+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_copynf(&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 fnowild;
+ dns_fixedname_t fsigner;
+ dns_fixedname_t fwild;
+ dns_name_t *fname = NULL;
+ dns_name_t *nowild = NULL;
+ dns_name_t *signer = NULL;
+ dns_name_t *wild = NULL;
+ 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;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_coveringnsec");
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_init(&sigrdataset);
+
+ /*
+ * 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, 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;
+ }
+
+ /*
+ * 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:
+ result = dns_nsec_noexistnodata(qctx->qtype, wild, nowild,
+ &rdataset, &exists, &data, NULL,
+ log_noexistnodata, qctx);
+ if (result != ISC_R_SUCCESS || exists) {
+ 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_synthnxdomain(qctx, 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_copynf(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_copynf(&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;
+ 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_copynf(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_copynf(&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_copynf(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;
+
+ 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_copynf(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;
+ dns_dbnode_t *node;
+ isc_result_t result, eresult;
+ 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");
+ /*
+ * Initialization.
+ */
+ eresult = ISC_R_SUCCESS;
+ name = NULL;
+ rdataset = NULL;
+ node = NULL;
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL, 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, 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, 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, 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_copynf(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_copynf(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 =
+ ns_interfacemgr_getaclenv(client->manager->interface->mgr);
+ const 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:
+ dns_message_setsortorder(client->message,
+ query_sortlist_order_1element, env,
+ NULL, order_arg);
+ break;
+ case NS_SORTLISTTYPE_2ELEMENT:
+ dns_message_setsortorder(client->message,
+ query_sortlist_order_2element, env,
+ order_arg, NULL);
+ 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;
+ 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:
+ 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;
+ } else {
+ client->query.fetchoptions |= DNS_FETCHOPT_QMIN_USE_A;
+ }
+ }
+
+ /*
+ * 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..63bea5d
--- /dev/null
+++ b/lib/ns/server.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.
+ */
+
+/*! \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);
+
+ 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);
+
+ 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->gethostname = 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;
+
+ 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);
+
+ 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);
+}
diff --git a/lib/ns/sortlist.c b/lib/ns/sortlist.c
new file mode 100644
index 0000000..5b7932f
--- /dev/null
+++ b/lib/ns/sortlist.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.
+ */
+
+/*! \file */
+
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/message.h>
+#include <dns/result.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,
+ const void **argp) {
+ unsigned int i;
+
+ if (acl == NULL) {
+ goto dont_sort;
+ }
+
+ for (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;
+ const 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,
+ &matched_elt))
+ {
+ if (order_elt != NULL) {
+ if (order_elt->type ==
+ dns_aclelementtype_nestedacl)
+ {
+ *argp = order_elt->nestedacl;
+ return (NS_SORTLISTTYPE_2ELEMENT);
+ } else if (order_elt->type ==
+ dns_aclelementtype_localhost &&
+ env->localhost != NULL)
+ {
+ *argp = env->localhost;
+ return (NS_SORTLISTTYPE_2ELEMENT);
+ } else if (order_elt->type ==
+ dns_aclelementtype_localnets &&
+ env->localnets != NULL)
+ {
+ *argp = env->localnets;
+ return (NS_SORTLISTTYPE_2ELEMENT);
+ } else {
+ /*
+ * BIND 8 allows a bare IP prefix as
+ * the 2nd element of a 2-element
+ * sortlist statement.
+ */
+ *argp = order_elt;
+ return (NS_SORTLISTTYPE_1ELEMENT);
+ }
+ } else {
+ INSIST(matched_elt != NULL);
+ *argp = matched_elt;
+ return (NS_SORTLISTTYPE_1ELEMENT);
+ }
+ }
+ }
+
+ /* No match; don't sort. */
+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;
+ const 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;
+ const 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, const 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(__FILE__, __LINE__,
+ "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/tests/Kyuafile b/lib/ns/tests/Kyuafile
new file mode 100644
index 0000000..22cdcff
--- /dev/null
+++ b/lib/ns/tests/Kyuafile
@@ -0,0 +1,18 @@
+-- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+--
+-- SPDX-License-Identifier: MPL-2.0
+--
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, you can obtain one at https://mozilla.org/MPL/2.0/.
+--
+-- See the COPYRIGHT file distributed with this work for additional
+-- information regarding copyright ownership.
+
+syntax(2)
+test_suite('bind9')
+
+tap_test_program{name='listenlist_test'}
+tap_test_program{name='notify_test'}
+tap_test_program{name='plugin_test'}
+tap_test_program{name='query_test'}
diff --git a/lib/ns/tests/Makefile.in b/lib/ns/tests/Makefile.in
new file mode 100644
index 0000000..9adcb37
--- /dev/null
+++ b/lib/ns/tests/Makefile.in
@@ -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.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+
+VERSION=@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+WRAP_OPTIONS = -Wl,--wrap=isc__nmhandle_detach -Wl,--wrap=isc__nmhandle_attach
+
+CINCLUDES = -I. -Iinclude ${NS_INCLUDES} ${DNS_INCLUDES} ${ISC_INCLUDES} \
+ ${OPENSSL_CFLAGS} \
+ @CMOCKA_CFLAGS@
+CDEFINES = -DTESTS="\"${top_builddir}/lib/ns/tests/\"" -DNAMED_PLUGINDIR=\"${plugindir}\"
+
+ISCLIBS = ../../isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@
+ISCDEPLIBS = ../../isc/libisc.@A@
+DNSLIBS = ../../dns/libdns.@A@ @NO_LIBTOOL_DNSLIBS@
+DNSDEPLIBS = ../../dns/libdns.@A@
+NSLIBS = ../libns.@A@
+NSDEPLIBS = ../libns.@A@
+
+LIBS = @LIBS@ @CMOCKA_LIBS@
+
+SO_CFLAGS = @CFLAGS@ @SO_CFLAGS@
+SO_LDFLAGS = @LDFLAGS@ @SO_LDFLAGS@
+
+OBJS = nstest.@O@
+SRCS = nstest.c \
+ listenlist_test.c \
+ notify_test.c \
+ plugin_test.c \
+ query_test.c
+
+SUBDIRS =
+TARGETS = listenlist_test@EXEEXT@ \
+ notify_test@EXEEXT@ \
+ plugin_test@EXEEXT@ \
+ query_test@EXEEXT@
+
+LD_WRAP_TESTS=@LD_WRAP_TESTS@
+
+@BIND9_MAKE_RULES@
+
+listenlist_test@EXEEXT@: listenlist_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS}
+ if test "${LD_WRAP_TESTS}" = true -a -z "${LIBTOOL}"; then WRAP="${WRAP_OPTIONS}"; fi; \
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} $${WRAP} -o $@ listenlist_test.@O@ nstest.@O@ \
+ ${NSLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+notify_test@EXEEXT@: notify_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS}
+ if test "${LD_WRAP_TESTS}" = true -a -z "${LIBTOOL}"; then WRAP="${WRAP_OPTIONS}"; fi; \
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} $${WRAP} -o $@ notify_test.@O@ nstest.@O@ \
+ ${NSLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+plugin_test@EXEEXT@: plugin_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS}
+ if test "${LD_WRAP_TESTS}" = true -a -z "${LIBTOOL}"; then WRAP="${WRAP_OPTIONS}"; fi; \
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} $${WRAP} -o $@ plugin_test.@O@ nstest.@O@ \
+ ${NSLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+query_test@EXEEXT@: query_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS}
+ if test "${LD_WRAP_TESTS}" = true -a -z "${LIBTOOL}"; then WRAP="${WRAP_OPTIONS}"; fi; \
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \
+ ${LDFLAGS} $${WRAP} -o $@ query_test.@O@ nstest.@O@ \
+ ${NSLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS}
+
+unit::
+ sh ${top_builddir}/unit/unittest.sh
+
+clean distclean::
+ rm -f ${TARGETS}
+ rm -f atf.out
diff --git a/lib/ns/tests/listenlist_test.c b/lib/ns/tests/listenlist_test.c
new file mode 100644
index 0000000..72cb36e
--- /dev/null
+++ b/lib/ns/tests/listenlist_test.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 <isc/util.h>
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/list.h>
+#include <isc/print.h>
+#include <isc/random.h>
+
+#include <dns/acl.h>
+
+#include <ns/listenlist.h>
+
+#include "nstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = ns_test_begin(NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ ns_test_end();
+
+ return (0);
+}
+
+/* test that ns_listenlist_default() works */
+static void
+ns_listenlist_default_test(void **state) {
+ isc_result_t result;
+ in_port_t port = 5300 + isc_random8();
+ ns_listenlist_t *list = NULL;
+ ns_listenelt_t *elt;
+ int count;
+
+ UNUSED(state);
+
+ result = ns_listenlist_default(mctx, port, -1, false, &list);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ assert_non_null(list);
+
+ assert_false(ISC_LIST_EMPTY(list->elts));
+
+ count = 0;
+ elt = ISC_LIST_HEAD(list->elts);
+ while (elt != NULL) {
+ ns_listenelt_t *next = ISC_LIST_NEXT(elt, link);
+ dns_acl_t *acl = NULL;
+
+ dns_acl_attach(elt->acl, &acl);
+ ISC_LIST_UNLINK(list->elts, elt, link);
+ ns_listenelt_destroy(elt);
+ elt = next;
+
+ assert_true(dns_acl_isnone(acl));
+ dns_acl_detach(&acl);
+ count++;
+ }
+
+ assert_true(ISC_LIST_EMPTY(list->elts));
+ assert_int_equal(count, 1);
+
+ ns_listenlist_detach(&list);
+
+ result = ns_listenlist_default(mctx, port, -1, true, &list);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_false(ISC_LIST_EMPTY(list->elts));
+
+ /* This time just use ns_listenlist_detach() to destroy elements */
+ count = 0;
+ elt = ISC_LIST_HEAD(list->elts);
+ while (elt != NULL) {
+ ns_listenelt_t *next = ISC_LIST_NEXT(elt, link);
+ assert_true(dns_acl_isany(elt->acl));
+ elt = next;
+ count++;
+ }
+
+ assert_int_equal(count, 1);
+
+ ns_listenlist_detach(&list);
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(ns_listenlist_default_test,
+ _setup, _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* HAVE_CMOCKA */
diff --git a/lib/ns/tests/notify_test.c b/lib/ns/tests/notify_test.c
new file mode 100644
index 0000000..82880fb
--- /dev/null
+++ b/lib/ns/tests/notify_test.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/util.h>
+
+#if HAVE_CMOCKA
+
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/event.h>
+#include <isc/print.h>
+#include <isc/task.h>
+
+#include <dns/acl.h>
+#include <dns/rcode.h>
+#include <dns/view.h>
+
+#include <ns/client.h>
+#include <ns/notify.h>
+
+#include "nstest.h"
+
+#if defined(USE_LIBTOOL) || LD_WRAP
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = ns_test_begin(NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ ns_test_end();
+
+ return (0);
+}
+
+static void
+check_response(isc_buffer_t *buf) {
+ isc_result_t result;
+ dns_message_t *message = NULL;
+ char rcodebuf[20];
+ isc_buffer_t b;
+
+ dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &message);
+
+ result = dns_message_parse(message, buf, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ isc_buffer_init(&b, rcodebuf, sizeof(rcodebuf));
+ result = dns_rcode_totext(message->rcode, &b);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ assert_int_equal(message->rcode, dns_rcode_noerror);
+
+ dns_message_detach(&message);
+}
+
+/* test ns_notify_start() */
+static void
+notify_start(void **state) {
+ isc_result_t result;
+ ns_client_t *client = NULL;
+ isc_nmhandle_t *handle = NULL;
+ dns_message_t *nmsg = NULL;
+ unsigned char ndata[4096];
+ isc_buffer_t nbuf;
+ size_t nsize;
+
+ UNUSED(state);
+
+ result = ns_test_getclient(NULL, false, &client);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = ns_test_makeview("view", false, &client->view);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ result = ns_test_serve_zone("example.com", "testdata/notify/zone1.db",
+ client->view);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Create a NOTIFY message by parsing a file in testdata.
+ * (XXX: use better message mocking method when available.)
+ */
+
+ result = ns_test_getdata("testdata/notify/notify1.msg", ndata,
+ sizeof(ndata), &nsize);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ isc_buffer_init(&nbuf, ndata, nsize);
+ isc_buffer_add(&nbuf, nsize);
+
+ dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &nmsg);
+
+ result = dns_message_parse(nmsg, &nbuf, 0);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ /*
+ * Set up client object with this message and test the NOTIFY
+ * handler.
+ */
+ if (client->message != NULL) {
+ dns_message_detach(&client->message);
+ }
+ client->message = nmsg;
+ nmsg = NULL;
+ client->sendcb = check_response;
+ ns_notify_start(client, client->handle);
+
+ /*
+ * Clean up
+ */
+ ns_test_cleanup_zone();
+
+ handle = client->handle;
+ isc_nmhandle_detach(&client->handle);
+ isc_nmhandle_detach(&handle);
+}
+#endif /* if defined(USE_LIBTOOL) || LD_WRAP */
+
+int
+main(void) {
+#if defined(USE_LIBTOOL) || LD_WRAP
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(notify_start, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+#else /* if defined(USE_LIBTOOL) || LD_WRAP */
+ print_message("1..0 # Skip notify_test requires libtool or LD_WRAP\n");
+#endif /* if defined(USE_LIBTOOL) || LD_WRAP */
+}
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* HAVE_CMOCKA */
diff --git a/lib/ns/tests/nstest.c b/lib/ns/tests/nstest.c
new file mode 100644
index 0000000..df15408
--- /dev/null
+++ b/lib/ns/tests/nstest.c
@@ -0,0 +1,1018 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include "nstest.h"
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <isc/app.h>
+#include <isc/buffer.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/managers.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/os.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/resource.h>
+#include <isc/socket.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/cache.h>
+#include <dns/db.h>
+#include <dns/dispatch.h>
+#include <dns/fixedname.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/result.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+
+#include <ns/client.h>
+#include <ns/hooks.h>
+#include <ns/interfacemgr.h>
+#include <ns/server.h>
+
+isc_mem_t *mctx = NULL;
+isc_log_t *lctx = NULL;
+isc_taskmgr_t *taskmgr = NULL;
+isc_task_t *maintask = NULL;
+isc_timermgr_t *timermgr = NULL;
+isc_socketmgr_t *socketmgr = NULL;
+isc_nm_t *netmgr = NULL;
+dns_zonemgr_t *zonemgr = NULL;
+dns_dispatchmgr_t *dispatchmgr = NULL;
+ns_clientmgr_t *clientmgr = NULL;
+ns_interfacemgr_t *interfacemgr = NULL;
+ns_server_t *sctx = NULL;
+bool app_running = false;
+int ncpus;
+bool debug_mem_record = true;
+static atomic_bool run_managers = false;
+
+static bool dst_active = false;
+static bool test_running = false;
+
+static dns_zone_t *served_zone = NULL;
+
+/*
+ * We don't want to use netmgr-based client accounting, we need to emulate it.
+ */
+atomic_uint_fast32_t client_refs[32];
+atomic_uintptr_t client_addrs[32];
+
+void
+__wrap_isc__nmhandle_attach(isc_nmhandle_t *source, isc_nmhandle_t **targetp);
+void
+__wrap_isc__nmhandle_detach(isc_nmhandle_t **handlep);
+
+void
+__wrap_isc__nmhandle_attach(isc_nmhandle_t *source, isc_nmhandle_t **targetp) {
+ ns_client_t *client = (ns_client_t *)source;
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ if (atomic_load(&client_addrs[i]) == (uintptr_t)client) {
+ break;
+ }
+ }
+ INSIST(i < 32);
+ INSIST(atomic_load(&client_refs[i]) > 0);
+
+ atomic_fetch_add(&client_refs[i], 1);
+
+ *targetp = source;
+ return;
+}
+
+void
+__wrap_isc__nmhandle_detach(isc_nmhandle_t **handlep) {
+ isc_nmhandle_t *handle = *handlep;
+ ns_client_t *client = (ns_client_t *)handle;
+ int i;
+
+ *handlep = NULL;
+
+ for (i = 0; i < 32; i++) {
+ if (atomic_load(&client_addrs[i]) == (uintptr_t)client) {
+ break;
+ }
+ }
+ INSIST(i < 32);
+
+ if (atomic_fetch_sub(&client_refs[i], 1) == 1) {
+ dns_view_detach(&client->view);
+ client->state = 4;
+ ns__client_reset_cb(client);
+ ns__client_put_cb(client);
+ isc_mem_put(mctx, client, sizeof(ns_client_t));
+ }
+
+ return;
+}
+
+#ifdef USE_LIBTOOL
+void
+isc__nmhandle_attach(isc_nmhandle_t *source, isc_nmhandle_t **targetp) {
+ __wrap_isc__nmhandle_attach(source, targetp);
+}
+void
+isc__nmhandle_detach(isc_nmhandle_t **handle) {
+ __wrap_isc__nmhandle_detach(handle);
+}
+#endif /* USE_LIBTOOL */
+
+/*
+ * Logging categories: this needs to match the list in lib/ns/log.c.
+ */
+static isc_logcategory_t categories[] = { { "", 0 },
+ { "client", 0 },
+ { "network", 0 },
+ { "update", 0 },
+ { "queries", 0 },
+ { "unmatched", 0 },
+ { "update-security", 0 },
+ { "query-errors", 0 },
+ { NULL, 0 } };
+
+static isc_result_t
+matchview(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr,
+ dns_message_t *message, dns_aclenv_t *env, isc_result_t *sigresultp,
+ dns_view_t **viewp) {
+ UNUSED(srcaddr);
+ UNUSED(destaddr);
+ UNUSED(message);
+ UNUSED(env);
+ UNUSED(sigresultp);
+ UNUSED(viewp);
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+/*
+ * These need to be shut down from a running task.
+ */
+static atomic_bool shutdown_done = false;
+static void
+shutdown_managers(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ if (interfacemgr != NULL) {
+ ns_interfacemgr_shutdown(interfacemgr);
+ ns_interfacemgr_detach(&interfacemgr);
+ }
+
+ if (dispatchmgr != NULL) {
+ dns_dispatchmgr_destroy(&dispatchmgr);
+ }
+
+ atomic_store(&shutdown_done, true);
+ atomic_store(&run_managers, false);
+
+ isc_event_free(&event);
+}
+
+static void
+cleanup_managers(void) {
+ atomic_store(&shutdown_done, false);
+
+ if (maintask != NULL) {
+ isc_task_shutdown(maintask);
+ isc_task_destroy(&maintask);
+ }
+
+ while (atomic_load(&run_managers) && !atomic_load(&shutdown_done)) {
+ /*
+ * There's no straightforward way to determine
+ * whether all the clients have shut down, so
+ * we'll just sleep for a bit and hope.
+ */
+ ns_test_nap(500000);
+ }
+
+ if (sctx != NULL) {
+ ns_server_detach(&sctx);
+ }
+ if (interfacemgr != NULL) {
+ ns_interfacemgr_detach(&interfacemgr);
+ }
+ if (socketmgr != NULL) {
+ isc_socketmgr_destroy(&socketmgr);
+ }
+
+ isc_managers_destroy(netmgr == NULL ? NULL : &netmgr,
+ taskmgr == NULL ? NULL : &taskmgr);
+
+ if (timermgr != NULL) {
+ isc_timermgr_destroy(&timermgr);
+ }
+ if (app_running) {
+ isc_app_finish();
+ }
+}
+
+static void
+scan_interfaces(isc_task_t *task, isc_event_t *event) {
+ UNUSED(task);
+
+ ns_interfacemgr_scan(interfacemgr, true);
+ isc_event_free(&event);
+}
+
+static isc_result_t
+create_managers(void) {
+ isc_result_t result;
+ in_port_t port = 5300 + isc_random8();
+ ns_listenlist_t *listenon = NULL;
+ isc_event_t *event = NULL;
+ ncpus = isc_os_ncpus();
+
+ CHECK(isc_managers_create(mctx, ncpus, 0, &netmgr, &taskmgr));
+ CHECK(isc_task_create_bound(taskmgr, 0, &maintask, 0));
+ isc_taskmgr_setexcltask(taskmgr, maintask);
+ CHECK(isc_task_onshutdown(maintask, shutdown_managers, NULL));
+
+ CHECK(isc_timermgr_create(mctx, &timermgr));
+
+ CHECK(isc_socketmgr_create(mctx, &socketmgr));
+
+ CHECK(ns_server_create(mctx, matchview, &sctx));
+
+ CHECK(dns_dispatchmgr_create(mctx, &dispatchmgr));
+
+ CHECK(ns_interfacemgr_create(mctx, sctx, taskmgr, timermgr, socketmgr,
+ netmgr, dispatchmgr, maintask, ncpus, NULL,
+ ncpus, &interfacemgr));
+
+ CHECK(ns_listenlist_default(mctx, port, -1, true, &listenon));
+ ns_interfacemgr_setlistenon4(interfacemgr, listenon);
+ ns_listenlist_detach(&listenon);
+
+ event = isc_event_allocate(mctx, maintask, ISC_TASKEVENT_TEST,
+ scan_interfaces, NULL, sizeof(isc_event_t));
+ isc_task_send(maintask, &event);
+
+ /*
+ * There's no straightforward way to determine
+ * whether the interfaces have been scanned,
+ * we'll just sleep for a bit and hope.
+ */
+ ns_test_nap(500000);
+ ns_interface_t *ifp = ns__interfacemgr_getif(interfacemgr);
+ clientmgr = ifp->clientmgr;
+
+ atomic_store(&run_managers, true);
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cleanup_managers();
+ return (result);
+}
+
+isc_result_t
+ns_test_begin(FILE *logfile, bool start_managers) {
+ isc_result_t result;
+
+ INSIST(!test_running);
+ test_running = true;
+
+ if (start_managers) {
+ isc_resourcevalue_t files;
+
+ /*
+ * The 'listenlist_test', 'notify_test', and 'query_test'
+ * tests need more than 256 descriptors with 8 cpus.
+ * Bump up to at least 1024.
+ */
+ result = isc_resource_getcurlimit(isc_resource_openfiles,
+ &files);
+ if (result == ISC_R_SUCCESS) {
+ if (files < 1024) {
+ files = 1024;
+ (void)isc_resource_setlimit(
+ isc_resource_openfiles, files);
+ }
+ }
+ CHECK(isc_app_start());
+ }
+ if (debug_mem_record) {
+ isc_mem_debugging |= ISC_MEM_DEBUGRECORD;
+ }
+
+ INSIST(mctx == NULL);
+ isc_mem_create(&mctx);
+
+ if (!dst_active) {
+ CHECK(dst_lib_init(mctx, NULL));
+ dst_active = true;
+ }
+
+ if (logfile != NULL) {
+ isc_logdestination_t destination;
+ isc_logconfig_t *logconfig = NULL;
+
+ INSIST(lctx == NULL);
+ isc_log_create(mctx, &lctx, &logconfig);
+
+ isc_log_registercategories(lctx, categories);
+ isc_log_setcontext(lctx);
+ dns_log_init(lctx);
+ dns_log_setcontext(lctx);
+
+ destination.file.stream = logfile;
+ destination.file.name = NULL;
+ destination.file.versions = ISC_LOG_ROLLNEVER;
+ destination.file.maximum_size = 0;
+ isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC,
+ ISC_LOG_DYNAMIC, &destination, 0);
+ CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL));
+ }
+
+ dns_result_register();
+
+ if (start_managers) {
+ CHECK(create_managers());
+ }
+
+ /*
+ * atf-run changes us to a /tmp directory, so tests
+ * that access test data files must first chdir to the proper
+ * location.
+ */
+ if (chdir(TESTS) == -1) {
+ CHECK(ISC_R_FAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ ns_test_end();
+ return (result);
+}
+
+void
+ns_test_end(void) {
+ cleanup_managers();
+
+ dst_lib_destroy();
+ dst_active = false;
+
+ if (lctx != NULL) {
+ isc_log_destroy(&lctx);
+ }
+
+ if (mctx != NULL) {
+ isc_mem_destroy(&mctx);
+ }
+
+ test_running = false;
+}
+
+isc_result_t
+ns_test_makeview(const char *name, bool with_cache, dns_view_t **viewp) {
+ dns_cache_t *cache = NULL;
+ dns_view_t *view = NULL;
+ isc_result_t result;
+
+ CHECK(dns_view_create(mctx, dns_rdataclass_in, name, &view));
+
+ if (with_cache) {
+ CHECK(dns_cache_create(mctx, mctx, taskmgr, timermgr,
+ dns_rdataclass_in, "", "rbt", 0, NULL,
+ &cache));
+ dns_view_setcache(view, cache, false);
+ /*
+ * Reference count for "cache" is now at 2, so decrement it in
+ * order for the cache to be automatically freed when "view"
+ * gets freed.
+ */
+ dns_cache_detach(&cache);
+ }
+
+ *viewp = view;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (view != NULL) {
+ dns_view_detach(&view);
+ }
+ return (result);
+}
+
+/*
+ * Create a zone with origin 'name', return a pointer to the zone object in
+ * 'zonep'. If 'view' is set, add the zone to that view; otherwise, create
+ * a new view for the purpose.
+ *
+ * If the created view is going to be needed by the caller subsequently,
+ * then 'keepview' should be set to true; this will prevent the view
+ * from being detached. In this case, the caller is responsible for
+ * detaching the view.
+ */
+isc_result_t
+ns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view,
+ bool keepview) {
+ isc_result_t result;
+ dns_zone_t *zone = NULL;
+ isc_buffer_t buffer;
+ dns_fixedname_t fixorigin;
+ dns_name_t *origin;
+
+ if (view == NULL) {
+ CHECK(dns_view_create(mctx, dns_rdataclass_in, "view", &view));
+ } else if (!keepview) {
+ keepview = true;
+ }
+
+ zone = *zonep;
+ if (zone == NULL) {
+ CHECK(dns_zone_create(&zone, mctx));
+ }
+
+ isc_buffer_constinit(&buffer, name, strlen(name));
+ isc_buffer_add(&buffer, strlen(name));
+ origin = dns_fixedname_initname(&fixorigin);
+ CHECK(dns_name_fromtext(origin, &buffer, dns_rootname, 0, NULL));
+ CHECK(dns_zone_setorigin(zone, origin));
+ dns_zone_setview(zone, view);
+ dns_zone_settype(zone, dns_zone_primary);
+ dns_zone_setclass(zone, view->rdclass);
+ dns_view_addzone(view, zone);
+
+ if (!keepview) {
+ dns_view_detach(&view);
+ }
+
+ *zonep = zone;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+ if (view != NULL) {
+ dns_view_detach(&view);
+ }
+ return (result);
+}
+
+isc_result_t
+ns_test_setupzonemgr(void) {
+ isc_result_t result;
+ REQUIRE(zonemgr == NULL);
+
+ result = dns_zonemgr_create(mctx, taskmgr, timermgr, socketmgr,
+ &zonemgr);
+ return (result);
+}
+
+isc_result_t
+ns_test_managezone(dns_zone_t *zone) {
+ isc_result_t result;
+ REQUIRE(zonemgr != NULL);
+
+ result = dns_zonemgr_setsize(zonemgr, 1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_zonemgr_managezone(zonemgr, zone);
+ return (result);
+}
+
+void
+ns_test_releasezone(dns_zone_t *zone) {
+ REQUIRE(zonemgr != NULL);
+ dns_zonemgr_releasezone(zonemgr, zone);
+}
+
+void
+ns_test_closezonemgr(void) {
+ REQUIRE(zonemgr != NULL);
+
+ dns_zonemgr_shutdown(zonemgr);
+ dns_zonemgr_detach(&zonemgr);
+}
+
+isc_result_t
+ns_test_serve_zone(const char *zonename, const char *filename,
+ dns_view_t *view) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+
+ /*
+ * Prepare zone structure for further processing.
+ */
+ result = ns_test_makezone(zonename, &served_zone, view, true);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Start zone manager.
+ */
+ result = ns_test_setupzonemgr();
+ if (result != ISC_R_SUCCESS) {
+ goto free_zone;
+ }
+
+ /*
+ * Add the zone to the zone manager.
+ */
+ result = ns_test_managezone(served_zone);
+ if (result != ISC_R_SUCCESS) {
+ goto close_zonemgr;
+ }
+
+ view->nocookieudp = 512;
+
+ /*
+ * Set path to the master file for the zone and then load it.
+ */
+ dns_zone_setfile(served_zone, filename, dns_masterformat_text,
+ &dns_master_style_default);
+ result = dns_zone_load(served_zone, false);
+ if (result != ISC_R_SUCCESS) {
+ goto release_zone;
+ }
+
+ /*
+ * The zone should now be loaded; test it.
+ */
+ result = dns_zone_getdb(served_zone, &db);
+ if (result != ISC_R_SUCCESS) {
+ goto release_zone;
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ return (ISC_R_SUCCESS);
+
+release_zone:
+ ns_test_releasezone(served_zone);
+close_zonemgr:
+ ns_test_closezonemgr();
+free_zone:
+ dns_zone_detach(&served_zone);
+
+ return (result);
+}
+
+void
+ns_test_cleanup_zone(void) {
+ ns_test_releasezone(served_zone);
+ ns_test_closezonemgr();
+
+ dns_zone_detach(&served_zone);
+}
+
+isc_result_t
+ns_test_getclient(ns_interface_t *ifp0, bool tcp, ns_client_t **clientp) {
+ isc_result_t result;
+ ns_client_t *client = isc_mem_get(mctx, sizeof(ns_client_t));
+ int i;
+
+ UNUSED(ifp0);
+ UNUSED(tcp);
+
+ result = ns__client_setup(client, clientmgr, true);
+
+ for (i = 0; i < 32; i++) {
+ if (atomic_load(&client_addrs[i]) == (uintptr_t)NULL ||
+ atomic_load(&client_addrs[i]) == (uintptr_t)client)
+ {
+ break;
+ }
+ }
+ REQUIRE(i < 32);
+
+ atomic_store(&client_refs[i], 2);
+ atomic_store(&client_addrs[i], (uintptr_t)client);
+ client->handle = (isc_nmhandle_t *)client; /* Hack */
+ *clientp = client;
+
+ return (result);
+}
+
+/*%
+ * Synthesize a DNS message based on supplied QNAME, QTYPE and flags, then
+ * parse it and store the results in client->message.
+ */
+static isc_result_t
+attach_query_msg_to_client(ns_client_t *client, const char *qnamestr,
+ dns_rdatatype_t qtype, unsigned int qflags) {
+ dns_rdataset_t *qrdataset = NULL;
+ dns_message_t *message = NULL;
+ unsigned char query[65536];
+ dns_name_t *qname = NULL;
+ isc_buffer_t querybuf;
+ dns_compress_t cctx;
+ isc_result_t result;
+
+ REQUIRE(client != NULL);
+ REQUIRE(qnamestr != NULL);
+
+ /*
+ * Create a new DNS message holding a query.
+ */
+ dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &message);
+
+ /*
+ * Set query ID to a random value.
+ */
+ message->id = isc_random16();
+
+ /*
+ * Set query flags as requested by the caller.
+ */
+ message->flags = qflags;
+
+ /*
+ * Allocate structures required to construct the query.
+ */
+ result = dns_message_gettemprdataset(message, &qrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto destroy_message;
+ }
+ result = dns_message_gettempname(message, &qname);
+ if (result != ISC_R_SUCCESS) {
+ goto put_rdataset;
+ }
+
+ /*
+ * Convert "qnamestr" to a DNS name, create a question rdataset of
+ * class IN and type "qtype", link the two and add the result to the
+ * QUESTION section of the query.
+ */
+ result = dns_name_fromstring(qname, qnamestr, 0, mctx);
+ if (result != ISC_R_SUCCESS) {
+ goto put_name;
+ }
+ dns_rdataset_makequestion(qrdataset, dns_rdataclass_in, qtype);
+ ISC_LIST_APPEND(qname->list, qrdataset, link);
+ dns_message_addname(message, qname, DNS_SECTION_QUESTION);
+
+ /*
+ * Render the query.
+ */
+ dns_compress_init(&cctx, -1, mctx);
+ isc_buffer_init(&querybuf, query, sizeof(query));
+ result = dns_message_renderbegin(message, &cctx, &querybuf);
+ if (result != ISC_R_SUCCESS) {
+ goto destroy_message;
+ }
+ result = dns_message_rendersection(message, DNS_SECTION_QUESTION, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto destroy_message;
+ }
+ result = dns_message_renderend(message);
+ if (result != ISC_R_SUCCESS) {
+ goto destroy_message;
+ }
+ dns_compress_invalidate(&cctx);
+
+ /*
+ * Destroy the created message as it was rendered into "querybuf" and
+ * the latter is all we are going to need from now on.
+ */
+ dns_message_detach(&message);
+
+ /*
+ * Parse the rendered query, storing results in client->message.
+ */
+ isc_buffer_first(&querybuf);
+ return (dns_message_parse(client->message, &querybuf, 0));
+
+put_name:
+ dns_message_puttempname(message, &qname);
+put_rdataset:
+ dns_message_puttemprdataset(message, &qrdataset);
+destroy_message:
+ dns_message_detach(&message);
+
+ return (result);
+}
+
+/*%
+ * A hook action which stores the query context pointed to by "arg" at
+ * "data". Causes execution to be interrupted at hook insertion
+ * point.
+ */
+static ns_hookresult_t
+extract_qctx(void *arg, void *data, isc_result_t *resultp) {
+ query_ctx_t **qctxp;
+ query_ctx_t *qctx;
+
+ REQUIRE(arg != NULL);
+ REQUIRE(data != NULL);
+ REQUIRE(resultp != NULL);
+
+ /*
+ * qctx is a stack variable in lib/ns/query.c. Its contents need to be
+ * duplicated or otherwise they will become invalidated once the stack
+ * gets unwound.
+ */
+ qctx = isc_mem_get(mctx, sizeof(*qctx));
+ if (qctx != NULL) {
+ memmove(qctx, (query_ctx_t *)arg, sizeof(*qctx));
+ }
+
+ qctxp = (query_ctx_t **)data;
+ /*
+ * If memory allocation failed, the supplied pointer will simply be set
+ * to NULL. We rely on the user of this hook to react properly.
+ */
+ *qctxp = qctx;
+ *resultp = ISC_R_UNSET;
+
+ return (NS_HOOK_RETURN);
+}
+
+/*%
+ * Initialize a query context for "client" and store it in "qctxp".
+ *
+ * Requires:
+ *
+ * \li "client->message" to hold a parsed DNS query.
+ */
+static isc_result_t
+create_qctx_for_client(ns_client_t *client, query_ctx_t **qctxp) {
+ ns_hooktable_t *saved_hook_table = NULL, *query_hooks = NULL;
+ const ns_hook_t hook = {
+ .action = extract_qctx,
+ .action_data = qctxp,
+ };
+
+ REQUIRE(client != NULL);
+ REQUIRE(qctxp != NULL);
+ REQUIRE(*qctxp == NULL);
+
+ /*
+ * Call ns_query_start() to initialize a query context for given
+ * client, but first hook into query_setup() so that we can just
+ * extract an initialized query context, without kicking off any
+ * further processing. Make sure we do not overwrite any previously
+ * set hooks.
+ */
+
+ ns_hooktable_create(mctx, &query_hooks);
+ ns_hook_add(query_hooks, mctx, NS_QUERY_SETUP, &hook);
+
+ saved_hook_table = ns__hook_table;
+ ns__hook_table = query_hooks;
+
+ ns_query_start(client, client->handle);
+
+ ns__hook_table = saved_hook_table;
+ ns_hooktable_free(mctx, (void **)&query_hooks);
+
+ isc_nmhandle_detach(&client->reqhandle);
+
+ if (*qctxp == NULL) {
+ return (ISC_R_NOMEMORY);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+isc_result_t
+ns_test_qctx_create(const ns_test_qctx_create_params_t *params,
+ query_ctx_t **qctxp) {
+ ns_client_t *client = NULL;
+ isc_result_t result;
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(params != NULL);
+ REQUIRE(params->qname != NULL);
+ REQUIRE(qctxp != NULL);
+ REQUIRE(*qctxp == NULL);
+
+ /*
+ * Allocate and initialize a client structure.
+ */
+ result = ns_test_getclient(NULL, false, &client);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ TIME_NOW(&client->tnow);
+
+ /*
+ * Every client needs to belong to a view.
+ */
+ result = ns_test_makeview("view", params->with_cache, &client->view);
+ if (result != ISC_R_SUCCESS) {
+ goto detach_client;
+ }
+
+ /*
+ * Synthesize a DNS query using given QNAME, QTYPE and flags, storing
+ * it in client->message.
+ */
+ result = attach_query_msg_to_client(client, params->qname,
+ params->qtype, params->qflags);
+ if (result != ISC_R_SUCCESS) {
+ goto detach_view;
+ }
+
+ /*
+ * Allow recursion for the client. As NS_CLIENTATTR_RA normally gets
+ * set in ns__client_request(), i.e. earlier than the unit tests hook
+ * into the call chain, just set it manually.
+ */
+ client->attributes |= NS_CLIENTATTR_RA;
+
+ /*
+ * Create a query context for a client sending the previously
+ * synthesized query.
+ */
+ result = create_qctx_for_client(client, qctxp);
+ if (result != ISC_R_SUCCESS) {
+ goto detach_query;
+ }
+
+ /*
+ * The reference count for "client" is now at 2, so we need to
+ * decrement it in order for it to drop to zero when "qctx" gets
+ * destroyed.
+ */
+ handle = client->handle;
+ isc_nmhandle_detach(&handle);
+
+ return (ISC_R_SUCCESS);
+
+detach_query:
+ dns_message_detach(&client->message);
+detach_view:
+ dns_view_detach(&client->view);
+detach_client:
+ isc_nmhandle_detach(&client->handle);
+
+ return (result);
+}
+
+void
+ns_test_qctx_destroy(query_ctx_t **qctxp) {
+ query_ctx_t *qctx;
+
+ REQUIRE(qctxp != NULL);
+ REQUIRE(*qctxp != NULL);
+
+ qctx = *qctxp;
+ *qctxp = NULL;
+
+ if (qctx->zone != NULL) {
+ dns_zone_detach(&qctx->zone);
+ }
+ if (qctx->db != NULL) {
+ dns_db_detach(&qctx->db);
+ }
+ if (qctx->client != NULL) {
+ isc_nmhandle_detach(&qctx->client->handle);
+ }
+
+ isc_mem_put(mctx, qctx, sizeof(*qctx));
+}
+
+ns_hookresult_t
+ns_test_hook_catch_call(void *arg, void *data, isc_result_t *resultp) {
+ UNUSED(arg);
+ UNUSED(data);
+
+ *resultp = ISC_R_UNSET;
+
+ return (NS_HOOK_RETURN);
+}
+
+/*
+ * Sleep for 'usec' microseconds.
+ */
+void
+ns_test_nap(uint32_t usec) {
+ struct timespec ts;
+
+ ts.tv_sec = usec / 1000000;
+ ts.tv_nsec = (usec % 1000000) * 1000;
+ nanosleep(&ts, NULL);
+}
+
+isc_result_t
+ns_test_loaddb(dns_db_t **db, dns_dbtype_t dbtype, const char *origin,
+ const char *testfile) {
+ isc_result_t result;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+
+ name = dns_fixedname_initname(&fixed);
+
+ result = dns_name_fromstring(name, origin, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_create(mctx, "rbt", name, dbtype, dns_rdataclass_in, 0,
+ NULL, db);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_db_load(*db, testfile, dns_masterformat_text, 0);
+ return (result);
+}
+
+static int
+fromhex(char c) {
+ if (c >= '0' && c <= '9') {
+ return (c - '0');
+ } else if (c >= 'a' && c <= 'f') {
+ return (c - 'a' + 10);
+ } else if (c >= 'A' && c <= 'F') {
+ return (c - 'A' + 10);
+ }
+
+ printf("bad input format: %02x\n", c);
+ exit(3);
+}
+
+isc_result_t
+ns_test_getdata(const char *file, unsigned char *buf, size_t bufsiz,
+ size_t *sizep) {
+ isc_result_t result;
+ unsigned char *bp;
+ char *rp, *wp;
+ char s[BUFSIZ];
+ size_t len, i;
+ FILE *f = NULL;
+ int n;
+
+ result = isc_stdio_open(file, "r", &f);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ bp = buf;
+ while (fgets(s, sizeof(s), f) != NULL) {
+ rp = s;
+ wp = s;
+ len = 0;
+ while (*rp != '\0') {
+ if (*rp == '#') {
+ break;
+ }
+ if (*rp != ' ' && *rp != '\t' && *rp != '\r' &&
+ *rp != '\n')
+ {
+ *wp++ = *rp;
+ len++;
+ }
+ rp++;
+ }
+ if (len == 0U) {
+ continue;
+ }
+ if (len % 2 != 0U) {
+ CHECK(ISC_R_UNEXPECTEDEND);
+ }
+ if (len > bufsiz * 2) {
+ CHECK(ISC_R_NOSPACE);
+ }
+ rp = s;
+ for (i = 0; i < len; i += 2) {
+ n = fromhex(*rp++);
+ n *= 16;
+ n += fromhex(*rp++);
+ *bp++ = n;
+ }
+ }
+
+ *sizep = bp - buf;
+
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ isc_stdio_close(f);
+ return (result);
+}
diff --git a/lib/ns/tests/nstest.h b/lib/ns/tests/nstest.h
new file mode 100644
index 0000000..87a26a6
--- /dev/null
+++ b/lib/ns/tests/nstest.h
@@ -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.
+ */
+
+/*! \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/string.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/result.h>
+#include <dns/zone.h>
+
+#include <ns/client.h>
+#include <ns/hooks.h>
+#include <ns/interfacemgr.h>
+
+typedef struct ns_test_id {
+ const char *description;
+ int lineno;
+} ns_test_id_t;
+
+#define NS_TEST_ID(desc) \
+ { \
+ .description = desc, .lineno = __LINE__ \
+ }
+
+#define CHECK(r) \
+ do { \
+ result = (r); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+extern isc_mem_t *mctx;
+extern isc_log_t *lctx;
+extern isc_taskmgr_t *taskmgr;
+extern isc_task_t *maintask;
+extern isc_timermgr_t *timermgr;
+extern isc_socketmgr_t *socketmgr;
+extern dns_zonemgr_t *zonemgr;
+extern dns_dispatchmgr_t *dispatchmgr;
+extern ns_clientmgr_t *clientmgr;
+extern ns_interfacemgr_t *interfacemgr;
+extern ns_server_t *sctx;
+extern bool app_running;
+extern int ncpus;
+extern bool debug_mem_record;
+
+#ifdef NETMGR_TRACE
+#define FLARG \
+ , const char *file __attribute__((unused)), \
+ unsigned int line __attribute__((unused)), \
+ const char *func __attribute__((unused))
+#else
+#define FLARG
+#endif
+
+isc_result_t
+ns_test_begin(FILE *logfile, bool create_managers);
+
+void
+ns_test_end(void);
+
+/*%
+ * Create a view. If "with_cache" is set to true, a cache database will
+ * also be created and attached to the created view.
+ */
+isc_result_t
+ns_test_makeview(const char *name, bool with_cache, dns_view_t **viewp);
+
+isc_result_t
+ns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view,
+ bool keepview);
+
+isc_result_t
+ns_test_setupzonemgr(void);
+
+isc_result_t
+ns_test_managezone(dns_zone_t *zone);
+
+void
+ns_test_releasezone(dns_zone_t *zone);
+
+void
+ns_test_closezonemgr(void);
+
+/*%
+ * Load data for zone "zonename" from file "filename" and start serving it to
+ * clients matching "view". Only one zone loaded using this function can be
+ * served at any given time.
+ */
+isc_result_t
+ns_test_serve_zone(const char *zonename, const char *filename,
+ dns_view_t *view);
+
+/*%
+ * Release the zone loaded by ns_test_serve_zone().
+ */
+void
+ns_test_cleanup_zone(void);
+
+void
+ns_test_nap(uint32_t usec);
+
+isc_result_t
+ns_test_loaddb(dns_db_t **db, dns_dbtype_t dbtype, const char *origin,
+ const char *testfile);
+
+isc_result_t
+ns_test_getdata(const char *file, unsigned char *buf, size_t bufsiz,
+ size_t *sizep);
+
+isc_result_t
+ns_test_getclient(ns_interface_t *ifp0, bool tcp, ns_client_t **clientp);
+
+/*%
+ * Structure containing parameters for ns_test_qctx_create().
+ */
+typedef struct ns_test_qctx_create_params {
+ const char *qname;
+ dns_rdatatype_t qtype;
+ unsigned int qflags;
+ bool with_cache;
+} ns_test_qctx_create_params_t;
+
+/*%
+ * Prepare a query context identical with one that would be prepared if a query
+ * with given QNAME, QTYPE and flags was received from a client. Recursion is
+ * assumed to be allowed for this client. If "with_cache" is set to true,
+ * a cache database will be created and associated with the view matching the
+ * incoming query.
+ */
+isc_result_t
+ns_test_qctx_create(const ns_test_qctx_create_params_t *params,
+ query_ctx_t **qctxp);
+
+/*%
+ * Destroy a query context created by ns_test_qctx_create().
+ */
+void
+ns_test_qctx_destroy(query_ctx_t **qctxp);
+
+/*%
+ * A hook callback interrupting execution at given hook's insertion point.
+ */
+ns_hookresult_t
+ns_test_hook_catch_call(void *arg, void *data, isc_result_t *resultp);
diff --git a/lib/ns/tests/plugin_test.c b/lib/ns/tests/plugin_test.c
new file mode 100644
index 0000000..7870f7a
--- /dev/null
+++ b/lib/ns/tests/plugin_test.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+
+#include <limits.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/mem.h>
+#include <isc/platform.h>
+#include <isc/result.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+ISC_PLATFORM_NORETURN_PRE void
+_fail(const char *const file, const int line) ISC_PLATFORM_NORETURN_POST;
+
+#include <ns/hooks.h>
+
+#include "nstest.h"
+
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = ns_test_begin(NULL, false);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ if (*state != NULL) {
+ isc_mem_free(mctx, *state);
+ }
+
+ ns_test_end();
+
+ return (0);
+}
+
+/*%
+ * Structure containing parameters for run_full_path_test().
+ */
+typedef struct {
+ const ns_test_id_t id; /* libns test identifier */
+ const char *input; /* source string - plugin name or path
+ * */
+ size_t output_size; /* size of target char array to
+ * allocate */
+ isc_result_t result; /* expected return value */
+ const char *output; /* expected output string */
+} ns_plugin_expandpath_test_params_t;
+
+/*%
+ * Perform a single ns_plugin_expandpath() check using given parameters.
+ */
+static void
+run_full_path_test(const ns_plugin_expandpath_test_params_t *test,
+ void **state) {
+ char **target = (char **)state;
+ isc_result_t result;
+
+ REQUIRE(test != NULL);
+ REQUIRE(test->id.description != NULL);
+ REQUIRE(test->input != NULL);
+ REQUIRE(test->result != ISC_R_SUCCESS || test->output != NULL);
+
+ /*
+ * Prepare a target buffer of given size. Store it in 'state' so that
+ * it can get cleaned up by _teardown() if the test fails.
+ */
+ *target = isc_mem_allocate(mctx, test->output_size);
+
+ /*
+ * Call ns_plugin_expandpath().
+ */
+ result = ns_plugin_expandpath(test->input, *target, test->output_size);
+
+ /*
+ * Check return value.
+ */
+ if (result != test->result) {
+ fail_msg("# test \"%s\" on line %d: "
+ "expected result %d (%s), got %d (%s)",
+ test->id.description, test->id.lineno, test->result,
+ isc_result_totext(test->result), result,
+ isc_result_totext(result));
+ }
+
+ /*
+ * Check output string if return value indicates success.
+ */
+ if (result == ISC_R_SUCCESS && strcmp(*target, test->output) != 0) {
+ fail_msg("# test \"%s\" on line %d: "
+ "expected output \"%s\", got \"%s\"",
+ test->id.description, test->id.lineno, test->output,
+ *target);
+ }
+
+ isc_mem_free(mctx, *target);
+}
+
+/* test ns_plugin_expandpath() */
+static void
+ns_plugin_expandpath_test(void **state) {
+ size_t i;
+
+ const ns_plugin_expandpath_test_params_t tests[] = {
+ {
+ NS_TEST_ID("correct use with an absolute path"),
+ .input = "/usr/lib/named/foo.so",
+ .output_size = PATH_MAX,
+ .result = ISC_R_SUCCESS,
+ .output = "/usr/lib/named/foo.so",
+ },
+ {
+ NS_TEST_ID("correct use with a relative path"),
+ .input = "../../foo.so",
+ .output_size = PATH_MAX,
+ .result = ISC_R_SUCCESS,
+ .output = "../../foo.so",
+ },
+ {
+ NS_TEST_ID("correct use with a filename"),
+ .input = "foo.so",
+ .output_size = PATH_MAX,
+ .result = ISC_R_SUCCESS,
+#ifndef WIN32
+ .output = NAMED_PLUGINDIR "/foo.so",
+#else /* ifndef WIN32 */
+ .output = "foo.so",
+#endif /* ifndef WIN32 */
+ },
+ {
+ NS_TEST_ID("no space at all in target buffer"),
+ .input = "/usr/lib/named/foo.so",
+ .output_size = 0,
+ .result = ISC_R_NOSPACE,
+ },
+ {
+ NS_TEST_ID("target buffer too small to fit input"),
+ .input = "/usr/lib/named/foo.so",
+ .output_size = 1,
+ .result = ISC_R_NOSPACE,
+ },
+ {
+ NS_TEST_ID("target buffer too small to fit NULL byte"),
+ .input = "/foo.so",
+ .output_size = 7,
+ .result = ISC_R_NOSPACE,
+ },
+#ifndef WIN32
+ {
+ NS_TEST_ID("target buffer too small to fit full path"),
+ .input = "foo.so",
+ .output_size = 7,
+ .result = ISC_R_NOSPACE,
+ },
+#endif /* ifndef WIN32 */
+ };
+
+ for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ run_full_path_test(&tests[i], state);
+ }
+}
+
+int
+main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(ns_plugin_expandpath_test,
+ _setup, _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+}
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/ns/tests/query_test.c b/lib/ns/tests/query_test.c
new file mode 100644
index 0000000..b3e8fc1
--- /dev/null
+++ b/lib/ns/tests/query_test.c
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/util.h>
+
+#if HAVE_CMOCKA
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <dns/badcache.h>
+#include <dns/view.h>
+
+#include <ns/client.h>
+#include <ns/hooks.h>
+#include <ns/query.h>
+
+#include "nstest.h"
+
+#if defined(USE_LIBTOOL) || LD_WRAP
+static int
+_setup(void **state) {
+ isc_result_t result;
+
+ UNUSED(state);
+
+ result = ns_test_begin(NULL, true);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ return (0);
+}
+
+static int
+_teardown(void **state) {
+ UNUSED(state);
+
+ ns_test_end();
+
+ return (0);
+}
+
+/*****
+***** ns__query_sfcache() tests
+*****/
+
+/*%
+ * Structure containing parameters for ns__query_sfcache_test().
+ */
+typedef struct {
+ const ns_test_id_t id; /* libns test identifier */
+ unsigned int qflags; /* query flags */
+ bool cache_entry_present; /* whether a SERVFAIL
+ * cache entry
+ * matching the query
+ * should be
+ * present */
+ uint32_t cache_entry_flags; /* NS_FAILCACHE_* flags to
+ * set for
+ * the SERVFAIL cache entry
+ * */
+ bool servfail_expected; /* whether a cached
+ * SERVFAIL is
+ * expected to be returned
+ * */
+} ns__query_sfcache_test_params_t;
+
+/*%
+ * Perform a single ns__query_sfcache() check using given parameters.
+ */
+static void
+run_sfcache_test(const ns__query_sfcache_test_params_t *test) {
+ ns_hooktable_t *query_hooks = NULL;
+ query_ctx_t *qctx = NULL;
+ isc_result_t result;
+ const ns_hook_t hook = {
+ .action = ns_test_hook_catch_call,
+ };
+
+ REQUIRE(test != NULL);
+ REQUIRE(test->id.description != NULL);
+ REQUIRE(test->cache_entry_present || test->cache_entry_flags == 0);
+
+ /*
+ * Interrupt execution if ns_query_done() is called.
+ */
+
+ ns_hooktable_create(mctx, &query_hooks);
+ ns_hook_add(query_hooks, mctx, NS_QUERY_DONE_BEGIN, &hook);
+ ns__hook_table = query_hooks;
+
+ /*
+ * Construct a query context for a ./NS query with given flags.
+ */
+ {
+ const ns_test_qctx_create_params_t qctx_params = {
+ .qname = ".",
+ .qtype = dns_rdatatype_ns,
+ .qflags = test->qflags,
+ .with_cache = true,
+ };
+
+ result = ns_test_qctx_create(&qctx_params, &qctx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ /*
+ * If this test wants a SERVFAIL cache entry matching the query to
+ * exist, create it.
+ */
+ if (test->cache_entry_present) {
+ isc_interval_t hour;
+ isc_time_t expire;
+
+ isc_interval_set(&hour, 3600, 0);
+ result = isc_time_nowplusinterval(&expire, &hour);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_badcache_add(qctx->client->view->failcache, dns_rootname,
+ dns_rdatatype_ns, true,
+ test->cache_entry_flags, &expire);
+ }
+
+ /*
+ * Check whether ns__query_sfcache() behaves as expected.
+ */
+ ns__query_sfcache(qctx);
+
+ if (test->servfail_expected) {
+ if (qctx->result != DNS_R_SERVFAIL) {
+ fail_msg("# test \"%s\" on line %d: "
+ "expected SERVFAIL, got %s",
+ test->id.description, test->id.lineno,
+ isc_result_totext(qctx->result));
+ }
+ } else {
+ if (qctx->result != ISC_R_SUCCESS) {
+ fail_msg("# test \"%s\" on line %d: "
+ "expected success, got %s",
+ test->id.description, test->id.lineno,
+ isc_result_totext(qctx->result));
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ ns_test_qctx_destroy(&qctx);
+ ns_hooktable_free(mctx, (void **)&query_hooks);
+}
+
+/* test ns__query_sfcache() */
+static void
+ns__query_sfcache_test(void **state) {
+ size_t i;
+
+ const ns__query_sfcache_test_params_t tests[] = {
+ /*
+ * Sanity check for an empty SERVFAIL cache.
+ */
+ {
+ NS_TEST_ID("query: RD=1, CD=0; cache: empty"),
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .cache_entry_present = false,
+ .servfail_expected = false,
+ },
+ /*
+ * Query: RD=1, CD=0. Cache entry: CD=0. Should SERVFAIL.
+ */
+ {
+ NS_TEST_ID("query: RD=1, CD=0; cache: CD=0"),
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .cache_entry_present = true,
+ .cache_entry_flags = 0,
+ .servfail_expected = true,
+ },
+ /*
+ * Query: RD=1, CD=1. Cache entry: CD=0. Should not SERVFAIL:
+ * failed validation should not influence CD=1 queries.
+ */
+ {
+ NS_TEST_ID("query: RD=1, CD=1; cache: CD=0"),
+ .qflags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD,
+ .cache_entry_present = true,
+ .cache_entry_flags = 0,
+ .servfail_expected = false,
+ },
+ /*
+ * Query: RD=1, CD=1. Cache entry: CD=1. Should SERVFAIL:
+ * SERVFAIL responses elicited by CD=1 queries can be
+ * "replayed" for other CD=1 queries during the lifetime of the
+ * SERVFAIL cache entry.
+ */
+ {
+ NS_TEST_ID("query: RD=1, CD=1; cache: CD=1"),
+ .qflags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD,
+ .cache_entry_present = true,
+ .cache_entry_flags = NS_FAILCACHE_CD,
+ .servfail_expected = true,
+ },
+ /*
+ * Query: RD=1, CD=0. Cache entry: CD=1. Should SERVFAIL: if
+ * a CD=1 query elicited a SERVFAIL, a CD=0 query for the same
+ * QNAME and QTYPE will SERVFAIL as well.
+ */
+ {
+ NS_TEST_ID("query: RD=1, CD=0; cache: CD=1"),
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .cache_entry_present = true,
+ .cache_entry_flags = NS_FAILCACHE_CD,
+ .servfail_expected = true,
+ },
+ /*
+ * Query: RD=0, CD=0. Cache entry: CD=0. Should not SERVFAIL
+ * despite a matching entry being present as the SERVFAIL cache
+ * should not be consulted for non-recursive queries.
+ */
+ {
+ NS_TEST_ID("query: RD=0, CD=0; cache: CD=0"),
+ .qflags = 0,
+ .cache_entry_present = true,
+ .cache_entry_flags = 0,
+ .servfail_expected = false,
+ },
+ };
+
+ UNUSED(state);
+
+ for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ run_sfcache_test(&tests[i]);
+ }
+}
+
+/*****
+***** ns__query_start() tests
+*****/
+
+/*%
+ * Structure containing parameters for ns__query_start_test().
+ */
+typedef struct {
+ const ns_test_id_t id; /* libns test identifier */
+ const char *qname; /* QNAME */
+ dns_rdatatype_t qtype; /* QTYPE */
+ unsigned int qflags; /* query flags */
+ bool disable_name_checks; /* if set to true, owner
+ * name
+ * checks will
+ * be disabled for the
+ * view created
+ * */
+ bool recursive_service; /* if set to true, the view
+ * created will
+ * have a cache
+ * database
+ * attached */
+ const char *auth_zone_origin; /* origin name of the zone
+ * the
+ * created view will be
+ * authoritative for */
+ const char *auth_zone_path; /* path to load the
+ * authoritative
+ * zone from */
+ enum { /* expected result: */
+ NS__QUERY_START_R_INVALID,
+ NS__QUERY_START_R_REFUSE, /* query should be REFUSED */
+ NS__QUERY_START_R_CACHE, /* query should be answered from
+ * cache */
+ NS__QUERY_START_R_AUTH, /* query should be answered using
+ * authoritative data */
+ } expected_result;
+} ns__query_start_test_params_t;
+
+/*%
+ * Perform a single ns__query_start() check using given parameters.
+ */
+static void
+run_start_test(const ns__query_start_test_params_t *test) {
+ ns_hooktable_t *query_hooks = NULL;
+ query_ctx_t *qctx = NULL;
+ isc_result_t result;
+ const ns_hook_t hook = {
+ .action = ns_test_hook_catch_call,
+ };
+
+ REQUIRE(test != NULL);
+ REQUIRE(test->id.description != NULL);
+ REQUIRE((test->auth_zone_origin == NULL &&
+ test->auth_zone_path == NULL) ||
+ (test->auth_zone_origin != NULL &&
+ test->auth_zone_path != NULL));
+
+ /*
+ * Interrupt execution if query_lookup() or ns_query_done() is called.
+ */
+ ns_hooktable_create(mctx, &query_hooks);
+ ns_hook_add(query_hooks, mctx, NS_QUERY_LOOKUP_BEGIN, &hook);
+ ns_hook_add(query_hooks, mctx, NS_QUERY_DONE_BEGIN, &hook);
+ ns__hook_table = query_hooks;
+
+ /*
+ * Construct a query context using the supplied parameters.
+ */
+ {
+ const ns_test_qctx_create_params_t qctx_params = {
+ .qname = test->qname,
+ .qtype = test->qtype,
+ .qflags = test->qflags,
+ .with_cache = test->recursive_service,
+ };
+ result = ns_test_qctx_create(&qctx_params, &qctx);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ /*
+ * Enable view->checknames by default, disable if requested.
+ */
+ qctx->client->view->checknames = !test->disable_name_checks;
+
+ /*
+ * Load zone from file and attach it to the client's view, if
+ * requested.
+ */
+ if (test->auth_zone_path != NULL) {
+ result = ns_test_serve_zone(test->auth_zone_origin,
+ test->auth_zone_path,
+ qctx->client->view);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ }
+
+ /*
+ * Check whether ns__query_start() behaves as expected.
+ */
+ ns__query_start(qctx);
+
+ switch (test->expected_result) {
+ case NS__QUERY_START_R_REFUSE:
+ if (qctx->result != DNS_R_REFUSED) {
+ fail_msg("# test \"%s\" on line %d: "
+ "expected REFUSED, got %s",
+ test->id.description, test->id.lineno,
+ isc_result_totext(qctx->result));
+ }
+ if (qctx->zone != NULL) {
+ fail_msg("# test \"%s\" on line %d: "
+ "no zone was expected to be attached to "
+ "query context, but some was",
+ test->id.description, test->id.lineno);
+ }
+ if (qctx->db != NULL) {
+ fail_msg("# test \"%s\" on line %d: "
+ "no database was expected to be attached to "
+ "query context, but some was",
+ test->id.description, test->id.lineno);
+ }
+ break;
+ case NS__QUERY_START_R_CACHE:
+ if (qctx->result != ISC_R_SUCCESS) {
+ fail_msg("# test \"%s\" on line %d: "
+ "expected success, got %s",
+ test->id.description, test->id.lineno,
+ isc_result_totext(qctx->result));
+ }
+ if (qctx->zone != NULL) {
+ fail_msg("# test \"%s\" on line %d: "
+ "no zone was expected to be attached to "
+ "query context, but some was",
+ test->id.description, test->id.lineno);
+ }
+ if (qctx->db == NULL || qctx->db != qctx->client->view->cachedb)
+ {
+ fail_msg("# test \"%s\" on line %d: "
+ "cache database was expected to be "
+ "attached to query context, but it was not",
+ test->id.description, test->id.lineno);
+ }
+ break;
+ case NS__QUERY_START_R_AUTH:
+ if (qctx->result != ISC_R_SUCCESS) {
+ fail_msg("# test \"%s\" on line %d: "
+ "expected success, got %s",
+ test->id.description, test->id.lineno,
+ isc_result_totext(qctx->result));
+ }
+ if (qctx->zone == NULL) {
+ fail_msg("# test \"%s\" on line %d: "
+ "a zone was expected to be attached to query "
+ "context, but it was not",
+ test->id.description, test->id.lineno);
+ }
+ if (qctx->db == qctx->client->view->cachedb) {
+ fail_msg("# test \"%s\" on line %d: "
+ "cache database was not expected to be "
+ "attached to query context, but it is",
+ test->id.description, test->id.lineno);
+ }
+ break;
+ case NS__QUERY_START_R_INVALID:
+ fail_msg("# test \"%s\" on line %d has no expected result set",
+ test->id.description, test->id.lineno);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ /*
+ * Clean up.
+ */
+ if (test->auth_zone_path != NULL) {
+ ns_test_cleanup_zone();
+ }
+ ns_test_qctx_destroy(&qctx);
+ ns_hooktable_free(mctx, (void **)&query_hooks);
+}
+
+/* test ns__query_start() */
+static void
+ns__query_start_test(void **state) {
+ size_t i;
+
+ const ns__query_start_test_params_t tests[] = {
+ /*
+ * Recursive foo/A query to a server without recursive service
+ * and no zones configured. Query should be REFUSED.
+ */
+ {
+ NS_TEST_ID("foo/A, no cache, no auth"),
+ .qname = "foo",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = false,
+ .expected_result = NS__QUERY_START_R_REFUSE,
+ },
+ /*
+ * Recursive foo/A query to a server with recursive service and
+ * no zones configured. Query should be answered from cache.
+ */
+ {
+ NS_TEST_ID("foo/A, cache, no auth"),
+ .qname = "foo",
+ .qtype = dns_rdatatype_a,
+ .recursive_service = true,
+ .expected_result = NS__QUERY_START_R_CACHE,
+ },
+ /*
+ * Recursive foo/A query to a server with recursive service and
+ * zone "foo" configured. Query should be answered from
+ * authoritative data.
+ */
+ {
+ NS_TEST_ID("foo/A, RD=1, cache, auth for foo"),
+ .qname = "foo",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = true,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_AUTH,
+ },
+ /*
+ * Recursive bar/A query to a server without recursive service
+ * and zone "foo" configured. Query should be REFUSED.
+ */
+ {
+ NS_TEST_ID("bar/A, RD=1, no cache, auth for foo"),
+ .qname = "bar",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = false,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_REFUSE,
+ },
+ /*
+ * Recursive bar/A query to a server with recursive service and
+ * zone "foo" configured. Query should be answered from
+ * cache.
+ */
+ {
+ NS_TEST_ID("bar/A, RD=1, cache, auth for foo"),
+ .qname = "bar",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = true,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_CACHE,
+ },
+ /*
+ * Recursive bar.foo/DS query to a server with recursive
+ * service and zone "foo" configured. Query should be answered
+ * from authoritative data.
+ */
+ {
+ NS_TEST_ID("bar.foo/DS, RD=1, cache, auth for foo"),
+ .qname = "bar.foo",
+ .qtype = dns_rdatatype_ds,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = true,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_AUTH,
+ },
+ /*
+ * Non-recursive bar.foo/DS query to a server with recursive
+ * service and zone "foo" configured. Query should be answered
+ * from authoritative data.
+ */
+ {
+ NS_TEST_ID("bar.foo/DS, RD=0, cache, auth for foo"),
+ .qname = "bar.foo",
+ .qtype = dns_rdatatype_ds,
+ .qflags = 0,
+ .recursive_service = true,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_AUTH,
+ },
+ /*
+ * Recursive foo/DS query to a server with recursive service
+ * and zone "foo" configured. Query should be answered from
+ * cache.
+ */
+ {
+ NS_TEST_ID("foo/DS, RD=1, cache, auth for foo"),
+ .qname = "foo",
+ .qtype = dns_rdatatype_ds,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = true,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_CACHE,
+ },
+ /*
+ * Non-recursive foo/DS query to a server with recursive
+ * service and zone "foo" configured. Query should be answered
+ * from authoritative data.
+ */
+ {
+ NS_TEST_ID("foo/DS, RD=0, cache, auth for foo"),
+ .qname = "foo",
+ .qtype = dns_rdatatype_ds,
+ .qflags = 0,
+ .recursive_service = true,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_AUTH,
+ },
+ /*
+ * Recursive _foo/A query to a server with recursive service,
+ * no zones configured and owner name checks disabled. Query
+ * should be answered from cache.
+ */
+ {
+ NS_TEST_ID("_foo/A, cache, no auth, name checks off"),
+ .qname = "_foo",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .disable_name_checks = true,
+ .recursive_service = true,
+ .expected_result = NS__QUERY_START_R_CACHE,
+ },
+ /*
+ * Recursive _foo/A query to a server with recursive service,
+ * no zones configured and owner name checks enabled. Query
+ * should be REFUSED.
+ */
+ {
+ NS_TEST_ID("_foo/A, cache, no auth, name checks on"),
+ .qname = "_foo",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .disable_name_checks = false,
+ .recursive_service = true,
+ .expected_result = NS__QUERY_START_R_REFUSE,
+ },
+ };
+
+ UNUSED(state);
+
+ for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ run_start_test(&tests[i]);
+ }
+}
+#endif /* if defined(USE_LIBTOOL) || LD_WRAP */
+
+int
+main(void) {
+#if defined(USE_LIBTOOL) || LD_WRAP
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(ns__query_sfcache_test, _setup,
+ _teardown),
+ cmocka_unit_test_setup_teardown(ns__query_start_test, _setup,
+ _teardown),
+ };
+
+ return (cmocka_run_group_tests(tests, NULL, NULL));
+#else /* if defined(USE_LIBTOOL) || LD_WRAP */
+ print_message("1..0 # Skip query_test requires libtool or LD_WRAP\n");
+#endif /* if defined(USE_LIBTOOL) || LD_WRAP */
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+ printf("1..0 # Skipped: cmocka not available\n");
+
+ return (SKIPPED_TEST_EXIT_CODE);
+}
+
+#endif /* HAVE_CMOCKA */
diff --git a/lib/ns/tests/testdata/notify/notify1.msg b/lib/ns/tests/testdata/notify/notify1.msg
new file mode 100644
index 0000000..a84193b
--- /dev/null
+++ b/lib/ns/tests/testdata/notify/notify1.msg
@@ -0,0 +1,3 @@
+# notify for example.com
+10 a6 10 10 00 01 00 00 00 00 00 00 07 65 78 61
+6d 70 6c 65 03 63 6f 6d 00 00 06 00 01
diff --git a/lib/ns/tests/testdata/notify/zone1.db b/lib/ns/tests/testdata/notify/zone1.db
new file mode 100644
index 0000000..b218d6d
--- /dev/null
+++ b/lib/ns/tests/testdata/notify/zone1.db
@@ -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.
+
+$TTL 1000
+@ in soa localhost. postmaster.localhost. (
+ 1993050801 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+ in ns ns.example.com.
+ in ns ns2.example.com.
+ in ns ns3.example.com.
+ns in a 10.0.0.1
+ns2 in a 10.0.0.2
+ns3 in a 10.0.0.3
+
+a in a 1.2.3.4
diff --git a/lib/ns/tests/testdata/query/foo.db b/lib/ns/tests/testdata/query/foo.db
new file mode 100644
index 0000000..5fb2c42
--- /dev/null
+++ b/lib/ns/tests/testdata/query/foo.db
@@ -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.
+
+$TTL 3600
+@ IN SOA localhost. postmaster.localhost. (
+ 1 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+ IN NS ns
+ns IN A 127.0.0.1
diff --git a/lib/ns/update.c b/lib/ns/update.c
new file mode 100644
index 0000000..4e51cdc
--- /dev/null
+++ b/lib/ns/update.c
@@ -0,0 +1,3696 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/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"; \
+ } \
+ 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"; \
+ } \
+ 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"; \
+ } \
+ 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;
+};
+
+/*%
+ * 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
+ * master based on IP address.
+ *
+ * 'message' contains the type of operation that is being attempted.
+ * 'slave' indicates if this is a slave zone. If 'acl' is NULL then
+ * log at debug=3.
+ * If the zone has no access controls configured ('acl' == NULL &&
+ * 'has_ssutable == ISC_FALS) log the attempt at info, otherwise
+ * at error.
+ *
+ * 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 slave, 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 (slave && 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, 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, 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_copynf(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);
+
+/*%
+ * 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;
+
+ /*
+ * 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);
+ }
+
+ rule_ok = dns_ssutable_checkrules(
+ ssuinfo->table, ssuinfo->signer, ssuinfo->name, ssuinfo->addr,
+ ssuinfo->tcp, ssuinfo->aclenv, rrset->type, ssuinfo->key);
+ 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);
+}
+
+/**************************************************************************/
+/*
+ * 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_copynf(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;
+ dns_aclenv_t *env =
+ ns_interfacemgr_getaclenv(client->manager->interface->mgr);
+ dns_rdataclass_t zoneclass;
+ dns_rdatatype_t covers;
+ dns_name_t *zonename = NULL;
+ 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.
+ */
+ for (result = dns_message_firstname(request, DNS_SECTION_UPDATE);
+ result == ISC_R_SUCCESS;
+ 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;
+
+ 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;
+ dst_key_t *tsigkey = NULL;
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+
+ if (client->message->tsigkey != NULL) {
+ tsigkey = client->message->tsigkey->key;
+ }
+
+ if (rdata.type != dns_rdatatype_any) {
+ if (!dns_ssutable_checkrules(
+ ssutable, client->signer, name,
+ &netaddr, TCPCLIENT(client), env,
+ rdata.type, tsigkey))
+ {
+ 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;
+
+ 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 (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 master.
+ */
+ 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,
+ dns_result_totext(DNS_R_MXISADDRESS));
+ ok = false;
+ } else if (isaddress) {
+ update_log(client, zone, ISC_LOG_WARNING,
+ "%s/MX: warning: '%s': %s", ownerbuf,
+ namebuf,
+ dns_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) {
+ 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);
+}
+
+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) {
+ dns_difftuple_t *tuple;
+ bool nseconly = false, nsec3 = false;
+ isc_result_t result;
+ unsigned int iterations = 0;
+ dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
+
+ /* Scan the tuples for an NSEC-only DNSKEY or an NSEC3PARAM */
+ for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL;
+ tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ if (tuple->op != DNS_DIFFOP_ADD) {
+ continue;
+ }
+
+ if (tuple->rdata.type == dns_rdatatype_dnskey) {
+ uint8_t alg;
+ alg = tuple->rdata.data[3];
+ if (alg == DST_ALG_RSASHA1) {
+ nseconly = true;
+ break;
+ }
+ } else if (tuple->rdata.type == dns_rdatatype_nsec3param) {
+ nsec3 = true;
+ break;
+ }
+ }
+
+ /* Check existing DB for NSEC-only DNSKEY */
+ if (!nseconly) {
+ result = dns_nsec_nseconly(db, ver, &nseconly);
+
+ /*
+ * An NSEC3PARAM update 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));
+ }
+
+ /* Refuse to allow NSEC3 with NSEC-only keys */
+ if (nseconly && nsec3) {
+ 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.
+ */
+ result = dns_nsec_nseconly(db, ver, &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;
+ 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(__FILE__, __LINE__,
+ "temp entry creation failed: "
+ "%s",
+ dns_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.
+ */
+ for (result = dns_message_firstname(request, DNS_SECTION_UPDATE);
+ result == ISC_R_SUCCESS;
+ 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;
+
+ get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name,
+ &rdata, &covers, &ttl, &update_class);
+
+ if (update_class == zoneclass) {
+ /*
+ * RFC1123 doesn't allow MF and MD in master zones.
+ */
+ 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 (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]",
+ dns_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 slaves 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",
+ dns_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",
+ dns_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 (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/version.c b/lib/ns/version.c
new file mode 100644
index 0000000..0efa262
--- /dev/null
+++ b/lib/ns/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ns/version.h>
+
+const char ns_version[] = VERSION;
diff --git a/lib/ns/win32/DLLMain.c b/lib/ns/win32/DLLMain.c
new file mode 100644
index 0000000..62c6e53
--- /dev/null
+++ b/lib/ns/win32/DLLMain.c
@@ -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.
+ */
+
+#include <signal.h>
+#include <windows.h>
+
+/*
+ * Called when we enter the DLL
+ */
+__declspec(dllexport) BOOL WINAPI
+ DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
+ switch (fdwReason) {
+ /*
+ * The DLL is loading due to process
+ * initialization or a call to LoadLibrary.
+ */
+ case DLL_PROCESS_ATTACH:
+ break;
+
+ /* The attached process creates a new thread. */
+ case DLL_THREAD_ATTACH:
+ break;
+
+ /* The thread of the attached process terminates. */
+ case DLL_THREAD_DETACH:
+ break;
+
+ /*
+ * The DLL is unloading from a process due to
+ * process termination or a call to FreeLibrary.
+ */
+ case DLL_PROCESS_DETACH:
+ break;
+
+ default:
+ break;
+ }
+ return (TRUE);
+}
diff --git a/lib/ns/win32/libns.def b/lib/ns/win32/libns.def
new file mode 100644
index 0000000..50edf86
--- /dev/null
+++ b/lib/ns/win32/libns.def
@@ -0,0 +1,107 @@
+LIBRARY libns
+
+; Exported Functions
+EXPORTS
+
+ns__client_put_cb
+ns__client_request
+ns__client_reset_cb
+ns__client_setup
+ns__interfacemgr_getif
+ns__interfacemgr_nextif
+ns__query_sfcache
+ns__query_start
+ns__client_tcpconn
+ns_client_aclmsg
+ns_client_addopt
+ns_client_checkacl
+ns_client_checkaclsilent
+ns_client_drop
+ns_client_dumprecursing
+ns_client_error
+ns_client_findversion
+ns_client_getdestaddr
+ns_client_getnamebuf
+ns_client_getsockaddr
+ns_client_keepname
+ns_client_killoldestquery
+ns_client_log
+ns_client_logv
+ns_client_newdbversion
+ns_client_newname
+ns_client_newnamebuf
+ns_client_newrdataset
+ns_client_putrdataset
+ns_client_qnamereplace
+ns_client_recursing
+ns_client_releasename
+ns_client_send
+ns_client_sendraw
+ns_client_settimeout
+ns_client_shuttingdown
+ns_client_sourceip
+ns_clientmgr_create
+ns_clientmgr_destroy
+ns_clientmgr_shutdown
+ns_hook_add
+ns_hooktable_create
+ns_hooktable_free
+ns_hooktable_init
+ns_interface_attach
+ns_interface_detach
+ns_interface_shutdown
+ns_interfacemgr_attach
+ns_interfacemgr_create
+ns_interfacemgr_detach
+ns_interfacemgr_dumprecursing
+ns_interfacemgr_getaclenv
+ns_interfacemgr_getserver
+ns_interfacemgr_islistening
+ns_interfacemgr_listeningon
+ns_interfacemgr_scan
+ns_interfacemgr_setbacklog
+ns_interfacemgr_setlistenon4
+ns_interfacemgr_setlistenon6
+ns_interfacemgr_shutdown
+ns_lib_init
+ns_lib_shutdown
+ns_listenelt_create
+ns_listenelt_destroy
+ns_listenlist_attach
+ns_listenlist_create
+ns_listenlist_default
+ns_listenlist_detach
+ns_log_init
+ns_log_setcontext
+ns_notify_start
+ns_plugin_check
+ns_plugin_expandpath
+ns_plugin_register
+ns_plugins_create
+ns_plugins_free
+ns_query_cancel
+ns_query_done
+ns_query_free
+ns_query_init
+ns_query_recurse
+ns_query_start
+ns_server_attach
+ns_server_create
+ns_server_detach
+ns_server_getoption
+ns_server_setoption
+ns_server_setserverid
+ns_sortlist_addrorder1
+ns_sortlist_addrorder2
+ns_sortlist_byaddrsetup
+ns_sortlist_setup
+ns_stats_attach
+ns_stats_create
+ns_stats_decrement
+ns_stats_detach
+ns_stats_get
+ns_stats_get_counter
+ns_stats_increment
+ns_stats_update_if_greater
+ns_update_start
+ns_xfr_start
diff --git a/lib/ns/win32/libns.vcxproj.filters b/lib/ns/win32/libns.vcxproj.filters
new file mode 100644
index 0000000..2931412
--- /dev/null
+++ b/lib/ns/win32/libns.vcxproj.filters
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="libns.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\client.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\interfacemgr.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\hooks.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\lib.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\listenlist.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\log.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\notify.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\query.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\server.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\sortlist.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\stats.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\update.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\xfrout.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DLLMain.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="version.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\ns\client.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\interfacemgr.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\hooks.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\lib.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\listenlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\log.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\notify.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\query.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\server.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\sortlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\stats.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\types.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\update.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\version.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\include\ns\xfrout.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/lib/ns/win32/libns.vcxproj.in b/lib/ns/win32/libns.vcxproj.in
new file mode 100644
index 0000000..0249982
--- /dev/null
+++ b/lib/ns/win32/libns.vcxproj.in
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="@TOOLS_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|@PLATFORM@">
+ <Configuration>Debug</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|@PLATFORM@">
+ <Configuration>Release</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{82ACD33C-E75F-45B8-BB6D-42643A10D7EE}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>libns</RootNamespace>
+ @WINDOWS_TARGET_PLATFORM_VERSION@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;@USE_GSSAPI@_DEBUG;_USRDLL;LIBNS_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;..\..\..\lib\dns\include;@LIBXML2_INC@@OPENSSL_INC@@GSSAPI_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BrowseInformation>true</BrowseInformation>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalLibraryDirectories>..\..\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libisc.lib;libdns.lib;@LIBXML2_LIB@@GSSAPI_LIB@@GEOIP_LIB@ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>@INTRINSIC@</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;@USE_GSSAPI@NDEBUG;_USRDLL;LIBNS_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
+ <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;..\..\..\lib\dns\include;@LIBXML2_INC@@OPENSSL_INC@@GSSAPI_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <StringPooling>true</StringPooling>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WholeProgramOptimization>false</WholeProgramOptimization>
+ <CompileAs>CompileAsC</CompileAs>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <AdditionalLibraryDirectories>..\..\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@libisc.lib;libdns.lib;@LIBXML2_LIB@@GSSAPI_LIB@@GEOIP_LIB@ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <None Include="libns.def" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\client.c" />
+ <ClCompile Include="..\interfacemgr.c" />
+ <ClCompile Include="..\hooks.c" />
+ <ClCompile Include="..\lib.c" />
+ <ClCompile Include="..\listenlist.c" />
+ <ClCompile Include="..\log.c" />
+ <ClCompile Include="..\notify.c" />
+ <ClCompile Include="..\query.c" />
+ <ClCompile Include="..\server.c" />
+ <ClCompile Include="..\sortlist.c" />
+ <ClCompile Include="..\stats.c" />
+ <ClCompile Include="..\update.c" />
+ <ClCompile Include="..\xfrout.c" />
+ <ClCompile Include="DLLMain.c" />
+ <ClCompile Include="version.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\include\ns\client.h" />
+ <ClInclude Include="..\include\ns\interfacemgr.h" />
+ <ClInclude Include="..\include\ns\hooks.h" />
+ <ClInclude Include="..\include\ns\lib.h" />
+ <ClInclude Include="..\include\ns\listenlist.h" />
+ <ClInclude Include="..\include\ns\log.h" />
+ <ClInclude Include="..\include\ns\notify.h" />
+ <ClInclude Include="..\include\ns\query.h" />
+ <ClInclude Include="..\include\ns\server.h" />
+ <ClInclude Include="..\include\ns\sortlist.h" />
+ <ClInclude Include="..\include\ns\stats.h" />
+ <ClInclude Include="..\include\ns\types.h" />
+ <ClInclude Include="..\include\ns\update.h" />
+ <ClInclude Include="..\include\ns\version.h" />
+ <ClInclude Include="..\include\ns\xfrout.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/lib/ns/win32/libns.vcxproj.user b/lib/ns/win32/libns.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/lib/ns/win32/libns.vcxproj.user
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+</Project> \ No newline at end of file
diff --git a/lib/ns/win32/version.c b/lib/ns/win32/version.c
new file mode 100644
index 0000000..ac7a537
--- /dev/null
+++ b/lib/ns/win32/version.c
@@ -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.
+ */
+
+/*! \file */
+
+#include <versions.h>
+
+#include <ns/version.h>
+
+LIBNS_EXTERNAL_DATA const char ns_version[] = VERSION;
+LIBNS_EXTERNAL_DATA const char ns_major[] = MAJOR;
+LIBNS_EXTERNAL_DATA const char ns_mapapi[] = MAPAPI;
diff --git a/lib/ns/xfrout.c b/lib/ns/xfrout.c
new file mode 100644
index 0000000..f0c52f2
--- /dev/null
+++ b/lib/ns/xfrout.c
@@ -0,0 +1,1813 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/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/result.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)) {
+ /*
+ * Master, slave, 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;
+
+ /*
+ * 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 == false) {
+ goto axfr_fallback;
+ }
+ }
+
+ 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
+ * slave.
+ */
+ 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);
+
+ 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");
+ 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);
+}
diff --git a/lib/win32/bindevt/bindevt.c b/lib/win32/bindevt/bindevt.c
new file mode 100644
index 0000000..31cb368
--- /dev/null
+++ b/lib/win32/bindevt/bindevt.c
@@ -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.
+ */
+
+/*
+ * bindevt.c : Defines the entry point for event log viewer DLL.
+ */
+
+#include <windows.h>
+
+BOOL APIENTRY
+DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
+ return (TRUE);
+}
diff --git a/lib/win32/bindevt/bindevt.mc b/lib/win32/bindevt/bindevt.mc
new file mode 100644
index 0000000..60a6346
--- /dev/null
+++ b/lib/win32/bindevt/bindevt.mc
@@ -0,0 +1,41 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+MessageIdTypedef=DWORD
+
+LanguageNames = (English=0x409:MSG00409)
+
+OutputBase = 16
+
+
+MessageId=0x1
+Severity=Error
+Facility=Application
+SymbolicName=BIND_ERR_MSG
+Language=English
+%1
+.
+
+MessageId=0x2
+Severity=Warning
+Facility=Application
+SymbolicName=BIND_WARN_MSG
+Language=English
+%1
+.
+
+MessageId=0x3
+Severity=Informational
+Facility=Application
+SymbolicName=BIND_INFO_MSG
+Language=English
+%1
+.
diff --git a/lib/win32/bindevt/bindevt.vcxproj.filters.in b/lib/win32/bindevt/bindevt.vcxproj.filters.in
new file mode 100644
index 0000000..2b9ee98
--- /dev/null
+++ b/lib/win32/bindevt/bindevt.vcxproj.filters.in
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="bindevt.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="bindevt.mc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="bindevt.rc" />
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/lib/win32/bindevt/bindevt.vcxproj.in b/lib/win32/bindevt/bindevt.vcxproj.in
new file mode 100644
index 0000000..78113e4
--- /dev/null
+++ b/lib/win32/bindevt/bindevt.vcxproj.in
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="@TOOLS_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|@PLATFORM@">
+ <Configuration>Debug</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|@PLATFORM@">
+ <Configuration>Release</Configuration>
+ <Platform>@PLATFORM@</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{0D745CD9-FC3B-49DC-99BE-1E6DF85593F0}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>bindevt</RootNamespace>
+ @WINDOWS_TARGET_PLATFORM_VERSION@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ @PLATFORM_TOOLSET@
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ <CustomBuildBeforeTargets>ResourceCompile</CustomBuildBeforeTargets>
+ <IgnoreImportLibrary>true</IgnoreImportLibrary>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>..\..\..\Build\$(Configuration)\</OutDir>
+ <IntDir>.\$(Configuration)\</IntDir>
+ <IntDirSharingDetected>None</IntDirSharingDetected>
+ <CustomBuildBeforeTargets>ResourceCompile</CustomBuildBeforeTargets>
+ <IgnoreImportLibrary>true</IgnoreImportLibrary>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;BINDEVT_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\include;..\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <BrowseInformation>true</BrowseInformation>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ </Link>
+ <CustomBuildStep>
+ <Command>mc bindevt.mc</Command>
+ </CustomBuildStep>
+ <CustomBuildStep>
+ <Outputs>$(TargetName).rc</Outputs>
+ </CustomBuildStep>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>false</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;BINDEVT_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\include;..\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <StringPooling>true</StringPooling>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation>
+ <ObjectFileName>.\$(Configuration)\</ObjectFileName>
+ <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WholeProgramOptimization>false</WholeProgramOptimization>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile>
+ <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
+ <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary>
+ </Link>
+ <CustomBuildStep>
+ <Command>mc bindevt.mc</Command>
+ </CustomBuildStep>
+ <CustomBuildStep>
+ <Outputs>$(TargetName).rc</Outputs>
+ </CustomBuildStep>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="bindevt.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="bindevt.mc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="bindevt.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/lib/win32/bindevt/bindevt.vcxproj.user b/lib/win32/bindevt/bindevt.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/lib/win32/bindevt/bindevt.vcxproj.user
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+</Project> \ No newline at end of file